Skip to content

Commit 0d20364

Browse files
bpo-38597: Never statically link extension initialization code on Windows (GH-18724)
(cherry picked from commit ce3a498) Co-authored-by: Steve Dower <[email protected]>
1 parent 9ddcb91 commit 0d20364

File tree

4 files changed

+13
-104
lines changed

4 files changed

+13
-104
lines changed

Lib/distutils/_msvccompiler.py

Lines changed: 7 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -97,28 +97,11 @@ def _find_vc2017():
9797
}
9898

9999
def _find_vcvarsall(plat_spec):
100+
# bpo-38597: Removed vcruntime return value
100101
_, best_dir = _find_vc2017()
101-
vcruntime = None
102-
103-
if plat_spec in PLAT_SPEC_TO_RUNTIME:
104-
vcruntime_plat = PLAT_SPEC_TO_RUNTIME[plat_spec]
105-
else:
106-
vcruntime_plat = 'x64' if 'amd64' in plat_spec else 'x86'
107-
108-
if best_dir:
109-
vcredist = os.path.join(best_dir, "..", "..", "redist", "MSVC", "**",
110-
vcruntime_plat, "Microsoft.VC14*.CRT", "vcruntime140.dll")
111-
try:
112-
import glob
113-
vcruntime = glob.glob(vcredist, recursive=True)[-1]
114-
except (ImportError, OSError, LookupError):
115-
vcruntime = None
116102

117103
if not best_dir:
118104
best_version, best_dir = _find_vc2015()
119-
if best_version:
120-
vcruntime = os.path.join(best_dir, 'redist', vcruntime_plat,
121-
"Microsoft.VC140.CRT", "vcruntime140.dll")
122105

123106
if not best_dir:
124107
log.debug("No suitable Visual C++ version found")
@@ -129,11 +112,7 @@ def _find_vcvarsall(plat_spec):
129112
log.debug("%s cannot be found", vcvarsall)
130113
return None, None
131114

132-
if not vcruntime or not os.path.isfile(vcruntime):
133-
log.debug("%s cannot be found", vcruntime)
134-
vcruntime = None
135-
136-
return vcvarsall, vcruntime
115+
return vcvarsall, None
137116

138117
def _get_vc_env(plat_spec):
139118
if os.getenv("DISTUTILS_USE_SDK"):
@@ -142,7 +121,7 @@ def _get_vc_env(plat_spec):
142121
for key, value in os.environ.items()
143122
}
144123

145-
vcvarsall, vcruntime = _find_vcvarsall(plat_spec)
124+
vcvarsall, _ = _find_vcvarsall(plat_spec)
146125
if not vcvarsall:
147126
raise DistutilsPlatformError("Unable to find vcvarsall.bat")
148127

@@ -163,8 +142,6 @@ def _get_vc_env(plat_spec):
163142
if key and value
164143
}
165144

166-
if vcruntime:
167-
env['py_vcruntime_redist'] = vcruntime
168145
return env
169146

170147
def _find_exe(exe, paths=None):
@@ -194,12 +171,6 @@ def _find_exe(exe, paths=None):
194171
'win-arm64' : 'x86_arm64'
195172
}
196173

197-
# A set containing the DLLs that are guaranteed to be available for
198-
# all micro versions of this Python version. Known extension
199-
# dependencies that are not in this set will be copied to the output
200-
# path.
201-
_BUNDLED_DLLS = frozenset(['vcruntime140.dll'])
202-
203174
class MSVCCompiler(CCompiler) :
204175
"""Concrete class that implements an interface to Microsoft Visual C++,
205176
as defined by the CCompiler abstract class."""
@@ -263,7 +234,6 @@ def initialize(self, plat_name=None):
263234
self.rc = _find_exe("rc.exe", paths) # resource compiler
264235
self.mc = _find_exe("mc.exe", paths) # message compiler
265236
self.mt = _find_exe("mt.exe", paths) # message compiler
266-
self._vcruntime_redist = vc_env.get('py_vcruntime_redist', '')
267237

268238
for dir in vc_env.get('include', '').split(os.pathsep):
269239
if dir:
@@ -274,13 +244,12 @@ def initialize(self, plat_name=None):
274244
self.add_library_dir(dir.rstrip(os.sep))
275245

276246
self.preprocess_options = None
277-
# If vcruntime_redist is available, link against it dynamically. Otherwise,
278-
# use /MT[d] to build statically, then switch from libucrt[d].lib to ucrt[d].lib
279-
# later to dynamically link to ucrtbase but not vcruntime.
247+
# bpo-38597: Always compile with dynamic linking
248+
# Future releases of Python 3.x will include all past
249+
# versions of vcruntime*.dll for compatibility.
280250
self.compile_options = [
281-
'/nologo', '/Ox', '/W3', '/GL', '/DNDEBUG'
251+
'/nologo', '/Ox', '/W3', '/GL', '/DNDEBUG', '/MD'
282252
]
283-
self.compile_options.append('/MD' if self._vcruntime_redist else '/MT')
284253

