Repository: pypa/cibuildwheel

Issue:
Run shell commands in a system shell rather than directly
This would allow things like `CIBW_BEFORE_BUILD=command && command2`


Discussion:

Resolving patch:
diff --git a/cibuildwheel/macos.py b/cibuildwheel/macos.py
--- a/cibuildwheel/macos.py
+++ b/cibuildwheel/macos.py
@@ -19,10 +19,14 @@ def build(project_dir, package_name, output_dir, test_command, test_requires, be
         PythonConfiguration(version='3.6', identifier='cp36-macosx_10_6_intel', url='https://www.python.org/ftp/python/3.6.0/python-3.6.0-macosx10.6.pkg'),
     ]
 
-    def shell(args, env=None, cwd=None):
+    def call(args, env=None, cwd=None, shell=False):
         # print the command executing for the logs
-        print('+ ' + ' '.join(shlex_quote(a) for a in args))
-        return subprocess.check_call(args, env=env, cwd=cwd)
+        if shell:
+            print('+ %s' % args)
+        else:
+            print('+ ' + ' '.join(shlex_quote(a) for a in args))
+
+        return subprocess.check_call(args, env=env, cwd=cwd, shell=shell)
 
     for config in python_configurations:
         if skip(config.identifier):
@@ -30,9 +34,9 @@ def shell(args, env=None, cwd=None):
             continue
 
         # download the pkg
-        shell(['curl', '-L', '-o', '/tmp/Python.pkg', config.url])
+        call(['curl', '-L', '-o', '/tmp/Python.pkg', config.url])
         # install
-        shell(['sudo', 'installer', '-pkg', '/tmp/Python.pkg', '-target', '/'])
+        call(['sudo', 'installer', '-pkg', '/tmp/Python.pkg', '-target', '/'])
 
         env = os.environ.copy()
         env['PATH'] = os.pathsep.join([
@@ -44,14 +48,14 @@ def shell(args, env=None, cwd=None):
         pip = 'pip3' if config.version[0] == '3' else 'pip2'
 
         # check what version we're on
-        shell(['which', python], env=env)
-        shell([python, '--version'], env=env)
+        call(['which', python], env=env)
+        call([python, '--version'], env=env)
 
         # install pip & wheel
-        shell([python, '-m', 'ensurepip', '--upgrade'], env=env)
-        shell([pip, '--version'], env=env)
-        shell([pip, 'install', 'wheel'], env=env)
-        shell([pip, 'install', 'delocate'], env=env)
+        call([python, '-m', 'ensurepip', '--upgrade'], env=env)
+        call([pip, '--version'], env=env)
+        call([pip, 'install', 'wheel'], env=env)
+        call([pip, 'install', 'delocate'], env=env)
 
         # setup dirs
         if os.path.exists('/tmp/built_wheel'):
@@ -64,10 +68,10 @@ def shell(args, env=None, cwd=None):
         # run the before_build command
         if before_build:
             before_build_prepared = prepare_command(before_build, python=python, pip=pip)
-            shell(shlex.split(before_build_prepared), env=env)
+            call(before_build_prepared, env=env, shell=True)
 
         # build the wheel
-        shell([pip, 'wheel', project_dir, '-w', '/tmp/built_wheel', '--no-deps'], env=env)
+        call([pip, 'wheel', project_dir, '-w', '/tmp/built_wheel', '--no-deps'], env=env)
         built_wheel = glob('/tmp/built_wheel/*.whl')[0]
 
         if built_wheel.endswith('none-any.whl'):
@@ -75,24 +79,24 @@ def shell(args, env=None, cwd=None):
             shutil.move(built_wheel, '/tmp/delocated_wheel')
         else:
             # list the dependencies
-            shell(['delocate-listdeps', built_wheel], env=env)
+            call(['delocate-listdeps', built_wheel], env=env)
             # rebuild the wheel with shared libraries included and place in output dir
-            shell(['delocate-wheel', '-w', '/tmp/delocated_wheel', built_wheel], env=env)
+            call(['delocate-wheel', '-w', '/tmp/delocated_wheel', built_wheel], env=env)
         delocated_wheel = glob('/tmp/delocated_wheel/*.whl')[0]
 
         # install the wheel
-        shell([pip, 'install', delocated_wheel], env=env)
+        call([pip, 'install', delocated_wheel], env=env)
 
         # test the wheel
         if test_requires:
-            shell([pip, 'install'] + test_requires, env=env)
+            call([pip, 'install'] + test_requires, env=env)
         if test_command:
             # run the tests from $HOME, with an absolute path in the command
             # (this ensures that Python runs the tests against the installed wheel
             # and not the repo code)
             abs_project_dir = os.path.abspath(project_dir)
             test_command_absolute = test_command.format(project=abs_project_dir)
-            shell(shlex.split(test_command_absolute), cwd=os.environ['HOME'], env=env)
+            call(shlex.split(test_command_absolute), cwd=os.environ['HOME'], env=env)
 
         # we're all done here; move it to output
         shutil.move(delocated_wheel, output_dir)


Test patch:
diff --git a/test/03_before_build/environment.json b/test/03_before_build/environment.json
--- a/test/03_before_build/environment.json
+++ b/test/03_before_build/environment.json
@@ -1,4 +1,4 @@
 {
-    "CIBW_BEFORE_BUILD": "{python} -c \"import sys; open('/tmp/pythonversion.txt', 'w').write(sys.version)\"",
-    "CIBW_BEFORE_BUILD_WINDOWS": "{python} -c \"import sys; open('c:\\pythonversion.txt', 'w').write(sys.version)\""
+    "CIBW_BEFORE_BUILD": "{python} -c \"import sys; open('/tmp/pythonversion.txt', 'w').write(sys.version)\" && {python} -c \"import sys; open('/tmp/pythonexecutable.txt', 'w').write(sys.executable)\"",
+    "CIBW_BEFORE_BUILD_WINDOWS": "{python} -c \"import sys; open('c:\\pythonversion.txt', 'w').write(sys.version)\" && {python} -c \"import sys; open('c:\\pythonexecutable.txt', 'w').write(sys.executable)\""
 }
diff --git a/test/03_before_build/setup.py b/test/03_before_build/setup.py
--- a/test/03_before_build/setup.py
+++ b/test/03_before_build/setup.py
@@ -1,8 +1,8 @@
 from setuptools import setup, Extension
-import sys
+import sys, os
 
 if sys.argv[-1] != '--name':
-    # assert that the Python version as written to version.txt in the CIBW_BEFORE_BUILD step
+    # assert that the Python version as written to pythonversion.txt in the CIBW_BEFORE_BUILD step
     # is the same one as is currently running.
     version_file = 'c:\\pythonversion.txt' if sys.platform == 'win32' else '/tmp/pythonversion.txt'
     with open(version_file) as f:
@@ -11,6 +11,15 @@
     print('sys.version', sys.version)
     assert stored_version == sys.version
 
+    # check that the executable also was written
+    executable_file = 'c:\\pythonexecutable.txt' if sys.platform == 'win32' else '/tmp/pythonexecutable.txt'
+    with open(executable_file) as f:
+        stored_executable = f.read()
+    print('stored_executable', stored_executable)
+    print('sys.executable', sys.executable)
+    # windows/mac are case insensitive
+    assert os.path.realpath(stored_executable).lower() == os.path.realpath(sys.executable).lower()
+
 setup(
     name="spam",
     ext_modules=[Extension('spam', sources=['spam.c'])],