# HG changeset patch # User Steve Dower # Date 1441771473 25200 # Tue Sep 08 21:04:33 2015 -0700 # Branch 3.5 # Node ID 4c5848f90c82a078a491feae9f319c09f79cafe6 # Parent daa0ef5c8665e39c9755c639501881ac92bb12f0 Issue #25027: Reverts partial-static build options and adds vcruntime140.dll to installation. diff --git a/Lib/distutils/_msvccompiler.py b/Lib/distutils/_msvccompiler.py --- a/Lib/distutils/_msvccompiler.py +++ b/Lib/distutils/_msvccompiler.py @@ -14,6 +14,8 @@ # ported to VS 2015 by Steve Dower import os +import shutil +import stat import subprocess from distutils.errors import DistutilsExecError, DistutilsPlatformError, \ @@ -25,7 +27,7 @@ import winreg from itertools import count -def _find_vcvarsall(): +def _find_vcvarsall(plat_spec): with winreg.OpenKeyEx( winreg.HKEY_LOCAL_MACHINE, r"Software\Microsoft\VisualStudio\SxS\VC7", @@ -33,7 +35,7 @@ ) as key: if not key: log.debug("Visual C++ is not registered") - return None + return None, None best_version = 0 best_dir = None @@ -51,14 +53,23 @@ best_version, best_dir = version, vc_dir if not best_version: log.debug("No suitable Visual C++ version found") - return None + return None, None vcvarsall = os.path.join(best_dir, "vcvarsall.bat") if not os.path.isfile(vcvarsall): log.debug("%s cannot be found", vcvarsall) - return None + return None, None - return vcvarsall + vcruntime = None + vcruntime_spec = _VCVARS_PLAT_TO_VCRUNTIME_REDIST.get(plat_spec) + if vcruntime_spec: + vcruntime = os.path.join(best_dir, + vcruntime_spec.format(best_version)) + if not os.path.isfile(vcruntime): + log.debug("%s cannot be found", vcruntime) + vcruntime = None + + return vcvarsall, vcruntime def _get_vc_env(plat_spec): if os.getenv("DISTUTILS_USE_SDK"): @@ -67,7 +78,7 @@ for key, value in os.environ.items() } - vcvarsall = _find_vcvarsall() + vcvarsall, vcruntime = _find_vcvarsall(plat_spec) if not vcvarsall: raise DistutilsPlatformError("Unable to find vcvarsall.bat") @@ -83,12 +94,16 @@ raise DistutilsPlatformError("Error executing {}" .format(exc.cmd)) - return { + env = { key.lower(): value for key, _, value in (line.partition('=') for line in out.splitlines()) if key and value } + + if vcruntime: + env['py_vcruntime_redist'] = vcruntime + return env def _find_exe(exe, paths=None): """Return path to an MSVC executable program. @@ -115,6 +130,20 @@ 'win-amd64' : 'amd64', } +# A map keyed by get_platform() return values to the file under +# the VC install directory containing the vcruntime redistributable. +_VCVARS_PLAT_TO_VCRUNTIME_REDIST = { + 'x86' : 'redist\\x86\\Microsoft.VC{0}0.CRT\\vcruntime{0}0.dll', + 'amd64' : 'redist\\x64\\Microsoft.VC{0}0.CRT\\vcruntime{0}0.dll', + 'x86_amd64' : 'redist\\x64\\Microsoft.VC{0}0.CRT\\vcruntime{0}0.dll', +} + +# A set containing the DLLs that are guaranteed to be available for +# all micro versions of this Python version. Known extension +# dependencies that are not in this set will be copied to the output +# path. +_BUNDLED_DLLS = frozenset(['vcruntime140.dll']) + class MSVCCompiler(CCompiler) : """Concrete class that implements an interface to Microsoft Visual C++, as defined by the CCompiler abstract class.""" @@ -189,6 +218,7 @@ self.rc = _find_exe("rc.exe", paths) # resource compiler self.mc = _find_exe("mc.exe", paths) # message compiler self.mt = _find_exe("mt.exe", paths) # message compiler + self._vcruntime_redist = vc_env.get('py_vcruntime_redist', '') for dir in vc_env.get('include', '').split(os.pathsep): if dir: @@ -199,20 +229,26 @@ self.add_library_dir(dir) self.preprocess_options = None - # Use /MT[d] to build statically, then switch from libucrt[d].lib to ucrt[d].lib + # If vcruntime_redist is available, link against it dynamically. Otherwise, + # use /MT[d] to build statically, then switch from libucrt[d].lib to ucrt[d].lib # later to dynamically link to ucrtbase but not vcruntime. self.compile_options = [ - '/nologo', '/Ox', '/MT', '/W3', '/GL', '/DNDEBUG' + '/nologo', '/Ox', '/W3', '/GL', '/DNDEBUG' ] + self.compile_options.append('/MD' if self._vcruntime_redist else '/MT') + self.compile_options_debug = [ - '/nologo', '/Od', '/MTd', '/Zi', '/W3', '/D_DEBUG' + '/nologo', '/Od', '/MDd', '/Zi', '/W3', '/D_DEBUG' ] ldflags = [ - '/nologo', '/INCREMENTAL:NO', '/LTCG', '/nodefaultlib:libucrt.lib', 'ucrt.lib', + '/nologo', '/INCREMENTAL:NO', '/LTCG' ] + if not self._vcruntime_redist: + ldflags.extend(('/nodefaultlib:libucrt.lib', 'ucrt.lib')) + ldflags_debug = [ - '/nologo', '/INCREMENTAL:NO', '/LTCG', '/DEBUG:FULL', '/nodefaultlib:libucrtd.lib', 'ucrtd.lib', + '/nologo', '/INCREMENTAL:NO', '/LTCG', '/DEBUG:FULL' ] self.ldflags_exe = [*ldflags, '/MANIFEST:EMBED,ID=1'] @@ -446,15 +482,29 @@ if extra_postargs: ld_args.extend(extra_postargs) - self.mkpath(os.path.dirname(output_filename)) + output_dir = os.path.dirname(os.path.abspath(output_filename)) + self.mkpath(output_dir) try: log.debug('Executing "%s" %s', self.linker, ' '.join(ld_args)) self.spawn([self.linker] + ld_args) + self._copy_vcruntime(output_dir) except DistutilsExecError as msg: raise LinkError(msg) else: log.debug("skipping %s (up-to-date)", output_filename) + def _copy_vcruntime(self, output_dir): + vcruntime = self._vcruntime_redist + if not vcruntime or not os.path.isfile(vcruntime): + return + + if os.path.basename(vcruntime).lower() in _BUNDLED_DLLS: + return + + log.debug('Copying "%s"', vcruntime) + vcruntime = shutil.copy(vcruntime, output_dir) + os.chmod(vcruntime, stat.S_IWRITE) + def spawn(self, cmd): old_path = os.getenv('path') try: diff --git a/Lib/distutils/tests/test_msvccompiler.py b/Lib/distutils/tests/test_msvccompiler.py --- a/Lib/distutils/tests/test_msvccompiler.py +++ b/Lib/distutils/tests/test_msvccompiler.py @@ -3,6 +3,8 @@ import unittest import os +import distutils._msvccompiler as _msvccompiler + from distutils.errors import DistutilsPlatformError from distutils.tests import support from test.support import run_unittest @@ -19,19 +21,65 @@ # makes sure query_vcvarsall raises # a DistutilsPlatformError if the compiler # is not found - from distutils._msvccompiler import _get_vc_env - def _find_vcvarsall(): - return None + def _find_vcvarsall(plat_spec): + return None, None - import distutils._msvccompiler as _msvccompiler old_find_vcvarsall = _msvccompiler._find_vcvarsall _msvccompiler._find_vcvarsall = _find_vcvarsall try: - self.assertRaises(DistutilsPlatformError, _get_vc_env, + self.assertRaises(DistutilsPlatformError, + _msvccompiler._get_vc_env, 'wont find this version') finally: _msvccompiler._find_vcvarsall = old_find_vcvarsall + def test_compiler_options(self): + # suppress path to vcruntime from _find_vcvarsall to + # check that /MT is added to compile options + old_find_vcvarsall = _msvccompiler._find_vcvarsall + def _find_vcvarsall(plat_spec): + return old_find_vcvarsall(plat_spec)[0], None + _msvccompiler._find_vcvarsall = _find_vcvarsall + try: + compiler = _msvccompiler.MSVCCompiler() + compiler.initialize() + + self.assertIn('/MT', compiler.compile_options) + self.assertNotIn('/MD', compiler.compile_options) + finally: + _msvccompiler._find_vcvarsall = old_find_vcvarsall + + def test_vcruntime_copy(self): + # force path to a known file - it doesn't matter + # what we copy as long as its name is not in + # _msvccompiler._BUNDLED_DLLS + old_find_vcvarsall = _msvccompiler._find_vcvarsall + def _find_vcvarsall(plat_spec): + return old_find_vcvarsall(plat_spec)[0], __file__ + _msvccompiler._find_vcvarsall = _find_vcvarsall + try: + tempdir = self.mkdtemp() + compiler = _msvccompiler.MSVCCompiler() + compiler.initialize() + compiler._copy_vcruntime(tempdir) + + self.assertTrue(os.path.isfile(os.path.join( + tempdir, os.path.basename(__file__)))) + finally: + _msvccompiler._find_vcvarsall = old_find_vcvarsall + + def test_vcruntime_skip_copy(self): + tempdir = self.mkdtemp() + compiler = _msvccompiler.MSVCCompiler() + compiler.initialize() + dll = compiler._vcruntime_redist + self.assertTrue(os.path.isfile(dll)) + + compiler._copy_vcruntime(tempdir) + + self.assertFalse(os.path.isfile(os.path.join( + tempdir, os.path.basename(dll)))) + def test_suite(): return unittest.makeSuite(msvccompilerTestCase) diff --git a/PCbuild/pyproject.props b/PCbuild/pyproject.props --- a/PCbuild/pyproject.props +++ b/PCbuild/pyproject.props @@ -36,7 +36,7 @@ true true - MultiThreaded + MultiThreadedDLL true Level3 ProgramDatabase @@ -47,7 +47,7 @@ Disabled false - MultiThreadedDebug + MultiThreadedDebugDLL $(OutDir);%(AdditionalLibraryDirectories) @@ -57,9 +57,7 @@ true true true - ucrtd.lib;%(AdditionalDependencies) - ucrt.lib;%(AdditionalDependencies) - LIBC;libucrt.lib;libucrtd.lib;%(IgnoreSpecificDefaultLibraries) + LIBC;%(IgnoreSpecificDefaultLibraries) MachineX86 MachineX64 $(OutDir)$(TargetName).pgd diff --git a/PCbuild/tcl.vcxproj b/PCbuild/tcl.vcxproj --- a/PCbuild/tcl.vcxproj +++ b/PCbuild/tcl.vcxproj @@ -61,8 +61,8 @@ - ucrt - symbols,ucrt + msvcrt + symbols,msvcrt INSTALLDIR="$(OutDir.TrimEnd(`\`))" INSTALL_DIR="$(OutDir.TrimEnd(`\`))" DEBUGFLAGS="-wd4456 -wd4457 -wd4458 -wd4459 -wd4996" setlocal diff --git a/PCbuild/tix.vcxproj b/PCbuild/tix.vcxproj --- a/PCbuild/tix.vcxproj +++ b/PCbuild/tix.vcxproj @@ -57,8 +57,8 @@ BUILDDIRTOP="$(BuildDirTop)" TCL_DIR="$(tclDir.TrimEnd(`\`))" TK_DIR="$(tkDir.TrimEnd(`\`))" INSTALL_DIR="$(OutDir.TrimEnd(`\`))" - DEBUG=1 NODEBUG=0 UCRT=1 TCL_DBGX=g TK_DBGX=g - DEBUG=0 NODEBUG=1 UCRT=1 + DEBUG=1 NODEBUG=0 TCL_DBGX=g TK_DBGX=g + DEBUG=0 NODEBUG=1 setlocal @(ExpectedOutputs->'if not exist "%(FullPath)" goto build',' ') diff --git a/PCbuild/tk.vcxproj b/PCbuild/tk.vcxproj --- a/PCbuild/tk.vcxproj +++ b/PCbuild/tk.vcxproj @@ -60,8 +60,8 @@ - ucrt - symbols,ucrt + msvcrt + symbols,msvcrt TCLDIR="$(tclDir.TrimEnd(`\`))" INSTALLDIR="$(OutDir.TrimEnd(`\`))" DEBUGFLAGS="-wd4456 -wd4457 -wd4458 -wd4459 -wd4996" setlocal diff --git a/Tools/msi/build.bat b/Tools/msi/build.bat --- a/Tools/msi/build.bat +++ b/Tools/msi/build.bat @@ -22,15 +22,15 @@ call "%PCBUILD%env.bat" x86 if defined BUILDX86 ( - call "%PCBUILD%build.bat" -d + call "%PCBUILD%build.bat" -d -e if errorlevel 1 goto :eof - call "%PCBUILD%build.bat" + call "%PCBUILD%build.bat" -e if errorlevel 1 goto :eof ) if defined BUILDX64 ( - call "%PCBUILD%build.bat" -p x64 -d + call "%PCBUILD%build.bat" -p x64 -d -e if errorlevel 1 goto :eof - call "%PCBUILD%build.bat" -p x64 + call "%PCBUILD%build.bat" -p x64 -e if errorlevel 1 goto :eof ) diff --git a/Tools/msi/buildrelease.bat b/Tools/msi/buildrelease.bat --- a/Tools/msi/buildrelease.bat +++ b/Tools/msi/buildrelease.bat @@ -121,7 +121,7 @@ if not "%SKIPBUILD%" EQU "1" ( call "%PCBUILD%build.bat" -e -p %BUILD_PLAT% -d -t %TARGET% %CERTOPTS% if errorlevel 1 exit /B - call "%PCBUILD%build.bat" -p %BUILD_PLAT% -t %TARGET% %CERTOPTS% + call "%PCBUILD%build.bat" -e -p %BUILD_PLAT% -t %TARGET% %CERTOPTS% if errorlevel 1 exit /B @rem build.bat turns echo back on, so we disable it again @echo off diff --git a/Tools/msi/exe/exe_files.wxs b/Tools/msi/exe/exe_files.wxs --- a/Tools/msi/exe/exe_files.wxs +++ b/Tools/msi/exe/exe_files.wxs @@ -29,6 +29,9 @@ + + + diff --git a/Tools/msi/make_zip.proj b/Tools/msi/make_zip.proj --- a/Tools/msi/make_zip.proj +++ b/Tools/msi/make_zip.proj @@ -16,7 +16,8 @@ $(OutputPath)\en-us\$(TargetName)$(TargetExt) "$(PythonExe)" "$(MSBuildThisFileDirectory)\make_zip.py" $(Arguments) -e -o "$(TargetPath)" -t "$(IntermediateOutputPath)\zip_$(ArchName)" -a $(ArchName) - set DOC_FILENAME=python$(PythonVersion).chm + set DOC_FILENAME=python$(PythonVersion).chm +set VCREDIST_PATH=$(VS140COMNTOOLS)\..\..\VC\redist\$(Platform)\Microsoft.VC140.CRT diff --git a/Tools/msi/make_zip.py b/Tools/msi/make_zip.py --- a/Tools/msi/make_zip.py +++ b/Tools/msi/make_zip.py @@ -64,9 +64,6 @@ ('Tools/', 'Tools', '**/*', include_in_tools), ] -if os.getenv('DOC_FILENAME'): - FULL_LAYOUT.append(('Doc/', 'Doc/build/htmlhelp', os.getenv('DOC_FILENAME'), None)) - EMBED_LAYOUT = [ ('/', 'PCBuild/$arch', 'python*.exe', is_not_debug), ('/', 'PCBuild/$arch', '*.pyd', is_not_debug), @@ -74,6 +71,12 @@ ('python35.zip', 'Lib', '**/*', include_in_lib), ] +if os.getenv('DOC_FILENAME'): + FULL_LAYOUT.append(('Doc/', 'Doc/build/htmlhelp', os.getenv('DOC_FILENAME'), None)) +if os.getenv('VCREDIST_PATH'): + FULL_LAYOUT.append(('/', os.getenv('VCREDIST_PATH'), 'vcruntime*.dll', None)) + EMBED_LAYOUT.append(('/', os.getenv('VCREDIST_PATH'), 'vcruntime*.dll', None)) + def copy_to_layout(target, rel_sources): count = 0 diff --git a/Tools/msi/msi.props b/Tools/msi/msi.props --- a/Tools/msi/msi.props +++ b/Tools/msi/msi.props @@ -118,6 +118,9 @@ redist + + redist +