mirror of
https://github.com/python/cpython.git
synced 2025-07-24 11:44:31 +00:00
Issue #24798: _msvccompiler.py doesn't properly support manifests
This commit is contained in:
parent
00cf3c394a
commit
31202eaa5c
3 changed files with 52 additions and 238 deletions
|
@ -15,7 +15,6 @@ for older versions in distutils.msvc9compiler and distutils.msvccompiler.
|
|||
|
||||
import os
|
||||
import subprocess
|
||||
import re
|
||||
|
||||
from distutils.errors import DistutilsExecError, DistutilsPlatformError, \
|
||||
CompileError, LibError, LinkError
|
||||
|
@ -182,7 +181,8 @@ class MSVCCompiler(CCompiler) :
|
|||
raise DistutilsPlatformError("Unable to find a compatible "
|
||||
"Visual Studio installation.")
|
||||
|
||||
paths = vc_env.get('path', '').split(os.pathsep)
|
||||
self._paths = vc_env.get('path', '')
|
||||
paths = self._paths.split(os.pathsep)
|
||||
self.cc = _find_exe("cl.exe", paths)
|
||||
self.linker = _find_exe("link.exe", paths)
|
||||
self.lib = _find_exe("lib.exe", paths)
|
||||
|
@ -199,23 +199,41 @@ 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
|
||||
# later to dynamically link to ucrtbase but not vcruntime.
|
||||
self.compile_options = [
|
||||
'/nologo', '/Ox', '/MD', '/W3', '/GL', '/DNDEBUG'
|
||||
'/nologo', '/Ox', '/MT', '/W3', '/GL', '/DNDEBUG'
|
||||
]
|
||||
self.compile_options_debug = [
|
||||
'/nologo', '/Od', '/MDd', '/Zi', '/W3', '/D_DEBUG'
|
||||
'/nologo', '/Od', '/MTd', '/Zi', '/W3', '/D_DEBUG'
|
||||
]
|
||||
|
||||
self.ldflags_shared = [
|
||||
'/nologo', '/DLL', '/INCREMENTAL:NO', '/LTCG', '/nodefaultlib:libucrt.lib', 'ucrt.lib'
|
||||
ldflags = [
|
||||
'/nologo', '/INCREMENTAL:NO', '/LTCG', '/nodefaultlib:libucrt.lib', 'ucrt.lib',
|
||||
]
|
||||
self.ldflags_shared_debug = [
|
||||
'/nologo', '/DLL', '/INCREMENTAL:no', '/LTCG', '/DEBUG:FULL', '/nodefaultlib:libucrtd.lib', 'ucrtd.lib'
|
||||
]
|
||||
self.ldflags_static = [
|
||||
'/nologo'
|
||||
ldflags_debug = [
|
||||
'/nologo', '/INCREMENTAL:NO', '/LTCG', '/DEBUG:FULL', '/nodefaultlib:libucrtd.lib', 'ucrtd.lib',
|
||||
]
|
||||
|
||||
self.ldflags_exe = [*ldflags, '/MANIFEST:EMBED,ID=1']
|
||||
self.ldflags_exe_debug = [*ldflags_debug, '/MANIFEST:EMBED,ID=1']
|
||||
self.ldflags_shared = [*ldflags, '/DLL', '/MANIFEST:EMBED,ID=2', '/MANIFESTUAC:NO']
|
||||
self.ldflags_shared_debug = [*ldflags_debug, '/DLL', '/MANIFEST:EMBED,ID=2', '/MANIFESTUAC:NO']
|
||||
self.ldflags_static = [*ldflags]
|
||||
self.ldflags_static_debug = [*ldflags_debug]
|
||||
|
||||
self._ldflags = {
|
||||
(CCompiler.EXECUTABLE, None): self.ldflags_exe,
|
||||
(CCompiler.EXECUTABLE, False): self.ldflags_exe,
|
||||
(CCompiler.EXECUTABLE, True): self.ldflags_exe_debug,
|
||||
(CCompiler.SHARED_OBJECT, None): self.ldflags_shared,
|
||||
(CCompiler.SHARED_OBJECT, False): self.ldflags_shared,
|
||||
(CCompiler.SHARED_OBJECT, True): self.ldflags_shared_debug,
|
||||
(CCompiler.SHARED_LIBRARY, None): self.ldflags_static,
|
||||
(CCompiler.SHARED_LIBRARY, False): self.ldflags_static,
|
||||
(CCompiler.SHARED_LIBRARY, True): self.ldflags_static_debug,
|
||||
}
|
||||
|
||||
self.initialized = True
|
||||
|
||||
# -- Worker methods ------------------------------------------------
|
||||
|
@ -224,9 +242,12 @@ class MSVCCompiler(CCompiler) :
|
|||
source_filenames,
|
||||
strip_dir=0,
|
||||
output_dir=''):
|
||||
ext_map = {ext: self.obj_extension for ext in self.src_extensions}
|
||||
ext_map.update((ext, self.res_extension)
|
||||
for ext in self._rc_extensions + self._mc_extensions)
|
||||
ext_map = {
|
||||
**{ext: self.obj_extension for ext in self.src_extensions},
|
||||
**{ext: self.res_extension for ext in self._rc_extensions + self._mc_extensions},
|
||||
}
|
||||
|
||||
output_dir = output_dir or ''
|
||||
|
||||
def make_out_path(p):
|
||||
base, ext = os.path.splitext(p)
|
||||
|
@ -237,18 +258,17 @@ class MSVCCompiler(CCompiler) :
|
|||
if base.startswith((os.path.sep, os.path.altsep)):
|
||||
base = base[1:]
|
||||
try:
|
||||
return base + ext_map[ext]
|
||||
# XXX: This may produce absurdly long paths. We should check
|
||||
# the length of the result and trim base until we fit within
|
||||
# 260 characters.
|
||||
return os.path.join(output_dir, base + ext_map[ext])
|
||||
except LookupError:
|
||||
# Better to raise an exception instead of silently continuing
|
||||
# and later complain about sources and targets having
|
||||
# different lengths
|
||||
raise CompileError("Don't know how to compile {}".format(p))
|
||||
|
||||
output_dir = output_dir or ''
|
||||
return [
|
||||
os.path.join(output_dir, make_out_path(src_name))
|
||||
for src_name in source_filenames
|
||||
]
|
||||
return list(map(make_out_path, source_filenames))
|
||||
|
||||
|
||||
def compile(self, sources,
|
||||
|
@ -359,6 +379,7 @@ class MSVCCompiler(CCompiler) :
|
|||
if debug:
|
||||
pass # XXX what goes here?
|
||||
try:
|
||||
log.debug('Executing "%s" %s', self.lib, ' '.join(lib_args))
|
||||
self.spawn([self.lib] + lib_args)
|
||||
except DistutilsExecError as msg:
|
||||
raise LibError(msg)
|
||||
|
@ -399,14 +420,9 @@ class MSVCCompiler(CCompiler) :
|
|||
output_filename = os.path.join(output_dir, output_filename)
|
||||
|
||||
if self._need_link(objects, output_filename):
|
||||
ldflags = (self.ldflags_shared_debug if debug
|
||||
else self.ldflags_shared)
|
||||
if target_desc == CCompiler.EXECUTABLE:
|
||||
ldflags = ldflags[1:]
|
||||
ldflags = self._ldflags[target_desc, debug]
|
||||
|
||||
export_opts = []
|
||||
for sym in (export_symbols or []):
|
||||
export_opts.append("/EXPORT:" + sym)
|
||||
export_opts = ["/EXPORT:" + sym for sym in (export_symbols or [])]
|
||||
|
||||
ld_args = (ldflags + lib_opts + export_opts +
|
||||
objects + ['/OUT:' + output_filename])
|
||||
|
@ -425,8 +441,6 @@ class MSVCCompiler(CCompiler) :
|
|||
self.library_filename(dll_name))
|
||||
ld_args.append ('/IMPLIB:' + implib_file)
|
||||
|
||||
self.manifest_setup_ldargs(output_filename, build_temp, ld_args)
|
||||
|
||||
if extra_preargs:
|
||||
ld_args[:0] = extra_preargs
|
||||
if extra_postargs:
|
||||
|
@ -434,101 +448,20 @@ class MSVCCompiler(CCompiler) :
|
|||
|
||||
self.mkpath(os.path.dirname(output_filename))
|
||||
try:
|
||||
log.debug('Executing "%s" %s', self.linker, ' '.join(ld_args))
|
||||
self.spawn([self.linker] + ld_args)
|
||||
except DistutilsExecError as msg:
|
||||
raise LinkError(msg)
|
||||
|
||||
# embed the manifest
|
||||
# XXX - this is somewhat fragile - if mt.exe fails, distutils
|
||||
# will still consider the DLL up-to-date, but it will not have a
|
||||
# manifest. Maybe we should link to a temp file? OTOH, that
|
||||
# implies a build environment error that shouldn't go undetected.
|
||||
mfinfo = self.manifest_get_embed_info(target_desc, ld_args)
|
||||
if mfinfo is not None:
|
||||
mffilename, mfid = mfinfo
|
||||
out_arg = '-outputresource:{};{}'.format(output_filename, mfid)
|
||||
try:
|
||||
self.spawn([self.mt, '-nologo', '-manifest',
|
||||
mffilename, out_arg])
|
||||
except DistutilsExecError as msg:
|
||||
raise LinkError(msg)
|
||||
else:
|
||||
log.debug("skipping %s (up-to-date)", output_filename)
|
||||
|
||||
def manifest_setup_ldargs(self, output_filename, build_temp, ld_args):
|
||||
# If we need a manifest at all, an embedded manifest is recommended.
|
||||
# See MSDN article titled
|
||||
# "How to: Embed a Manifest Inside a C/C++ Application"
|
||||
# (currently at http://msdn2.microsoft.com/en-us/library/ms235591(VS.80).aspx)
|
||||
# Ask the linker to generate the manifest in the temp dir, so
|
||||
# we can check it, and possibly embed it, later.
|
||||
temp_manifest = os.path.join(
|
||||
build_temp,
|
||||
os.path.basename(output_filename) + ".manifest")
|
||||
ld_args.append('/MANIFESTFILE:' + temp_manifest)
|
||||
|
||||
def manifest_get_embed_info(self, target_desc, ld_args):
|
||||
# If a manifest should be embedded, return a tuple of
|
||||
# (manifest_filename, resource_id). Returns None if no manifest
|
||||
# should be embedded. See http://bugs.python.org/issue7833 for why
|
||||
# we want to avoid any manifest for extension modules if we can)
|
||||
for arg in ld_args:
|
||||
if arg.startswith("/MANIFESTFILE:"):
|
||||
temp_manifest = arg.split(":", 1)[1]
|
||||
break
|
||||
else:
|
||||
# no /MANIFESTFILE so nothing to do.
|
||||
return None
|
||||
if target_desc == CCompiler.EXECUTABLE:
|
||||
# by default, executables always get the manifest with the
|
||||
# CRT referenced.
|
||||
mfid = 1
|
||||
else:
|
||||
# Extension modules try and avoid any manifest if possible.
|
||||
mfid = 2
|
||||
temp_manifest = self._remove_visual_c_ref(temp_manifest)
|
||||
if temp_manifest is None:
|
||||
return None
|
||||
return temp_manifest, mfid
|
||||
|
||||
def _remove_visual_c_ref(self, manifest_file):
|
||||
def spawn(self, cmd):
|
||||
old_path = os.getenv('path')
|
||||
try:
|
||||
# Remove references to the Visual C runtime, so they will
|
||||
# fall through to the Visual C dependency of Python.exe.
|
||||
# This way, when installed for a restricted user (e.g.
|
||||
# runtimes are not in WinSxS folder, but in Python's own
|
||||
# folder), the runtimes do not need to be in every folder
|
||||
# with .pyd's.
|
||||
# Returns either the filename of the modified manifest or
|
||||
# None if no manifest should be embedded.
|
||||
manifest_f = open(manifest_file)
|
||||
try:
|
||||
manifest_buf = manifest_f.read()
|
||||
finally:
|
||||
manifest_f.close()
|
||||
pattern = re.compile(
|
||||
r"""<assemblyIdentity.*?name=("|')Microsoft\."""\
|
||||
r"""VC\d{2}\.CRT("|').*?(/>|</assemblyIdentity>)""",
|
||||
re.DOTALL)
|
||||
manifest_buf = re.sub(pattern, "", manifest_buf)
|
||||
pattern = "<dependentAssembly>\s*</dependentAssembly>"
|
||||
manifest_buf = re.sub(pattern, "", manifest_buf)
|
||||
# Now see if any other assemblies are referenced - if not, we
|
||||
# don't want a manifest embedded.
|
||||
pattern = re.compile(
|
||||
r"""<assemblyIdentity.*?name=(?:"|')(.+?)(?:"|')"""
|
||||
r""".*?(?:/>|</assemblyIdentity>)""", re.DOTALL)
|
||||
if re.search(pattern, manifest_buf) is None:
|
||||
return None
|
||||
|
||||
manifest_f = open(manifest_file, 'w')
|
||||
try:
|
||||
manifest_f.write(manifest_buf)
|
||||
return manifest_file
|
||||
finally:
|
||||
manifest_f.close()
|
||||
except OSError:
|
||||
pass
|
||||
os.environ['path'] = self._paths
|
||||
return super().spawn(cmd)
|
||||
finally:
|
||||
os.environ['path'] = old_path
|
||||
|
||||
# -- Miscellaneous methods -----------------------------------------
|
||||
# These are all used by the 'gen_lib_options() function, in
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue