Repository: pypa/setuptools

Issue:
rc.exe randomly not found (race condition?)
I've run into a issue on GitHub Actions where in like a small percent of the VMs spinned up, pip will fail with the error `LINK : fatal error LNK1158: cannot run 'rc.exe'`:

```
error: command 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Enterprise\\VC\\Tools\\MSVC\\14.26.28801\\bin\\HostX86\\x64\\link.exe' failed with exit status 1158
ERROR: Command errored out with exit status 1: 'C:\hostedtoolcache\windows\Python\3.6.8\x64\python.exe' -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'C:\\Users\\runneradmin\\kivy\\setup.py'"'"'; __file__='"'"'C:\\Users\\runneradmin\\kivy\\setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' develop --no-deps Check the logs for full command output.
Exception information:
Traceback (most recent call last):
  File "C:\hostedtoolcache\windows\Python\3.6.8\x64\lib\site-packages\pip\_internal\cli\base_command.py", line 188, in _main
    status = self.run(options, args)
  File "C:\hostedtoolcache\windows\Python\3.6.8\x64\lib\site-packages\pip\_internal\cli\req_command.py", line 185, in wrapper
    return func(self, options, args)
  File "C:\hostedtoolcache\windows\Python\3.6.8\x64\lib\site-packages\pip\_internal\commands\install.py", line 407, in run
    use_user_site=options.use_user_site,
  File "C:\hostedtoolcache\windows\Python\3.6.8\x64\lib\site-packages\pip\_internal\req\__init__.py", line 71, in install_given_reqs
    **kwargs
  File "C:\hostedtoolcache\windows\Python\3.6.8\x64\lib\site-packages\pip\_internal\req\req_install.py", line 790, in install
    unpacked_source_directory=self.unpacked_source_directory,
  File "C:\hostedtoolcache\windows\Python\3.6.8\x64\lib\site-packages\pip\_internal\operations\install\editable_legacy.py", line 51, in install_editable
    cwd=unpacked_source_directory,
  File "C:\hostedtoolcache\windows\Python\3.6.8\x64\lib\site-packages\pip\_internal\utils\subprocess.py", line 241, in call_subprocess
    raise InstallationError(exc_msg)
pip._internal.exceptions.InstallationError: Command errored out with exit status 1: 'C:\hostedtoolcache\windows\Python\3.6.8\x64\python.exe' -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'C:\\Users\\runneradmin\\kivy\\setup.py'"'"'; __file__='"'"'C:\\Users\\runneradmin\\kivy\\setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' develop --no-deps Check the logs for full command output.
```
Simply re-running the actions normally solves the issue. Here's an example of a good run https://github.com/kivy/kivy/runs/791883878?check_suite_focus=true, and a bad run https://github.com/kivy/kivy/runs/791983410?check_suite_focus=true.

My first thought was that this a GitHub Action environment issue, so I opened a bug report there: https://github.com/actions/virtual-environments/issues/1087. However, after some investigation, it seemed this is more likely a bug in setuptools. The reason is that using process monitor to check all instantiations of `link.exe` we looked at the environmental `PATH` variable of the process when it started. And in the failing examples, python would call `link.exe` multiple times for each cython generated c-file, but some of these would be missing all the visual studio compiler paths from its `PATH`.

E.g. process monitor shows this during the compile step: ![](https://user-images.githubusercontent.com/47745270/85335019-f7228800-b4e4-11ea-9010-a265c1849cc9.png).

But the first one's (6396) PATH is:
```
C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\\Extensions\Microsoft\IntelliCode\CLI
C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.26.28801\bin\HostX86\x64
C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.26.28801\bin\HostX86\x86
C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\VC\VCPackages
C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\CommonExtensions\Microsoft\TestWindow
C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer
C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\bin\Roslyn
C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Team Tools\Performance Tools\x64
C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Team Tools\Performance Tools
C:\Program Files (x86)\Microsoft Visual Studio\Shared\Common\VSPerfCollectionTools\vs2019\\x64
C:\Program Files (x86)\Microsoft Visual Studio\Shared\Common\VSPerfCollectionTools\vs2019\
C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\
C:\Program Files (x86)\HTML Help Workshop
C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\CommonExtensions\Microsoft\FSharp\
C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x86
C:\Program Files (x86)\Windows Kits\10\bin\x86
C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\\MSBuild\Current\Bin
C:\windows\Microsoft.NET\Framework\v4.0.30319
C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\
C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\Tools\
C:\hostedtoolcache\windows\Python\3.8.3\x64\share\sdl2\bin
C:\hostedtoolcache\windows\Python\3.8.3\x64\share\glew\bin
C:\hostedtoolcache\windows\Python\3.8.3\x64\share\gstreamer\bin
....
```

while the second one's (6520) is much shorter and is missing 18 paths (Doesn't contain C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x86 with rc.exe):
```
C:\hostedtoolcache\windows\Python\3.8.3\x64\share\sdl2\bin
C:\hostedtoolcache\windows\Python\3.8.3\x64\share\glew\bin
C:\hostedtoolcache\windows\Python\3.8.3\x64\share\gstreamer\bin
....
```