285254
self.compile_options_debug = [
286255
'/nologo', '/Od', '/MDd', '/Zi', '/W3', '/D_DEBUG'
@@ -289,8 +258,6 @@ def initialize(self, plat_name=None):
289258
ldflags = [
290259
'/nologo', '/INCREMENTAL:NO', '/LTCG'
291260
]
292-
if not self._vcruntime_redist:
293-
ldflags.extend(('/nodefaultlib:libucrt.lib', 'ucrt.lib'))
294261

295262
ldflags_debug = [
296263
'/nologo', '/INCREMENTAL:NO', '/LTCG', '/DEBUG:FULL'
@@ -532,24 +499,11 @@ def link(self,
532499
try:
533500
log.debug('Executing "%s" %s', self.linker, ' '.join(ld_args))
534501
self.spawn([self.linker] + ld_args)
535-
self._copy_vcruntime(output_dir)
536502
except DistutilsExecError as msg:
537503
raise LinkError(msg)
538504
else:
539505
log.debug("skipping %s (up-to-date)", output_filename)
540506

541-
def _copy_vcruntime(self, output_dir):
542-
vcruntime = self._vcruntime_redist
543-
if not vcruntime or not os.path.isfile(vcruntime):
544-
return
545-
546-
if os.path.basename(vcruntime).lower() in _BUNDLED_DLLS:
547-
return
548-
549-
log.debug('Copying "%s"', vcruntime)
550-
vcruntime = shutil.copy(vcruntime, output_dir)
551-
os.chmod(vcruntime, stat.S_IWRITE)
552-
553507
def spawn(self, cmd):
554508
old_path = os.getenv('path')
555509
try:

Lib/distutils/tests/test_msvccompiler.py

Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -32,57 +32,6 @@ def _find_vcvarsall(plat_spec):
3232
finally:
3333
_msvccompiler._find_vcvarsall = old_find_vcvarsall
3434

35-
def test_compiler_options(self):
36-
import distutils._msvccompiler as _msvccompiler
37-
# suppress path to vcruntime from _find_vcvarsall to
38-
# check that /MT is added to compile options
39-
old_find_vcvarsall = _msvccompiler._find_vcvarsall
40-
def _find_vcvarsall(plat_spec):
41-
return old_find_vcvarsall(plat_spec)[0], None
42-
_msvccompiler._find_vcvarsall = _find_vcvarsall
43-
try:
44-
compiler = _msvccompiler.MSVCCompiler()
45-
compiler.initialize()
46-
47-
self.assertIn('/MT', compiler.compile_options)
48-
self.assertNotIn('/MD', compiler.compile_options)
49-
finally:
50-
_msvccompiler._find_vcvarsall = old_find_vcvarsall
51-
52-
def test_vcruntime_copy(self):
53-
import distutils._msvccompiler as _msvccompiler
54-
# force path to a known file - it doesn't matter
55-
# what we copy as long as its name is not in
56-
# _msvccompiler._BUNDLED_DLLS
57-
old_find_vcvarsall = _msvccompiler._find_vcvarsall
58-
def _find_vcvarsall(plat_spec):
59-
return old_find_vcvarsall(plat_spec)[0], __file__
60-
_msvccompiler._find_vcvarsall = _find_vcvarsall
61-
try:
62-
tempdir = self.mkdtemp()
63-
compiler = _msvccompiler.MSVCCompiler()
64-
compiler.initialize()
65-
compiler._copy_vcruntime(tempdir)
66-
67-
self.assertTrue(os.path.isfile(os.path.join(
68-
tempdir, os.path.basename(__file__))))
69-
finally:
70-
_msvccompiler._find_vcvarsall = old_find_vcvarsall
71-
72-
def test_vcruntime_skip_copy(self):
73-
import distutils._msvccompiler as _msvccompiler
74-
75-
tempdir = self.mkdtemp()
76-
compiler = _msvccompiler.MSVCCompiler()
77-
compiler.initialize()
78-
dll = compiler._vcruntime_redist
79-
self.assertTrue(os.path.isfile(dll), dll or "<None>")
80-
81-
compiler._copy_vcruntime(tempdir)
82-
83-
self.assertFalse(os.path.isfile(os.path.join(
84-
tempdir, os.path.basename(dll))), dll or "<None>")
85-
8635
def test_get_vc_env_unicode(self):
8736
import distutils._msvccompiler as _msvccompiler
8837

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
:mod:`distutils` will no longer statically link :file:`vcruntime140.dll`
2+
when a redistributable version is unavailable. All future releases of
3+
CPython will include a copy of this DLL to ensure distributed extensions can
4+
continue to load.

PCbuild/pythoncore.vcxproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,8 @@
528528
<VCRuntimeDLL Include="$(VCRedistDir)\**\vcruntime*.dll" />
529529
</ItemGroup>
530530
<Target Name="_CopyVCRuntime" AfterTargets="Build" Inputs="@(VCRuntimeDLL)" Outputs="$(OutDir)%(Filename)%(Extension)">
531+
<!-- bpo-38597: When we switch to another VCRuntime DLL, include vcruntime140.dll as well -->
532+
<Warning Text="A copy of vcruntime140.dll is also required" Condition="!$(VCToolsRedistVersion.StartsWith(`14.`))" />
531533
<Copy SourceFiles="%(VCRuntimeDLL.FullPath)" DestinationFolder="$(OutDir)" />
532534
</Target>
533535
<Target Name="_CleanVCRuntime" AfterTargets="Clean">

0 commit comments

Comments
 (0)