Repository: conda/conda-build

Issue:
Problem: Whitespaces in paths don't work
While working with `--croot` I stumbled across a problem with whitespaces in paths on both, Windows and Linux. Crashes are happening all over the place, always where external tools are called, because spaces are interpreted as argument separators..

Currently conda and conda-build do not allow whitespaces in at least:

- the root prefix
- the croot prefix
- the recipe directory

While Obviously whitespaces are quite evil and not such a big problem on Linux, on Windows they are *real* and used even in system directories.

I googled quite a while to look for strategies in python on how to escape paths properly and came up with these alternatives:
- putting the path in double quotes: `"\"" + path + "\""`, found here: http://stackoverflow.com/questions/14852140/whitespaces-in-the-path-of-windows-filepath
- parsing the path as raw string: `r"C:\path\with spaces\"`, found in the same place
- using `shlex.quote(..)`, found here: http://stackoverflow.com/questions/35817/how-to-escape-os-system-calls-in-python, https://docs.python.org/dev/library/shlex.html#shlex.quote
  - this converted my test string into 'single quotes'

@msarahan Any thoughts?


Discussion:
Thanks for raising. On conda with the activate scripts, I made the tests all run in folders with spaces. I need to do the same for conda build.  I need to tag 2.1.4 without this, but will fix it for another release soon.

Resolving patch:
diff --git a/conda_build/build.py b/conda_build/build.py
--- a/conda_build/build.py
+++ b/conda_build/build.py
@@ -1348,19 +1348,19 @@ def test(recipedir_or_package_or_metadata, config, move_broken=True):
             # use pythonw for import tests when osx_is_app is set
             if metadata.get_value('build/osx_is_app') and sys.platform == 'darwin':
                 test_python = test_python + 'w'
-            tf.write("{python} -s {test_file}\n".format(
+            tf.write('"{python}" -s "{test_file}"\n'.format(
                 python=config.test_python,
                 test_file=join(config.test_dir, 'run_test.py')))
             if utils.on_win:
                 tf.write("if errorlevel 1 exit 1\n")
         if pl_files:
-            tf.write("{perl} {test_file}\n".format(
+            tf.write('"{perl}" "{test_file}"\n'.format(
                 perl=config.test_perl,
                 test_file=join(config.test_dir, 'run_test.pl')))
             if utils.on_win:
                 tf.write("if errorlevel 1 exit 1\n")
         if lua_files:
-            tf.write("{lua} {test_file}\n".format(
+            tf.write('"{lua}" "{test_file}"\n'.format(
                 lua=config.test_lua,
                 test_file=join(config.test_dir, 'run_test.lua')))
             if utils.on_win:
@@ -1368,13 +1368,13 @@ def test(recipedir_or_package_or_metadata, config, move_broken=True):
         if shell_files:
             test_file = join(config.test_dir, 'run_test.' + suffix)
             if utils.on_win:
-                tf.write("call {test_file}\n".format(test_file=test_file))
+                tf.write('call "{test_file}"\n'.format(test_file=test_file))
                 if utils.on_win:
                     tf.write("if errorlevel 1 exit 1\n")
             else:
                 # TODO: Run the test/commands here instead of in run_test.py
-                tf.write("{shell_path} -x -e {test_file}\n".format(shell_path=shell_path,
-                                                                    test_file=test_file))
+                tf.write('"{shell_path}" -x -e "{test_file}"\n'.format(shell_path=shell_path,
+                                                                       test_file=test_file))
 
     if utils.on_win:
         cmd = ['cmd.exe', "/d", "/c", test_script]


Test patch:
diff --git a/tests/test_api_build.py b/tests/test_api_build.py
--- a/tests/test_api_build.py
+++ b/tests/test_api_build.py
@@ -887,3 +887,10 @@ def test_recursion_layers(test_config):
 def test_pin_depends(test_metadata):
     test_metadata.meta['build']['pin_depends'] = 'record'
     api.build(test_metadata)
+
+
+@pytest.mark.skipif(sys.platform != 'win32', reason=("spaces break openssl prefix "
+                                                     "replacement on *nix"))
+def test_croot_with_spaces(test_metadata, testing_workdir):
+    test_metadata.config.croot = os.path.join(testing_workdir, "space path")
+    api.build(test_metadata)