You can inspect a pared down process monitor log here (you need to open it in process monitor):
[logfile.zip](https://github.com/pypa/setuptools/files/4821977/logfile.zip) or get the full 100+MB file from this action: https://github.com/kivy/kivy/actions/runs/145338811. `procmon-3.8-x64` is the good log, `procmon-3.7-x64` is the bad log.

As far as I'm aware, the only path type stuff we manipulate is with the following when some package is imported. But I fail to see how this could cause it:
```py
_root = sys.prefix
dep_bins = [join(_root, 'share', 'sdl2', 'bin')]
if isdir(dep_bins[0]):
    os.environ["PATH"] = dep_bins[0] + os.pathsep + os.environ["PATH"]
```

The question is, why randomly within one `python -m pip install -e .` command does some `link.exe` get created without the proper visual studio paths in its environment? It seems like it must be some kind of race condition where in some cases, the compiler gets called with the wrong env?


Discussion:
I'm afraid I don't have a lot to offer. You've already performed quite a bit of analysis deeper than I could. I did look at the failing kivy build log, and I don't see anything there that strikes me as abnormal.

I do observe that `distutils` (on which Setuptools is based) does [patch the PATH envar](https://github.com/python/cpython/blob/4c8f09d7cef8c7aa07d5b5232b5b64f63819a743/Lib/distutils/_msvccompiler.py#L503-L509) ([also here](https://github.com/python/cpython/blob/4c8f09d7cef8c7aa07d5b5232b5b64f63819a743/Lib/distutils/msvc9compiler.py#L398)) for MSVC compiler. I'm not certain that's the code path that gets hit, but it does seem likely. In particular, the `spawn` patch is obviously thread-unsafe, so could be implicated in a race condition. I'm not aware of any concurrent processing that would allow for such a race.

There are some recent builds of Setuptools that are attempting to adopt distutils (>=48.x,<49.1). If you want to try building with those, you can see if they're any better (as they'll incorporate many updates to distutils for Python 3.7-3.9). Soon I hope, Setuptools will adopt distutils in the latest release and we can add some debugging code.

Let me know if there's more I can do to help you investigate.
Thanks for the info, that helped me get a starting point.

For a first step, I copied over `_msvccompiler.py` and `msvc9compiler.py` and added print statements to see which spawn is being hit. And it turns out `_msvccompiler.py` spawn is where the environment gets set and reset to include the visualstudio paths.

I then tested replacing that `super().spawn(cmd)` with `subprocess.check_output(cmd)` and the issue went away so far after 7 CI runs. However, that doesn't mean anything if it's a race condition, because that could simply mess with the timing so the condition is not hit.

So, to figure out where there threads are coming from given that there's no obvious thread usage, I added code to detect if spawn is called from multiple threads (https://gist.github.com/matham/a0da3beae05834a3607a8fdc9a6aacd4#file-_msvccompiler-py-L513). Turns out there are almost always two simultaneous calls to spawn from two threads. Following is the stack trace from one example where it detected that another thread concurrently called `spawn`:

```
found dupps:
C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.26.28801\bin\HostX86\x64\cl.exe /c /nologo /Ox /W3 /GL /DNDEBUG /MD -IC:\Users\runneradmin\kivy\kivy\include -IC:\Users\RUNNER~1\AppData\Local\Temp\pip-build-env-_ye_1j1z\overlay\Include -IC:\hostedtoolcache\windows\Python\3.7.8\x64\include -IC:\hostedtoolcache\windows\Python\3.7.8\x64\include -IC:\hostedtoolcache\windows\Python\3.7.8\x64\include -IC:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.26.28801\ATLMFC\include -IC:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.26.28801\include -IC:\Program Files (x86)\Windows Kits\NETFXSDK\4.8\include\um -IC:\Program Files (x86)\Windows Kits\10\include\10.0.19041.0\ucrt -IC:\Program Files (x86)\Windows Kits\10\include\10.0.19041.0\shared -IC:\Program Files (x86)\Windows Kits\10\include\10.0.19041.0\um -IC:\Program Files (x86)\Windows Kits\10\include\10.0.19041.0\winrt -IC:\Program Files (x86)\Windows Kits\10\include\10.0.19041.0\cppwinrt /TcC:\Users\runneradmin\kivy\kivy\_event.c /Fobuild\temp.win-amd64-3.7\Release\Users\runneradmin\kivy\kivy\_event.obj
File "C:\hostedtoolcache\windows\Python\3.7.8\x64\lib\threading.py", line 890, in _bootstrap
self._bootstrap_inner()
File "C:\hostedtoolcache\windows\Python\3.7.8\x64\lib\threading.py", line 926, in _bootstrap_inner
self.run()
File "C:\hostedtoolcache\windows\Python\3.7.8\x64\lib\threading.py", line 870, in run
self._target(*self._args, **self._kwargs)
File "C:\hostedtoolcache\windows\Python\3.7.8\x64\lib\concurrent\futures\thread.py", line 80, in _worker
work_item.run()
File "C:\hostedtoolcache\windows\Python\3.7.8\x64\lib\concurrent\futures\thread.py", line 57, in run
result = self.fn(*self.args, **self.kwargs)
File "C:\hostedtoolcache\windows\Python\3.7.8\x64\lib\distutils\command\build_ext.py", line 534, in build_extension
depends=ext.depends)
File "C:\hostedtoolcache\windows\Python\3.7.8\x64\lib\distutils\_msvccompiler.py", line 404, in compile
self.spawn(args)
File "C:\hostedtoolcache\windows\Python\3.7.8\x64\lib\distutils\_msvccompiler.py", line 512, in spawn
traces[obj] = [' '.join(cmd), '\n\n'] + traceback.format_stack()


C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.26.28801\bin\HostX86\x64\cl.exe /c /nologo /Ox /W3 /GL /DNDEBUG /MD -IC:\hostedtoolcache\windows\Python\3.7.8\x64\include -IC:\hostedtoolcache\windows\Python\3.7.8\x64\include -IC:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.26.28801\ATLMFC\include -IC:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.26.28801\include -IC:\Program Files (x86)\Windows Kits\NETFXSDK\4.8\include\um -IC:\Program Files (x86)\Windows Kits\10\include\10.0.19041.0\ucrt -IC:\Program Files (x86)\Windows Kits\10\include\10.0.19041.0\shared -IC:\Program Files (x86)\Windows Kits\10\include\10.0.19041.0\um -IC:\Program Files (x86)\Windows Kits\10\include\10.0.19041.0\winrt -IC:\Program Files (x86)\Windows Kits\10\include\10.0.19041.0\cppwinrt -IC:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.26.28801\ATLMFC\include -IC:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.26.28801\include -IC:\Program Files (x86)\Windows Kits\NETFXSDK\4.8\include\um -IC:\Program Files (x86)\Windows Kits\10\include\10.0.19041.0\ucrt -IC:\Program Files (x86)\Windows Kits\10\include\10.0.19041.0\shared -IC:\Program Files (x86)\Windows Kits\10\include\10.0.19041.0\um -IC:\Program Files (x86)\Windows Kits\10\include\10.0.19041.0\winrt -IC:\Program Files (x86)\Windows Kits\10\include\10.0.19041.0\cppwinrt /TcC:\Users\runneradmin\kivy\kivy\_clock.c /Fobuild\temp.win-amd64-3.7\Release\Users\runneradmin\kivy\kivy\_clock.obj
File "C:\hostedtoolcache\windows\Python\3.7.8\x64\lib\threading.py", line 890, in _bootstrap
self._bootstrap_inner()
File "C:\hostedtoolcache\windows\Python\3.7.8\x64\lib\threading.py", line 926, in _bootstrap_inner
self.run()
File "C:\hostedtoolcache\windows\Python\3.7.8\x64\lib\threading.py", line 870, in run
self._target(*self._args, **self._kwargs)
File "C:\hostedtoolcache\windows\Python\3.7.8\x64\lib\concurrent\futures\thread.py", line 80, in _worker
work_item.run()
File "C:\hostedtoolcache\windows\Python\3.7.8\x64\lib\concurrent\futures\thread.py", line 57, in run
result = self.fn(*self.args, **self.kwargs)
File "C:\hostedtoolcache\windows\Python\3.7.8\x64\lib\distutils\command\build_ext.py", line 534, in build_extension
depends=ext.depends)
File "C:\hostedtoolcache\windows\Python\3.7.8\x64\lib\distutils\_msvccompiler.py", line 404, in compile
self.spawn(args)
File "C:\hostedtoolcache\windows\Python\3.7.8\x64\lib\distutils\_msvccompiler.py", line 512, in spawn
traces[obj] = [' '.join(cmd), '\n\n'] + traceback.format_stack()
```

and e.g.
```
found dupps:
C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.26.28801\bin\HostX86\x64\link.exe /nologo /INCREMENTAL:NO /LTCG /DLL /MANIFEST:EMBED,ID=2 /MANIFESTUAC:NO /LIBPATH:C:\hostedtoolcache\windows\Python\3.7.8\x64\libs /LIBPATH:C:\hostedtoolcache\windows\Python\3.7.8\x64\PCbuild\amd64 /LIBPATH:C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.26.28801\ATLMFC\lib\x64 /LIBPATH:C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.26.28801\lib\x64 /LIBPATH:C:\Program Files (x86)\Windows Kits\NETFXSDK\4.8\lib\um\x64 /LIBPATH:C:\Program Files (x86)\Windows Kits\10\lib\10.0.19041.0\ucrt\x64 /LIBPATH:C:\Program Files (x86)\Windows Kits\10\lib\10.0.19041.0\um\x64 /LIBPATH:C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.26.28801\ATLMFC\lib\x64 /LIBPATH:C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.26.28801\lib\x64 /LIBPATH:C:\Program Files (x86)\Windows Kits\NETFXSDK\4.8\lib\um\x64 /LIBPATH:C:\Program Files (x86)\Windows Kits\10\lib\10.0.19041.0\ucrt\x64 /LIBPATH:C:\Program Files (x86)\Windows Kits\10\lib\10.0.19041.0\um\x64 /EXPORT:PyInit__clock build\temp.win-amd64-3.7\Release\Users\runneradmin\kivy\kivy\_clock.obj /OUT:C:\Users\runneradmin\kivy\kivy\_clock.cp37-win_amd64.pyd /IMPLIB:build\temp.win-amd64-3.7\Release\Users\runneradmin\kivy\kivy\_clock.cp37-win_amd64.lib
File "C:\hostedtoolcache\windows\Python\3.7.8\x64\lib\threading.py", line 890, in _bootstrap
self._bootstrap_inner()
File "C:\hostedtoolcache\windows\Python\3.7.8\x64\lib\threading.py", line 926, in _bootstrap_inner
self.run()
File "C:\hostedtoolcache\windows\Python\3.7.8\x64\lib\threading.py", line 870, in run
self._target(*self._args, **self._kwargs)
File "C:\hostedtoolcache\windows\Python\3.7.8\x64\lib\concurrent\futures\thread.py", line 80, in _worker
work_item.run()
File "C:\hostedtoolcache\windows\Python\3.7.8\x64\lib\concurrent\futures\thread.py", line 57, in run
result = self.fn(*self.args, **self.kwargs)
File "C:\hostedtoolcache\windows\Python\3.7.8\x64\lib\distutils\command\build_ext.py", line 559, in build_extension
target_lang=language)
File "C:\hostedtoolcache\windows\Python\3.7.8\x64\lib\distutils\ccompiler.py", line 717, in link_shared_object
extra_preargs, extra_postargs, build_temp, target_lang)
File "C:\hostedtoolcache\windows\Python\3.7.8\x64\lib\distutils\_msvccompiler.py", line 500, in link
self.spawn([self.linker] + ld_args)
File "C:\hostedtoolcache\windows\Python\3.7.8\x64\lib\distutils\_msvccompiler.py", line 512, in spawn
traces[obj] = [' '.join(cmd), '\n\n'] + traceback.format_stack()


C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.26.28801\bin\HostX86\x64\cl.exe /c /nologo /Ox /W3 /GL /DNDEBUG /MD -IC:\hostedtoolcache\windows\Python\3.7.8\x64\include -IC:\hostedtoolcache\windows\Python\3.7.8\x64\include -IC:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.26.28801\ATLMFC\include -IC:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.26.28801\include -IC:\Program Files (x86)\Windows Kits\NETFXSDK\4.8\include\um -IC:\Program Files (x86)\Windows Kits\10\include\10.0.19041.0\ucrt -IC:\Program Files (x86)\Windows Kits\10\include\10.0.19041.0\shared -IC:\Program Files (x86)\Windows Kits\10\include\10.0.19041.0\um -IC:\Program Files (x86)\Windows Kits\10\include\10.0.19041.0\winrt -IC:\Program Files (x86)\Windows Kits\10\include\10.0.19041.0\cppwinrt -IC:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.26.28801\ATLMFC\include -IC:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.26.28801\include -IC:\Program Files (x86)\Windows Kits\NETFXSDK\4.8\include\um -IC:\Program Files (x86)\Windows Kits\10\include\10.0.19041.0\ucrt -IC:\Program Files (x86)\Windows Kits\10\include\10.0.19041.0\shared -IC:\Program Files (x86)\Windows Kits\10\include\10.0.19041.0\um -IC:\Program Files (x86)\Windows Kits\10\include\10.0.19041.0\winrt -IC:\Program Files (x86)\Windows Kits\10\include\10.0.19041.0\cppwinrt /TcC:\Users\runneradmin\kivy\kivy\weakproxy.c /Fobuild\temp.win-amd64-3.7\Release\Users\runneradmin\kivy\kivy\weakproxy.obj
File "C:\hostedtoolcache\windows\Python\3.7.8\x64\lib\threading.py", line 890, in _bootstrap
self._bootstrap_inner()
File "C:\hostedtoolcache\windows\Python\3.7.8\x64\lib\threading.py", line 926, in _bootstrap_inner
self.run()
File "C:\hostedtoolcache\windows\Python\3.7.8\x64\lib\threading.py", line 870, in run
self._target(*self._args, **self._kwargs)
File "C:\hostedtoolcache\windows\Python\3.7.8\x64\lib\concurrent\futures\thread.py", line 80, in _worker
work_item.run()
File "C:\hostedtoolcache\windows\Python\3.7.8\x64\lib\concurrent\futures\thread.py", line 57, in run
result = self.fn(*self.args, **self.kwargs)
File "C:\hostedtoolcache\windows\Python\3.7.8\x64\lib\distutils\command\build_ext.py", line 534, in build_extension
depends=ext.depends)
File "C:\hostedtoolcache\windows\Python\3.7.8\x64\lib\distutils\_msvccompiler.py", line 404, in compile
self.spawn(args)
File "C:\hostedtoolcache\windows\Python\3.7.8\x64\lib\distutils\_msvccompiler.py", line 512, in spawn
traces[obj] = [' '.join(cmd), '\n\n'] + traceback.format_stack()
```

Could this be the issue? If so, is there anything we could do for this, short for replacing `spawn` with `subprocess` and explicitly pass in the env variable, rather than changing it in parent process?

Resolving patch:
diff --git a/setuptools/_distutils/_msvccompiler.py b/setuptools/_distutils/_msvccompiler.py
--- a/setuptools/_distutils/_msvccompiler.py
+++ b/setuptools/_distutils/_msvccompiler.py
@@ -501,12 +501,8 @@ def link(self,
             log.debug("skipping %s (up-to-date)", output_filename)
 
     def spawn(self, cmd):
-        old_path = os.getenv('path')
-        try:
-            os.environ['path'] = self._paths
-            return super().spawn(cmd)
-        finally:
-            os.environ['path'] = old_path
+        env = dict(os.environ, path=self._paths)
+        return super().spawn(cmd, env=env)
 
     # -- Miscellaneous methods -----------------------------------------
     # These are all used by the 'gen_lib_options() function, in
diff --git a/setuptools/_distutils/spawn.py b/setuptools/_distutils/spawn.py
--- a/setuptools/_distutils/spawn.py
+++ b/setuptools/_distutils/spawn.py
@@ -20,7 +20,7 @@
     _cfg_target_split = None
 
 
-def spawn(cmd, search_path=1, verbose=0, dry_run=0):
+def spawn(cmd, search_path=1, verbose=0, dry_run=0, env=None):
     """Run another program, specified as a command list 'cmd', in a new process.
 
     'cmd' is just the argument list for the new process, ie.
@@ -49,7 +49,8 @@ def spawn(cmd, search_path=1, verbose=0, dry_run=0):
         if executable is not None:
             cmd[0] = executable
 
-    env = None
+    env = env if env is not None else dict(os.environ)
+
     if sys.platform == 'darwin':
         global _cfg_target, _cfg_target_split
         if _cfg_target is None:
@@ -68,8 +69,7 @@ def spawn(cmd, search_path=1, verbose=0, dry_run=0):
                           'now "%s" but "%s" during configure'
                                 % (cur_target, _cfg_target))
                 raise DistutilsPlatformError(my_msg)
-            env = dict(os.environ,
-                       MACOSX_DEPLOYMENT_TARGET=cur_target)
+            env.update(MACOSX_DEPLOYMENT_TARGET=cur_target)
 
     try:
         proc = subprocess.Popen(cmd, env=env)


Test patch: