diff --git a/newsfragments/4606.removal.rst b/newsfragments/4606.removal.rst
new file mode 100644
index 0000000000..682bc193e6
--- /dev/null
+++ b/newsfragments/4606.removal.rst
@@ -0,0 +1 @@
+Synced with pypa/distutils@58fe058e4, including consolidating Visual Studio 2017 support (#4600, pypa/distutils#289), removal of deprecated legacy MSVC compiler modules (pypa/distutils#287), suppressing of errors when the home directory is missing (pypa/distutils#278), removal of wininst binaries (pypa/distutils#282).
diff --git a/setuptools/_distutils/_msvccompiler.py b/setuptools/_distutils/_msvccompiler.py
index b0322410c5..cc89ec1344 100644
--- a/setuptools/_distutils/_msvccompiler.py
+++ b/setuptools/_distutils/_msvccompiler.py
@@ -33,7 +33,7 @@
LibError,
LinkError,
)
-from .util import get_platform
+from .util import get_host_platform, get_platform
def _find_vc2015():
@@ -79,32 +79,40 @@ def _find_vc2017():
if not root:
return None, None
- try:
- path = subprocess.check_output(
- [
- os.path.join(
- root, "Microsoft Visual Studio", "Installer", "vswhere.exe"
- ),
- "-latest",
- "-prerelease",
- "-requires",
- "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
- "-property",
- "installationPath",
- "-products",
- "*",
- ],
- encoding="mbcs",
- errors="strict",
- ).strip()
- except (subprocess.CalledProcessError, OSError, UnicodeDecodeError):
- return None, None
+ variant = 'arm64' if get_platform() == 'win-arm64' else 'x86.x64'
+ suitable_components = (
+ f"Microsoft.VisualStudio.Component.VC.Tools.{variant}",
+ "Microsoft.VisualStudio.Workload.WDExpress",
+ )
+
+ for component in suitable_components:
+ # Workaround for `-requiresAny` (only available on VS 2017 > 15.6)
+ with contextlib.suppress(
+ subprocess.CalledProcessError, OSError, UnicodeDecodeError
+ ):
+ path = (
+ subprocess.check_output([
+ os.path.join(
+ root, "Microsoft Visual Studio", "Installer", "vswhere.exe"
+ ),
+ "-latest",
+ "-prerelease",
+ "-requires",
+ component,
+ "-property",
+ "installationPath",
+ "-products",
+ "*",
+ ])
+ .decode(encoding="mbcs", errors="strict")
+ .strip()
+ )
- path = os.path.join(path, "VC", "Auxiliary", "Build")
- if os.path.isdir(path):
- return 15, path
+ path = os.path.join(path, "VC", "Auxiliary", "Build")
+ if os.path.isdir(path):
+ return 15, path
- return None, None
+ return None, None # no suitable component found
PLAT_SPEC_TO_RUNTIME = {
@@ -140,7 +148,11 @@ def _get_vc_env(plat_spec):
vcvarsall, _ = _find_vcvarsall(plat_spec)
if not vcvarsall:
- raise DistutilsPlatformError("Unable to find vcvarsall.bat")
+ raise DistutilsPlatformError(
+ 'Microsoft Visual C++ 14.0 or greater is required. '
+ 'Get it with "Microsoft C++ Build Tools": '
+ 'https://visualstudio.microsoft.com/visual-cpp-build-tools/'
+ )
try:
out = subprocess.check_output(
@@ -178,17 +190,43 @@ def _find_exe(exe, paths=None):
return exe
-# A map keyed by get_platform() return values to values accepted by
-# 'vcvarsall.bat'. Always cross-compile from x86 to work with the
-# lighter-weight MSVC installs that do not include native 64-bit tools.
-PLAT_TO_VCVARS = {
+_vcvars_names = {
'win32': 'x86',
- 'win-amd64': 'x86_amd64',
- 'win-arm32': 'x86_arm',
- 'win-arm64': 'x86_arm64',
+ 'win-amd64': 'amd64',
+ 'win-arm32': 'arm',
+ 'win-arm64': 'arm64',
}
+def _get_vcvars_spec(host_platform, platform):
+ """
+ Given a host platform and platform, determine the spec for vcvarsall.
+
+ Uses the native MSVC host if the host platform would need expensive
+ emulation for x86.
+
+ >>> _get_vcvars_spec('win-arm64', 'win32')
+ 'arm64_x86'
+ >>> _get_vcvars_spec('win-arm64', 'win-amd64')
+ 'arm64_amd64'
+
+ Otherwise, always cross-compile from x86 to work with the
+ lighter-weight MSVC installs that do not include native 64-bit tools.
+
+ >>> _get_vcvars_spec('win32', 'win32')
+ 'x86'
+ >>> _get_vcvars_spec('win-arm32', 'win-arm32')
+ 'x86_arm'
+ >>> _get_vcvars_spec('win-amd64', 'win-arm64')
+ 'x86_arm64'
+ """
+ if host_platform != 'win-arm64':
+ host_platform = 'win32'
+ vc_hp = _vcvars_names[host_platform]
+ vc_plat = _vcvars_names[platform]
+ return vc_hp if vc_hp == vc_plat else f'{vc_hp}_{vc_plat}'
+
+
class MSVCCompiler(CCompiler):
"""Concrete class that implements an interface to Microsoft Visual C++,
as defined by the CCompiler abstract class."""
@@ -242,13 +280,12 @@ def initialize(self, plat_name=None):
if plat_name is None:
plat_name = get_platform()
# sanity check for platforms to prevent obscure errors later.
- if plat_name not in PLAT_TO_VCVARS:
+ if plat_name not in _vcvars_names:
raise DistutilsPlatformError(
- f"--plat-name must be one of {tuple(PLAT_TO_VCVARS)}"
+ f"--plat-name must be one of {tuple(_vcvars_names)}"
)
- # Get the vcvarsall.bat spec for the requested platform.
- plat_spec = PLAT_TO_VCVARS[plat_name]
+ plat_spec = _get_vcvars_spec(get_host_platform(), get_platform())
vc_env = _get_vc_env(plat_spec)
if not vc_env:
diff --git a/setuptools/_distutils/archive_util.py b/setuptools/_distutils/archive_util.py
index 07cd97f4d0..cc4699b1a3 100644
--- a/setuptools/_distutils/archive_util.py
+++ b/setuptools/_distutils/archive_util.py
@@ -266,8 +266,7 @@ def make_archive(
raise ValueError(f"unknown archive format '{format}'")
func = format_info[0]
- for arg, val in format_info[1]:
- kwargs[arg] = val
+ kwargs.update(format_info[1])
if format != 'zip':
kwargs['owner'] = owner
diff --git a/setuptools/_distutils/command/build_ext.py b/setuptools/_distutils/command/build_ext.py
index cf475fe824..a7e3038be6 100644
--- a/setuptools/_distutils/command/build_ext.py
+++ b/setuptools/_distutils/command/build_ext.py
@@ -638,8 +638,7 @@ def swig_sources(self, sources, extension):
# Do not override commandline arguments
if not self.swig_opts:
- for o in extension.swig_opts:
- swig_cmd.append(o)
+ swig_cmd.extend(extension.swig_opts)
for source in swig_sources:
target = swig_targets[source]
diff --git a/setuptools/_distutils/command/install.py b/setuptools/_distutils/command/install.py
index 1fc09eef89..b83e061e02 100644
--- a/setuptools/_distutils/command/install.py
+++ b/setuptools/_distutils/command/install.py
@@ -680,7 +680,7 @@ def create_home_path(self):
if not self.user:
return
home = convert_path(os.path.expanduser("~"))
- for _name, path in self.config_vars.items():
+ for path in self.config_vars.values():
if str(path).startswith(home) and not os.path.isdir(path):
self.debug_print(f"os.makedirs('{path}', 0o700)")
os.makedirs(path, 0o700)
diff --git a/setuptools/_distutils/command/install_lib.py b/setuptools/_distutils/command/install_lib.py
index 01579d46b4..4c1230a286 100644
--- a/setuptools/_distutils/command/install_lib.py
+++ b/setuptools/_distutils/command/install_lib.py
@@ -81,9 +81,9 @@ def finalize_options(self):
if not isinstance(self.optimize, int):
try:
self.optimize = int(self.optimize)
- if self.optimize not in (0, 1, 2):
- raise AssertionError
- except (ValueError, AssertionError):
+ except ValueError:
+ pass
+ if self.optimize not in (0, 1, 2):
raise DistutilsOptionError("optimize must be 0, 1, or 2")
def run(self):
diff --git a/setuptools/_distutils/command/wininst-10.0-amd64.exe b/setuptools/_distutils/command/wininst-10.0-amd64.exe
deleted file mode 100644
index 6fa0dce163..0000000000
Binary files a/setuptools/_distutils/command/wininst-10.0-amd64.exe and /dev/null differ
diff --git a/setuptools/_distutils/command/wininst-10.0.exe b/setuptools/_distutils/command/wininst-10.0.exe
deleted file mode 100644
index afc3bc6c14..0000000000
Binary files a/setuptools/_distutils/command/wininst-10.0.exe and /dev/null differ
diff --git a/setuptools/_distutils/command/wininst-14.0-amd64.exe b/setuptools/_distutils/command/wininst-14.0-amd64.exe
deleted file mode 100644
index 253c2e2ecc..0000000000
Binary files a/setuptools/_distutils/command/wininst-14.0-amd64.exe and /dev/null differ
diff --git a/setuptools/_distutils/command/wininst-14.0.exe b/setuptools/_distutils/command/wininst-14.0.exe
deleted file mode 100644
index 46f5f35667..0000000000
Binary files a/setuptools/_distutils/command/wininst-14.0.exe and /dev/null differ
diff --git a/setuptools/_distutils/command/wininst-6.0.exe b/setuptools/_distutils/command/wininst-6.0.exe
deleted file mode 100644
index f57c855a61..0000000000
Binary files a/setuptools/_distutils/command/wininst-6.0.exe and /dev/null differ
diff --git a/setuptools/_distutils/command/wininst-7.1.exe b/setuptools/_distutils/command/wininst-7.1.exe
deleted file mode 100644
index 1433bc1ad3..0000000000
Binary files a/setuptools/_distutils/command/wininst-7.1.exe and /dev/null differ
diff --git a/setuptools/_distutils/command/wininst-8.0.exe b/setuptools/_distutils/command/wininst-8.0.exe
deleted file mode 100644
index 7403bfabf5..0000000000
Binary files a/setuptools/_distutils/command/wininst-8.0.exe and /dev/null differ
diff --git a/setuptools/_distutils/command/wininst-9.0-amd64.exe b/setuptools/_distutils/command/wininst-9.0-amd64.exe
deleted file mode 100644
index 94fbd4341b..0000000000
Binary files a/setuptools/_distutils/command/wininst-9.0-amd64.exe and /dev/null differ
diff --git a/setuptools/_distutils/command/wininst-9.0.exe b/setuptools/_distutils/command/wininst-9.0.exe
deleted file mode 100644
index 2ec261f9fd..0000000000
Binary files a/setuptools/_distutils/command/wininst-9.0.exe and /dev/null differ
diff --git a/setuptools/_distutils/cygwinccompiler.py b/setuptools/_distutils/cygwinccompiler.py
index ce412e8329..18b1b3557b 100644
--- a/setuptools/_distutils/cygwinccompiler.py
+++ b/setuptools/_distutils/cygwinccompiler.py
@@ -172,8 +172,7 @@ def link(
# Generate .def file
contents = [f"LIBRARY {os.path.basename(output_filename)}", "EXPORTS"]
- for sym in export_symbols:
- contents.append(sym)
+ contents.extend(export_symbols)
self.execute(write_file, (def_file, contents), f"writing {def_file}")
# next add options for def-file
@@ -309,6 +308,9 @@ def check_config_h():
fn = sysconfig.get_config_h_filename()
try:
config_h = pathlib.Path(fn).read_text(encoding='utf-8')
+ except OSError as exc:
+ return (CONFIG_H_UNCERTAIN, f"couldn't read '{fn}': {exc.strerror}")
+ else:
substring = '__GNUC__'
if substring in config_h:
code = CONFIG_H_OK
@@ -317,8 +319,6 @@ def check_config_h():
code = CONFIG_H_NOTOK
mention_inflected = 'does not mention'
return code, f"{fn!r} {mention_inflected} {substring!r}"
- except OSError as exc:
- return (CONFIG_H_UNCERTAIN, f"couldn't read '{fn}': {exc.strerror}")
def is_cygwincc(cc):
diff --git a/setuptools/_distutils/dist.py b/setuptools/_distutils/dist.py
index 115302b3e7..60edc5b514 100644
--- a/setuptools/_distutils/dist.py
+++ b/setuptools/_distutils/dist.py
@@ -10,17 +10,12 @@
import pathlib
import re
import sys
+import warnings
from collections.abc import Iterable
from email import message_from_file
-from ._vendor.packaging.utils import canonicalize_name, canonicalize_version
-
-try:
- import warnings
-except ImportError:
- warnings = None
-
from ._log import log
+from ._vendor.packaging.utils import canonicalize_name, canonicalize_version
from .debug import DEBUG
from .errors import (
DistutilsArgError,
@@ -249,10 +244,7 @@ def __init__(self, attrs=None): # noqa: C901
attrs['license'] = attrs['licence']
del attrs['licence']
msg = "'licence' distribution option is deprecated; use 'license'"
- if warnings is not None:
- warnings.warn(msg)
- else:
- sys.stderr.write(msg + "\n")
+ warnings.warn(msg)
# Now work on the rest of the attributes. Any attribute that's
# not already defined is invalid!
@@ -354,7 +346,8 @@ def _gen_paths(self):
prefix = '.' * (os.name == 'posix')
filename = prefix + 'pydistutils.cfg'
if self.want_user_cfg:
- yield pathlib.Path('~').expanduser() / filename
+ with contextlib.suppress(RuntimeError):
+ yield pathlib.Path('~').expanduser() / filename
# All platforms support local setup.cfg
yield pathlib.Path('setup.cfg')
@@ -741,9 +734,7 @@ def print_commands(self):
import distutils.command
std_commands = distutils.command.__all__
- is_std = set()
- for cmd in std_commands:
- is_std.add(cmd)
+ is_std = set(std_commands)
extra_commands = [cmd for cmd in self.cmdclass.keys() if cmd not in is_std]
@@ -769,9 +760,7 @@ def get_command_list(self):
import distutils.command
std_commands = distutils.command.__all__
- is_std = set()
- for cmd in std_commands:
- is_std.add(cmd)
+ is_std = set(std_commands)
extra_commands = [cmd for cmd in self.cmdclass.keys() if cmd not in is_std]
diff --git a/setuptools/_distutils/extension.py b/setuptools/_distutils/extension.py
index b302082f7a..33159079c1 100644
--- a/setuptools/_distutils/extension.py
+++ b/setuptools/_distutils/extension.py
@@ -105,7 +105,7 @@ def __init__(
**kw, # To catch unknown keywords
):
if not isinstance(name, str):
- raise AssertionError("'name' must be a string")
+ raise AssertionError("'name' must be a string") # noqa: TRY004
if not (
isinstance(sources, list)
and all(isinstance(v, (str, os.PathLike)) for v in sources)
diff --git a/setuptools/_distutils/file_util.py b/setuptools/_distutils/file_util.py
index b19a5dcfa4..85ee4dafcb 100644
--- a/setuptools/_distutils/file_util.py
+++ b/setuptools/_distutils/file_util.py
@@ -140,12 +140,13 @@ def copy_file( # noqa: C901
if not (os.path.exists(dst) and os.path.samefile(src, dst)):
try:
os.link(src, dst)
- return (dst, 1)
except OSError:
# If hard linking fails, fall back on copying file
# (some special filesystems don't support hard linking
# even under Unix, see issue #8876).
pass
+ else:
+ return (dst, 1)
elif link == 'sym':
if not (os.path.exists(dst) and os.path.samefile(src, dst)):
os.symlink(src, dst)
diff --git a/setuptools/_distutils/msvc9compiler.py b/setuptools/_distutils/msvc9compiler.py
deleted file mode 100644
index 4c70848730..0000000000
--- a/setuptools/_distutils/msvc9compiler.py
+++ /dev/null
@@ -1,822 +0,0 @@
-"""distutils.msvc9compiler
-
-Contains MSVCCompiler, an implementation of the abstract CCompiler class
-for the Microsoft Visual Studio 2008.
-
-The module is compatible with VS 2005 and VS 2008. You can find legacy support
-for older versions of VS in distutils.msvccompiler.
-"""
-
-# Written by Perry Stoll
-# hacked by Robin Becker and Thomas Heller to do a better job of
-# finding DevStudio (through the registry)
-# ported to VS2005 and VS 2008 by Christian Heimes
-
-import os
-import re
-import subprocess
-import sys
-import warnings
-import winreg
-
-from ._log import log
-from .ccompiler import CCompiler, gen_lib_options
-from .errors import (
- CompileError,
- DistutilsExecError,
- DistutilsPlatformError,
- LibError,
- LinkError,
-)
-from .util import get_platform
-
-warnings.warn(
- "msvc9compiler is deprecated and slated to be removed "
- "in the future. Please discontinue use or file an issue "
- "with pypa/distutils describing your use case.",
- DeprecationWarning,
-)
-
-RegOpenKeyEx = winreg.OpenKeyEx
-RegEnumKey = winreg.EnumKey
-RegEnumValue = winreg.EnumValue
-RegError = winreg.error
-
-HKEYS = (
- winreg.HKEY_USERS,
- winreg.HKEY_CURRENT_USER,
- winreg.HKEY_LOCAL_MACHINE,
- winreg.HKEY_CLASSES_ROOT,
-)
-
-NATIVE_WIN64 = sys.platform == 'win32' and sys.maxsize > 2**32
-if NATIVE_WIN64:
- # Visual C++ is a 32-bit application, so we need to look in
- # the corresponding registry branch, if we're running a
- # 64-bit Python on Win64
- VS_BASE = r"Software\Wow6432Node\Microsoft\VisualStudio\%0.1f"
- WINSDK_BASE = r"Software\Wow6432Node\Microsoft\Microsoft SDKs\Windows"
- NET_BASE = r"Software\Wow6432Node\Microsoft\.NETFramework"
-else:
- VS_BASE = r"Software\Microsoft\VisualStudio\%0.1f"
- WINSDK_BASE = r"Software\Microsoft\Microsoft SDKs\Windows"
- NET_BASE = r"Software\Microsoft\.NETFramework"
-
-# A map keyed by get_platform() return values to values accepted by
-# 'vcvarsall.bat'. Note a cross-compile may combine these (eg, 'x86_amd64' is
-# the param to cross-compile on x86 targeting amd64.)
-PLAT_TO_VCVARS = {
- 'win32': 'x86',
- 'win-amd64': 'amd64',
-}
-
-
-class Reg:
- """Helper class to read values from the registry"""
-
- def get_value(cls, path, key):
- for base in HKEYS:
- d = cls.read_values(base, path)
- if d and key in d:
- return d[key]
- raise KeyError(key)
-
- get_value = classmethod(get_value)
-
- def read_keys(cls, base, key):
- """Return list of registry keys."""
- try:
- handle = RegOpenKeyEx(base, key)
- except RegError:
- return None
- L = []
- i = 0
- while True:
- try:
- k = RegEnumKey(handle, i)
- except RegError:
- break
- L.append(k)
- i += 1
- return L
-
- read_keys = classmethod(read_keys)
-
- def read_values(cls, base, key):
- """Return dict of registry keys and values.
-
- All names are converted to lowercase.
- """
- try:
- handle = RegOpenKeyEx(base, key)
- except RegError:
- return None
- d = {}
- i = 0
- while True:
- try:
- name, value, type = RegEnumValue(handle, i)
- except RegError:
- break
- name = name.lower()
- d[cls.convert_mbcs(name)] = cls.convert_mbcs(value)
- i += 1
- return d
-
- read_values = classmethod(read_values)
-
- def convert_mbcs(s):
- dec = getattr(s, "decode", None)
- if dec is not None:
- try:
- s = dec("mbcs")
- except UnicodeError:
- pass
- return s
-
- convert_mbcs = staticmethod(convert_mbcs)
-
-
-class MacroExpander:
- def __init__(self, version):
- self.macros = {}
- self.vsbase = VS_BASE % version
- self.load_macros(version)
-
- def set_macro(self, macro, path, key):
- self.macros[f"$({macro})"] = Reg.get_value(path, key)
-
- def load_macros(self, version):
- self.set_macro("VCInstallDir", self.vsbase + r"\Setup\VC", "productdir")
- self.set_macro("VSInstallDir", self.vsbase + r"\Setup\VS", "productdir")
- self.set_macro("FrameworkDir", NET_BASE, "installroot")
- try:
- if version >= 8.0:
- self.set_macro("FrameworkSDKDir", NET_BASE, "sdkinstallrootv2.0")
- else:
- raise KeyError("sdkinstallrootv2.0")
- except KeyError:
- raise DistutilsPlatformError(
- """Python was built with Visual Studio 2008;
-extensions must be built with a compiler than can generate compatible binaries.
-Visual Studio 2008 was not found on this system. If you have Cygwin installed,
-you can try compiling with MingW32, by passing "-c mingw32" to setup.py."""
- )
-
- if version >= 9.0:
- self.set_macro("FrameworkVersion", self.vsbase, "clr version")
- self.set_macro("WindowsSdkDir", WINSDK_BASE, "currentinstallfolder")
- else:
- p = r"Software\Microsoft\NET Framework Setup\Product"
- for base in HKEYS:
- try:
- h = RegOpenKeyEx(base, p)
- except RegError:
- continue
- key = RegEnumKey(h, 0)
- d = Reg.get_value(base, rf"{p}\{key}")
- self.macros["$(FrameworkVersion)"] = d["version"]
-
- def sub(self, s):
- for k, v in self.macros.items():
- s = s.replace(k, v)
- return s
-
-
-def get_build_version():
- """Return the version of MSVC that was used to build Python.
-
- For Python 2.3 and up, the version number is included in
- sys.version. For earlier versions, assume the compiler is MSVC 6.
- """
- prefix = "MSC v."
- i = sys.version.find(prefix)
- if i == -1:
- return 6
- i = i + len(prefix)
- s, rest = sys.version[i:].split(" ", 1)
- majorVersion = int(s[:-2]) - 6
- if majorVersion >= 13:
- # v13 was skipped and should be v14
- majorVersion += 1
- minorVersion = int(s[2:3]) / 10.0
- # I don't think paths are affected by minor version in version 6
- if majorVersion == 6:
- minorVersion = 0
- if majorVersion >= 6:
- return majorVersion + minorVersion
- # else we don't know what version of the compiler this is
- return None
-
-
-def normalize_and_reduce_paths(paths):
- """Return a list of normalized paths with duplicates removed.
-
- The current order of paths is maintained.
- """
- # Paths are normalized so things like: /a and /a/ aren't both preserved.
- reduced_paths = []
- for p in paths:
- np = os.path.normpath(p)
- # XXX(nnorwitz): O(n**2), if reduced_paths gets long perhaps use a set.
- if np not in reduced_paths:
- reduced_paths.append(np)
- return reduced_paths
-
-
-def removeDuplicates(variable):
- """Remove duplicate values of an environment variable."""
- oldList = variable.split(os.pathsep)
- newList = []
- for i in oldList:
- if i not in newList:
- newList.append(i)
- newVariable = os.pathsep.join(newList)
- return newVariable
-
-
-def find_vcvarsall(version):
- """Find the vcvarsall.bat file
-
- At first it tries to find the productdir of VS 2008 in the registry. If
- that fails it falls back to the VS90COMNTOOLS env var.
- """
- vsbase = VS_BASE % version
- try:
- productdir = Reg.get_value(rf"{vsbase}\Setup\VC", "productdir")
- except KeyError:
- log.debug("Unable to find productdir in registry")
- productdir = None
-
- if not productdir or not os.path.isdir(productdir):
- toolskey = f"VS{version:0.0f}0COMNTOOLS"
- toolsdir = os.environ.get(toolskey, None)
-
- if toolsdir and os.path.isdir(toolsdir):
- productdir = os.path.join(toolsdir, os.pardir, os.pardir, "VC")
- productdir = os.path.abspath(productdir)
- if not os.path.isdir(productdir):
- log.debug(f"{productdir} is not a valid directory")
- return None
- else:
- log.debug(f"Env var {toolskey} is not set or invalid")
- if not productdir:
- log.debug("No productdir found")
- return None
- vcvarsall = os.path.join(productdir, "vcvarsall.bat")
- if os.path.isfile(vcvarsall):
- return vcvarsall
- log.debug("Unable to find vcvarsall.bat")
- return None
-
-
-def query_vcvarsall(version, arch="x86"):
- """Launch vcvarsall.bat and read the settings from its environment"""
- vcvarsall = find_vcvarsall(version)
- interesting = {"include", "lib", "libpath", "path"}
- result = {}
-
- if vcvarsall is None:
- raise DistutilsPlatformError("Unable to find vcvarsall.bat")
- log.debug("Calling 'vcvarsall.bat %s' (version=%s)", arch, version)
- popen = subprocess.Popen(
- f'"{vcvarsall}" {arch} & set',
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- )
- try:
- stdout, stderr = popen.communicate()
- if popen.wait() != 0:
- raise DistutilsPlatformError(stderr.decode("mbcs"))
-
- stdout = stdout.decode("mbcs")
- for line in stdout.split("\n"):
- line = Reg.convert_mbcs(line)
- if '=' not in line:
- continue
- line = line.strip()
- key, value = line.split('=', 1)
- key = key.lower()
- if key in interesting:
- if value.endswith(os.pathsep):
- value = value[:-1]
- result[key] = removeDuplicates(value)
-
- finally:
- popen.stdout.close()
- popen.stderr.close()
-
- if len(result) != len(interesting):
- raise ValueError(str(list(result.keys())))
-
- return result
-
-
-# More globals
-VERSION = get_build_version()
-# MACROS = MacroExpander(VERSION)
-
-
-class MSVCCompiler(CCompiler):
- """Concrete class that implements an interface to Microsoft Visual C++,
- as defined by the CCompiler abstract class."""
-
- compiler_type = 'msvc'
-
- # Just set this so CCompiler's constructor doesn't barf. We currently
- # don't use the 'set_executables()' bureaucracy provided by CCompiler,
- # as it really isn't necessary for this sort of single-compiler class.
- # Would be nice to have a consistent interface with UnixCCompiler,
- # though, so it's worth thinking about.
- executables = {}
-
- # Private class data (need to distinguish C from C++ source for compiler)
- _c_extensions = ['.c']
- _cpp_extensions = ['.cc', '.cpp', '.cxx']
- _rc_extensions = ['.rc']
- _mc_extensions = ['.mc']
-
- # Needed for the filename generation methods provided by the
- # base class, CCompiler.
- src_extensions = _c_extensions + _cpp_extensions + _rc_extensions + _mc_extensions
- res_extension = '.res'
- obj_extension = '.obj'
- static_lib_extension = '.lib'
- shared_lib_extension = '.dll'
- static_lib_format = shared_lib_format = '%s%s'
- exe_extension = '.exe'
-
- def __init__(self, verbose=False, dry_run=False, force=False):
- super().__init__(verbose, dry_run, force)
- self.__version = VERSION
- self.__root = r"Software\Microsoft\VisualStudio"
- # self.__macros = MACROS
- self.__paths = []
- # target platform (.plat_name is consistent with 'bdist')
- self.plat_name = None
- self.__arch = None # deprecated name
- self.initialized = False
-
- def initialize(self, plat_name=None): # noqa: C901
- # multi-init means we would need to check platform same each time...
- assert not self.initialized, "don't init multiple times"
- if self.__version < 8.0:
- raise DistutilsPlatformError(
- f"VC {self.__version:0.1f} is not supported by this module"
- )
- if plat_name is None:
- plat_name = get_platform()
- # sanity check for platforms to prevent obscure errors later.
- ok_plats = 'win32', 'win-amd64'
- if plat_name not in ok_plats:
- raise DistutilsPlatformError(f"--plat-name must be one of {ok_plats}")
-
- if (
- "DISTUTILS_USE_SDK" in os.environ
- and "MSSdk" in os.environ
- and self.find_exe("cl.exe")
- ):
- # Assume that the SDK set up everything alright; don't try to be
- # smarter
- self.cc = "cl.exe"
- self.linker = "link.exe"
- self.lib = "lib.exe"
- self.rc = "rc.exe"
- self.mc = "mc.exe"
- else:
- # On x86, 'vcvars32.bat amd64' creates an env that doesn't work;
- # to cross compile, you use 'x86_amd64'.
- # On AMD64, 'vcvars32.bat amd64' is a native build env; to cross
- # compile use 'x86' (ie, it runs the x86 compiler directly)
- if plat_name in (get_platform(), 'win32'):
- # native build or cross-compile to win32
- plat_spec = PLAT_TO_VCVARS[plat_name]
- else:
- # cross compile from win32 -> some 64bit
- plat_spec = (
- PLAT_TO_VCVARS[get_platform()] + '_' + PLAT_TO_VCVARS[plat_name]
- )
-
- vc_env = query_vcvarsall(VERSION, plat_spec)
-
- self.__paths = vc_env['path'].split(os.pathsep)
- os.environ['lib'] = vc_env['lib']
- os.environ['include'] = vc_env['include']
-
- if len(self.__paths) == 0:
- raise DistutilsPlatformError(
- f"Python was built with {self.__product}, "
- "and extensions need to be built with the same "
- "version of the compiler, but it isn't installed."
- )
-
- self.cc = self.find_exe("cl.exe")
- self.linker = self.find_exe("link.exe")
- self.lib = self.find_exe("lib.exe")
- self.rc = self.find_exe("rc.exe") # resource compiler
- self.mc = self.find_exe("mc.exe") # message compiler
- # self.set_path_env_var('lib')
- # self.set_path_env_var('include')
-
- # extend the MSVC path with the current path
- try:
- for p in os.environ['path'].split(';'):
- self.__paths.append(p)
- except KeyError:
- pass
- self.__paths = normalize_and_reduce_paths(self.__paths)
- os.environ['path'] = ";".join(self.__paths)
-
- self.preprocess_options = None
- if self.__arch == "x86":
- self.compile_options = ['/nologo', '/O2', '/MD', '/W3', '/DNDEBUG']
- self.compile_options_debug = [
- '/nologo',
- '/Od',
- '/MDd',
- '/W3',
- '/Z7',
- '/D_DEBUG',
- ]
- else:
- # Win64
- self.compile_options = ['/nologo', '/O2', '/MD', '/W3', '/GS-', '/DNDEBUG']
- self.compile_options_debug = [
- '/nologo',
- '/Od',
- '/MDd',
- '/W3',
- '/GS-',
- '/Z7',
- '/D_DEBUG',
- ]
-
- self.ldflags_shared = ['/DLL', '/nologo', '/INCREMENTAL:NO']
- if self.__version >= 7:
- self.ldflags_shared_debug = ['/DLL', '/nologo', '/INCREMENTAL:no', '/DEBUG']
- self.ldflags_static = ['/nologo']
-
- self.initialized = True
-
- # -- Worker methods ------------------------------------------------
-
- def object_filenames(self, source_filenames, strip_dir=False, output_dir=''):
- # Copied from ccompiler.py, extended to return .res as 'object'-file
- # for .rc input file
- if output_dir is None:
- output_dir = ''
- obj_names = []
- for src_name in source_filenames:
- (base, ext) = os.path.splitext(src_name)
- base = os.path.splitdrive(base)[1] # Chop off the drive
- base = base[os.path.isabs(base) :] # If abs, chop off leading /
- if ext not in self.src_extensions:
- # Better to raise an exception instead of silently continuing
- # and later complain about sources and targets having
- # different lengths
- raise CompileError(f"Don't know how to compile {src_name}")
- if strip_dir:
- base = os.path.basename(base)
- if ext in self._rc_extensions:
- obj_names.append(os.path.join(output_dir, base + self.res_extension))
- elif ext in self._mc_extensions:
- obj_names.append(os.path.join(output_dir, base + self.res_extension))
- else:
- obj_names.append(os.path.join(output_dir, base + self.obj_extension))
- return obj_names
-
- def compile( # noqa: C901
- self,
- sources,
- output_dir=None,
- macros=None,
- include_dirs=None,
- debug=False,
- extra_preargs=None,
- extra_postargs=None,
- depends=None,
- ):
- if not self.initialized:
- self.initialize()
- compile_info = self._setup_compile(
- output_dir, macros, include_dirs, sources, depends, extra_postargs
- )
- macros, objects, extra_postargs, pp_opts, build = compile_info
-
- compile_opts = extra_preargs or []
- compile_opts.append('/c')
- if debug:
- compile_opts.extend(self.compile_options_debug)
- else:
- compile_opts.extend(self.compile_options)
-
- for obj in objects:
- try:
- src, ext = build[obj]
- except KeyError:
- continue
- if debug:
- # pass the full pathname to MSVC in debug mode,
- # this allows the debugger to find the source file
- # without asking the user to browse for it
- src = os.path.abspath(src)
-
- if ext in self._c_extensions:
- input_opt = "/Tc" + src
- elif ext in self._cpp_extensions:
- input_opt = "/Tp" + src
- elif ext in self._rc_extensions:
- # compile .RC to .RES file
- input_opt = src
- output_opt = "/fo" + obj
- try:
- self.spawn([self.rc] + pp_opts + [output_opt] + [input_opt])
- except DistutilsExecError as msg:
- raise CompileError(msg)
- continue
- elif ext in self._mc_extensions:
- # Compile .MC to .RC file to .RES file.
- # * '-h dir' specifies the directory for the
- # generated include file
- # * '-r dir' specifies the target directory of the
- # generated RC file and the binary message resource
- # it includes
- #
- # For now (since there are no options to change this),
- # we use the source-directory for the include file and
- # the build directory for the RC file and message
- # resources. This works at least for win32all.
- h_dir = os.path.dirname(src)
- rc_dir = os.path.dirname(obj)
- try:
- # first compile .MC to .RC and .H file
- self.spawn([self.mc] + ['-h', h_dir, '-r', rc_dir] + [src])
- base, _ = os.path.splitext(os.path.basename(src))
- rc_file = os.path.join(rc_dir, base + '.rc')
- # then compile .RC to .RES file
- self.spawn([self.rc] + ["/fo" + obj] + [rc_file])
-
- except DistutilsExecError as msg:
- raise CompileError(msg)
- continue
- else:
- # how to handle this file?
- raise CompileError(f"Don't know how to compile {src} to {obj}")
-
- output_opt = "/Fo" + obj
- try:
- self.spawn(
- [self.cc]
- + compile_opts
- + pp_opts
- + [input_opt, output_opt]
- + extra_postargs
- )
- except DistutilsExecError as msg:
- raise CompileError(msg)
-
- return objects
-
- def create_static_lib(
- self, objects, output_libname, output_dir=None, debug=False, target_lang=None
- ):
- if not self.initialized:
- self.initialize()
- (objects, output_dir) = self._fix_object_args(objects, output_dir)
- output_filename = self.library_filename(output_libname, output_dir=output_dir)
-
- if self._need_link(objects, output_filename):
- lib_args = objects + ['/OUT:' + output_filename]
- if debug:
- pass # XXX what goes here?
- try:
- self.spawn([self.lib] + lib_args)
- except DistutilsExecError as msg:
- raise LibError(msg)
- else:
- log.debug("skipping %s (up-to-date)", output_filename)
-
- def link( # noqa: C901
- self,
- target_desc,
- objects,
- output_filename,
- output_dir=None,
- libraries=None,
- library_dirs=None,
- runtime_library_dirs=None,
- export_symbols=None,
- debug=False,
- extra_preargs=None,
- extra_postargs=None,
- build_temp=None,
- target_lang=None,
- ):
- if not self.initialized:
- self.initialize()
- (objects, output_dir) = self._fix_object_args(objects, output_dir)
- fixed_args = self._fix_lib_args(libraries, library_dirs, runtime_library_dirs)
- (libraries, library_dirs, runtime_library_dirs) = fixed_args
-
- if runtime_library_dirs:
- self.warn(
- "I don't know what to do with 'runtime_library_dirs': "
- + str(runtime_library_dirs)
- )
-
- lib_opts = gen_lib_options(self, library_dirs, runtime_library_dirs, libraries)
- if output_dir is not None:
- output_filename = os.path.join(output_dir, output_filename)
-
- if self._need_link(objects, output_filename):
- if target_desc == CCompiler.EXECUTABLE:
- if debug:
- ldflags = self.ldflags_shared_debug[1:]
- else:
- ldflags = self.ldflags_shared[1:]
- else:
- if debug:
- ldflags = self.ldflags_shared_debug
- else:
- ldflags = self.ldflags_shared
-
- export_opts = [f"/EXPORT:{sym}" for sym in export_symbols or []]
-
- ld_args = (
- ldflags + lib_opts + export_opts + objects + ['/OUT:' + output_filename]
- )
-
- # The MSVC linker generates .lib and .exp files, which cannot be
- # suppressed by any linker switches. The .lib files may even be
- # needed! Make sure they are generated in the temporary build
- # directory. Since they have different names for debug and release
- # builds, they can go into the same directory.
- build_temp = os.path.dirname(objects[0])
- if export_symbols is not None:
- (dll_name, dll_ext) = os.path.splitext(
- os.path.basename(output_filename)
- )
- implib_file = os.path.join(build_temp, 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:
- ld_args.extend(extra_postargs)
-
- self.mkpath(os.path.dirname(output_filename))
- try:
- 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 = f'-outputresource:{output_filename};{mfid}'
- try:
- self.spawn(['mt.exe', '-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
- # "Understanding manifest generation for C/C++ programs"
- # (currently at https://learn.microsoft.com/en-us/cpp/build/understanding-manifest-generation-for-c-cpp-programs)
- # 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 https://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):
- 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"""|)""",
- re.DOTALL,
- )
- manifest_buf = re.sub(pattern, "", manifest_buf)
- pattern = r"\s*"
- 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"""|)""",
- 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
-
- # -- Miscellaneous methods -----------------------------------------
- # These are all used by the 'gen_lib_options() function, in
- # ccompiler.py.
-
- def library_dir_option(self, dir):
- return "/LIBPATH:" + dir
-
- def runtime_library_dir_option(self, dir):
- raise DistutilsPlatformError(
- "don't know how to set runtime library search path for MSVC++"
- )
-
- def library_option(self, lib):
- return self.library_filename(lib)
-
- def find_library_file(self, dirs, lib, debug=False):
- # Prefer a debugging library if found (and requested), but deal
- # with it if we don't have one.
- if debug:
- try_names = [lib + "_d", lib]
- else:
- try_names = [lib]
- for dir in dirs:
- for name in try_names:
- libfile = os.path.join(dir, self.library_filename(name))
- if os.path.exists(libfile):
- return libfile
- else:
- # Oops, didn't find it in *any* of 'dirs'
- return None
-
- # Helper methods for using the MSVC registry settings
-
- def find_exe(self, exe):
- """Return path to an MSVC executable program.
-
- Tries to find the program in several places: first, one of the
- MSVC program search paths from the registry; next, the directories
- in the PATH environment variable. If any of those work, return an
- absolute path that is known to exist. If none of them work, just
- return the original program name, 'exe'.
- """
- for p in self.__paths:
- fn = os.path.join(os.path.abspath(p), exe)
- if os.path.isfile(fn):
- return fn
-
- # didn't find it; try existing path
- for p in os.environ['Path'].split(';'):
- fn = os.path.join(os.path.abspath(p), exe)
- if os.path.isfile(fn):
- return fn
-
- return exe
diff --git a/setuptools/_distutils/msvccompiler.py b/setuptools/_distutils/msvccompiler.py
deleted file mode 100644
index 2a5e61d78d..0000000000
--- a/setuptools/_distutils/msvccompiler.py
+++ /dev/null
@@ -1,687 +0,0 @@
-"""distutils.msvccompiler
-
-Contains MSVCCompiler, an implementation of the abstract CCompiler class
-for the Microsoft Visual Studio.
-"""
-
-# Written by Perry Stoll
-# hacked by Robin Becker and Thomas Heller to do a better job of
-# finding DevStudio (through the registry)
-
-import os
-import sys
-import warnings
-
-from ._log import log
-from .ccompiler import CCompiler, gen_lib_options
-from .errors import (
- CompileError,
- DistutilsExecError,
- DistutilsPlatformError,
- LibError,
- LinkError,
-)
-
-_can_read_reg = False
-try:
- import winreg
-
- _can_read_reg = True
- hkey_mod = winreg
-
- RegOpenKeyEx = winreg.OpenKeyEx
- RegEnumKey = winreg.EnumKey
- RegEnumValue = winreg.EnumValue
- RegError = winreg.error
-
-except ImportError:
- try:
- import win32api
- import win32con
-
- _can_read_reg = True
- hkey_mod = win32con
-
- RegOpenKeyEx = win32api.RegOpenKeyEx
- RegEnumKey = win32api.RegEnumKey
- RegEnumValue = win32api.RegEnumValue
- RegError = win32api.error
- except ImportError:
- log.info(
- "Warning: Can't read registry to find the "
- "necessary compiler setting\n"
- "Make sure that Python modules winreg, "
- "win32api or win32con are installed."
- )
- pass
-
-if _can_read_reg:
- HKEYS = (
- hkey_mod.HKEY_USERS,
- hkey_mod.HKEY_CURRENT_USER,
- hkey_mod.HKEY_LOCAL_MACHINE,
- hkey_mod.HKEY_CLASSES_ROOT,
- )
-
-
-warnings.warn(
- "msvccompiler is deprecated and slated to be removed "
- "in the future. Please discontinue use or file an issue "
- "with pypa/distutils describing your use case.",
- DeprecationWarning,
-)
-
-
-def read_keys(base, key):
- """Return list of registry keys."""
- try:
- handle = RegOpenKeyEx(base, key)
- except RegError:
- return None
- L = []
- i = 0
- while True:
- try:
- k = RegEnumKey(handle, i)
- except RegError:
- break
- L.append(k)
- i += 1
- return L
-
-
-def read_values(base, key):
- """Return dict of registry keys and values.
-
- All names are converted to lowercase.
- """
- try:
- handle = RegOpenKeyEx(base, key)
- except RegError:
- return None
- d = {}
- i = 0
- while True:
- try:
- name, value, type = RegEnumValue(handle, i)
- except RegError:
- break
- name = name.lower()
- d[convert_mbcs(name)] = convert_mbcs(value)
- i += 1
- return d
-
-
-def convert_mbcs(s):
- dec = getattr(s, "decode", None)
- if dec is not None:
- try:
- s = dec("mbcs")
- except UnicodeError:
- pass
- return s
-
-
-class MacroExpander:
- def __init__(self, version):
- self.macros = {}
- self.load_macros(version)
-
- def set_macro(self, macro, path, key):
- for base in HKEYS:
- d = read_values(base, path)
- if d:
- self.macros[f"$({macro})"] = d[key]
- break
-
- def load_macros(self, version):
- vsbase = rf"Software\Microsoft\VisualStudio\{version:0.1f}"
- self.set_macro("VCInstallDir", vsbase + r"\Setup\VC", "productdir")
- self.set_macro("VSInstallDir", vsbase + r"\Setup\VS", "productdir")
- net = r"Software\Microsoft\.NETFramework"
- self.set_macro("FrameworkDir", net, "installroot")
- try:
- if version > 7.0:
- self.set_macro("FrameworkSDKDir", net, "sdkinstallrootv1.1")
- else:
- self.set_macro("FrameworkSDKDir", net, "sdkinstallroot")
- except KeyError:
- raise DistutilsPlatformError(
- """Python was built with Visual Studio 2003;
-extensions must be built with a compiler than can generate compatible binaries.
-Visual Studio 2003 was not found on this system. If you have Cygwin installed,
-you can try compiling with MingW32, by passing "-c mingw32" to setup.py."""
- )
-
- p = r"Software\Microsoft\NET Framework Setup\Product"
- for base in HKEYS:
- try:
- h = RegOpenKeyEx(base, p)
- except RegError:
- continue
- key = RegEnumKey(h, 0)
- d = read_values(base, rf"{p}\{key}")
- self.macros["$(FrameworkVersion)"] = d["version"]
-
- def sub(self, s):
- for k, v in self.macros.items():
- s = s.replace(k, v)
- return s
-
-
-def get_build_version():
- """Return the version of MSVC that was used to build Python.
-
- For Python 2.3 and up, the version number is included in
- sys.version. For earlier versions, assume the compiler is MSVC 6.
- """
- prefix = "MSC v."
- i = sys.version.find(prefix)
- if i == -1:
- return 6
- i = i + len(prefix)
- s, rest = sys.version[i:].split(" ", 1)
- majorVersion = int(s[:-2]) - 6
- if majorVersion >= 13:
- # v13 was skipped and should be v14
- majorVersion += 1
- minorVersion = int(s[2:3]) / 10.0
- # I don't think paths are affected by minor version in version 6
- if majorVersion == 6:
- minorVersion = 0
- if majorVersion >= 6:
- return majorVersion + minorVersion
- # else we don't know what version of the compiler this is
- return None
-
-
-def get_build_architecture():
- """Return the processor architecture.
-
- Possible results are "Intel" or "AMD64".
- """
-
- prefix = " bit ("
- i = sys.version.find(prefix)
- if i == -1:
- return "Intel"
- j = sys.version.find(")", i)
- return sys.version[i + len(prefix) : j]
-
-
-def normalize_and_reduce_paths(paths):
- """Return a list of normalized paths with duplicates removed.
-
- The current order of paths is maintained.
- """
- # Paths are normalized so things like: /a and /a/ aren't both preserved.
- reduced_paths = []
- for p in paths:
- np = os.path.normpath(p)
- # XXX(nnorwitz): O(n**2), if reduced_paths gets long perhaps use a set.
- if np not in reduced_paths:
- reduced_paths.append(np)
- return reduced_paths
-
-
-class MSVCCompiler(CCompiler):
- """Concrete class that implements an interface to Microsoft Visual C++,
- as defined by the CCompiler abstract class."""
-
- compiler_type = 'msvc'
-
- # Just set this so CCompiler's constructor doesn't barf. We currently
- # don't use the 'set_executables()' bureaucracy provided by CCompiler,
- # as it really isn't necessary for this sort of single-compiler class.
- # Would be nice to have a consistent interface with UnixCCompiler,
- # though, so it's worth thinking about.
- executables = {}
-
- # Private class data (need to distinguish C from C++ source for compiler)
- _c_extensions = ['.c']
- _cpp_extensions = ['.cc', '.cpp', '.cxx']
- _rc_extensions = ['.rc']
- _mc_extensions = ['.mc']
-
- # Needed for the filename generation methods provided by the
- # base class, CCompiler.
- src_extensions = _c_extensions + _cpp_extensions + _rc_extensions + _mc_extensions
- res_extension = '.res'
- obj_extension = '.obj'
- static_lib_extension = '.lib'
- shared_lib_extension = '.dll'
- static_lib_format = shared_lib_format = '%s%s'
- exe_extension = '.exe'
-
- def __init__(self, verbose=False, dry_run=False, force=False):
- super().__init__(verbose, dry_run, force)
- self.__version = get_build_version()
- self.__arch = get_build_architecture()
- if self.__arch == "Intel":
- # x86
- if self.__version >= 7:
- self.__root = r"Software\Microsoft\VisualStudio"
- self.__macros = MacroExpander(self.__version)
- else:
- self.__root = r"Software\Microsoft\Devstudio"
- self.__product = f"Visual Studio version {self.__version}"
- else:
- # Win64. Assume this was built with the platform SDK
- self.__product = "Microsoft SDK compiler %s" % (self.__version + 6)
-
- self.initialized = False
-
- def initialize(self):
- self.__paths = []
- if (
- "DISTUTILS_USE_SDK" in os.environ
- and "MSSdk" in os.environ
- and self.find_exe("cl.exe")
- ):
- # Assume that the SDK set up everything alright; don't try to be
- # smarter
- self.cc = "cl.exe"
- self.linker = "link.exe"
- self.lib = "lib.exe"
- self.rc = "rc.exe"
- self.mc = "mc.exe"
- else:
- self.__paths = self.get_msvc_paths("path")
-
- if len(self.__paths) == 0:
- raise DistutilsPlatformError(
- f"Python was built with {self.__product}, "
- "and extensions need to be built with the same "
- "version of the compiler, but it isn't installed."
- )
-
- self.cc = self.find_exe("cl.exe")
- self.linker = self.find_exe("link.exe")
- self.lib = self.find_exe("lib.exe")
- self.rc = self.find_exe("rc.exe") # resource compiler
- self.mc = self.find_exe("mc.exe") # message compiler
- self.set_path_env_var('lib')
- self.set_path_env_var('include')
-
- # extend the MSVC path with the current path
- try:
- for p in os.environ['path'].split(';'):
- self.__paths.append(p)
- except KeyError:
- pass
- self.__paths = normalize_and_reduce_paths(self.__paths)
- os.environ['path'] = ";".join(self.__paths)
-
- self.preprocess_options = None
- if self.__arch == "Intel":
- self.compile_options = ['/nologo', '/O2', '/MD', '/W3', '/GX', '/DNDEBUG']
- self.compile_options_debug = [
- '/nologo',
- '/Od',
- '/MDd',
- '/W3',
- '/GX',
- '/Z7',
- '/D_DEBUG',
- ]
- else:
- # Win64
- self.compile_options = ['/nologo', '/O2', '/MD', '/W3', '/GS-', '/DNDEBUG']
- self.compile_options_debug = [
- '/nologo',
- '/Od',
- '/MDd',
- '/W3',
- '/GS-',
- '/Z7',
- '/D_DEBUG',
- ]
-
- self.ldflags_shared = ['/DLL', '/nologo', '/INCREMENTAL:NO']
- if self.__version >= 7:
- self.ldflags_shared_debug = ['/DLL', '/nologo', '/INCREMENTAL:no', '/DEBUG']
- else:
- self.ldflags_shared_debug = [
- '/DLL',
- '/nologo',
- '/INCREMENTAL:no',
- '/pdb:None',
- '/DEBUG',
- ]
- self.ldflags_static = ['/nologo']
-
- self.initialized = True
-
- # -- Worker methods ------------------------------------------------
-
- def object_filenames(self, source_filenames, strip_dir=False, output_dir=''):
- # Copied from ccompiler.py, extended to return .res as 'object'-file
- # for .rc input file
- if output_dir is None:
- output_dir = ''
- obj_names = []
- for src_name in source_filenames:
- (base, ext) = os.path.splitext(src_name)
- base = os.path.splitdrive(base)[1] # Chop off the drive
- base = base[os.path.isabs(base) :] # If abs, chop off leading /
- if ext not in self.src_extensions:
- # Better to raise an exception instead of silently continuing
- # and later complain about sources and targets having
- # different lengths
- raise CompileError(f"Don't know how to compile {src_name}")
- if strip_dir:
- base = os.path.basename(base)
- if ext in self._rc_extensions:
- obj_names.append(os.path.join(output_dir, base + self.res_extension))
- elif ext in self._mc_extensions:
- obj_names.append(os.path.join(output_dir, base + self.res_extension))
- else:
- obj_names.append(os.path.join(output_dir, base + self.obj_extension))
- return obj_names
-
- def compile( # noqa: C901
- self,
- sources,
- output_dir=None,
- macros=None,
- include_dirs=None,
- debug=False,
- extra_preargs=None,
- extra_postargs=None,
- depends=None,
- ):
- if not self.initialized:
- self.initialize()
- compile_info = self._setup_compile(
- output_dir, macros, include_dirs, sources, depends, extra_postargs
- )
- macros, objects, extra_postargs, pp_opts, build = compile_info
-
- compile_opts = extra_preargs or []
- compile_opts.append('/c')
- if debug:
- compile_opts.extend(self.compile_options_debug)
- else:
- compile_opts.extend(self.compile_options)
-
- for obj in objects:
- try:
- src, ext = build[obj]
- except KeyError:
- continue
- if debug:
- # pass the full pathname to MSVC in debug mode,
- # this allows the debugger to find the source file
- # without asking the user to browse for it
- src = os.path.abspath(src)
-
- if ext in self._c_extensions:
- input_opt = "/Tc" + src
- elif ext in self._cpp_extensions:
- input_opt = "/Tp" + src
- elif ext in self._rc_extensions:
- # compile .RC to .RES file
- input_opt = src
- output_opt = "/fo" + obj
- try:
- self.spawn([self.rc] + pp_opts + [output_opt] + [input_opt])
- except DistutilsExecError as msg:
- raise CompileError(msg)
- continue
- elif ext in self._mc_extensions:
- # Compile .MC to .RC file to .RES file.
- # * '-h dir' specifies the directory for the
- # generated include file
- # * '-r dir' specifies the target directory of the
- # generated RC file and the binary message resource
- # it includes
- #
- # For now (since there are no options to change this),
- # we use the source-directory for the include file and
- # the build directory for the RC file and message
- # resources. This works at least for win32all.
- h_dir = os.path.dirname(src)
- rc_dir = os.path.dirname(obj)
- try:
- # first compile .MC to .RC and .H file
- self.spawn([self.mc] + ['-h', h_dir, '-r', rc_dir] + [src])
- base, _ = os.path.splitext(os.path.basename(src))
- rc_file = os.path.join(rc_dir, base + '.rc')
- # then compile .RC to .RES file
- self.spawn([self.rc] + ["/fo" + obj] + [rc_file])
-
- except DistutilsExecError as msg:
- raise CompileError(msg)
- continue
- else:
- # how to handle this file?
- raise CompileError(f"Don't know how to compile {src} to {obj}")
-
- output_opt = "/Fo" + obj
- try:
- self.spawn(
- [self.cc]
- + compile_opts
- + pp_opts
- + [input_opt, output_opt]
- + extra_postargs
- )
- except DistutilsExecError as msg:
- raise CompileError(msg)
-
- return objects
-
- def create_static_lib(
- self, objects, output_libname, output_dir=None, debug=False, target_lang=None
- ):
- if not self.initialized:
- self.initialize()
- (objects, output_dir) = self._fix_object_args(objects, output_dir)
- output_filename = self.library_filename(output_libname, output_dir=output_dir)
-
- if self._need_link(objects, output_filename):
- lib_args = objects + ['/OUT:' + output_filename]
- if debug:
- pass # XXX what goes here?
- try:
- self.spawn([self.lib] + lib_args)
- except DistutilsExecError as msg:
- raise LibError(msg)
- else:
- log.debug("skipping %s (up-to-date)", output_filename)
-
- def link( # noqa: C901
- self,
- target_desc,
- objects,
- output_filename,
- output_dir=None,
- libraries=None,
- library_dirs=None,
- runtime_library_dirs=None,
- export_symbols=None,
- debug=False,
- extra_preargs=None,
- extra_postargs=None,
- build_temp=None,
- target_lang=None,
- ):
- if not self.initialized:
- self.initialize()
- (objects, output_dir) = self._fix_object_args(objects, output_dir)
- fixed_args = self._fix_lib_args(libraries, library_dirs, runtime_library_dirs)
- (libraries, library_dirs, runtime_library_dirs) = fixed_args
-
- if runtime_library_dirs:
- self.warn(
- "I don't know what to do with 'runtime_library_dirs': "
- + str(runtime_library_dirs)
- )
-
- lib_opts = gen_lib_options(self, library_dirs, runtime_library_dirs, libraries)
- if output_dir is not None:
- output_filename = os.path.join(output_dir, output_filename)
-
- if self._need_link(objects, output_filename):
- if target_desc == CCompiler.EXECUTABLE:
- if debug:
- ldflags = self.ldflags_shared_debug[1:]
- else:
- ldflags = self.ldflags_shared[1:]
- else:
- if debug:
- ldflags = self.ldflags_shared_debug
- else:
- ldflags = self.ldflags_shared
-
- export_opts = [f"/EXPORT:{sym}" for sym in export_symbols or []]
-
- ld_args = (
- ldflags + lib_opts + export_opts + objects + ['/OUT:' + output_filename]
- )
-
- # The MSVC linker generates .lib and .exp files, which cannot be
- # suppressed by any linker switches. The .lib files may even be
- # needed! Make sure they are generated in the temporary build
- # directory. Since they have different names for debug and release
- # builds, they can go into the same directory.
- if export_symbols is not None:
- (dll_name, dll_ext) = os.path.splitext(
- os.path.basename(output_filename)
- )
- implib_file = os.path.join(
- os.path.dirname(objects[0]), self.library_filename(dll_name)
- )
- ld_args.append('/IMPLIB:' + implib_file)
-
- if extra_preargs:
- ld_args[:0] = extra_preargs
- if extra_postargs:
- ld_args.extend(extra_postargs)
-
- self.mkpath(os.path.dirname(output_filename))
- try:
- self.spawn([self.linker] + ld_args)
- except DistutilsExecError as msg:
- raise LinkError(msg)
-
- else:
- log.debug("skipping %s (up-to-date)", output_filename)
-
- # -- Miscellaneous methods -----------------------------------------
- # These are all used by the 'gen_lib_options() function, in
- # ccompiler.py.
-
- def library_dir_option(self, dir):
- return "/LIBPATH:" + dir
-
- def runtime_library_dir_option(self, dir):
- raise DistutilsPlatformError(
- "don't know how to set runtime library search path for MSVC++"
- )
-
- def library_option(self, lib):
- return self.library_filename(lib)
-
- def find_library_file(self, dirs, lib, debug=False):
- # Prefer a debugging library if found (and requested), but deal
- # with it if we don't have one.
- if debug:
- try_names = [lib + "_d", lib]
- else:
- try_names = [lib]
- for dir in dirs:
- for name in try_names:
- libfile = os.path.join(dir, self.library_filename(name))
- if os.path.exists(libfile):
- return libfile
- else:
- # Oops, didn't find it in *any* of 'dirs'
- return None
-
- # Helper methods for using the MSVC registry settings
-
- def find_exe(self, exe):
- """Return path to an MSVC executable program.
-
- Tries to find the program in several places: first, one of the
- MSVC program search paths from the registry; next, the directories
- in the PATH environment variable. If any of those work, return an
- absolute path that is known to exist. If none of them work, just
- return the original program name, 'exe'.
- """
- for p in self.__paths:
- fn = os.path.join(os.path.abspath(p), exe)
- if os.path.isfile(fn):
- return fn
-
- # didn't find it; try existing path
- for p in os.environ['Path'].split(';'):
- fn = os.path.join(os.path.abspath(p), exe)
- if os.path.isfile(fn):
- return fn
-
- return exe
-
- def get_msvc_paths(self, path, platform='x86'):
- """Get a list of devstudio directories (include, lib or path).
-
- Return a list of strings. The list will be empty if unable to
- access the registry or appropriate registry keys not found.
- """
- if not _can_read_reg:
- return []
-
- path = path + " dirs"
- if self.__version >= 7:
- key = rf"{self.__root}\{self.__version:0.1f}\VC\VC_OBJECTS_PLATFORM_INFO\Win32\Directories"
- else:
- key = (
- rf"{self.__root}\6.0\Build System\Components\Platforms"
- rf"\Win32 ({platform})\Directories"
- )
-
- for base in HKEYS:
- d = read_values(base, key)
- if d:
- if self.__version >= 7:
- return self.__macros.sub(d[path]).split(";")
- else:
- return d[path].split(";")
- # MSVC 6 seems to create the registry entries we need only when
- # the GUI is run.
- if self.__version == 6:
- for base in HKEYS:
- if read_values(base, rf"{self.__root}\6.0") is not None:
- self.warn(
- "It seems you have Visual Studio 6 installed, "
- "but the expected registry settings are not present.\n"
- "You must at least run the Visual Studio GUI once "
- "so that these entries are created."
- )
- break
- return []
-
- def set_path_env_var(self, name):
- """Set environment variable 'name' to an MSVC path type value.
-
- This is equivalent to a SET command prior to execution of spawned
- commands.
- """
-
- if name == "lib":
- p = self.get_msvc_paths("library")
- else:
- p = self.get_msvc_paths(name)
- if p:
- os.environ[name] = ';'.join(p)
-
-
-if get_build_version() >= 8.0:
- log.debug("Importing new compiler from distutils.msvc9compiler")
- OldMSVCCompiler = MSVCCompiler
- # get_build_architecture not really relevant now we support cross-compile
- from distutils.msvc9compiler import (
- MacroExpander,
- MSVCCompiler,
- )
diff --git a/setuptools/_distutils/sysconfig.py b/setuptools/_distutils/sysconfig.py
index fbdd5d73ae..28a7c571dc 100644
--- a/setuptools/_distutils/sysconfig.py
+++ b/setuptools/_distutils/sysconfig.py
@@ -236,7 +236,7 @@ def get_python_lib(plat_specific=False, standard_lib=False, prefix=None):
if prefix is None:
prefix = PREFIX
if standard_lib:
- return os.path.join(prefix, "lib-python", sys.version[0])
+ return os.path.join(prefix, "lib-python", sys.version_info.major)
return os.path.join(prefix, 'site-packages')
early_prefix = prefix
diff --git a/setuptools/_distutils/tests/test_msvc9compiler.py b/setuptools/_distutils/tests/test_msvc9compiler.py
deleted file mode 100644
index 6f6aabee4d..0000000000
--- a/setuptools/_distutils/tests/test_msvc9compiler.py
+++ /dev/null
@@ -1,184 +0,0 @@
-"""Tests for distutils.msvc9compiler."""
-
-import os
-import sys
-from distutils.errors import DistutilsPlatformError
-from distutils.tests import support
-
-import pytest
-
-# A manifest with the only assembly reference being the msvcrt assembly, so
-# should have the assembly completely stripped. Note that although the
-# assembly has a reference the assembly is removed - that is
-# currently a "feature", not a bug :)
-_MANIFEST_WITH_ONLY_MSVC_REFERENCE = """\
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-"""
-
-# A manifest with references to assemblies other than msvcrt. When processed,
-# this assembly should be returned with just the msvcrt part removed.
-_MANIFEST_WITH_MULTIPLE_REFERENCES = """\
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-"""
-
-_CLEANED_MANIFEST = """\
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-"""
-
-if sys.platform == "win32":
- from distutils.msvccompiler import get_build_version
-
- if get_build_version() >= 8.0:
- SKIP_MESSAGE = None
- else:
- SKIP_MESSAGE = "These tests are only for MSVC8.0 or above"
-else:
- SKIP_MESSAGE = "These tests are only for win32"
-
-
-@pytest.mark.skipif('SKIP_MESSAGE', reason=SKIP_MESSAGE)
-class Testmsvc9compiler(support.TempdirManager):
- def test_no_compiler(self):
- # makes sure query_vcvarsall raises
- # a DistutilsPlatformError if the compiler
- # is not found
- from distutils.msvc9compiler import query_vcvarsall
-
- def _find_vcvarsall(version):
- return None
-
- from distutils import msvc9compiler
-
- old_find_vcvarsall = msvc9compiler.find_vcvarsall
- msvc9compiler.find_vcvarsall = _find_vcvarsall
- try:
- with pytest.raises(DistutilsPlatformError):
- query_vcvarsall('wont find this version')
- finally:
- msvc9compiler.find_vcvarsall = old_find_vcvarsall
-
- def test_reg_class(self):
- from distutils.msvc9compiler import Reg
-
- with pytest.raises(KeyError):
- Reg.get_value('xxx', 'xxx')
-
- # looking for values that should exist on all
- # windows registry versions.
- path = r'Control Panel\Desktop'
- v = Reg.get_value(path, 'dragfullwindows')
- assert v in ('0', '1', '2')
-
- import winreg
-
- HKCU = winreg.HKEY_CURRENT_USER
- keys = Reg.read_keys(HKCU, 'xxxx')
- assert keys is None
-
- keys = Reg.read_keys(HKCU, r'Control Panel')
- assert 'Desktop' in keys
-
- def test_remove_visual_c_ref(self):
- from distutils.msvc9compiler import MSVCCompiler
-
- tempdir = self.mkdtemp()
- manifest = os.path.join(tempdir, 'manifest')
- f = open(manifest, 'w')
- try:
- f.write(_MANIFEST_WITH_MULTIPLE_REFERENCES)
- finally:
- f.close()
-
- compiler = MSVCCompiler()
- compiler._remove_visual_c_ref(manifest)
-
- # see what we got
- f = open(manifest)
- try:
- # removing trailing spaces
- content = '\n'.join([line.rstrip() for line in f])
- finally:
- f.close()
-
- # makes sure the manifest was properly cleaned
- assert content == _CLEANED_MANIFEST
-
- def test_remove_entire_manifest(self):
- from distutils.msvc9compiler import MSVCCompiler
-
- tempdir = self.mkdtemp()
- manifest = os.path.join(tempdir, 'manifest')
- f = open(manifest, 'w')
- try:
- f.write(_MANIFEST_WITH_ONLY_MSVC_REFERENCE)
- finally:
- f.close()
-
- compiler = MSVCCompiler()
- got = compiler._remove_visual_c_ref(manifest)
- assert got is None