summaryrefslogtreecommitdiffstats
path: root/Lib/distutils
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/distutils')
-rw-r--r--Lib/distutils/_msvccompiler.py76
-rw-r--r--Lib/distutils/tests/test_msvccompiler.py61
2 files changed, 119 insertions, 18 deletions
diff --git a/Lib/distutils/_msvccompiler.py b/Lib/distutils/_msvccompiler.py
index b344616..03a5f10 100644
--- a/Lib/distutils/_msvccompiler.py
+++ b/Lib/distutils/_msvccompiler.py
@@ -14,6 +14,8 @@ for older versions in distutils.msvc9compiler and distutils.msvccompiler.
# 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 @@ from distutils.util import get_platform
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 @@ def _find_vcvarsall():
) 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 @@ def _find_vcvarsall():
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
+
+ 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
+ return vcvarsall, vcruntime
def _get_vc_env(plat_spec):
if os.getenv("DISTUTILS_USE_SDK"):
@@ -67,7 +78,7 @@ def _get_vc_env(plat_spec):
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,13 +94,17 @@ def _get_vc_env(plat_spec):
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 @@ PLAT_TO_VCVARS = {
'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 @@ class MSVCCompiler(CCompiler) :
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 @@ class MSVCCompiler(CCompiler) :
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 @@ class MSVCCompiler(CCompiler) :
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
index 1f88907..c4d911f 100644
--- a/Lib/distutils/tests/test_msvccompiler.py
+++ b/Lib/distutils/tests/test_msvccompiler.py
@@ -16,22 +16,73 @@ class msvccompilerTestCase(support.TempdirManager,
unittest.TestCase):
def test_no_compiler(self):
+ import distutils._msvccompiler as _msvccompiler
# 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):
+ import distutils._msvccompiler as _msvccompiler
+ # 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):
+ import distutils._msvccompiler as _msvccompiler
+ # 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):
+ import distutils._msvccompiler as _msvccompiler
+
+ 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)