This commit is contained in:
lz_db
2026-10-16 22:48:30 +08:00
parent 190de72571
commit 0b36fd3383
3161 changed files with 12787 additions and 77274 deletions
@@ -1,7 +1,17 @@
import importlib
import sys
"""distutils
__version__, _, _ = sys.version.partition(' ')
The main package for the Python Module Distribution Utilities. Normally
used from a setup script as
from distutils.core import setup
setup (...)
"""
import sys
import importlib
__version__ = sys.version[: sys.version.index(' ')]
try:
@@ -1,3 +0,0 @@
import logging
log = logging.getLogger()
@@ -1,5 +1,5 @@
import importlib
import sys
import importlib
def bypass_compiler_fixup(cmd, args):
@@ -1,95 +0,0 @@
"""Timestamp comparison of files and groups of files."""
from __future__ import annotations
import functools
import os.path
from collections.abc import Callable, Iterable
from typing import Literal, TypeVar
from jaraco.functools import splat
from .compat.py39 import zip_strict
from .errors import DistutilsFileError
_SourcesT = TypeVar(
"_SourcesT", bound="str | bytes | os.PathLike[str] | os.PathLike[bytes]"
)
_TargetsT = TypeVar(
"_TargetsT", bound="str | bytes | os.PathLike[str] | os.PathLike[bytes]"
)
def _newer(source, target):
return not os.path.exists(target) or (
os.path.getmtime(source) > os.path.getmtime(target)
)
def newer(
source: str | bytes | os.PathLike[str] | os.PathLike[bytes],
target: str | bytes | os.PathLike[str] | os.PathLike[bytes],
) -> bool:
"""
Is source modified more recently than target.
Returns True if 'source' is modified more recently than
'target' or if 'target' does not exist.
Raises DistutilsFileError if 'source' does not exist.
"""
if not os.path.exists(source):
raise DistutilsFileError(f"file {os.path.abspath(source)!r} does not exist")
return _newer(source, target)
def newer_pairwise(
sources: Iterable[_SourcesT],
targets: Iterable[_TargetsT],
newer: Callable[[_SourcesT, _TargetsT], bool] = newer,
) -> tuple[list[_SourcesT], list[_TargetsT]]:
"""
Filter filenames where sources are newer than targets.
Walk two filename iterables in parallel, testing if each source is newer
than its corresponding target. Returns a pair of lists (sources,
targets) where source is newer than target, according to the semantics
of 'newer()'.
"""
newer_pairs = filter(splat(newer), zip_strict(sources, targets))
return tuple(map(list, zip(*newer_pairs))) or ([], [])
def newer_group(
sources: Iterable[str | bytes | os.PathLike[str] | os.PathLike[bytes]],
target: str | bytes | os.PathLike[str] | os.PathLike[bytes],
missing: Literal["error", "ignore", "newer"] = "error",
) -> bool:
"""
Is target out-of-date with respect to any file in sources.
Return True if 'target' is out-of-date with respect to any file
listed in 'sources'. In other words, if 'target' exists and is newer
than every file in 'sources', return False; otherwise return True.
``missing`` controls how to handle a missing source file:
- error (default): allow the ``stat()`` call to fail.
- ignore: silently disregard any missing source files.
- newer: treat missing source files as "target out of date". This
mode is handy in "dry-run" mode: it will pretend to carry out
commands that wouldn't work because inputs are missing, but
that doesn't matter because dry-run won't run the commands.
"""
def missing_as_newer(source):
return missing == 'newer' and not os.path.exists(source)
ignored = os.path.exists if missing == 'ignore' else None
return not os.path.exists(target) or any(
missing_as_newer(source) or _newer(source, target)
for source in filter(ignored, sources)
)
newer_pairwise_group = functools.partial(newer_pairwise, newer=newer_group)
@@ -1,16 +1,572 @@
"""distutils._msvccompiler
Contains MSVCCompiler, an implementation of the abstract CCompiler class
for Microsoft Visual Studio 2015.
The module is compatible with VS 2015 and later. You can find legacy support
for older versions in distutils.msvc9compiler and 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 VS 2005 and VS 2008 by Christian Heimes
# ported to VS 2015 by Steve Dower
import os
import subprocess
import contextlib
import warnings
import unittest.mock as mock
from .compilers.C import msvc
with contextlib.suppress(ImportError):
import winreg
__all__ = ["MSVCCompiler"]
from distutils.errors import (
DistutilsExecError,
DistutilsPlatformError,
CompileError,
LibError,
LinkError,
)
from distutils.ccompiler import CCompiler, gen_lib_options
from distutils import log
from distutils.util import get_platform
MSVCCompiler = msvc.Compiler
from itertools import count
def __getattr__(name):
if name == '_get_vc_env':
warnings.warn(
"_get_vc_env is private; find an alternative (pypa/distutils#340)"
def _find_vc2015():
try:
key = winreg.OpenKeyEx(
winreg.HKEY_LOCAL_MACHINE,
r"Software\Microsoft\VisualStudio\SxS\VC7",
access=winreg.KEY_READ | winreg.KEY_WOW64_32KEY,
)
return msvc._get_vc_env
raise AttributeError(name)
except OSError:
log.debug("Visual C++ is not registered")
return None, None
best_version = 0
best_dir = None
with key:
for i in count():
try:
v, vc_dir, vt = winreg.EnumValue(key, i)
except OSError:
break
if v and vt == winreg.REG_SZ and os.path.isdir(vc_dir):
try:
version = int(float(v))
except (ValueError, TypeError):
continue
if version >= 14 and version > best_version:
best_version, best_dir = version, vc_dir
return best_version, best_dir
def _find_vc2017():
"""Returns "15, path" based on the result of invoking vswhere.exe
If no install is found, returns "None, None"
The version is returned to avoid unnecessarily changing the function
result. It may be ignored when the path is not None.
If vswhere.exe is not available, by definition, VS 2017 is not
installed.
"""
root = os.environ.get("ProgramFiles(x86)") or os.environ.get("ProgramFiles")
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
path = os.path.join(path, "VC", "Auxiliary", "Build")
if os.path.isdir(path):
return 15, path
return None, None
PLAT_SPEC_TO_RUNTIME = {
'x86': 'x86',
'x86_amd64': 'x64',
'x86_arm': 'arm',
'x86_arm64': 'arm64',
}
def _find_vcvarsall(plat_spec):
# bpo-38597: Removed vcruntime return value
_, best_dir = _find_vc2017()
if not best_dir:
best_version, best_dir = _find_vc2015()
if not best_dir:
log.debug("No suitable Visual C++ version found")
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, None
return vcvarsall, None
def _get_vc_env(plat_spec):
if os.getenv("DISTUTILS_USE_SDK"):
return {key.lower(): value for key, value in os.environ.items()}
vcvarsall, _ = _find_vcvarsall(plat_spec)
if not vcvarsall:
raise DistutilsPlatformError("Unable to find vcvarsall.bat")
try:
out = subprocess.check_output(
f'cmd /u /c "{vcvarsall}" {plat_spec} && set',
stderr=subprocess.STDOUT,
).decode('utf-16le', errors='replace')
except subprocess.CalledProcessError as exc:
log.error(exc.output)
raise DistutilsPlatformError(f"Error executing {exc.cmd}")
env = {
key.lower(): value
for key, _, value in (line.partition('=') for line in out.splitlines())
if key and value
}
return env
def _find_exe(exe, paths=None):
"""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'.
"""
if not paths:
paths = os.getenv('path').split(os.pathsep)
for p in paths:
fn = os.path.join(os.path.abspath(p), exe)
if os.path.isfile(fn):
return fn
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 = {
'win32': 'x86',
'win-amd64': 'x86_amd64',
'win-arm32': 'x86_arm',
'win-arm64': 'x86_arm64',
}
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=0, dry_run=0, force=0):
super().__init__(verbose, dry_run, force)
# target platform (.plat_name is consistent with 'bdist')
self.plat_name = None
self.initialized = False
@classmethod
def _configure(cls, vc_env):
"""
Set class-level include/lib dirs.
"""
cls.include_dirs = cls._parse_path(vc_env.get('include', ''))
cls.library_dirs = cls._parse_path(vc_env.get('lib', ''))
@staticmethod
def _parse_path(val):
return [dir.rstrip(os.sep) for dir in val.split(os.pathsep) if dir]
def initialize(self, plat_name=None):
# multi-init means we would need to check platform same each time...
assert not self.initialized, "don't init multiple times"
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:
raise DistutilsPlatformError(
f"--plat-name must be one of {tuple(PLAT_TO_VCVARS)}"
)
# Get the vcvarsall.bat spec for the requested platform.
plat_spec = PLAT_TO_VCVARS[plat_name]
vc_env = _get_vc_env(plat_spec)
if not vc_env:
raise DistutilsPlatformError(
"Unable to find a compatible " "Visual Studio installation."
)
self._configure(vc_env)
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)
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.preprocess_options = None
# bpo-38597: Always compile with dynamic linking
# Future releases of Python 3.x will include all past
# versions of vcruntime*.dll for compatibility.
self.compile_options = ['/nologo', '/O2', '/W3', '/GL', '/DNDEBUG', '/MD']
self.compile_options_debug = [
'/nologo',
'/Od',
'/MDd',
'/Zi',
'/W3',
'/D_DEBUG',
]
ldflags = ['/nologo', '/INCREMENTAL:NO', '/LTCG']
ldflags_debug = ['/nologo', '/INCREMENTAL:NO', '/LTCG', '/DEBUG:FULL']
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 ------------------------------------------------
@property
def out_extensions(self):
return {
**super().out_extensions,
**{
ext: self.res_extension
for ext in self._rc_extensions + self._mc_extensions
},
}
def compile( # noqa: C901
self,
sources,
output_dir=None,
macros=None,
include_dirs=None,
debug=0,
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)
add_cpp_opts = False
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
add_cpp_opts = True
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}")
args = [self.cc] + compile_opts + pp_opts
if add_cpp_opts:
args.append('/EHsc')
args.append(input_opt)
args.append("/Fo" + obj)
args.extend(extra_postargs)
try:
self.spawn(args)
except DistutilsExecError as msg:
raise CompileError(msg)
return objects
def create_static_lib(
self, objects, output_libname, output_dir=None, debug=0, 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:
log.debug('Executing "%s" %s', self.lib, ' '.join(lib_args))
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(
self,
target_desc,
objects,
output_filename,
output_dir=None,
libraries=None,
library_dirs=None,
runtime_library_dirs=None,
export_symbols=None,
debug=0,
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):
ldflags = self._ldflags[target_desc, debug]
export_opts = ["/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)
if extra_preargs:
ld_args[:0] = extra_preargs
if extra_postargs:
ld_args.extend(extra_postargs)
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)
except DistutilsExecError as msg:
raise LinkError(msg)
else:
log.debug("skipping %s (up-to-date)", output_filename)
def spawn(self, cmd):
env = dict(os.environ, PATH=self._paths)
with self._fallback_spawn(cmd, env) as fallback:
return super().spawn(cmd, env=env)
return fallback.value
@contextlib.contextmanager
def _fallback_spawn(self, cmd, env):
"""
Discovered in pypa/distutils#15, some tools monkeypatch the compiler,
so the 'env' kwarg causes a TypeError. Detect this condition and
restore the legacy, unsafe behavior.
"""
bag = type('Bag', (), {})()
try:
yield bag
except TypeError as exc:
if "unexpected keyword argument 'env'" not in str(exc):
raise
else:
return
warnings.warn("Fallback spawn triggered. Please update distutils monkeypatch.")
with mock.patch.dict('os.environ', env):
bag.value = super().spawn(cmd)
# -- 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=0):
# 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.isfile(libfile):
return libfile
else:
# Oops, didn't find it in *any* of 'dirs'
return None
@@ -3,10 +3,9 @@
Utility functions for creating archive files (tarballs, zip files,
that sort of thing)."""
from __future__ import annotations
import os
from typing import Literal, overload
from warnings import warn
import sys
try:
import zipfile
@@ -14,10 +13,10 @@ except ImportError:
zipfile = None
from ._log import log
from .dir_util import mkpath
from .errors import DistutilsExecError
from .spawn import spawn
from distutils.errors import DistutilsExecError
from distutils.spawn import spawn
from distutils.dir_util import mkpath
from distutils import log
try:
from pwd import getpwnam
@@ -57,18 +56,13 @@ def _get_uid(name):
def make_tarball(
base_name: str,
base_dir: str | os.PathLike[str],
compress: Literal["gzip", "bzip2", "xz"] | None = "gzip",
verbose: bool = False,
dry_run: bool = False,
owner: str | None = None,
group: str | None = None,
) -> str:
base_name, base_dir, compress="gzip", verbose=0, dry_run=0, owner=None, group=None
):
"""Create a (possibly compressed) tar file from all the files under
'base_dir'.
'compress' must be "gzip" (the default), "bzip2", "xz", or None.
'compress' must be "gzip" (the default), "bzip2", "xz", "compress", or
None. ("compress" will be deprecated in Python 3.2)
'owner' and 'group' can be used to define an owner and a group for the
archive that is being built. If not provided, the current owner and group
@@ -84,17 +78,20 @@ def make_tarball(
'bzip2': 'bz2',
'xz': 'xz',
None: '',
'compress': '',
}
compress_ext = {'gzip': '.gz', 'bzip2': '.bz2', 'xz': '.xz'}
compress_ext = {'gzip': '.gz', 'bzip2': '.bz2', 'xz': '.xz', 'compress': '.Z'}
# flags for compression program, each element of list will be an argument
if compress is not None and compress not in compress_ext.keys():
raise ValueError(
"bad value for 'compress': must be None, 'gzip', 'bzip2', 'xz'"
"bad value for 'compress': must be None, 'gzip', 'bzip2', "
"'xz' or 'compress'"
)
archive_name = base_name + '.tar'
archive_name += compress_ext.get(compress, '')
if compress != 'compress':
archive_name += compress_ext.get(compress, '')
mkpath(os.path.dirname(archive_name), dry_run=dry_run)
@@ -116,21 +113,28 @@ def make_tarball(
return tarinfo
if not dry_run:
tar = tarfile.open(archive_name, f'w|{tar_compression[compress]}')
tar = tarfile.open(archive_name, 'w|%s' % tar_compression[compress])
try:
tar.add(base_dir, filter=_set_uid_gid)
finally:
tar.close()
# compression using `compress`
if compress == 'compress':
warn("'compress' is deprecated.", DeprecationWarning)
# the option varies depending on the platform
compressed_name = archive_name + compress_ext[compress]
if sys.platform == 'win32':
cmd = [compress, archive_name, compressed_name]
else:
cmd = [compress, '-f', archive_name]
spawn(cmd, dry_run=dry_run)
return compressed_name
return archive_name
def make_zipfile( # noqa: C901
base_name: str,
base_dir: str | os.PathLike[str],
verbose: bool = False,
dry_run: bool = False,
) -> str:
def make_zipfile(base_name, base_dir, verbose=0, dry_run=0): # noqa: C901
"""Create a zip file from all the files under 'base_dir'.
The output zip file will be named 'base_name' + ".zip". Uses either the
@@ -156,9 +160,12 @@ def make_zipfile( # noqa: C901
# XXX really should distinguish between "couldn't find
# external 'zip' command" and "zip failed".
raise DistutilsExecError(
f"unable to create zip file '{zip_filename}': "
"could neither import the 'zipfile' module nor "
"find a standalone zip utility"
(
"unable to create zip file '%s': "
"could neither import the 'zipfile' module nor "
"find a standalone zip utility"
)
% zip_filename
)
else:
@@ -212,38 +219,16 @@ def check_archive_formats(formats):
return None
@overload
def make_archive(
base_name: str,
format: str,
root_dir: str | os.PathLike[str] | bytes | os.PathLike[bytes] | None = None,
base_dir: str | None = None,
verbose: bool = False,
dry_run: bool = False,
owner: str | None = None,
group: str | None = None,
) -> str: ...
@overload
def make_archive(
base_name: str | os.PathLike[str],
format: str,
root_dir: str | os.PathLike[str] | bytes | os.PathLike[bytes],
base_dir: str | None = None,
verbose: bool = False,
dry_run: bool = False,
owner: str | None = None,
group: str | None = None,
) -> str: ...
def make_archive(
base_name: str | os.PathLike[str],
format: str,
root_dir: str | os.PathLike[str] | bytes | os.PathLike[bytes] | None = None,
base_dir: str | None = None,
verbose: bool = False,
dry_run: bool = False,
owner: str | None = None,
group: str | None = None,
) -> str:
base_name,
format,
root_dir=None,
base_dir=None,
verbose=0,
dry_run=0,
owner=None,
group=None,
):
"""Create an archive file (eg. zip or tar).
'base_name' is the name of the file to create, minus any format-specific
@@ -275,10 +260,11 @@ def make_archive(
try:
format_info = ARCHIVE_FORMATS[format]
except KeyError:
raise ValueError(f"unknown archive format '{format}'")
raise ValueError("unknown archive format '%s'" % format)
func = format_info[0]
kwargs.update(format_info[1])
for arg, val in format_info[1]:
kwargs[arg] = val
if format != 'zip':
kwargs['owner'] = owner
File diff suppressed because it is too large Load Diff
@@ -4,31 +4,12 @@ Provides the Command class, the base class for the command classes
in the distutils.command package.
"""
from __future__ import annotations
import logging
import sys
import os
import re
import sys
from abc import abstractmethod
from collections.abc import Callable, MutableSequence
from typing import TYPE_CHECKING, Any, ClassVar, TypeVar, overload
from . import _modified, archive_util, dir_util, file_util, util
from ._log import log
from .errors import DistutilsOptionError
if TYPE_CHECKING:
# type-only import because of mutual dependence between these classes
from distutils.dist import Distribution
from typing_extensions import TypeVarTuple, Unpack
_Ts = TypeVarTuple("_Ts")
_StrPathT = TypeVar("_StrPathT", bound="str | os.PathLike[str]")
_BytesPathT = TypeVar("_BytesPathT", bound="bytes | os.PathLike[bytes]")
_CommandT = TypeVar("_CommandT", bound="Command")
from distutils.errors import DistutilsOptionError
from distutils import util, dir_util, file_util, archive_util, dep_util
from distutils import log
class Command:
@@ -61,18 +42,11 @@ class Command:
# 'sub_commands' is usually defined at the *end* of a class, because
# predicates can be unbound methods, so they must already have been
# defined. The canonical example is the "install" command.
sub_commands: ClassVar[ # Any to work around variance issues
list[tuple[str, Callable[[Any], bool] | None]]
] = []
user_options: ClassVar[
# Specifying both because list is invariant. Avoids mypy override assignment issues
list[tuple[str, str, str]] | list[tuple[str, str | None, str]]
] = []
sub_commands = []
# -- Creation/initialization methods -------------------------------
def __init__(self, dist: Distribution) -> None:
def __init__(self, dist):
"""Create and initialize a new Command object. Most importantly,
invokes the 'initialize_options()' method, which is the real
initializer and depends on the actual command being
@@ -111,13 +85,13 @@ class Command:
# The 'help' flag is just used for command-line parsing, so
# none of that complicated bureaucracy is needed.
self.help = False
self.help = 0
# 'finalized' records whether or not 'finalize_options()' has been
# called. 'finalize_options()' itself should not pay attention to
# this flag: it is the business of 'ensure_finalized()', which
# always calls 'finalize_options()', to respect/update it.
self.finalized = False
self.finalized = 0
# XXX A more explicit way to customize dry_run would be better.
def __getattr__(self, attr):
@@ -130,10 +104,10 @@ class Command:
else:
raise AttributeError(attr)
def ensure_finalized(self) -> None:
def ensure_finalized(self):
if not self.finalized:
self.finalize_options()
self.finalized = True
self.finalized = 1
# Subclasses must define:
# initialize_options()
@@ -148,8 +122,7 @@ class Command:
# run the command: do whatever it is we're here to do,
# controlled by the command's various option values
@abstractmethod
def initialize_options(self) -> None:
def initialize_options(self):
"""Set default values for all the options that this command
supports. Note that these defaults may be overridden by other
commands, by the setup script, by config files, or by the
@@ -160,11 +133,10 @@ class Command:
This method must be implemented by all command classes.
"""
raise RuntimeError(
f"abstract method -- subclass {self.__class__} must override"
"abstract method -- subclass %s must override" % self.__class__
)
@abstractmethod
def finalize_options(self) -> None:
def finalize_options(self):
"""Set final values for all the options that this command supports.
This is always called as late as possible, ie. after any option
assignments from the command-line or from other commands have been
@@ -176,25 +148,24 @@ class Command:
This method must be implemented by all command classes.
"""
raise RuntimeError(
f"abstract method -- subclass {self.__class__} must override"
"abstract method -- subclass %s must override" % self.__class__
)
def dump_options(self, header=None, indent=""):
from distutils.fancy_getopt import longopt_xlate
if header is None:
header = f"command options for '{self.get_command_name()}':"
self.announce(indent + header, level=logging.INFO)
header = "command options for '%s':" % self.get_command_name()
self.announce(indent + header, level=log.INFO)
indent = indent + " "
for option, _, _ in self.user_options:
for (option, _, _) in self.user_options:
option = option.translate(longopt_xlate)
if option[-1] == "=":
option = option[:-1]
value = getattr(self, option)
self.announce(indent + f"{option} = {value}", level=logging.INFO)
self.announce(indent + "{} = {}".format(option, value), level=log.INFO)
@abstractmethod
def run(self) -> None:
def run(self):
"""A command's raison d'etre: carry out the action it exists to
perform, controlled by the options initialized in
'initialize_options()', customized by other commands, the setup
@@ -205,13 +176,16 @@ class Command:
This method must be implemented by all command classes.
"""
raise RuntimeError(
f"abstract method -- subclass {self.__class__} must override"
"abstract method -- subclass %s must override" % self.__class__
)
def announce(self, msg: object, level: int = logging.DEBUG) -> None:
def announce(self, msg, level=1):
"""If the current verbosity level is of greater than or equal to
'level' print 'msg' to stdout.
"""
log.log(level, msg)
def debug_print(self, msg: object) -> None:
def debug_print(self, msg):
"""Print 'msg' to stdout if the global DEBUG (taken from the
DISTUTILS_DEBUG environment variable) flag is true.
"""
@@ -240,16 +214,18 @@ class Command:
setattr(self, option, default)
return default
elif not isinstance(val, str):
raise DistutilsOptionError(f"'{option}' must be a {what} (got `{val}`)")
raise DistutilsOptionError(
"'{}' must be a {} (got `{}`)".format(option, what, val)
)
return val
def ensure_string(self, option: str, default: str | None = None) -> None:
def ensure_string(self, option, default=None):
"""Ensure that 'option' is a string; if not defined, set it to
'default'.
"""
self._ensure_stringlike(option, "string", default)
def ensure_string_list(self, option: str) -> None:
def ensure_string_list(self, option):
r"""Ensure that 'option' is a list of strings. If 'option' is
currently a string, we split it either on /,\s*/ or /\s+/, so
"foo bar baz", "foo,bar,baz", and "foo, bar baz" all become
@@ -267,7 +243,7 @@ class Command:
ok = False
if not ok:
raise DistutilsOptionError(
f"'{option}' must be a list of strings (got {val!r})"
"'{}' must be a list of strings (got {!r})".format(option, val)
)
def _ensure_tested_string(self, option, tester, what, error_fmt, default=None):
@@ -277,13 +253,13 @@ class Command:
("error in '%s' option: " + error_fmt) % (option, val)
)
def ensure_filename(self, option: str) -> None:
def ensure_filename(self, option):
"""Ensure that 'option' is the name of an existing file."""
self._ensure_tested_string(
option, os.path.isfile, "filename", "'%s' does not exist or is not a file"
)
def ensure_dirname(self, option: str) -> None:
def ensure_dirname(self, option):
self._ensure_tested_string(
option,
os.path.isdir,
@@ -293,15 +269,13 @@ class Command:
# -- Convenience methods for commands ------------------------------
def get_command_name(self) -> str:
def get_command_name(self):
if hasattr(self, 'command_name'):
return self.command_name
else:
return self.__class__.__name__
def set_undefined_options(
self, src_cmd: str, *option_pairs: tuple[str, str]
) -> None:
def set_undefined_options(self, src_cmd, *option_pairs):
"""Set the values of any "undefined" options from corresponding
option values in some other command object. "Undefined" here means
"is None", which is the convention used to indicate that an option
@@ -318,13 +292,11 @@ class Command:
# Option_pairs: list of (src_option, dst_option) tuples
src_cmd_obj = self.distribution.get_command_obj(src_cmd)
src_cmd_obj.ensure_finalized()
for src_option, dst_option in option_pairs:
for (src_option, dst_option) in option_pairs:
if getattr(self, dst_option) is None:
setattr(self, dst_option, getattr(src_cmd_obj, src_option))
# NOTE: Because distutils is private to Setuptools and not all commands are exposed here,
# not every possible command is enumerated in the signature.
def get_finalized_command(self, command: str, create: bool = True) -> Command:
def get_finalized_command(self, command, create=1):
"""Wrapper around Distribution's 'get_command_obj()' method: find
(create if necessary and 'create' is true) the command object for
'command', call its 'ensure_finalized()' method, and return the
@@ -336,27 +308,17 @@ class Command:
# XXX rename to 'get_reinitialized_command()'? (should do the
# same in dist.py, if so)
@overload
def reinitialize_command(
self, command: str, reinit_subcommands: bool = False
) -> Command: ...
@overload
def reinitialize_command(
self, command: _CommandT, reinit_subcommands: bool = False
) -> _CommandT: ...
def reinitialize_command(
self, command: str | Command, reinit_subcommands=False
) -> Command:
def reinitialize_command(self, command, reinit_subcommands=0):
return self.distribution.reinitialize_command(command, reinit_subcommands)
def run_command(self, command: str) -> None:
def run_command(self, command):
"""Run some other command: uses the 'run_command()' method of
Distribution, which creates and finalizes the command object if
necessary and then invokes its 'run()' method.
"""
self.distribution.run_command(command)
def get_sub_commands(self) -> list[str]:
def get_sub_commands(self):
"""Determine the sub-commands that are relevant in the current
distribution (ie., that need to be run). This is based on the
'sub_commands' class attribute: each tuple in that list may include
@@ -364,57 +326,25 @@ class Command:
run for the current distribution. Return a list of command names.
"""
commands = []
for cmd_name, method in self.sub_commands:
for (cmd_name, method) in self.sub_commands:
if method is None or method(self):
commands.append(cmd_name)
return commands
# -- External world manipulation -----------------------------------
def warn(self, msg: object) -> None:
log.warning("warning: %s: %s\n", self.get_command_name(), msg)
def warn(self, msg):
log.warn("warning: %s: %s\n", self.get_command_name(), msg)
def execute(
self,
func: Callable[[Unpack[_Ts]], object],
args: tuple[Unpack[_Ts]],
msg: object = None,
level: int = 1,
) -> None:
def execute(self, func, args, msg=None, level=1):
util.execute(func, args, msg, dry_run=self.dry_run)
def mkpath(self, name: str, mode: int = 0o777) -> None:
def mkpath(self, name, mode=0o777):
dir_util.mkpath(name, mode, dry_run=self.dry_run)
@overload
def copy_file(
self,
infile: str | os.PathLike[str],
outfile: _StrPathT,
preserve_mode: bool = True,
preserve_times: bool = True,
link: str | None = None,
level: int = 1,
) -> tuple[_StrPathT | str, bool]: ...
@overload
def copy_file(
self,
infile: bytes | os.PathLike[bytes],
outfile: _BytesPathT,
preserve_mode: bool = True,
preserve_times: bool = True,
link: str | None = None,
level: int = 1,
) -> tuple[_BytesPathT | bytes, bool]: ...
def copy_file(
self,
infile: str | os.PathLike[str] | bytes | os.PathLike[bytes],
outfile: str | os.PathLike[str] | bytes | os.PathLike[bytes],
preserve_mode: bool = True,
preserve_times: bool = True,
link: str | None = None,
level: int = 1,
) -> tuple[str | os.PathLike[str] | bytes | os.PathLike[bytes], bool]:
self, infile, outfile, preserve_mode=1, preserve_times=1, link=None, level=1
):
"""Copy a file respecting verbose, dry-run and force flags. (The
former two default to whatever is in the Distribution object, and
the latter defaults to false for commands that don't define it.)"""
@@ -430,13 +360,13 @@ class Command:
def copy_tree(
self,
infile: str | os.PathLike[str],
outfile: str,
preserve_mode: bool = True,
preserve_times: bool = True,
preserve_symlinks: bool = False,
level: int = 1,
) -> list[str]:
infile,
outfile,
preserve_mode=1,
preserve_times=1,
preserve_symlinks=0,
level=1,
):
"""Copy an entire directory tree respecting verbose, dry-run,
and force flags.
"""
@@ -450,60 +380,19 @@ class Command:
dry_run=self.dry_run,
)
@overload
def move_file(
self, src: str | os.PathLike[str], dst: _StrPathT, level: int = 1
) -> _StrPathT | str: ...
@overload
def move_file(
self, src: bytes | os.PathLike[bytes], dst: _BytesPathT, level: int = 1
) -> _BytesPathT | bytes: ...
def move_file(
self,
src: str | os.PathLike[str] | bytes | os.PathLike[bytes],
dst: str | os.PathLike[str] | bytes | os.PathLike[bytes],
level: int = 1,
) -> str | os.PathLike[str] | bytes | os.PathLike[bytes]:
def move_file(self, src, dst, level=1):
"""Move a file respecting dry-run flag."""
return file_util.move_file(src, dst, dry_run=self.dry_run)
def spawn(
self, cmd: MutableSequence[str], search_path: bool = True, level: int = 1
) -> None:
def spawn(self, cmd, search_path=1, level=1):
"""Spawn an external command respecting dry-run flag."""
from distutils.spawn import spawn
spawn(cmd, search_path, dry_run=self.dry_run)
@overload
def make_archive(
self,
base_name: str,
format: str,
root_dir: str | os.PathLike[str] | bytes | os.PathLike[bytes] | None = None,
base_dir: str | None = None,
owner: str | None = None,
group: str | None = None,
) -> str: ...
@overload
def make_archive(
self,
base_name: str | os.PathLike[str],
format: str,
root_dir: str | os.PathLike[str] | bytes | os.PathLike[bytes],
base_dir: str | None = None,
owner: str | None = None,
group: str | None = None,
) -> str: ...
def make_archive(
self,
base_name: str | os.PathLike[str],
format: str,
root_dir: str | os.PathLike[str] | bytes | os.PathLike[bytes] | None = None,
base_dir: str | None = None,
owner: str | None = None,
group: str | None = None,
) -> str:
self, base_name, format, root_dir=None, base_dir=None, owner=None, group=None
):
return archive_util.make_archive(
base_name,
format,
@@ -515,15 +404,8 @@ class Command:
)
def make_file(
self,
infiles: str | list[str] | tuple[str, ...],
outfile: str | os.PathLike[str] | bytes | os.PathLike[bytes],
func: Callable[[Unpack[_Ts]], object],
args: tuple[Unpack[_Ts]],
exec_msg: object = None,
skip_msg: object = None,
level: int = 1,
) -> None:
self, infiles, outfile, func, args, exec_msg=None, skip_msg=None, level=1
):
"""Special case of 'execute()' for operations that process one or
more input files and generate one output file. Works just like
'execute()', except the operation is skipped and a different
@@ -533,7 +415,7 @@ class Command:
timestamp checks.
"""
if skip_msg is None:
skip_msg = f"skipping {outfile} (inputs unchanged)"
skip_msg = "skipping %s (inputs unchanged)" % outfile
# Allow 'infiles' to be a single string
if isinstance(infiles, str):
@@ -547,7 +429,7 @@ class Command:
# If 'outfile' must be regenerated (either because it doesn't
# exist, is out-of-date, or the 'force' flag is true) then
# perform the action that presumably regenerates it
if self.force or _modified.newer_group(infiles, outfile):
if self.force or dep_util.newer_group(infiles, outfile):
self.execute(func, args, exec_msg, level)
# Otherwise, print the "skip" message
else:
@@ -3,7 +3,7 @@
Package containing implementation of all the standard Distutils
commands."""
__all__ = [
__all__ = [ # noqa: F822
'build',
'build_py',
'build_ext',
@@ -16,8 +16,10 @@ __all__ = [
'install_scripts',
'install_data',
'sdist',
'register',
'bdist',
'bdist_dumb',
'bdist_rpm',
'check',
'upload',
]
@@ -2,14 +2,15 @@
Backward compatibility for homebrew builds on macOS.
"""
import functools
import os
import subprocess
import sys
import os
import functools
import subprocess
import sysconfig
@functools.lru_cache
@functools.lru_cache()
def enabled():
"""
Only enabled for Python 3.9 framework homebrew builds
@@ -37,7 +38,7 @@ schemes = dict(
)
@functools.lru_cache
@functools.lru_cache()
def vars():
if not enabled():
return {}
@@ -3,49 +3,37 @@
Implements the Distutils 'bdist' command (create a built [binary]
distribution)."""
from __future__ import annotations
import os
import warnings
from collections.abc import Callable
from typing import TYPE_CHECKING, ClassVar
from ..core import Command
from ..errors import DistutilsOptionError, DistutilsPlatformError
from ..util import get_platform
if TYPE_CHECKING:
from typing_extensions import deprecated
else:
def deprecated(message):
return lambda fn: fn
from distutils.core import Command
from distutils.errors import DistutilsPlatformError, DistutilsOptionError
from distutils.util import get_platform
def show_formats():
"""Print list of available formats (arguments to "--format" option)."""
from ..fancy_getopt import FancyGetopt
from distutils.fancy_getopt import FancyGetopt
formats = [
("formats=" + format, None, bdist.format_commands[format][1])
for format in bdist.format_commands
]
formats = []
for format in bdist.format_commands:
formats.append(("formats=" + format, None, bdist.format_commands[format][1]))
pretty_printer = FancyGetopt(formats)
pretty_printer.print_help("List of available distribution formats:")
class ListCompat(dict[str, tuple[str, str]]):
class ListCompat(dict):
# adapter to allow for Setuptools compatibility in format_commands
@deprecated("format_commands is now a dict. append is deprecated.")
def append(self, item: object) -> None:
def append(self, item):
warnings.warn(
"format_commands is now a dict. append is deprecated.",
"""format_commands is now a dict. append is deprecated.""",
DeprecationWarning,
stacklevel=2,
)
class bdist(Command):
description = "create a built (binary) distribution"
user_options = [
@@ -54,50 +42,52 @@ class bdist(Command):
'plat-name=',
'p',
"platform name to embed in generated filenames "
f"[default: {get_platform()}]",
"(default: %s)" % get_platform(),
),
('formats=', None, "formats for distribution (comma-separated list)"),
(
'dist-dir=',
'd',
"directory to put final built distributions in [default: dist]",
"directory to put final built distributions in " "[default: dist]",
),
('skip-build', None, "skip rebuilding everything (for testing/debugging)"),
(
'owner=',
'u',
"Owner name used when creating a tar file [default: current user]",
"Owner name used when creating a tar file" " [default: current user]",
),
(
'group=',
'g',
"Group name used when creating a tar file [default: current group]",
"Group name used when creating a tar file" " [default: current group]",
),
]
boolean_options: ClassVar[list[str]] = ['skip-build']
boolean_options = ['skip-build']
help_options: ClassVar[list[tuple[str, str | None, str, Callable[[], object]]]] = [
help_options = [
('help-formats', None, "lists available distribution formats", show_formats),
]
# The following commands do not take a format option from bdist
no_format_option: ClassVar[tuple[str, ...]] = ('bdist_rpm',)
no_format_option = ('bdist_rpm',)
# This won't do in reality: will need to distinguish RPM-ish Linux,
# Debian-ish Linux, Solaris, FreeBSD, ..., Windows, Mac OS.
default_format: ClassVar[dict[str, str]] = {'posix': 'gztar', 'nt': 'zip'}
default_format = {'posix': 'gztar', 'nt': 'zip'}
# Define commands in preferred order for the --help-formats option
format_commands = ListCompat({
'rpm': ('bdist_rpm', "RPM distribution"),
'gztar': ('bdist_dumb', "gzip'ed tar file"),
'bztar': ('bdist_dumb', "bzip2'ed tar file"),
'xztar': ('bdist_dumb', "xz'ed tar file"),
'ztar': ('bdist_dumb', "compressed tar file"),
'tar': ('bdist_dumb', "tar file"),
'zip': ('bdist_dumb', "ZIP file"),
})
format_commands = ListCompat(
{
'rpm': ('bdist_rpm', "RPM distribution"),
'gztar': ('bdist_dumb', "gzip'ed tar file"),
'bztar': ('bdist_dumb', "bzip2'ed tar file"),
'xztar': ('bdist_dumb', "xz'ed tar file"),
'ztar': ('bdist_dumb', "compressed tar file"),
'tar': ('bdist_dumb', "tar file"),
'zip': ('bdist_dumb', "ZIP file"),
}
)
# for compatibility until consumers only reference format_commands
format_command = format_commands
@@ -107,11 +97,11 @@ class bdist(Command):
self.plat_name = None
self.formats = None
self.dist_dir = None
self.skip_build = False
self.skip_build = 0
self.group = None
self.owner = None
def finalize_options(self) -> None:
def finalize_options(self):
# have to finalize 'plat_name' before 'bdist_base'
if self.plat_name is None:
if self.skip_build:
@@ -133,20 +123,20 @@ class bdist(Command):
except KeyError:
raise DistutilsPlatformError(
"don't know how to create built distributions "
f"on platform {os.name}"
"on platform %s" % os.name
)
if self.dist_dir is None:
self.dist_dir = "dist"
def run(self) -> None:
def run(self):
# Figure out which sub-commands we need to run.
commands = []
for format in self.formats:
try:
commands.append(self.format_commands[format][0])
except KeyError:
raise DistutilsOptionError(f"invalid format '{format}'")
raise DistutilsOptionError("invalid format '%s'" % format)
# Reinitialize and run each command.
for i in range(len(self.formats)):
@@ -163,5 +153,5 @@ class bdist(Command):
# If we're going to need to run this command again, tell it to
# keep its temporary files around so subsequent runs go faster.
if cmd_name in commands[i + 1 :]:
sub_cmd.keep_temp = True
sub_cmd.keep_temp = 1
self.run_command(cmd_name)
@@ -5,17 +5,16 @@ distribution -- i.e., just an archive to be unpacked under $prefix or
$exec_prefix)."""
import os
from distutils._log import log
from typing import ClassVar
from ..core import Command
from ..dir_util import ensure_relative, remove_tree
from ..errors import DistutilsPlatformError
from ..sysconfig import get_python_version
from ..util import get_platform
from distutils.core import Command
from distutils.util import get_platform
from distutils.dir_util import remove_tree, ensure_relative
from distutils.errors import DistutilsPlatformError
from distutils.sysconfig import get_python_version
from distutils import log
class bdist_dumb(Command):
description = "create a \"dumb\" built distribution"
user_options = [
@@ -24,38 +23,39 @@ class bdist_dumb(Command):
'plat-name=',
'p',
"platform name to embed in generated filenames "
f"[default: {get_platform()}]",
"(default: %s)" % get_platform(),
),
(
'format=',
'f',
"archive format to create (tar, gztar, bztar, xztar, ztar, zip)",
"archive format to create (tar, gztar, bztar, xztar, " "ztar, zip)",
),
(
'keep-temp',
'k',
"keep the pseudo-installation tree around after creating the distribution archive",
"keep the pseudo-installation tree around after "
+ "creating the distribution archive",
),
('dist-dir=', 'd', "directory to put final built distributions in"),
('skip-build', None, "skip rebuilding everything (for testing/debugging)"),
(
'relative',
None,
"build the archive using relative paths [default: false]",
"build the archive using relative paths " "(default: false)",
),
(
'owner=',
'u',
"Owner name used when creating a tar file [default: current user]",
"Owner name used when creating a tar file" " [default: current user]",
),
(
'group=',
'g',
"Group name used when creating a tar file [default: current group]",
"Group name used when creating a tar file" " [default: current group]",
),
]
boolean_options: ClassVar[list[str]] = ['keep-temp', 'skip-build', 'relative']
boolean_options = ['keep-temp', 'skip-build', 'relative']
default_format = {'posix': 'gztar', 'nt': 'zip'}
@@ -63,10 +63,10 @@ class bdist_dumb(Command):
self.bdist_dir = None
self.plat_name = None
self.format = None
self.keep_temp = False
self.keep_temp = 0
self.dist_dir = None
self.skip_build = None
self.relative = False
self.relative = 0
self.owner = None
self.group = None
@@ -81,7 +81,7 @@ class bdist_dumb(Command):
except KeyError:
raise DistutilsPlatformError(
"don't know how to create dumb built distributions "
f"on platform {os.name}"
"on platform %s" % os.name
)
self.set_undefined_options(
@@ -95,17 +95,19 @@ class bdist_dumb(Command):
if not self.skip_build:
self.run_command('build')
install = self.reinitialize_command('install', reinit_subcommands=True)
install = self.reinitialize_command('install', reinit_subcommands=1)
install.root = self.bdist_dir
install.skip_build = self.skip_build
install.warn_dir = False
install.warn_dir = 0
log.info("installing to %s", self.bdist_dir)
self.run_command('install')
# And make an archive relative to the root of the
# pseudo-installation tree.
archive_basename = f"{self.distribution.get_fullname()}.{self.plat_name}"
archive_basename = "{}.{}".format(
self.distribution.get_fullname(), self.plat_name
)
pseudoinstall_root = os.path.join(self.dist_dir, archive_basename)
if not self.relative:
@@ -116,7 +118,8 @@ class bdist_dumb(Command):
):
raise DistutilsPlatformError(
"can't make a dumb built distribution where "
f"base and platbase are different ({install.install_base!r}, {install.install_platbase!r})"
"base and platbase are different (%s, %s)"
% (repr(install.install_base), repr(install.install_platbase))
)
else:
archive_root = os.path.join(
@@ -3,25 +3,25 @@
Implements the Distutils 'bdist_rpm' command (create RPM source and binary
distributions)."""
import os
import subprocess
import sys
from distutils._log import log
from typing import ClassVar
import os
from ..core import Command
from ..debug import DEBUG
from ..errors import (
DistutilsExecError,
DistutilsFileError,
from distutils.core import Command
from distutils.debug import DEBUG
from distutils.file_util import write_file
from distutils.errors import (
DistutilsOptionError,
DistutilsPlatformError,
DistutilsFileError,
DistutilsExecError,
)
from ..file_util import write_file
from ..sysconfig import get_python_version
from distutils.sysconfig import get_python_version
from distutils import log
class bdist_rpm(Command):
description = "create an RPM distribution"
user_options = [
@@ -35,13 +35,13 @@ class bdist_rpm(Command):
(
'dist-dir=',
'd',
"directory to put final RPM files in (and .spec files if --spec-only)",
"directory to put final RPM files in " "(and .spec files if --spec-only)",
),
(
'python=',
None,
"path to Python interpreter to hard-code in the .spec file "
"[default: \"python\"]",
"(default: \"python\")",
),
(
'fix-python',
@@ -76,7 +76,7 @@ class bdist_rpm(Command):
(
'packager=',
None,
"RPM packager (eg. \"Jane Doe <jane@example.net>\") [default: vendor]",
"RPM packager (eg. \"Jane Doe <jane@example.net>\") " "[default: vendor]",
),
('doc-files=', None, "list of documentation files (space or comma-separated)"),
('changelog=', None, "RPM changelog"),
@@ -137,7 +137,7 @@ class bdist_rpm(Command):
('quiet', 'q', "Run the INSTALL phase of RPM building in quiet mode"),
]
boolean_options: ClassVar[list[str]] = [
boolean_options = [
'keep-temp',
'use-rpm-opt-flags',
'rpm3-mode',
@@ -145,7 +145,7 @@ class bdist_rpm(Command):
'quiet',
]
negative_opt: ClassVar[dict[str, str]] = {
negative_opt = {
'no-keep-temp': 'keep-temp',
'no-rpm-opt-flags': 'use-rpm-opt-flags',
'rpm2-mode': 'rpm3-mode',
@@ -188,15 +188,15 @@ class bdist_rpm(Command):
self.build_requires = None
self.obsoletes = None
self.keep_temp = False
self.use_rpm_opt_flags = True
self.rpm3_mode = True
self.no_autoreq = False
self.keep_temp = 0
self.use_rpm_opt_flags = 1
self.rpm3_mode = 1
self.no_autoreq = 0
self.force_arch = None
self.quiet = False
self.quiet = 0
def finalize_options(self) -> None:
def finalize_options(self):
self.set_undefined_options('bdist', ('bdist_base', 'bdist_base'))
if self.rpm_base is None:
if not self.rpm3_mode:
@@ -215,7 +215,7 @@ class bdist_rpm(Command):
if os.name != 'posix':
raise DistutilsPlatformError(
f"don't know how to create RPM distributions on platform {os.name}"
"don't know how to create RPM " "distributions on platform %s" % os.name
)
if self.binary_only and self.source_only:
raise DistutilsOptionError(
@@ -224,16 +224,17 @@ class bdist_rpm(Command):
# don't pass CFLAGS to pure python distributions
if not self.distribution.has_ext_modules():
self.use_rpm_opt_flags = False
self.use_rpm_opt_flags = 0
self.set_undefined_options('bdist', ('dist_dir', 'dist_dir'))
self.finalize_package_data()
def finalize_package_data(self) -> None:
def finalize_package_data(self):
self.ensure_string('group', "Development/Libraries")
self.ensure_string(
'vendor',
f"{self.distribution.get_contact()} <{self.distribution.get_contact_email()}>",
"%s <%s>"
% (self.distribution.get_contact(), self.distribution.get_contact_email()),
)
self.ensure_string('packager')
self.ensure_string_list('doc_files')
@@ -275,7 +276,7 @@ class bdist_rpm(Command):
self.ensure_string('force_arch')
def run(self) -> None: # noqa: C901
def run(self): # noqa: C901
if DEBUG:
print("before _get_package_data():")
print("vendor =", self.vendor)
@@ -296,9 +297,9 @@ class bdist_rpm(Command):
# Spec file goes into 'dist_dir' if '--spec-only specified',
# build/rpm.<plat> otherwise.
spec_path = os.path.join(spec_dir, f"{self.distribution.get_name()}.spec")
spec_path = os.path.join(spec_dir, "%s.spec" % self.distribution.get_name())
self.execute(
write_file, (spec_path, self._make_spec_file()), f"writing '{spec_path}'"
write_file, (spec_path, self._make_spec_file()), "writing '%s'" % spec_path
)
if self.spec_only: # stop if requested
@@ -323,7 +324,7 @@ class bdist_rpm(Command):
if os.path.exists(self.icon):
self.copy_file(self.icon, source_dir)
else:
raise DistutilsFileError(f"icon file '{self.icon}' does not exist")
raise DistutilsFileError("icon file '%s' does not exist" % self.icon)
# build package
log.info("building RPMs")
@@ -335,9 +336,9 @@ class bdist_rpm(Command):
rpm_cmd.append('-bb')
else:
rpm_cmd.append('-ba')
rpm_cmd.extend(['--define', f'__python {self.python}'])
rpm_cmd.extend(['--define', '__python %s' % self.python])
if self.rpm3_mode:
rpm_cmd.extend(['--define', f'_topdir {os.path.abspath(self.rpm_base)}'])
rpm_cmd.extend(['--define', '_topdir %s' % os.path.abspath(self.rpm_base)])
if not self.keep_temp:
rpm_cmd.append('--clean')
@@ -352,7 +353,11 @@ class bdist_rpm(Command):
nvr_string = "%{name}-%{version}-%{release}"
src_rpm = nvr_string + ".src.rpm"
non_src_rpm = "%{arch}/" + nvr_string + ".%{arch}.rpm"
q_cmd = rf"rpm -q --qf '{src_rpm} {non_src_rpm}\n' --specfile '{spec_path}'"
q_cmd = r"rpm -q --qf '{} {}\n' --specfile '{}'".format(
src_rpm,
non_src_rpm,
spec_path,
)
out = os.popen(q_cmd)
try:
@@ -371,7 +376,7 @@ class bdist_rpm(Command):
status = out.close()
if status:
raise DistutilsExecError(f"Failed to execute: {q_cmd!r}")
raise DistutilsExecError("Failed to execute: %s" % repr(q_cmd))
finally:
out.close()
@@ -397,11 +402,9 @@ class bdist_rpm(Command):
if os.path.exists(rpm):
self.move_file(rpm, self.dist_dir)
filename = os.path.join(self.dist_dir, os.path.basename(rpm))
self.distribution.dist_files.append((
'bdist_rpm',
pyversion,
filename,
))
self.distribution.dist_files.append(
('bdist_rpm', pyversion, filename)
)
def _dist_path(self, path):
return os.path.join(self.dist_dir, os.path.basename(path))
@@ -426,14 +429,14 @@ class bdist_rpm(Command):
# Generate a potential replacement value for __os_install_post (whilst
# normalizing the whitespace to simplify the test for whether the
# invocation of brp-python-bytecompile passes in __python):
vendor_hook = '\n'.join([
f' {line.strip()} \\' for line in vendor_hook.splitlines()
])
vendor_hook = '\n'.join(
[' %s \\' % line.strip() for line in vendor_hook.splitlines()]
)
problem = "brp-python-bytecompile \\\n"
fixed = "brp-python-bytecompile %{__python} \\\n"
fixed_hook = vendor_hook.replace(problem, fixed)
if fixed_hook != vendor_hook:
spec_file.append('# Workaround for https://bugs.python.org/issue14443')
spec_file.append('# Workaround for http://bugs.python.org/issue14443')
spec_file.append('%define __os_install_post ' + fixed_hook + '\n')
# put locale summaries into spec file
@@ -443,11 +446,13 @@ class bdist_rpm(Command):
# spec_file.append('Summary(%s): %s' % (locale,
# self.summaries[locale]))
spec_file.extend([
'Name: %{name}',
'Version: %{version}',
'Release: %{release}',
])
spec_file.extend(
[
'Name: %{name}',
'Version: %{version}',
'Release: %{release}',
]
)
# XXX yuck! this filename is available from the "sdist" command,
# but only after it has run: and we create the spec file before
@@ -457,19 +462,21 @@ class bdist_rpm(Command):
else:
spec_file.append('Source0: %{name}-%{unmangled_version}.tar.gz')
spec_file.extend([
'License: ' + (self.distribution.get_license() or "UNKNOWN"),
'Group: ' + self.group,
'BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot',
'Prefix: %{_prefix}',
])
spec_file.extend(
[
'License: ' + (self.distribution.get_license() or "UNKNOWN"),
'Group: ' + self.group,
'BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot',
'Prefix: %{_prefix}',
]
)
if not self.force_arch:
# noarch if no extension modules
if not self.distribution.has_ext_modules():
spec_file.append('BuildArch: noarch')
else:
spec_file.append(f'BuildArch: {self.force_arch}')
spec_file.append('BuildArch: %s' % self.force_arch)
for field in (
'Vendor',
@@ -483,7 +490,7 @@ class bdist_rpm(Command):
if isinstance(val, list):
spec_file.append('{}: {}'.format(field, ' '.join(val)))
elif val is not None:
spec_file.append(f'{field}: {val}')
spec_file.append('{}: {}'.format(field, val))
if self.distribution.get_url():
spec_file.append('Url: ' + self.distribution.get_url())
@@ -500,11 +507,13 @@ class bdist_rpm(Command):
if self.no_autoreq:
spec_file.append('AutoReq: 0')
spec_file.extend([
'',
'%description',
self.distribution.get_long_description() or "",
])
spec_file.extend(
[
'',
'%description',
self.distribution.get_long_description() or "",
]
)
# put locale descriptions into spec file
# XXX again, suppressed because config file syntax doesn't
@@ -518,8 +527,8 @@ class bdist_rpm(Command):
# rpm scripts
# figure out default build script
def_setup_call = f"{self.python} {os.path.basename(sys.argv[0])}"
def_build = f"{def_setup_call} build"
def_setup_call = "{} {}".format(self.python, os.path.basename(sys.argv[0]))
def_build = "%s build" % def_setup_call
if self.use_rpm_opt_flags:
def_build = 'env CFLAGS="$RPM_OPT_FLAGS" ' + def_build
@@ -529,7 +538,9 @@ class bdist_rpm(Command):
# that we open and interpolate into the spec file, but the defaults
# are just text that we drop in as-is. Hmmm.
install_cmd = f'{def_setup_call} install -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES'
install_cmd = (
'%s install -O1 --root=$RPM_BUILD_ROOT ' '--record=INSTALLED_FILES'
) % def_setup_call
script_options = [
('prep', 'prep_script', "%setup -n %{name}-%{unmangled_version}"),
@@ -543,15 +554,17 @@ class bdist_rpm(Command):
('postun', 'post_uninstall', None),
]
for rpm_opt, attr, default in script_options:
for (rpm_opt, attr, default) in script_options:
# Insert contents of file referred to, if no file is referred to
# use 'default' as contents of script
val = getattr(self, attr)
if val or default:
spec_file.extend([
'',
'%' + rpm_opt,
])
spec_file.extend(
[
'',
'%' + rpm_opt,
]
)
if val:
with open(val) as f:
spec_file.extend(f.read().split('\n'))
@@ -559,20 +572,24 @@ class bdist_rpm(Command):
spec_file.append(default)
# files section
spec_file.extend([
'',
'%files -f INSTALLED_FILES',
'%defattr(-,root,root)',
])
spec_file.extend(
[
'',
'%files -f INSTALLED_FILES',
'%defattr(-,root,root)',
]
)
if self.doc_files:
spec_file.append('%doc ' + ' '.join(self.doc_files))
if self.changelog:
spec_file.extend([
'',
'%changelog',
])
spec_file.extend(
[
'',
'%changelog',
]
)
spec_file.extend(self.changelog)
return spec_file
@@ -2,21 +2,21 @@
Implements the Distutils 'build' command."""
from __future__ import annotations
import os
import sys
import sysconfig
from collections.abc import Callable
from typing import ClassVar
import os
from distutils.core import Command
from distutils.errors import DistutilsOptionError
from distutils.util import get_platform
from ..ccompiler import show_compilers
from ..core import Command
from ..errors import DistutilsOptionError
from ..util import get_platform
def show_compilers():
from distutils.ccompiler import show_compilers
show_compilers()
class build(Command):
description = "build everything needed to install"
user_options = [
@@ -26,14 +26,16 @@ class build(Command):
(
'build-lib=',
None,
"build directory for all distribution (defaults to either build-purelib or build-platlib",
"build directory for all distribution (defaults to either "
+ "build-purelib or build-platlib",
),
('build-scripts=', None, "build directory for scripts"),
('build-temp=', 't', "temporary build directory"),
(
'plat-name=',
'p',
f"platform name to build for, if supported [default: {get_platform()}]",
"platform name to build for, if supported "
"(default: %s)" % get_platform(),
),
('compiler=', 'c', "specify the compiler type"),
('parallel=', 'j', "number of parallel build jobs"),
@@ -42,9 +44,9 @@ class build(Command):
('executable=', 'e', "specify final destination interpreter path (build.py)"),
]
boolean_options: ClassVar[list[str]] = ['debug', 'force']
boolean_options = ['debug', 'force']
help_options: ClassVar[list[tuple[str, str | None, str, Callable[[], object]]]] = [
help_options = [
('help-compiler', None, "list available compilers", show_compilers),
]
@@ -60,11 +62,11 @@ class build(Command):
self.compiler = None
self.plat_name = None
self.debug = None
self.force = False
self.force = 0
self.executable = None
self.parallel = None
def finalize_options(self) -> None: # noqa: C901
def finalize_options(self): # noqa: C901
if self.plat_name is None:
self.plat_name = get_platform()
else:
@@ -77,11 +79,7 @@ class build(Command):
"using './configure --help' on your platform)"
)
plat_specifier = f".{self.plat_name}-{sys.implementation.cache_tag}"
# Python 3.13+ with --disable-gil shouldn't share build directories
if sysconfig.get_config_var('Py_GIL_DISABLED'):
plat_specifier += 't'
plat_specifier = ".{}-{}".format(self.plat_name, sys.implementation.cache_tag)
# Make it so Python 2.x and Python 2.x with --with-pydebug don't
# share the same build directories. Doing so confuses the build
@@ -112,8 +110,7 @@ class build(Command):
self.build_temp = os.path.join(self.build_base, 'temp' + plat_specifier)
if self.build_scripts is None:
self.build_scripts = os.path.join(
self.build_base,
f'scripts-{sys.version_info.major}.{sys.version_info.minor}',
self.build_base, 'scripts-%d.%d' % sys.version_info[:2]
)
if self.executable is None and sys.executable:
@@ -125,7 +122,7 @@ class build(Command):
except ValueError:
raise DistutilsOptionError("parallel should be an integer")
def run(self) -> None:
def run(self):
# Run all relevant sub-commands. This will be some subset of:
# - build_py - pure Python modules
# - build_clib - standalone C libraries
@@ -4,6 +4,7 @@ Implements the Distutils 'build_clib' command, to build a C/C++ library
that is included in the module distribution and needed by an extension
module."""
# XXX this module has *lots* of code ripped-off quite transparently from
# build_ext.py -- not surprisingly really, as the work required to build
# a static library from a collection of C source files is not really all
@@ -12,23 +13,25 @@ module."""
# necessary refactoring to account for the overlap in code between the
# two modules, mainly because a number of subtle details changed in the
# cut 'n paste. Sigh.
from __future__ import annotations
import os
from collections.abc import Callable
from distutils._log import log
from typing import ClassVar
from distutils.core import Command
from distutils.errors import DistutilsSetupError
from distutils.sysconfig import customize_compiler
from distutils import log
from ..ccompiler import new_compiler, show_compilers
from ..core import Command
from ..errors import DistutilsSetupError
from ..sysconfig import customize_compiler
def show_compilers():
from distutils.ccompiler import show_compilers
show_compilers()
class build_clib(Command):
description = "build C/C++ libraries used by Python extensions"
user_options: ClassVar[list[tuple[str, str, str]]] = [
user_options = [
('build-clib=', 'b', "directory to build C/C++ libraries to"),
('build-temp=', 't', "directory to put temporary build by-products"),
('debug', 'g', "compile with debugging information"),
@@ -36,9 +39,9 @@ class build_clib(Command):
('compiler=', 'c', "specify the compiler type"),
]
boolean_options: ClassVar[list[str]] = ['debug', 'force']
boolean_options = ['debug', 'force']
help_options: ClassVar[list[tuple[str, str | None, str, Callable[[], object]]]] = [
help_options = [
('help-compiler', None, "list available compilers", show_compilers),
]
@@ -54,10 +57,10 @@ class build_clib(Command):
self.define = None
self.undef = None
self.debug = None
self.force = False
self.force = 0
self.compiler = None
def finalize_options(self) -> None:
def finalize_options(self):
# This might be confusing: both build-clib and build-temp default
# to build-temp as defined by the "build" command. This is because
# I think that C libraries are really just temporary build
@@ -84,10 +87,13 @@ class build_clib(Command):
# XXX same as for build_ext -- what about 'self.define' and
# 'self.undef' ?
def run(self) -> None:
def run(self):
if not self.libraries:
return
# Yech -- this is cut 'n pasted from build_ext.py!
from distutils.ccompiler import new_compiler
self.compiler = new_compiler(
compiler=self.compiler, dry_run=self.dry_run, force=self.force
)
@@ -97,7 +103,7 @@ class build_clib(Command):
self.compiler.set_include_dirs(self.include_dirs)
if self.define is not None:
# 'define' option is a list of (name,value) tuples
for name, value in self.define:
for (name, value) in self.define:
self.compiler.define_macro(name, value)
if self.undef is not None:
for macro in self.undef:
@@ -105,7 +111,7 @@ class build_clib(Command):
self.build_libraries(self.libraries)
def check_library_list(self, libraries) -> None:
def check_library_list(self, libraries):
"""Ensure that the list of libraries is valid.
`library` is presumably provided as a command option 'libraries'.
@@ -132,7 +138,8 @@ class build_clib(Command):
if '/' in name or (os.sep != '/' and os.sep in name):
raise DistutilsSetupError(
f"bad library name '{lib[0]}': may not contain directory separators"
"bad library name '%s': "
"may not contain directory separators" % lib[0]
)
if not isinstance(build_info, dict):
@@ -148,33 +155,33 @@ class build_clib(Command):
return None
lib_names = []
for lib_name, _build_info in self.libraries:
for (lib_name, build_info) in self.libraries:
lib_names.append(lib_name)
return lib_names
def get_source_files(self):
self.check_library_list(self.libraries)
filenames = []
for lib_name, build_info in self.libraries:
for (lib_name, build_info) in self.libraries:
sources = build_info.get('sources')
if sources is None or not isinstance(sources, (list, tuple)):
raise DistutilsSetupError(
f"in 'libraries' option (library '{lib_name}'), "
"in 'libraries' option (library '%s'), "
"'sources' must be present and must be "
"a list of source filenames"
"a list of source filenames" % lib_name
)
filenames.extend(sources)
return filenames
def build_libraries(self, libraries) -> None:
for lib_name, build_info in libraries:
def build_libraries(self, libraries):
for (lib_name, build_info) in libraries:
sources = build_info.get('sources')
if sources is None or not isinstance(sources, (list, tuple)):
raise DistutilsSetupError(
f"in 'libraries' option (library '{lib_name}'), "
"in 'libraries' option (library '%s'), "
"'sources' must be present and must be "
"a list of source filenames"
"a list of source filenames" % lib_name
)
sources = list(sources)
@@ -4,38 +4,42 @@ Implements the Distutils 'build_ext' command, for building extension
modules (currently limited to C extensions, should accommodate C++
extensions ASAP)."""
from __future__ import annotations
import contextlib
import os
import re
import sys
from collections.abc import Callable
from distutils._log import log
from site import USER_BASE
from typing import ClassVar
from .._modified import newer_group
from ..ccompiler import new_compiler, show_compilers
from ..core import Command
from ..errors import (
CCompilerError,
CompileError,
DistutilsError,
from distutils.core import Command
from distutils.errors import (
DistutilsOptionError,
DistutilsPlatformError,
DistutilsSetupError,
CCompilerError,
DistutilsError,
CompileError,
DistutilsPlatformError,
)
from ..extension import Extension
from ..sysconfig import customize_compiler, get_config_h_filename, get_python_version
from ..util import get_platform, is_freethreaded, is_mingw
from distutils.sysconfig import customize_compiler, get_python_version
from distutils.sysconfig import get_config_h_filename
from distutils.dep_util import newer_group
from distutils.extension import Extension
from distutils.util import get_platform
from distutils import log
from . import py37compat
from site import USER_BASE
# An extension name is just a dot-separated list of Python NAMEs (ie.
# the same as a fully-qualified module name).
extension_name_re = re.compile(r'^[a-zA-Z_][a-zA-Z_0-9]*(\.[a-zA-Z_][a-zA-Z_0-9]*)*$')
def show_compilers():
from distutils.ccompiler import show_compilers
show_compilers()
class build_ext(Command):
description = "build C/C++ extensions (compile/link to build directory)"
# XXX thoughts on how to deal with complex command-line options like
@@ -56,7 +60,7 @@ class build_ext(Command):
# takes care of both command-line and client options
# in between initialize_options() and finalize_options())
sep_by = f" (separated by '{os.pathsep}')"
sep_by = " (separated by '%s')" % os.pathsep
user_options = [
('build-lib=', 'b', "directory for compiled extension modules"),
('build-temp=', 't', "directory for temporary files (build by-products)"),
@@ -64,13 +68,13 @@ class build_ext(Command):
'plat-name=',
'p',
"platform name to cross-compile for, if supported "
f"[default: {get_platform()}]",
"(default: %s)" % get_platform(),
),
(
'inplace',
'i',
"ignore build-lib and put compiled extensions into the source "
"directory alongside your pure Python modules",
+ "directory alongside your pure Python modules",
),
(
'include-dirs=',
@@ -97,15 +101,9 @@ class build_ext(Command):
('user', None, "add user include, library and rpath"),
]
boolean_options: ClassVar[list[str]] = [
'inplace',
'debug',
'force',
'swig-cpp',
'user',
]
boolean_options = ['inplace', 'debug', 'force', 'swig-cpp', 'user']
help_options: ClassVar[list[tuple[str, str | None, str, Callable[[], object]]]] = [
help_options = [
('help-compiler', None, "list available compilers", show_compilers),
]
@@ -114,7 +112,7 @@ class build_ext(Command):
self.build_lib = None
self.plat_name = None
self.build_temp = None
self.inplace = False
self.inplace = 0
self.package = None
self.include_dirs = None
@@ -133,32 +131,7 @@ class build_ext(Command):
self.user = None
self.parallel = None
@staticmethod
def _python_lib_dir(sysconfig):
"""
Resolve Python's library directory for building extensions
that rely on a shared Python library.
See python/cpython#44264 and python/cpython#48686
"""
if not sysconfig.get_config_var('Py_ENABLE_SHARED'):
return
if sysconfig.python_build:
yield '.'
return
if sys.platform == 'zos':
# On z/OS, a user is not required to install Python to
# a predetermined path, but can use Python portably
installed_dir = sysconfig.get_config_var('base')
lib_dir = sysconfig.get_config_var('platlibdir')
yield os.path.join(installed_dir, lib_dir)
else:
# building third party extensions
yield sysconfig.get_config_var('LIBDIR')
def finalize_options(self) -> None: # noqa: C901
def finalize_options(self): # noqa: C901
from distutils import sysconfig
self.set_undefined_options(
@@ -180,7 +153,7 @@ class build_ext(Command):
# Make sure Python's include directories (for Python.h, pyconfig.h,
# etc.) are in the include search path.
py_include = sysconfig.get_python_inc()
plat_py_include = sysconfig.get_python_inc(plat_specific=True)
plat_py_include = sysconfig.get_python_inc(plat_specific=1)
if self.include_dirs is None:
self.include_dirs = self.distribution.include_dirs or []
if isinstance(self.include_dirs, str):
@@ -217,7 +190,7 @@ class build_ext(Command):
# for extensions under windows use different directories
# for Release and Debug builds.
# also Python's library directory must be appended to library_dirs
if os.name == 'nt' and not is_mingw():
if os.name == 'nt':
# the 'libs' directory is for binary installs - we assume that
# must be the *native* platform. But we don't really support
# cross-compiling via a binary install anyway, so we let it go.
@@ -259,7 +232,16 @@ class build_ext(Command):
# building python standard extensions
self.library_dirs.append('.')
self.library_dirs.extend(self._python_lib_dir(sysconfig))
# For building extensions with a shared Python library,
# Python's library directory must be appended to library_dirs
# See Issues: #1600860, #4366
if sysconfig.get_config_var('Py_ENABLE_SHARED'):
if not sysconfig.python_build:
# building third party extensions
self.library_dirs.append(sysconfig.get_config_var('LIBDIR'))
else:
# building python standard extensions
self.library_dirs.append('.')
# The argument parsing will result in self.define being a string, but
# it has to be a list of 2-tuples. All the preprocessor symbols
@@ -297,7 +279,9 @@ class build_ext(Command):
except ValueError:
raise DistutilsOptionError("parallel should be an integer")
def run(self) -> None: # noqa: C901
def run(self): # noqa: C901
from distutils.ccompiler import new_compiler
# 'self.extensions', as supplied by setup.py, is a list of
# Extension instances. See the documentation for Extension (in
# distutils.extension) for details.
@@ -336,12 +320,6 @@ class build_ext(Command):
if os.name == 'nt' and self.plat_name != get_platform():
self.compiler.initialize(self.plat_name)
# The official Windows free threaded Python installer doesn't set
# Py_GIL_DISABLED because its pyconfig.h is shared with the
# default build, so define it here (pypa/setuptools#4662).
if os.name == 'nt' and is_freethreaded():
self.compiler.define_macro('Py_GIL_DISABLED', '1')
# And make sure that any compile/link-related options (which might
# come from the command-line or from the setup script) are set in
# that CCompiler object -- that way, they automatically apply to
@@ -350,7 +328,7 @@ class build_ext(Command):
self.compiler.set_include_dirs(self.include_dirs)
if self.define is not None:
# 'define' option is a list of (name,value) tuples
for name, value in self.define:
for (name, value) in self.define:
self.compiler.define_macro(name, value)
if self.undef is not None:
for macro in self.undef:
@@ -367,7 +345,7 @@ class build_ext(Command):
# Now actually compile and link everything.
self.build_extensions()
def check_extensions_list(self, extensions) -> None: # noqa: C901
def check_extensions_list(self, extensions): # noqa: C901
"""Ensure that the list of extensions (presumably provided as a
command option 'extensions') is valid, i.e. it is a list of
Extension objects. We also support the old-style list of 2-tuples,
@@ -395,7 +373,7 @@ class build_ext(Command):
ext_name, build_info = ext
log.warning(
log.warn(
"old-style (ext_name, build_info) tuple found in "
"ext_modules for extension '%s' "
"-- please convert to Extension instance",
@@ -435,7 +413,7 @@ class build_ext(Command):
# Medium-easy stuff: same syntax/semantics, different names.
ext.runtime_library_dirs = build_info.get('rpath')
if 'def_file' in build_info:
log.warning("'def_file' element of build info dict no longer supported")
log.warn("'def_file' element of build info dict " "no longer supported")
# Non-trivial stuff: 'macros' split into 'define_macros'
# and 'undef_macros'.
@@ -446,7 +424,8 @@ class build_ext(Command):
for macro in macros:
if not (isinstance(macro, tuple) and len(macro) in (1, 2)):
raise DistutilsSetupError(
"'macros' element of build info dict must be 1- or 2-tuple"
"'macros' element of build info dict "
"must be 1- or 2-tuple"
)
if len(macro) == 1:
ext.undef_macros.append(macro[0])
@@ -473,9 +452,12 @@ class build_ext(Command):
# And build the list of output (built) filenames. Note that this
# ignores the 'inplace' flag, and assumes everything goes in the
# "build" tree.
return [self.get_ext_fullpath(ext.name) for ext in self.extensions]
outputs = []
for ext in self.extensions:
outputs.append(self.get_ext_fullpath(ext.name))
return outputs
def build_extensions(self) -> None:
def build_extensions(self):
# First, sanity-check the 'extensions' list
self.check_extensions_list(self.extensions)
if self.parallel:
@@ -516,15 +498,15 @@ class build_ext(Command):
except (CCompilerError, DistutilsError, CompileError) as e:
if not ext.optional:
raise
self.warn(f'building extension "{ext.name}" failed: {e}')
self.warn('building extension "{}" failed: {}'.format(ext.name, e))
def build_extension(self, ext) -> None:
def build_extension(self, ext):
sources = ext.sources
if sources is None or not isinstance(sources, (list, tuple)):
raise DistutilsSetupError(
f"in 'ext_modules' option (extension '{ext.name}'), "
"in 'ext_modules' option (extension '%s'), "
"'sources' must be present and must be "
"a list of source filenames"
"a list of source filenames" % ext.name
)
# sort to make the resulting .so file build reproducible
sources = sorted(sources)
@@ -615,7 +597,7 @@ class build_ext(Command):
# the temp dir.
if self.swig_cpp:
log.warning("--swig-cpp is deprecated - use --swig-opts=-c++")
log.warn("--swig-cpp is deprecated - use --swig-opts=-c++")
if (
self.swig_cpp
@@ -646,7 +628,8 @@ class build_ext(Command):
# Do not override commandline arguments
if not self.swig_opts:
swig_cmd.extend(extension.swig_opts)
for o in extension.swig_opts:
swig_cmd.append(o)
for source in swig_sources:
target = swig_targets[source]
@@ -667,19 +650,20 @@ class build_ext(Command):
# Windows (or so I presume!). If we find it there, great;
# if not, act like Unix and assume it's in the PATH.
for vers in ("1.3", "1.2", "1.1"):
fn = os.path.join(f"c:\\swig{vers}", "swig.exe")
fn = os.path.join("c:\\swig%s" % vers, "swig.exe")
if os.path.isfile(fn):
return fn
else:
return "swig.exe"
else:
raise DistutilsPlatformError(
f"I don't know how to find (much less run) SWIG on platform '{os.name}'"
"I don't know how to find (much less run) SWIG "
"on platform '%s'" % os.name
)
# -- Name generators -----------------------------------------------
# (extension names, filenames, whatever)
def get_ext_fullpath(self, ext_name: str) -> str:
def get_ext_fullpath(self, ext_name):
"""Returns the path of the filename for a given extension.
The file is located in `build_lib` or directly in the package
@@ -706,7 +690,7 @@ class build_ext(Command):
# package_dir/filename
return os.path.join(package_dir, filename)
def get_ext_fullname(self, ext_name: str) -> str:
def get_ext_fullname(self, ext_name):
"""Returns the fullname of a given extension name.
Adds the `package.` prefix"""
@@ -715,27 +699,27 @@ class build_ext(Command):
else:
return self.package + '.' + ext_name
def get_ext_filename(self, ext_name: str) -> str:
def get_ext_filename(self, ext_name):
r"""Convert the name of an extension (eg. "foo.bar") into the name
of the file from which it will be loaded (eg. "foo/bar.so", or
"foo\bar.pyd").
"""
from ..sysconfig import get_config_var
from distutils.sysconfig import get_config_var
ext_path = ext_name.split('.')
ext_suffix = get_config_var('EXT_SUFFIX')
return os.path.join(*ext_path) + ext_suffix
def get_export_symbols(self, ext: Extension) -> list[str]:
def get_export_symbols(self, ext):
"""Return the list of symbols that a shared extension has to
export. This either uses 'ext.export_symbols' or, if it's not
provided, "PyInit_" + module_name. Only relevant on Windows, where
the .pyd file (DLL) must export the module "PyInit_" function.
"""
name = self._get_module_name_for_symbol(ext)
name = ext.name.split('.')[-1]
try:
# Unicode module name support as defined in PEP-489
# https://peps.python.org/pep-0489/#export-hook-name
# https://www.python.org/dev/peps/pep-0489/#export-hook-name
name.encode('ascii')
except UnicodeEncodeError:
suffix = 'U_' + name.encode('punycode').replace(b'-', b'_').decode('ascii')
@@ -747,16 +731,7 @@ class build_ext(Command):
ext.export_symbols.append(initfunc_name)
return ext.export_symbols
def _get_module_name_for_symbol(self, ext):
# Package name should be used for `__init__` modules
# https://github.com/python/cpython/issues/80074
# https://github.com/pypa/setuptools/issues/4826
parts = ext.name.split(".")
if parts[-1] == "__init__" and len(parts) >= 2:
return parts[-2]
return parts[-1]
def get_libraries(self, ext: Extension) -> list[str]: # noqa: C901
def get_libraries(self, ext): # noqa: C901
"""Return the list of libraries to link against when building a
shared extension. On most platforms, this is just 'ext.libraries';
on Windows, we add the Python library (eg. python20.dll).
@@ -766,8 +741,8 @@ class build_ext(Command):
# pyconfig.h that MSVC groks. The other Windows compilers all seem
# to need it mentioned explicitly, though, so that's what we do.
# Append '_d' to the python import library on debug builds.
if sys.platform == "win32" and not is_mingw():
from .._msvccompiler import MSVCCompiler
if sys.platform == "win32":
from distutils._msvccompiler import MSVCCompiler
if not isinstance(self.compiler, MSVCCompiler):
template = "python%d%d"
@@ -789,14 +764,14 @@ class build_ext(Command):
# On Cygwin (and if required, other POSIX-like platforms based on
# Windows like MinGW) it is simply necessary that all symbols in
# shared libraries are resolved at link time.
from ..sysconfig import get_config_var
from distutils.sysconfig import get_config_var
link_libpython = False
if get_config_var('Py_ENABLE_SHARED'):
# A native build on an Android device or on Cygwin
if hasattr(sys, 'getandroidapilevel'):
link_libpython = True
elif sys.platform == 'cygwin' or is_mingw():
elif sys.platform == 'cygwin':
link_libpython = True
elif '_PYTHON_HOST_PLATFORM' in os.environ:
# We are cross-compiling for one of the relevant platforms
@@ -809,4 +784,4 @@ class build_ext(Command):
ldversion = get_config_var('LDVERSION')
return ext.libraries + ['python' + ldversion]
return ext.libraries
return ext.libraries + py37compat.pythonlib()
@@ -2,19 +2,19 @@
Implements the Distutils 'build_py' command."""
import glob
import importlib.util
import os
import importlib.util
import sys
from distutils._log import log
from typing import ClassVar
import glob
from ..core import Command
from ..errors import DistutilsFileError, DistutilsOptionError
from ..util import convert_path
from distutils.core import Command
from distutils.errors import DistutilsOptionError, DistutilsFileError
from distutils.util import convert_path
from distutils import log
class build_py(Command):
description = "\"build\" pure Python modules (copy to build directory)"
user_options = [
@@ -30,8 +30,8 @@ class build_py(Command):
('force', 'f', "forcibly build everything (ignore file timestamps)"),
]
boolean_options: ClassVar[list[str]] = ['compile', 'force']
negative_opt: ClassVar[dict[str, str]] = {'no-compile': 'compile'}
boolean_options = ['compile', 'force']
negative_opt = {'no-compile': 'compile'}
def initialize_options(self):
self.build_lib = None
@@ -39,11 +39,11 @@ class build_py(Command):
self.package = None
self.package_data = None
self.package_dir = None
self.compile = False
self.compile = 0
self.optimize = 0
self.force = None
def finalize_options(self) -> None:
def finalize_options(self):
self.set_undefined_options(
'build', ('build_lib', 'build_lib'), ('force', 'force')
)
@@ -68,7 +68,7 @@ class build_py(Command):
except (ValueError, AssertionError):
raise DistutilsOptionError("optimize must be 0, 1, or 2")
def run(self) -> None:
def run(self):
# XXX copy_file by default preserves atime and mtime. IMHO this is
# the right thing to do, but perhaps it should be an option -- in
# particular, a site administrator might want installed files to
@@ -96,7 +96,7 @@ class build_py(Command):
self.build_packages()
self.build_package_data()
self.byte_compile(self.get_outputs(include_bytecode=False))
self.byte_compile(self.get_outputs(include_bytecode=0))
def get_data_files(self):
"""Generate list of '(package,src_dir,build_dir,filenames)' tuples"""
@@ -130,14 +130,14 @@ class build_py(Command):
os.path.join(glob.escape(src_dir), convert_path(pattern))
)
# Files that match more than one pattern are only added once
files.extend([
fn for fn in filelist if fn not in files and os.path.isfile(fn)
])
files.extend(
[fn for fn in filelist if fn not in files and os.path.isfile(fn)]
)
return files
def build_package_data(self) -> None:
def build_package_data(self):
"""Copy data files into build directory"""
for _package, src_dir, build_dir, filenames in self.data_files:
for package, src_dir, build_dir, filenames in self.data_files:
for filename in filenames:
target = os.path.join(build_dir, filename)
self.mkpath(os.path.dirname(target))
@@ -192,12 +192,12 @@ class build_py(Command):
if package_dir != "":
if not os.path.exists(package_dir):
raise DistutilsFileError(
f"package directory '{package_dir}' does not exist"
"package directory '%s' does not exist" % package_dir
)
if not os.path.isdir(package_dir):
raise DistutilsFileError(
f"supposed package directory '{package_dir}' exists, "
"but is not a directory"
"supposed package directory '%s' exists, "
"but is not a directory" % package_dir
)
# Directories without __init__.py are namespace packages (PEP 420).
@@ -212,7 +212,7 @@ class build_py(Command):
def check_module(self, module, module_file):
if not os.path.isfile(module_file):
log.warning("file %s (for module %s) not found", module_file, module)
log.warn("file %s (for module %s) not found", module_file, module)
return False
else:
return True
@@ -229,7 +229,7 @@ class build_py(Command):
module = os.path.splitext(os.path.basename(f))[0]
modules.append((package, module, f))
else:
self.debug_print(f"excluding {setup_script}")
self.debug_print("excluding %s" % setup_script)
return modules
def find_modules(self):
@@ -265,7 +265,7 @@ class build_py(Command):
(package_dir, checked) = packages[package]
except KeyError:
package_dir = self.get_package_dir(package)
checked = False
checked = 0
if not checked:
init_py = self.check_package(package, package_dir)
@@ -307,10 +307,10 @@ class build_py(Command):
outfile_path = [build_dir] + list(package) + [module + ".py"]
return os.path.join(*outfile_path)
def get_outputs(self, include_bytecode: bool = True) -> list[str]:
def get_outputs(self, include_bytecode=1):
modules = self.find_all_modules()
outputs = []
for package, module, _module_file in modules:
for (package, module, module_file) in modules:
package = package.split('.')
filename = self.get_module_outfile(self.build_lib, package, module)
outputs.append(filename)
@@ -348,18 +348,18 @@ class build_py(Command):
outfile = self.get_module_outfile(self.build_lib, package, module)
dir = os.path.dirname(outfile)
self.mkpath(dir)
return self.copy_file(module_file, outfile, preserve_mode=False)
return self.copy_file(module_file, outfile, preserve_mode=0)
def build_modules(self) -> None:
def build_modules(self):
modules = self.find_modules()
for package, module, module_file in modules:
for (package, module, module_file) in modules:
# Now "build" the module -- ie. copy the source file to
# self.build_lib (the build directory for Python source).
# (Actually, it gets copied to the directory for this package
# under self.build_lib.)
self.build_module(module, module_file, package)
def build_packages(self) -> None:
def build_packages(self):
for package in self.packages:
# Get list of (package, module, module_file) tuples based on
# scanning the package directory. 'package' is only included
@@ -375,16 +375,16 @@ class build_py(Command):
# Now loop over the modules we found, "building" each one (just
# copy it to self.build_lib).
for package_, module, module_file in modules:
for (package_, module, module_file) in modules:
assert package == package_
self.build_module(module, module_file, package)
def byte_compile(self, files) -> None:
def byte_compile(self, files):
if sys.dont_write_bytecode:
self.warn('byte-compiling is disabled, skipping.')
return
from ..util import byte_compile
from distutils.util import byte_compile
prefix = self.build_lib
if prefix[-1] != os.sep:
@@ -4,14 +4,13 @@ Implements the Distutils 'build_scripts' command."""
import os
import re
import tokenize
from distutils._log import log
from stat import ST_MODE
from typing import ClassVar
from .._modified import newer
from ..core import Command
from ..util import convert_path
from distutils import sysconfig
from distutils.core import Command
from distutils.dep_util import newer
from distutils.util import convert_path
from distutils import log
import tokenize
shebang_pattern = re.compile('^#!.*python[0-9.]*([ \t].*)?$')
"""
@@ -23,15 +22,16 @@ first_line_re = shebang_pattern
class build_scripts(Command):
description = "\"build\" scripts (copy and fixup #! line)"
user_options: ClassVar[list[tuple[str, str, str]]] = [
user_options = [
('build-dir=', 'd', "directory to \"build\" (copy) to"),
('force', 'f', "forcibly build everything (ignore file timestamps"),
('executable=', 'e', "specify final destination interpreter path"),
]
boolean_options: ClassVar[list[str]] = ['force']
boolean_options = ['force']
def initialize_options(self):
self.build_dir = None
@@ -75,7 +75,7 @@ class build_scripts(Command):
return outfiles, updated_files
def _copy_script(self, script, outfiles, updated_files):
def _copy_script(self, script, outfiles, updated_files): # noqa: C901
shebang_match = None
script = convert_path(script)
outfile = os.path.join(self.build_dir, os.path.basename(script))
@@ -96,7 +96,7 @@ class build_scripts(Command):
else:
first_line = f.readline()
if not first_line:
self.warn(f"{script} is an empty file (skipping)")
self.warn("%s is an empty file (skipping)" % script)
return
shebang_match = shebang_pattern.match(first_line)
@@ -105,8 +105,19 @@ class build_scripts(Command):
if shebang_match:
log.info("copying and adjusting %s -> %s", script, self.build_dir)
if not self.dry_run:
if not sysconfig.python_build:
executable = self.executable
else:
executable = os.path.join(
sysconfig.get_config_var("BINDIR"),
"python%s%s"
% (
sysconfig.get_config_var("VERSION"),
sysconfig.get_config_var("EXE"),
),
)
post_interp = shebang_match.group(1) or ''
shebang = f"#!python{post_interp}\n"
shebang = "#!" + executable + post_interp + "\n"
self._validate_shebang(shebang, f.encoding)
with open(outfile, "w", encoding=f.encoding) as outf:
outf.write(shebang)
@@ -146,7 +157,9 @@ class build_scripts(Command):
try:
shebang.encode('utf-8')
except UnicodeEncodeError:
raise ValueError(f"The shebang ({shebang!r}) is not encodable to utf-8")
raise ValueError(
"The shebang ({!r}) is not encodable " "to utf-8".format(shebang)
)
# If the script is encoded to a custom encoding (use a
# #coding:xxx cookie), the shebang has to be encodable to
@@ -155,6 +168,6 @@ class build_scripts(Command):
shebang.encode(encoding)
except UnicodeEncodeError:
raise ValueError(
f"The shebang ({shebang!r}) is not encodable "
f"to the script encoding ({encoding})"
"The shebang ({!r}) is not encodable "
"to the script encoding ({})".format(shebang, encoding)
)
@@ -2,18 +2,16 @@
Implements the Distutils 'check' command.
"""
import contextlib
from typing import ClassVar
from ..core import Command
from ..errors import DistutilsSetupError
from distutils.core import Command
from distutils.errors import DistutilsSetupError
with contextlib.suppress(ImportError):
import docutils.utils
import docutils.parsers.rst
import docutils.frontend
import docutils.nodes
import docutils.parsers.rst
import docutils.utils
class SilentReporter(docutils.utils.Reporter):
def __init__(
@@ -22,7 +20,7 @@ with contextlib.suppress(ImportError):
report_level,
halt_level,
stream=None,
debug=False,
debug=0,
encoding='ascii',
error_handler='replace',
):
@@ -34,7 +32,7 @@ with contextlib.suppress(ImportError):
def system_message(self, level, message, *children, **kwargs):
self.messages.append((level, message, children, kwargs))
return docutils.nodes.system_message(
message, *children, level=level, type=self.levels[level], **kwargs
message, level=level, type=self.levels[level], *children, **kwargs
)
@@ -42,23 +40,26 @@ class check(Command):
"""This command checks the meta-data of the package."""
description = "perform some checks on the package"
user_options: ClassVar[list[tuple[str, str, str]]] = [
user_options = [
('metadata', 'm', 'Verify meta-data'),
(
'restructuredtext',
'r',
'Checks if long string meta-data syntax are reStructuredText-compliant',
(
'Checks if long string meta-data syntax '
'are reStructuredText-compliant'
),
),
('strict', 's', 'Will exit with an error if a check fails'),
]
boolean_options: ClassVar[list[str]] = ['metadata', 'restructuredtext', 'strict']
boolean_options = ['metadata', 'restructuredtext', 'strict']
def initialize_options(self):
"""Sets default values for options."""
self.restructuredtext = False
self.restructuredtext = 0
self.metadata = 1
self.strict = False
self.strict = 0
self._warnings = 0
def finalize_options(self):
@@ -98,12 +99,13 @@ class check(Command):
"""
metadata = self.distribution.metadata
missing = [
attr for attr in ('name', 'version') if not getattr(metadata, attr, None)
]
missing = []
for attr in 'name', 'version':
if not getattr(metadata, attr, None):
missing.append(attr)
if missing:
self.warn("missing required meta-data: {}".format(', '.join(missing)))
self.warn("missing required meta-data: %s" % ', '.join(missing))
def check_restructuredtext(self):
"""Checks if the long string fields are reST-compliant."""
@@ -113,7 +115,7 @@ class check(Command):
if line is None:
warning = warning[1]
else:
warning = f'{warning[1]} (line {line})'
warning = '{} (line {})'.format(warning[1], line)
self.warn(warning)
def _check_rst_data(self, data):
@@ -141,12 +143,9 @@ class check(Command):
document.note_source(source_path, -1)
try:
parser.parse(data, document)
except (AttributeError, TypeError) as e:
reporter.messages.append((
-1,
f'Could not finish the parsing: {e}.',
'',
{},
))
except AttributeError as e:
reporter.messages.append(
(-1, 'Could not finish the parsing: %s.' % e, '', {})
)
return reporter.messages
@@ -5,33 +5,32 @@ Implements the Distutils 'clean' command."""
# contributed by Bastian Kleineidam <calvin@cs.uni-sb.de>, added 2000-03-18
import os
from distutils._log import log
from typing import ClassVar
from ..core import Command
from ..dir_util import remove_tree
from distutils.core import Command
from distutils.dir_util import remove_tree
from distutils import log
class clean(Command):
description = "clean up temporary files from 'build' command"
user_options = [
('build-base=', 'b', "base build directory [default: 'build.build-base']"),
('build-base=', 'b', "base build directory (default: 'build.build-base')"),
(
'build-lib=',
None,
"build directory for all modules [default: 'build.build-lib']",
"build directory for all modules (default: 'build.build-lib')",
),
('build-temp=', 't', "temporary build directory [default: 'build.build-temp']"),
('build-temp=', 't', "temporary build directory (default: 'build.build-temp')"),
(
'build-scripts=',
None,
"build directory for scripts [default: 'build.build-scripts']",
"build directory for scripts (default: 'build.build-scripts')",
),
('bdist-base=', None, "temporary directory for built distributions"),
('all', 'a', "remove all build output, not just temporary by-products"),
]
boolean_options: ClassVar[list[str]] = ['all']
boolean_options = ['all']
def initialize_options(self):
self.build_base = None
@@ -65,7 +64,7 @@ class clean(Command):
if os.path.exists(directory):
remove_tree(directory, dry_run=self.dry_run)
else:
log.warning("'%s' does not exist -- can't clean it", directory)
log.warn("'%s' does not exist -- can't clean it", directory)
# just for the heck of it, try to remove the base build directory:
# we might have emptied it right now, but if not we don't care
@@ -9,23 +9,19 @@ configure-like tasks: "try to compile this C code", or "figure out where
this header file lives".
"""
from __future__ import annotations
import os
import pathlib
import re
from collections.abc import Sequence
from distutils._log import log
from ..ccompiler import CCompiler, CompileError, LinkError, new_compiler
from ..core import Command
from ..errors import DistutilsExecError
from ..sysconfig import customize_compiler
from distutils.core import Command
from distutils.errors import DistutilsExecError
from distutils.sysconfig import customize_compiler
from distutils import log
LANG_EXT = {"c": ".c", "c++": ".cxx"}
class config(Command):
description = "prepare to build"
user_options = [
@@ -89,9 +85,13 @@ class config(Command):
"""Check that 'self.compiler' really is a CCompiler object;
if not, make it one.
"""
# We do this late, and only on-demand, because this is an expensive
# import.
from distutils.ccompiler import CCompiler, new_compiler
if not isinstance(self.compiler, CCompiler):
self.compiler = new_compiler(
compiler=self.compiler, dry_run=self.dry_run, force=True
compiler=self.compiler, dry_run=self.dry_run, force=1
)
customize_compiler(self.compiler)
if self.include_dirs:
@@ -103,10 +103,10 @@ class config(Command):
def _gen_temp_sourcefile(self, body, headers, lang):
filename = "_configtest" + LANG_EXT[lang]
with open(filename, "w", encoding='utf-8') as file:
with open(filename, "w") as file:
if headers:
for header in headers:
file.write(f"#include <{header}>\n")
file.write("#include <%s>\n" % header)
file.write("\n")
file.write(body)
if body[-1] != "\n":
@@ -123,7 +123,7 @@ class config(Command):
def _compile(self, body, headers, include_dirs, lang):
src = self._gen_temp_sourcefile(body, headers, lang)
if self.dump_source:
dump_file(src, f"compiling '{src}':")
dump_file(src, "compiling '%s':" % src)
(obj,) = self.compiler.object_filenames([src])
self.temp_files.extend([src, obj])
self.compiler.compile([src], include_dirs=include_dirs)
@@ -174,6 +174,8 @@ class config(Command):
preprocessor succeeded, false if there were any errors.
('body' probably isn't of much use, but what the heck.)
"""
from distutils.ccompiler import CompileError
self._check_compiler()
ok = True
try:
@@ -198,8 +200,15 @@ class config(Command):
if isinstance(pattern, str):
pattern = re.compile(pattern)
with open(out, encoding='utf-8') as file:
match = any(pattern.search(line) for line in file)
with open(out) as file:
match = False
while True:
line = file.readline()
if line == '':
break
if pattern.search(line):
match = True
break
self._clean()
return match
@@ -208,6 +217,8 @@ class config(Command):
"""Try to compile a source file built from 'body' and 'headers'.
Return true on success, false otherwise.
"""
from distutils.ccompiler import CompileError
self._check_compiler()
try:
self._compile(body, headers, include_dirs, lang)
@@ -232,6 +243,8 @@ class config(Command):
'headers', to executable form. Return true on success, false
otherwise.
"""
from distutils.ccompiler import CompileError, LinkError
self._check_compiler()
try:
self._link(body, headers, include_dirs, libraries, library_dirs, lang)
@@ -256,6 +269,8 @@ class config(Command):
built from 'body' and 'headers'. Return true on success, false
otherwise.
"""
from distutils.ccompiler import CompileError, LinkError
self._check_compiler()
try:
src, obj, exe = self._link(
@@ -281,8 +296,8 @@ class config(Command):
include_dirs=None,
libraries=None,
library_dirs=None,
decl=False,
call=False,
decl=0,
call=0,
):
"""Determine if function 'func' is available by constructing a
source file that refers to 'func', and compiles and links it.
@@ -300,12 +315,12 @@ class config(Command):
self._check_compiler()
body = []
if decl:
body.append(f"int {func} ();")
body.append("int %s ();" % func)
body.append("int main () {")
if call:
body.append(f" {func}();")
body.append(" %s();" % func)
else:
body.append(f" {func};")
body.append(" %s;" % func)
body.append("}")
body = "\n".join(body) + "\n"
@@ -317,7 +332,7 @@ class config(Command):
library_dirs=None,
headers=None,
include_dirs=None,
other_libraries: Sequence[str] = [],
other_libraries=[],
):
"""Determine if 'library' is available to be linked against,
without actually checking that any particular symbols are provided
@@ -332,7 +347,7 @@ class config(Command):
"int main (void) { }",
headers,
include_dirs,
[library] + list(other_libraries),
[library] + other_libraries,
library_dirs,
)
@@ -355,4 +370,8 @@ def dump_file(filename, head=None):
log.info('%s', filename)
else:
log.info(head)
log.info(pathlib.Path(filename).read_text(encoding='utf-8'))
file = open(filename)
try:
log.info(file.read())
finally:
file.close()
@@ -2,25 +2,25 @@
Implements the Distutils 'install' command."""
from __future__ import annotations
import collections
import contextlib
import itertools
import os
import sys
import os
import contextlib
import sysconfig
from distutils._log import log
from site import USER_BASE, USER_SITE
from typing import ClassVar
import itertools
from ..core import Command
from ..debug import DEBUG
from ..errors import DistutilsOptionError, DistutilsPlatformError
from ..file_util import write_file
from ..sysconfig import get_config_vars
from ..util import change_root, convert_path, get_platform, subst_vars
from distutils import log
from distutils.core import Command
from distutils.debug import DEBUG
from distutils.sysconfig import get_config_vars
from distutils.file_util import write_file
from distutils.util import convert_path, subst_vars, change_root
from distutils.util import get_platform
from distutils.errors import DistutilsOptionError, DistutilsPlatformError
from . import _framework_compat as fw
from .. import _collections
from site import USER_BASE
from site import USER_SITE
HAS_USER_SITE = True
@@ -144,7 +144,7 @@ def _resolve_scheme(name):
try:
resolved = sysconfig.get_preferred_scheme(key)
except Exception:
resolved = fw.scheme(name)
resolved = fw.scheme(_pypy_hack(name))
return resolved
@@ -161,7 +161,7 @@ def _inject_headers(name, scheme):
"""
# Bypass the preferred scheme, which may not
# have defined headers.
fallback = _load_scheme(name)
fallback = _load_scheme(_pypy_hack(name))
scheme.setdefault('headers', fallback['headers'])
return scheme
@@ -171,7 +171,16 @@ def _scheme_attrs(scheme):
return {f'install_{key}': scheme[key] for key in SCHEME_KEYS}
def _pypy_hack(name):
PY37 = sys.version_info < (3, 8)
old_pypy = hasattr(sys, 'pypy_version_info') and PY37
prefix = not name.endswith(('_user', '_home'))
pypy_name = 'pypy' + '_nt' * (os.name == 'nt')
return pypy_name if old_pypy and prefix else name
class install(Command):
description = "install everything from build directory"
user_options = [
@@ -188,7 +197,8 @@ class install(Command):
(
'install-platbase=',
None,
"base installation directory for platform-specific files (instead of --exec-prefix or --home)",
"base installation directory for platform-specific files "
+ "(instead of --exec-prefix or --home)",
),
('root=', None, "install everything relative to this alternate root directory"),
# Or, explicitly set the installation scheme
@@ -205,7 +215,8 @@ class install(Command):
(
'install-lib=',
None,
"installation directory for all module distributions (overrides --install-purelib and --install-platlib)",
"installation directory for all module distributions "
+ "(overrides --install-purelib and --install-platlib)",
),
('install-headers=', None, "installation directory for C/C++ headers"),
('install-scripts=', None, "installation directory for Python scripts"),
@@ -232,33 +243,31 @@ class install(Command):
('record=', None, "filename in which to record list of installed files"),
]
boolean_options: ClassVar[list[str]] = ['compile', 'force', 'skip-build']
boolean_options = ['compile', 'force', 'skip-build']
if HAS_USER_SITE:
user_options.append((
'user',
None,
f"install in user site-package '{USER_SITE}'",
))
user_options.append(
('user', None, "install in user site-package '%s'" % USER_SITE)
)
boolean_options.append('user')
negative_opt: ClassVar[dict[str, str]] = {'no-compile': 'compile'}
negative_opt = {'no-compile': 'compile'}
def initialize_options(self) -> None:
def initialize_options(self):
"""Initializes options."""
# High-level options: these select both an installation base
# and scheme.
self.prefix: str | None = None
self.exec_prefix: str | None = None
self.home: str | None = None
self.user = False
self.prefix = None
self.exec_prefix = None
self.home = None
self.user = 0
# These select only the installation base; it's up to the user to
# specify the installation scheme (currently, that means supplying
# the --install-{platlib,purelib,scripts,data} options).
self.install_base = None
self.install_platbase = None
self.root: str | None = None
self.root = None
# These options are the actual installation directories; if not
# supplied by the user, they are filled in using the installation
@@ -267,7 +276,7 @@ class install(Command):
self.install_purelib = None # for pure module distributions
self.install_platlib = None # non-pure (dists w/ extensions)
self.install_headers = None # for C/C++ headers
self.install_lib: str | None = None # set to either purelib or platlib
self.install_lib = None # set to either purelib or platlib
self.install_scripts = None
self.install_data = None
self.install_userbase = USER_BASE
@@ -286,7 +295,7 @@ class install(Command):
# 'install_path_file' is always true unless some outsider meddles
# with it.
self.extra_path = None
self.install_path_file = True
self.install_path_file = 1
# 'force' forces installation, even if target files are not
# out-of-date. 'skip_build' skips running the "build" command,
@@ -294,9 +303,9 @@ class install(Command):
# a user option, it's just there so the bdist_* commands can turn
# it off) determines whether we warn about installing to a
# directory not in sys.path.
self.force = False
self.skip_build = False
self.warn_dir = True
self.force = 0
self.skip_build = 0
self.warn_dir = 1
# These are only here as a conduit from the 'build' command to the
# 'install_*' commands that do the real work. ('build_base' isn't
@@ -321,7 +330,7 @@ class install(Command):
# party Python modules on various platforms given a wide
# array of user input is decided. Yes, it's quite complex!)
def finalize_options(self) -> None: # noqa: C901
def finalize_options(self): # noqa: C901
"""Finalizes options."""
# This method (and its helpers, like 'finalize_unix()',
# 'finalize_other()', and 'select_scheme()') is where the default
@@ -341,7 +350,8 @@ class install(Command):
self.install_base or self.install_platbase
):
raise DistutilsOptionError(
"must supply either prefix/exec-prefix/home or install-base/install-platbase -- not both"
"must supply either prefix/exec-prefix/home or "
+ "install-base/install-platbase -- not both"
)
if self.home and (self.prefix or self.exec_prefix):
@@ -401,8 +411,8 @@ class install(Command):
'dist_version': self.distribution.get_version(),
'dist_fullname': self.distribution.get_fullname(),
'py_version': py_version,
'py_version_short': f'{sys.version_info.major}.{sys.version_info.minor}',
'py_version_nodot': f'{sys.version_info.major}{sys.version_info.minor}',
'py_version_short': '%d.%d' % sys.version_info[:2],
'py_version_nodot': '%d%d' % sys.version_info[:2],
'sys_prefix': prefix,
'prefix': prefix,
'sys_exec_prefix': exec_prefix,
@@ -423,11 +433,8 @@ class install(Command):
local_vars['userbase'] = self.install_userbase
local_vars['usersite'] = self.install_usersite
self.config_vars = collections.ChainMap(
local_vars,
sysconfig.get_config_vars(),
compat_vars,
fw.vars(),
self.config_vars = _collections.DictStack(
[fw.vars(), compat_vars, sysconfig.get_config_vars(), local_vars]
)
self.expand_basedirs()
@@ -504,11 +511,11 @@ class install(Command):
# Punt on doc directories for now -- after all, we're punting on
# documentation completely!
def dump_dirs(self, msg) -> None:
def dump_dirs(self, msg):
"""Dumps the list of user options."""
if not DEBUG:
return
from ..fancy_getopt import longopt_xlate
from distutils.fancy_getopt import longopt_xlate
log.debug(msg + ":")
for opt in self.user_options:
@@ -524,7 +531,7 @@ class install(Command):
val = getattr(self, opt_name)
log.debug(" %s: %s", opt_name, val)
def finalize_unix(self) -> None:
def finalize_unix(self):
"""Finalizes options for posix platforms."""
if self.install_base is not None or self.install_platbase is not None:
incomplete_scheme = (
@@ -573,7 +580,7 @@ class install(Command):
self.install_platbase = self.exec_prefix
self.select_scheme("posix_prefix")
def finalize_other(self) -> None:
def finalize_other(self):
"""Finalizes options for non-posix platforms"""
if self.user:
if self.install_userbase is None:
@@ -592,50 +599,52 @@ class install(Command):
self.select_scheme(os.name)
except KeyError:
raise DistutilsPlatformError(
f"I don't know how to install stuff on '{os.name}'"
"I don't know how to install stuff on '%s'" % os.name
)
def select_scheme(self, name) -> None:
def select_scheme(self, name):
_select_scheme(self, name)
def _expand_attrs(self, attrs):
for attr in attrs:
val = getattr(self, attr)
if val is not None:
if os.name in ('posix', 'nt'):
if os.name == 'posix' or os.name == 'nt':
val = os.path.expanduser(val)
val = subst_vars(val, self.config_vars)
setattr(self, attr, val)
def expand_basedirs(self) -> None:
def expand_basedirs(self):
"""Calls `os.path.expanduser` on install_base, install_platbase and
root."""
self._expand_attrs(['install_base', 'install_platbase', 'root'])
def expand_dirs(self) -> None:
def expand_dirs(self):
"""Calls `os.path.expanduser` on install dirs."""
self._expand_attrs([
'install_purelib',
'install_platlib',
'install_lib',
'install_headers',
'install_scripts',
'install_data',
])
self._expand_attrs(
[
'install_purelib',
'install_platlib',
'install_lib',
'install_headers',
'install_scripts',
'install_data',
]
)
def convert_paths(self, *names) -> None:
def convert_paths(self, *names):
"""Call `convert_path` over `names`."""
for name in names:
attr = "install_" + name
setattr(self, attr, convert_path(getattr(self, attr)))
def handle_extra_path(self) -> None:
def handle_extra_path(self):
"""Set `path_file` and `extra_dirs` using `extra_path`."""
if self.extra_path is None:
self.extra_path = self.distribution.extra_path
if self.extra_path is not None:
log.warning(
log.warn(
"Distribution option extra_path is deprecated. "
"See issue27919 for details."
)
@@ -664,20 +673,20 @@ class install(Command):
self.path_file = path_file
self.extra_dirs = extra_dirs
def change_roots(self, *names) -> None:
def change_roots(self, *names):
"""Change the install directories pointed by name using root."""
for name in names:
attr = "install_" + name
setattr(self, attr, change_root(self.root, getattr(self, attr)))
def create_home_path(self) -> None:
def create_home_path(self):
"""Create directories under ~."""
if not self.user:
return
home = convert_path(os.path.expanduser("~"))
for path in self.config_vars.values():
for name, path in self.config_vars.items():
if str(path).startswith(home) and not os.path.isdir(path):
self.debug_print(f"os.makedirs('{path}', 0o700)")
self.debug_print("os.makedirs('%s', 0o700)" % path)
os.makedirs(path, 0o700)
# -- Command execution methods -------------------------------------
@@ -693,7 +702,7 @@ class install(Command):
# internally, and not to sys.path, so we don't check the platform
# matches what we are running.
if self.warn_dir and build_plat != get_platform():
raise DistutilsPlatformError("Can't install when cross-compiling")
raise DistutilsPlatformError("Can't install when " "cross-compiling")
# Run all sub-commands (at least those that need to be run)
for cmd_name in self.get_sub_commands():
@@ -712,7 +721,7 @@ class install(Command):
self.execute(
write_file,
(self.record, outputs),
f"writing list of installed files to '{self.record}'",
"writing list of installed files to '%s'" % self.record,
)
sys_path = map(os.path.normpath, sys.path)
@@ -737,10 +746,10 @@ class install(Command):
filename = os.path.join(self.install_libbase, self.path_file + ".pth")
if self.install_path_file:
self.execute(
write_file, (filename, [self.extra_dirs]), f"creating {filename}"
write_file, (filename, [self.extra_dirs]), "creating %s" % filename
)
else:
self.warn(f"path file '{filename}' not created")
self.warn("path file '%s' not created" % filename)
# -- Reporting methods ---------------------------------------------
@@ -5,41 +5,37 @@ platform-independent data files."""
# contributed by Bastian Kleineidam
from __future__ import annotations
import functools
import os
from collections.abc import Iterable
from typing import ClassVar
from ..core import Command
from ..util import change_root, convert_path
from distutils.core import Command
from distutils.util import change_root, convert_path
class install_data(Command):
description = "install data files"
user_options = [
(
'install-dir=',
'd',
"base directory for installing data files [default: installation base dir]",
"base directory for installing data files "
"(default: installation base dir)",
),
('root=', None, "install everything relative to this alternate root directory"),
('force', 'f', "force installation (overwrite existing files)"),
]
boolean_options: ClassVar[list[str]] = ['force']
boolean_options = ['force']
def initialize_options(self):
self.install_dir = None
self.outfiles = []
self.root = None
self.force = False
self.force = 0
self.data_files = self.distribution.data_files
self.warn_dir = True
self.warn_dir = 1
def finalize_options(self) -> None:
def finalize_options(self):
self.set_undefined_options(
'install',
('install_data', 'install_dir'),
@@ -47,45 +43,39 @@ class install_data(Command):
('force', 'force'),
)
def run(self) -> None:
def run(self):
self.mkpath(self.install_dir)
for f in self.data_files:
self._copy(f)
@functools.singledispatchmethod
def _copy(self, f: tuple[str | os.PathLike, Iterable[str | os.PathLike]]):
# it's a tuple with path to install to and a list of files
dir = convert_path(f[0])
if not os.path.isabs(dir):
dir = os.path.join(self.install_dir, dir)
elif self.root:
dir = change_root(self.root, dir)
self.mkpath(dir)
if f[1] == []:
# If there are no files listed, the user must be
# trying to create an empty directory, so add the
# directory to the list of output files.
self.outfiles.append(dir)
else:
# Copy files, adding them to the list of output files.
for data in f[1]:
data = convert_path(data)
(out, _) = self.copy_file(data, dir)
if isinstance(f, str):
# it's a simple file, so copy it
f = convert_path(f)
if self.warn_dir:
self.warn(
"setup script did not provide a directory for "
"'%s' -- installing right in '%s'" % (f, self.install_dir)
)
(out, _) = self.copy_file(f, self.install_dir)
self.outfiles.append(out)
else:
# it's a tuple with path to install to and a list of files
dir = convert_path(f[0])
if not os.path.isabs(dir):
dir = os.path.join(self.install_dir, dir)
elif self.root:
dir = change_root(self.root, dir)
self.mkpath(dir)
@_copy.register(str)
@_copy.register(os.PathLike)
def _(self, f: str | os.PathLike):
# it's a simple file, so copy it
f = convert_path(f)
if self.warn_dir:
self.warn(
"setup script did not provide a directory for "
f"'{f}' -- installing right in '{self.install_dir}'"
)
(out, _) = self.copy_file(f, self.install_dir)
self.outfiles.append(out)
if f[1] == []:
# If there are no files listed, the user must be
# trying to create an empty directory, so add the
# directory to the list of output files.
self.outfiles.append(dir)
else:
# Copy files, adding them to the list of output files.
for data in f[1]:
data = convert_path(data)
(out, _) = self.copy_file(data, dir)
self.outfiles.append(out)
def get_inputs(self):
return self.data_files or []
@@ -6,20 +6,18 @@ a package's PKG-INFO metadata.
"""
import os
import re
import sys
from typing import ClassVar
import re
from .. import dir_util
from .._log import log
from ..cmd import Command
from distutils.cmd import Command
from distutils import log, dir_util
class install_egg_info(Command):
"""Install an .egg-info file for the package"""
description = "Install package's PKG-INFO metadata as an .egg-info file"
user_options: ClassVar[list[tuple[str, str, str]]] = [
user_options = [
('install-dir=', 'd', "directory to install to"),
]
@@ -32,9 +30,11 @@ class install_egg_info(Command):
Allow basename to be overridden by child class.
Ref pypa/distutils#2.
"""
name = to_filename(safe_name(self.distribution.get_name()))
version = to_filename(safe_version(self.distribution.get_version()))
return f"{name}-{version}-py{sys.version_info.major}.{sys.version_info.minor}.egg-info"
return "%s-%s-py%d.%d.egg-info" % (
to_filename(safe_name(self.distribution.get_name())),
to_filename(safe_version(self.distribution.get_version())),
*sys.version_info[:2],
)
def finalize_options(self):
self.set_undefined_options('install_lib', ('install_dir', 'install_dir'))
@@ -3,25 +3,24 @@
Implements the Distutils 'install_headers' command, to install C/C++ header
files to the Python include directory."""
from typing import ClassVar
from ..core import Command
from distutils.core import Command
# XXX force is never used
class install_headers(Command):
description = "install C/C++ header files"
user_options: ClassVar[list[tuple[str, str, str]]] = [
user_options = [
('install-dir=', 'd', "directory to install header files to"),
('force', 'f', "force installation (overwrite existing files)"),
]
boolean_options: ClassVar[list[str]] = ['force']
boolean_options = ['force']
def initialize_options(self):
self.install_dir = None
self.force = False
self.force = 0
self.outfiles = []
def finalize_options(self):
@@ -3,21 +3,20 @@
Implements the Distutils 'install_lib' command
(install all Python modules)."""
from __future__ import annotations
import importlib.util
import os
import importlib.util
import sys
from typing import Any, ClassVar
from ..core import Command
from ..errors import DistutilsOptionError
from distutils.core import Command
from distutils.errors import DistutilsOptionError
# Extension for Python source files.
PYTHON_SOURCE_EXTENSION = ".py"
class install_lib(Command):
description = "install all Python modules (extensions and pure Python)"
# The byte-compilation options are a tad confusing. Here are the
@@ -50,19 +49,19 @@ class install_lib(Command):
('skip-build', None, "skip the build steps"),
]
boolean_options: ClassVar[list[str]] = ['force', 'compile', 'skip-build']
negative_opt: ClassVar[dict[str, str]] = {'no-compile': 'compile'}
boolean_options = ['force', 'compile', 'skip-build']
negative_opt = {'no-compile': 'compile'}
def initialize_options(self):
# let the 'install' command dictate our installation directory
self.install_dir = None
self.build_dir = None
self.force = False
self.force = 0
self.compile = None
self.optimize = None
self.skip_build = None
def finalize_options(self) -> None:
def finalize_options(self):
# Get all the information we need to install pure Python modules
# from the umbrella 'install' command -- build (source) directory,
# install (target) directory, and whether to compile .py files.
@@ -84,12 +83,12 @@ class install_lib(Command):
if not isinstance(self.optimize, int):
try:
self.optimize = int(self.optimize)
except ValueError:
pass
if self.optimize not in (0, 1, 2):
if self.optimize not in (0, 1, 2):
raise AssertionError
except (ValueError, AssertionError):
raise DistutilsOptionError("optimize must be 0, 1, or 2")
def run(self) -> None:
def run(self):
# Make sure we have built everything we need first
self.build()
@@ -105,30 +104,29 @@ class install_lib(Command):
# -- Top-level worker functions ------------------------------------
# (called from 'run()')
def build(self) -> None:
def build(self):
if not self.skip_build:
if self.distribution.has_pure_modules():
self.run_command('build_py')
if self.distribution.has_ext_modules():
self.run_command('build_ext')
# Any: https://typing.readthedocs.io/en/latest/guides/writing_stubs.html#the-any-trick
def install(self) -> list[str] | Any:
def install(self):
if os.path.isdir(self.build_dir):
outfiles = self.copy_tree(self.build_dir, self.install_dir)
else:
self.warn(
f"'{self.build_dir}' does not exist -- no Python modules to install"
"'%s' does not exist -- no Python modules to install" % self.build_dir
)
return
return outfiles
def byte_compile(self, files) -> None:
def byte_compile(self, files):
if sys.dont_write_bytecode:
self.warn('byte-compiling is disabled, skipping.')
return
from ..util import byte_compile
from distutils.util import byte_compile
# Get the "--root" directory supplied to the "install" command,
# and use it as a prefix to strip off the purported filename
@@ -165,7 +163,9 @@ class install_lib(Command):
build_dir = getattr(build_cmd, cmd_option)
prefix_len = len(build_dir) + len(os.sep)
outputs = [os.path.join(output_dir, file[prefix_len:]) for file in build_files]
outputs = []
for file in build_files:
outputs.append(os.path.join(output_dir, file[prefix_len:]))
return outputs
@@ -6,14 +6,13 @@ Python scripts."""
# contributed by Bastian Kleineidam
import os
from distutils._log import log
from distutils.core import Command
from distutils import log
from stat import ST_MODE
from typing import ClassVar
from ..core import Command
class install_scripts(Command):
description = "install scripts (Python or otherwise)"
user_options = [
@@ -23,15 +22,15 @@ class install_scripts(Command):
('skip-build', None, "skip the build steps"),
]
boolean_options: ClassVar[list[str]] = ['force', 'skip-build']
boolean_options = ['force', 'skip-build']
def initialize_options(self):
self.install_dir = None
self.force = False
self.force = 0
self.build_dir = None
self.skip_build = None
def finalize_options(self) -> None:
def finalize_options(self):
self.set_undefined_options('build', ('build_scripts', 'build_dir'))
self.set_undefined_options(
'install',
@@ -40,7 +39,7 @@ class install_scripts(Command):
('skip_build', 'skip_build'),
)
def run(self) -> None:
def run(self):
if not self.skip_build:
self.run_command('build_scripts')
self.outfiles = self.copy_tree(self.build_dir, self.install_dir)
@@ -2,42 +2,41 @@
Implements the Distutils 'sdist' command (create a source distribution)."""
from __future__ import annotations
import os
import sys
from collections.abc import Callable
from distutils import archive_util, dir_util, file_util
from distutils._log import log
from glob import glob
from itertools import filterfalse
from typing import ClassVar
from warnings import warn
from ..core import Command
from ..errors import DistutilsOptionError, DistutilsTemplateError
from ..filelist import FileList
from ..text_file import TextFile
from ..util import convert_path
from distutils.core import Command
from distutils import dir_util
from distutils import file_util
from distutils import archive_util
from distutils.text_file import TextFile
from distutils.filelist import FileList
from distutils import log
from distutils.util import convert_path
from distutils.errors import DistutilsOptionError, DistutilsTemplateError
def show_formats():
"""Print all possible values for the 'formats' option (used by
the "--help-formats" command-line option).
"""
from ..archive_util import ARCHIVE_FORMATS
from ..fancy_getopt import FancyGetopt
from distutils.fancy_getopt import FancyGetopt
from distutils.archive_util import ARCHIVE_FORMATS
formats = sorted(
("formats=" + format, None, ARCHIVE_FORMATS[format][2])
for format in ARCHIVE_FORMATS.keys()
)
formats = []
for format in ARCHIVE_FORMATS.keys():
formats.append(("formats=" + format, None, ARCHIVE_FORMATS[format][2]))
formats.sort()
FancyGetopt(formats).print_help("List of available source distribution formats:")
class sdist(Command):
description = "create a source distribution (tarball, zip file, etc.)"
def checking_metadata(self) -> bool:
def checking_metadata(self):
"""Callable used for the check sub-command.
Placed here so user_options can view it"""
@@ -64,7 +63,7 @@ class sdist(Command):
(
'manifest-only',
'o',
"just regenerate the manifest and then stop (implies --force-manifest)",
"just regenerate the manifest and then stop " "(implies --force-manifest)",
),
(
'force-manifest',
@@ -81,7 +80,7 @@ class sdist(Command):
(
'dist-dir=',
'd',
"directory to put the source distribution archive(s) in [default: dist]",
"directory to put the source distribution archive(s) in " "[default: dist]",
),
(
'metadata-check',
@@ -101,7 +100,7 @@ class sdist(Command):
),
]
boolean_options: ClassVar[list[str]] = [
boolean_options = [
'use-defaults',
'prune',
'manifest-only',
@@ -110,18 +109,15 @@ class sdist(Command):
'metadata-check',
]
help_options: ClassVar[list[tuple[str, str | None, str, Callable[[], object]]]] = [
help_options = [
('help-formats', None, "list available distribution formats", show_formats),
]
negative_opt: ClassVar[dict[str, str]] = {
'no-defaults': 'use-defaults',
'no-prune': 'prune',
}
negative_opt = {'no-defaults': 'use-defaults', 'no-prune': 'prune'}
sub_commands = [('check', checking_metadata)]
READMES: ClassVar[tuple[str, ...]] = ('README', 'README.txt', 'README.rst')
READMES = ('README', 'README.txt', 'README.rst')
def initialize_options(self):
# 'template' and 'manifest' are, respectively, the names of
@@ -131,22 +127,22 @@ class sdist(Command):
# 'use_defaults': if true, we will include the default file set
# in the manifest
self.use_defaults = True
self.prune = True
self.use_defaults = 1
self.prune = 1
self.manifest_only = False
self.force_manifest = False
self.manifest_only = 0
self.force_manifest = 0
self.formats = ['gztar']
self.keep_temp = False
self.keep_temp = 0
self.dist_dir = None
self.archive_files = None
self.metadata_check = True
self.metadata_check = 1
self.owner = None
self.group = None
def finalize_options(self) -> None:
def finalize_options(self):
if self.manifest is None:
self.manifest = "MANIFEST"
if self.template is None:
@@ -156,12 +152,12 @@ class sdist(Command):
bad_format = archive_util.check_archive_formats(self.formats)
if bad_format:
raise DistutilsOptionError(f"unknown archive format '{bad_format}'")
raise DistutilsOptionError("unknown archive format '%s'" % bad_format)
if self.dist_dir is None:
self.dist_dir = "dist"
def run(self) -> None:
def run(self):
# 'filelist' contains the list of files that will make up the
# manifest
self.filelist = FileList()
@@ -183,7 +179,18 @@ class sdist(Command):
# or zipfile, or whatever.
self.make_distribution()
def get_file_list(self) -> None:
def check_metadata(self):
"""Deprecated API."""
warn(
"distutils.command.sdist.check_metadata is deprecated, \
use the check command instead",
PendingDeprecationWarning,
)
check = self.distribution.get_command_obj('check')
check.ensure_finalized()
check.run()
def get_file_list(self):
"""Figure out the list of files to include in the source
distribution, and put it in 'self.filelist'. This might involve
reading the manifest template (and writing the manifest), or just
@@ -224,11 +231,11 @@ class sdist(Command):
self.filelist.remove_duplicates()
self.write_manifest()
def add_defaults(self) -> None:
def add_defaults(self):
"""Add all the default files to self.filelist:
- README or README.txt
- setup.py
- tests/test*.py and test/test*.py
- test/test*.py
- all pure Python modules mentioned in setup script
- all files pointed by package_data (build_py)
- all files defined in data_files.
@@ -283,10 +290,10 @@ class sdist(Command):
if self._cs_path_exists(fn):
self.filelist.append(fn)
else:
self.warn(f"standard file '{fn}' not found")
self.warn("standard file '%s' not found" % fn)
def _add_defaults_optional(self):
optional = ['tests/test*.py', 'test/test*.py', 'setup.cfg']
optional = ['test/test*.py', 'setup.cfg']
for pattern in optional:
files = filter(os.path.isfile, glob(pattern))
self.filelist.extend(files)
@@ -303,7 +310,7 @@ class sdist(Command):
# getting package_data files
# (computed in build_py.data_files by build_py.finalize_options)
for _pkg, src_dir, _build_dir, filenames in build_py.data_files:
for pkg, src_dir, build_dir, filenames in build_py.data_files:
for filename in filenames:
self.filelist.append(os.path.join(src_dir, filename))
@@ -339,7 +346,7 @@ class sdist(Command):
build_scripts = self.get_finalized_command('build_scripts')
self.filelist.extend(build_scripts.get_source_files())
def read_template(self) -> None:
def read_template(self):
"""Read and parse manifest template file named by self.template.
(usually "MANIFEST.in") The parsing and processing is done by
@@ -348,12 +355,12 @@ class sdist(Command):
log.info("reading manifest template '%s'", self.template)
template = TextFile(
self.template,
strip_comments=True,
skip_blanks=True,
join_lines=True,
lstrip_ws=True,
rstrip_ws=True,
collapse_join=True,
strip_comments=1,
skip_blanks=1,
join_lines=1,
lstrip_ws=1,
rstrip_ws=1,
collapse_join=1,
)
try:
@@ -369,12 +376,13 @@ class sdist(Command):
# convert_path function
except (DistutilsTemplateError, ValueError) as msg:
self.warn(
f"{template.filename}, line {int(template.current_line)}: {msg}"
"%s, line %d: %s"
% (template.filename, template.current_line, msg)
)
finally:
template.close()
def prune_file_list(self) -> None:
def prune_file_list(self):
"""Prune off branches that might slip into the file list as created
by 'read_template()', but really don't belong there:
* the build tree (typically "build")
@@ -385,7 +393,7 @@ class sdist(Command):
build = self.get_finalized_command('build')
base_dir = self.distribution.get_fullname()
self.filelist.exclude_pattern(None, prefix=os.fspath(build.build_base))
self.filelist.exclude_pattern(None, prefix=build.build_base)
self.filelist.exclude_pattern(None, prefix=base_dir)
if sys.platform == 'win32':
@@ -395,16 +403,17 @@ class sdist(Command):
vcs_dirs = ['RCS', 'CVS', r'\.svn', r'\.hg', r'\.git', r'\.bzr', '_darcs']
vcs_ptrn = r'(^|{})({})({}).*'.format(seps, '|'.join(vcs_dirs), seps)
self.filelist.exclude_pattern(vcs_ptrn, is_regex=True)
self.filelist.exclude_pattern(vcs_ptrn, is_regex=1)
def write_manifest(self) -> None:
def write_manifest(self):
"""Write the file list in 'self.filelist' (presumably as filled in
by 'add_defaults()' and 'read_template()') to the manifest file
named by 'self.manifest'.
"""
if self._manifest_is_not_generated():
log.info(
f"not writing to manually maintained manifest file '{self.manifest}'"
"not writing to manually maintained "
"manifest file '%s'" % self.manifest
)
return
@@ -413,7 +422,7 @@ class sdist(Command):
self.execute(
file_util.write_file,
(self.manifest, content),
f"writing manifest file '{self.manifest}'",
"writing manifest file '%s'" % self.manifest,
)
def _manifest_is_not_generated(self):
@@ -421,23 +430,28 @@ class sdist(Command):
if not os.path.isfile(self.manifest):
return False
with open(self.manifest, encoding='utf-8') as fp:
first_line = next(fp)
fp = open(self.manifest)
try:
first_line = fp.readline()
finally:
fp.close()
return first_line != '# file GENERATED by distutils, do NOT edit\n'
def read_manifest(self) -> None:
def read_manifest(self):
"""Read the manifest file (named by 'self.manifest') and use it to
fill in 'self.filelist', the list of files to include in the source
distribution.
"""
log.info("reading manifest file '%s'", self.manifest)
with open(self.manifest, encoding='utf-8') as lines:
self.filelist.extend(
with open(self.manifest) as manifest:
for line in manifest:
# ignore comments and blank lines
filter(None, filterfalse(is_comment, map(str.strip, lines)))
)
line = line.strip()
if line.startswith('#') or not line:
continue
self.filelist.append(line)
def make_release_tree(self, base_dir, files) -> None:
def make_release_tree(self, base_dir, files):
"""Create the directory tree that will become the source
distribution archive. All directories implied by the filenames in
'files' are created under 'base_dir', and then we hard link or copy
@@ -461,25 +475,25 @@ class sdist(Command):
if hasattr(os, 'link'): # can make hard links on this system
link = 'hard'
msg = f"making hard links in {base_dir}..."
msg = "making hard links in %s..." % base_dir
else: # nope, have to copy
link = None
msg = f"copying files to {base_dir}..."
msg = "copying files to %s..." % base_dir
if not files:
log.warning("no files to distribute -- empty manifest?")
log.warn("no files to distribute -- empty manifest?")
else:
log.info(msg)
for file in files:
if not os.path.isfile(file):
log.warning("'%s' not a regular file -- skipping", file)
log.warn("'%s' not a regular file -- skipping", file)
else:
dest = os.path.join(base_dir, file)
self.copy_file(file, dest, link=link)
self.distribution.metadata.write_pkg_info(base_dir)
def make_distribution(self) -> None:
def make_distribution(self):
"""Create the source distribution(s). First, we create the release
tree with 'make_release_tree()'; then, we create all required
archive files (according to 'self.formats') from the release tree.
@@ -515,7 +529,3 @@ class sdist(Command):
was run, or None if the command hasn't run yet.
"""
return self.archive_files
def is_comment(line: str) -> bool:
return line.startswith('#')
@@ -1,18 +0,0 @@
from __future__ import annotations
from collections.abc import Iterable
from typing import TypeVar
_IterableT = TypeVar("_IterableT", bound="Iterable[str]")
def consolidate_linker_args(args: _IterableT) -> _IterableT | str:
"""
Ensure the return value is a string for backward compatibility.
Retain until at least 2025-04-31. See pypa/distutils#246
"""
if not all(arg.startswith('-Wl,') for arg in args):
return args
return '-Wl,' + ','.join(arg.removeprefix('-Wl,') for arg in args)
@@ -1,2 +0,0 @@
# required for older numpy versions on Pythons prior to 3.12; see pypa/setuptools#4876
from ..compilers.C.base import _default_compilers, compiler_class # noqa: F401
@@ -1,66 +0,0 @@
import functools
import itertools
import platform
import sys
def add_ext_suffix_39(vars):
"""
Ensure vars contains 'EXT_SUFFIX'. pypa/distutils#130
"""
import _imp
ext_suffix = _imp.extension_suffixes()[0]
vars.update(
EXT_SUFFIX=ext_suffix,
# sysconfig sets SO to match EXT_SUFFIX, so maintain
# that expectation.
# https://github.com/python/cpython/blob/785cc6770588de087d09e89a69110af2542be208/Lib/sysconfig.py#L671-L673
SO=ext_suffix,
)
needs_ext_suffix = sys.version_info < (3, 10) and platform.system() == 'Windows'
add_ext_suffix = add_ext_suffix_39 if needs_ext_suffix else lambda vars: None
# from more_itertools
class UnequalIterablesError(ValueError):
def __init__(self, details=None):
msg = 'Iterables have different lengths'
if details is not None:
msg += (': index 0 has length {}; index {} has length {}').format(*details)
super().__init__(msg)
# from more_itertools
def _zip_equal_generator(iterables):
_marker = object()
for combo in itertools.zip_longest(*iterables, fillvalue=_marker):
for val in combo:
if val is _marker:
raise UnequalIterablesError()
yield combo
# from more_itertools
def _zip_equal(*iterables):
# Check whether the iterables are all the same size.
try:
first_size = len(iterables[0])
for i, it in enumerate(iterables[1:], 1):
size = len(it)
if size != first_size:
raise UnequalIterablesError(details=(first_size, i, size))
# All sizes are equal, we can use the built-in zip.
return zip(*iterables)
# If any one of the iterables didn't have a length, start reading
# them until one runs out.
except TypeError:
return _zip_equal_generator(iterables)
zip_strict = (
_zip_equal if sys.version_info < (3, 10) else functools.partial(zip, strict=True)
)
@@ -1,340 +0,0 @@
"""distutils.cygwinccompiler
Provides the CygwinCCompiler class, a subclass of UnixCCompiler that
handles the Cygwin port of the GNU C compiler to Windows. It also contains
the Mingw32CCompiler class which handles the mingw32 port of GCC (same as
cygwin in no-cygwin mode).
"""
import copy
import os
import pathlib
import shlex
import sys
import warnings
from subprocess import check_output
from ...errors import (
DistutilsExecError,
DistutilsPlatformError,
)
from ...file_util import write_file
from ...sysconfig import get_config_vars
from ...version import LooseVersion, suppress_known_deprecation
from . import unix
from .errors import (
CompileError,
Error,
)
def get_msvcr():
"""No longer needed, but kept for backward compatibility."""
return []
_runtime_library_dirs_msg = (
"Unable to set runtime library search path on Windows, "
"usually indicated by `runtime_library_dirs` parameter to Extension"
)
class Compiler(unix.Compiler):
"""Handles the Cygwin port of the GNU C compiler to Windows."""
compiler_type = 'cygwin'
obj_extension = ".o"
static_lib_extension = ".a"
shared_lib_extension = ".dll.a"
dylib_lib_extension = ".dll"
static_lib_format = "lib%s%s"
shared_lib_format = "lib%s%s"
dylib_lib_format = "cyg%s%s"
exe_extension = ".exe"
def __init__(self, verbose=False, dry_run=False, force=False):
super().__init__(verbose, dry_run, force)
status, details = check_config_h()
self.debug_print(f"Python's GCC status: {status} (details: {details})")
if status is not CONFIG_H_OK:
self.warn(
"Python's pyconfig.h doesn't seem to support your compiler. "
f"Reason: {details}. "
"Compiling may fail because of undefined preprocessor macros."
)
self.cc, self.cxx = get_config_vars('CC', 'CXX')
# Override 'CC' and 'CXX' environment variables for
# building using MINGW compiler for MSVC python.
self.cc = os.environ.get('CC', self.cc or 'gcc')
self.cxx = os.environ.get('CXX', self.cxx or 'g++')
self.linker_dll = self.cc
self.linker_dll_cxx = self.cxx
shared_option = "-shared"
self.set_executables(
compiler=f'{self.cc} -mcygwin -O -Wall',
compiler_so=f'{self.cc} -mcygwin -mdll -O -Wall',
compiler_cxx=f'{self.cxx} -mcygwin -O -Wall',
compiler_so_cxx=f'{self.cxx} -mcygwin -mdll -O -Wall',
linker_exe=f'{self.cc} -mcygwin',
linker_so=f'{self.linker_dll} -mcygwin {shared_option}',
linker_exe_cxx=f'{self.cxx} -mcygwin',
linker_so_cxx=f'{self.linker_dll_cxx} -mcygwin {shared_option}',
)
self.dll_libraries = get_msvcr()
@property
def gcc_version(self):
# Older numpy depended on this existing to check for ancient
# gcc versions. This doesn't make much sense with clang etc so
# just hardcode to something recent.
# https://github.com/numpy/numpy/pull/20333
warnings.warn(
"gcc_version attribute of CygwinCCompiler is deprecated. "
"Instead of returning actual gcc version a fixed value 11.2.0 is returned.",
DeprecationWarning,
stacklevel=2,
)
with suppress_known_deprecation():
return LooseVersion("11.2.0")
def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts):
"""Compiles the source by spawning GCC and windres if needed."""
if ext in ('.rc', '.res'):
# gcc needs '.res' and '.rc' compiled to object files !!!
try:
self.spawn(["windres", "-i", src, "-o", obj])
except DistutilsExecError as msg:
raise CompileError(msg)
else: # for other files use the C-compiler
try:
if self.detect_language(src) == 'c++':
self.spawn(
self.compiler_so_cxx
+ cc_args
+ [src, '-o', obj]
+ extra_postargs
)
else:
self.spawn(
self.compiler_so + cc_args + [src, '-o', obj] + extra_postargs
)
except DistutilsExecError as msg:
raise CompileError(msg)
def link(
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,
):
"""Link the objects."""
# use separate copies, so we can modify the lists
extra_preargs = copy.copy(extra_preargs or [])
libraries = copy.copy(libraries or [])
objects = copy.copy(objects or [])
if runtime_library_dirs:
self.warn(_runtime_library_dirs_msg)
# Additional libraries
libraries.extend(self.dll_libraries)
# handle export symbols by creating a def-file
# with executables this only works with gcc/ld as linker
if (export_symbols is not None) and (
target_desc != self.EXECUTABLE or self.linker_dll == "gcc"
):
# (The linker doesn't do anything if output is up-to-date.
# So it would probably better to check if we really need this,
# but for this we had to insert some unchanged parts of
# UnixCCompiler, and this is not what we want.)
# we want to put some files in the same directory as the
# object files are, build_temp doesn't help much
# where are the object files
temp_dir = os.path.dirname(objects[0])
# name of dll to give the helper files the same base name
(dll_name, dll_extension) = os.path.splitext(
os.path.basename(output_filename)
)
# generate the filenames for these files
def_file = os.path.join(temp_dir, dll_name + ".def")
# Generate .def file
contents = [f"LIBRARY {os.path.basename(output_filename)}", "EXPORTS"]
contents.extend(export_symbols)
self.execute(write_file, (def_file, contents), f"writing {def_file}")
# next add options for def-file
# for gcc/ld the def-file is specified as any object files
objects.append(def_file)
# end: if ((export_symbols is not None) and
# (target_desc != self.EXECUTABLE or self.linker_dll == "gcc")):
# who wants symbols and a many times larger output file
# should explicitly switch the debug mode on
# otherwise we let ld strip the output file
# (On my machine: 10KiB < stripped_file < ??100KiB
# unstripped_file = stripped_file + XXX KiB
# ( XXX=254 for a typical python extension))
if not debug:
extra_preargs.append("-s")
super().link(
target_desc,
objects,
output_filename,
output_dir,
libraries,
library_dirs,
runtime_library_dirs,
None, # export_symbols, we do this in our def-file
debug,
extra_preargs,
extra_postargs,
build_temp,
target_lang,
)
def runtime_library_dir_option(self, dir):
# cygwin doesn't support rpath. While in theory we could error
# out like MSVC does, code might expect it to work like on Unix, so
# just warn and hope for the best.
self.warn(_runtime_library_dirs_msg)
return []
# -- Miscellaneous methods -----------------------------------------
def _make_out_path(self, output_dir, strip_dir, src_name):
# use normcase to make sure '.rc' is really '.rc' and not '.RC'
norm_src_name = os.path.normcase(src_name)
return super()._make_out_path(output_dir, strip_dir, norm_src_name)
@property
def out_extensions(self):
"""
Add support for rc and res files.
"""
return {
**super().out_extensions,
**{ext: ext + self.obj_extension for ext in ('.res', '.rc')},
}
# the same as cygwin plus some additional parameters
class MinGW32Compiler(Compiler):
"""Handles the Mingw32 port of the GNU C compiler to Windows."""
compiler_type = 'mingw32'
def __init__(self, verbose=False, dry_run=False, force=False):
super().__init__(verbose, dry_run, force)
shared_option = "-shared"
if is_cygwincc(self.cc):
raise Error('Cygwin gcc cannot be used with --compiler=mingw32')
self.set_executables(
compiler=f'{self.cc} -O -Wall',
compiler_so=f'{self.cc} -shared -O -Wall',
compiler_so_cxx=f'{self.cxx} -shared -O -Wall',
compiler_cxx=f'{self.cxx} -O -Wall',
linker_exe=f'{self.cc}',
linker_so=f'{self.linker_dll} {shared_option}',
linker_exe_cxx=f'{self.cxx}',
linker_so_cxx=f'{self.linker_dll_cxx} {shared_option}',
)
def runtime_library_dir_option(self, dir):
raise DistutilsPlatformError(_runtime_library_dirs_msg)
# Because these compilers aren't configured in Python's pyconfig.h file by
# default, we should at least warn the user if he is using an unmodified
# version.
CONFIG_H_OK = "ok"
CONFIG_H_NOTOK = "not ok"
CONFIG_H_UNCERTAIN = "uncertain"
def check_config_h():
"""Check if the current Python installation appears amenable to building
extensions with GCC.
Returns a tuple (status, details), where 'status' is one of the following
constants:
- CONFIG_H_OK: all is well, go ahead and compile
- CONFIG_H_NOTOK: doesn't look good
- CONFIG_H_UNCERTAIN: not sure -- unable to read pyconfig.h
'details' is a human-readable string explaining the situation.
Note there are two ways to conclude "OK": either 'sys.version' contains
the string "GCC" (implying that this Python was built with GCC), or the
installed "pyconfig.h" contains the string "__GNUC__".
"""
# XXX since this function also checks sys.version, it's not strictly a
# "pyconfig.h" check -- should probably be renamed...
from distutils import sysconfig
# if sys.version contains GCC then python was compiled with GCC, and the
# pyconfig.h file should be OK
if "GCC" in sys.version:
return CONFIG_H_OK, "sys.version mentions 'GCC'"
# Clang would also work
if "Clang" in sys.version:
return CONFIG_H_OK, "sys.version mentions 'Clang'"
# let's see if __GNUC__ is mentioned in python.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
mention_inflected = 'mentions'
else:
code = CONFIG_H_NOTOK
mention_inflected = 'does not mention'
return code, f"{fn!r} {mention_inflected} {substring!r}"
def is_cygwincc(cc):
"""Try to determine if the compiler that would be used is from cygwin."""
out_string = check_output(shlex.split(cc) + ['-dumpmachine'])
return out_string.strip().endswith(b'cygwin')
get_versions = None
"""
A stand-in for the previous get_versions() function to prevent failures
when monkeypatched. See pypa/setuptools#2969.
"""
@@ -1,24 +0,0 @@
class Error(Exception):
"""Some compile/link operation failed."""
class PreprocessError(Error):
"""Failure to preprocess one or more C/C++ files."""
class CompileError(Error):
"""Failure to compile one or more C/C++ source files."""
class LibError(Error):
"""Failure to create a static library from one or more C/C++ object
files."""
class LinkError(Error):
"""Failure to link one or more C/C++ object files into an executable
or shared library file."""
class UnknownFileType(Error):
"""Attempt to process an unknown file type."""
@@ -1,614 +0,0 @@
"""distutils._msvccompiler
Contains MSVCCompiler, an implementation of the abstract CCompiler class
for Microsoft Visual Studio 2015.
This module requires VS 2015 or later.
"""
# Written by Perry Stoll
# hacked by Robin Becker and Thomas Heller to do a better job of
# finding DevStudio (through the registry)
# ported to VS 2005 and VS 2008 by Christian Heimes
# ported to VS 2015 by Steve Dower
from __future__ import annotations
import contextlib
import os
import subprocess
import unittest.mock as mock
import warnings
from collections.abc import Iterable
with contextlib.suppress(ImportError):
import winreg
from itertools import count
from ..._log import log
from ...errors import (
DistutilsExecError,
DistutilsPlatformError,
)
from ...util import get_host_platform, get_platform
from . import base
from .base import gen_lib_options
from .errors import (
CompileError,
LibError,
LinkError,
)
def _find_vc2015():
try:
key = winreg.OpenKeyEx(
winreg.HKEY_LOCAL_MACHINE,
r"Software\Microsoft\VisualStudio\SxS\VC7",
access=winreg.KEY_READ | winreg.KEY_WOW64_32KEY,
)
except OSError:
log.debug("Visual C++ is not registered")
return None, None
best_version = 0
best_dir = None
with key:
for i in count():
try:
v, vc_dir, vt = winreg.EnumValue(key, i)
except OSError:
break
if v and vt == winreg.REG_SZ and os.path.isdir(vc_dir):
try:
version = int(float(v))
except (ValueError, TypeError):
continue
if version >= 14 and version > best_version:
best_version, best_dir = version, vc_dir
return best_version, best_dir
def _find_vc2017():
"""Returns "15, path" based on the result of invoking vswhere.exe
If no install is found, returns "None, None"
The version is returned to avoid unnecessarily changing the function
result. It may be ignored when the path is not None.
If vswhere.exe is not available, by definition, VS 2017 is not
installed.
"""
root = os.environ.get("ProgramFiles(x86)") or os.environ.get("ProgramFiles")
if not root:
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
return None, None # no suitable component found
PLAT_SPEC_TO_RUNTIME = {
'x86': 'x86',
'x86_amd64': 'x64',
'x86_arm': 'arm',
'x86_arm64': 'arm64',
}
def _find_vcvarsall(plat_spec):
# bpo-38597: Removed vcruntime return value
_, best_dir = _find_vc2017()
if not best_dir:
best_version, best_dir = _find_vc2015()
if not best_dir:
log.debug("No suitable Visual C++ version found")
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, None
return vcvarsall, None
def _get_vc_env(plat_spec):
if os.getenv("DISTUTILS_USE_SDK"):
return {key.lower(): value for key, value in os.environ.items()}
vcvarsall, _ = _find_vcvarsall(plat_spec)
if not vcvarsall:
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(
f'cmd /u /c "{vcvarsall}" {plat_spec} && set',
stderr=subprocess.STDOUT,
).decode('utf-16le', errors='replace')
except subprocess.CalledProcessError as exc:
log.error(exc.output)
raise DistutilsPlatformError(f"Error executing {exc.cmd}")
env = {
key.lower(): value
for key, _, value in (line.partition('=') for line in out.splitlines())
if key and value
}
return env
def _find_exe(exe, paths=None):
"""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'.
"""
if not paths:
paths = os.getenv('path').split(os.pathsep)
for p in paths:
fn = os.path.join(os.path.abspath(p), exe)
if os.path.isfile(fn):
return fn
return exe
_vcvars_names = {
'win32': 'x86',
'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 Compiler(base.Compiler):
"""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) -> None:
super().__init__(verbose, dry_run, force)
# target platform (.plat_name is consistent with 'bdist')
self.plat_name = None
self.initialized = False
@classmethod
def _configure(cls, vc_env):
"""
Set class-level include/lib dirs.
"""
cls.include_dirs = cls._parse_path(vc_env.get('include', ''))
cls.library_dirs = cls._parse_path(vc_env.get('lib', ''))
@staticmethod
def _parse_path(val):
return [dir.rstrip(os.sep) for dir in val.split(os.pathsep) if dir]
def initialize(self, plat_name: str | None = None) -> None:
# multi-init means we would need to check platform same each time...
assert not self.initialized, "don't init multiple times"
if plat_name is None:
plat_name = get_platform()
# sanity check for platforms to prevent obscure errors later.
if plat_name not in _vcvars_names:
raise DistutilsPlatformError(
f"--plat-name must be one of {tuple(_vcvars_names)}"
)
plat_spec = _get_vcvars_spec(get_host_platform(), plat_name)
vc_env = _get_vc_env(plat_spec)
if not vc_env:
raise DistutilsPlatformError(
"Unable to find a compatible Visual Studio installation."
)
self._configure(vc_env)
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)
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.preprocess_options = None
# bpo-38597: Always compile with dynamic linking
# Future releases of Python 3.x will include all past
# versions of vcruntime*.dll for compatibility.
self.compile_options = ['/nologo', '/O2', '/W3', '/GL', '/DNDEBUG', '/MD']
self.compile_options_debug = [
'/nologo',
'/Od',
'/MDd',
'/Zi',
'/W3',
'/D_DEBUG',
]
ldflags = ['/nologo', '/INCREMENTAL:NO', '/LTCG']
ldflags_debug = ['/nologo', '/INCREMENTAL:NO', '/LTCG', '/DEBUG:FULL']
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 = {
(base.Compiler.EXECUTABLE, None): self.ldflags_exe,
(base.Compiler.EXECUTABLE, False): self.ldflags_exe,
(base.Compiler.EXECUTABLE, True): self.ldflags_exe_debug,
(base.Compiler.SHARED_OBJECT, None): self.ldflags_shared,
(base.Compiler.SHARED_OBJECT, False): self.ldflags_shared,
(base.Compiler.SHARED_OBJECT, True): self.ldflags_shared_debug,
(base.Compiler.SHARED_LIBRARY, None): self.ldflags_static,
(base.Compiler.SHARED_LIBRARY, False): self.ldflags_static,
(base.Compiler.SHARED_LIBRARY, True): self.ldflags_static_debug,
}
self.initialized = True
# -- Worker methods ------------------------------------------------
@property
def out_extensions(self) -> dict[str, str]:
return {
**super().out_extensions,
**{
ext: self.res_extension
for ext in self._rc_extensions + self._mc_extensions
},
}
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)
add_cpp_opts = False
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 = f"/Tc{src}"
elif ext in self._cpp_extensions:
input_opt = f"/Tp{src}"
add_cpp_opts = True
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}")
args = [self.cc] + compile_opts + pp_opts
if add_cpp_opts:
args.append('/EHsc')
args.extend((input_opt, "/Fo" + obj))
args.extend(extra_postargs)
try:
self.spawn(args)
except DistutilsExecError as msg:
raise CompileError(msg)
return objects
def create_static_lib(
self,
objects: list[str] | tuple[str, ...],
output_libname: str,
output_dir: str | None = None,
debug: bool = False,
target_lang: str | None = None,
) -> 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:
log.debug('Executing "%s" %s', self.lib, ' '.join(lib_args))
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(
self,
target_desc: str,
objects: list[str] | tuple[str, ...],
output_filename: str,
output_dir: str | None = None,
libraries: list[str] | tuple[str, ...] | None = None,
library_dirs: list[str] | tuple[str, ...] | None = None,
runtime_library_dirs: list[str] | tuple[str, ...] | None = None,
export_symbols: Iterable[str] | None = None,
debug: bool = False,
extra_preargs: list[str] | None = None,
extra_postargs: Iterable[str] | None = None,
build_temp: str | os.PathLike[str] | None = None,
target_lang: str | None = None,
) -> 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):
ldflags = self._ldflags[target_desc, debug]
export_opts = ["/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)
if extra_preargs:
ld_args[:0] = extra_preargs
if extra_postargs:
ld_args.extend(extra_postargs)
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)
except DistutilsExecError as msg:
raise LinkError(msg)
else:
log.debug("skipping %s (up-to-date)", output_filename)
def spawn(self, cmd):
env = dict(os.environ, PATH=self._paths)
with self._fallback_spawn(cmd, env) as fallback:
return super().spawn(cmd, env=env)
return fallback.value
@contextlib.contextmanager
def _fallback_spawn(self, cmd, env):
"""
Discovered in pypa/distutils#15, some tools monkeypatch the compiler,
so the 'env' kwarg causes a TypeError. Detect this condition and
restore the legacy, unsafe behavior.
"""
bag = type('Bag', (), {})()
try:
yield bag
except TypeError as exc:
if "unexpected keyword argument 'env'" not in str(exc):
raise
else:
return
warnings.warn("Fallback spawn triggered. Please update distutils monkeypatch.")
with mock.patch.dict('os.environ', env):
bag.value = super().spawn(cmd)
# -- 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.isfile(libfile):
return libfile
else:
# Oops, didn't find it in *any* of 'dirs'
return None
@@ -1,83 +0,0 @@
import platform
import sysconfig
import textwrap
import pytest
from .. import base
pytestmark = pytest.mark.usefixtures('suppress_path_mangle')
@pytest.fixture
def c_file(tmp_path):
c_file = tmp_path / 'foo.c'
gen_headers = ('Python.h',)
is_windows = platform.system() == "Windows"
plat_headers = ('windows.h',) * is_windows
all_headers = gen_headers + plat_headers
headers = '\n'.join(f'#include <{header}>\n' for header in all_headers)
payload = (
textwrap.dedent(
"""
#headers
void PyInit_foo(void) {}
"""
)
.lstrip()
.replace('#headers', headers)
)
c_file.write_text(payload, encoding='utf-8')
return c_file
def test_set_include_dirs(c_file):
"""
Extensions should build even if set_include_dirs is invoked.
In particular, compiler-specific paths should not be overridden.
"""
compiler = base.new_compiler()
python = sysconfig.get_paths()['include']
compiler.set_include_dirs([python])
compiler.compile([c_file])
# do it again, setting include dirs after any initialization
compiler.set_include_dirs([python])
compiler.compile([c_file])
def test_has_function_prototype():
# Issue https://github.com/pypa/setuptools/issues/3648
# Test prototype-generating behavior.
compiler = base.new_compiler()
# Every C implementation should have these.
assert compiler.has_function('abort')
assert compiler.has_function('exit')
with pytest.deprecated_call(match='includes is deprecated'):
# abort() is a valid expression with the <stdlib.h> prototype.
assert compiler.has_function('abort', includes=['stdlib.h'])
with pytest.deprecated_call(match='includes is deprecated'):
# But exit() is not valid with the actual prototype in scope.
assert not compiler.has_function('exit', includes=['stdlib.h'])
# And setuptools_does_not_exist is not declared or defined at all.
assert not compiler.has_function('setuptools_does_not_exist')
with pytest.deprecated_call(match='includes is deprecated'):
assert not compiler.has_function(
'setuptools_does_not_exist', includes=['stdio.h']
)
def test_include_dirs_after_multiple_compile_calls(c_file):
"""
Calling compile multiple times should not change the include dirs
(regression test for setuptools issue #3591).
"""
compiler = base.new_compiler()
python = sysconfig.get_paths()['include']
compiler.set_include_dirs([python])
compiler.compile([c_file])
assert compiler.include_dirs == [python]
compiler.compile([c_file])
assert compiler.include_dirs == [python]
@@ -1,76 +0,0 @@
"""Tests for distutils.cygwinccompiler."""
import os
import sys
from distutils import sysconfig
from distutils.tests import support
import pytest
from .. import cygwin
@pytest.fixture(autouse=True)
def stuff(request, monkeypatch, distutils_managed_tempdir):
self = request.instance
self.python_h = os.path.join(self.mkdtemp(), 'python.h')
monkeypatch.setattr(sysconfig, 'get_config_h_filename', self._get_config_h_filename)
monkeypatch.setattr(sys, 'version', sys.version)
class TestCygwinCCompiler(support.TempdirManager):
def _get_config_h_filename(self):
return self.python_h
@pytest.mark.skipif('sys.platform != "cygwin"')
@pytest.mark.skipif('not os.path.exists("/usr/lib/libbash.dll.a")')
def test_find_library_file(self):
from distutils.cygwinccompiler import CygwinCCompiler
compiler = CygwinCCompiler()
link_name = "bash"
linkable_file = compiler.find_library_file(["/usr/lib"], link_name)
assert linkable_file is not None
assert os.path.exists(linkable_file)
assert linkable_file == f"/usr/lib/lib{link_name:s}.dll.a"
@pytest.mark.skipif('sys.platform != "cygwin"')
def test_runtime_library_dir_option(self):
from distutils.cygwinccompiler import CygwinCCompiler
compiler = CygwinCCompiler()
assert compiler.runtime_library_dir_option('/foo') == []
def test_check_config_h(self):
# check_config_h looks for "GCC" in sys.version first
# returns CONFIG_H_OK if found
sys.version = (
'2.6.1 (r261:67515, Dec 6 2008, 16:42:21) \n[GCC '
'4.0.1 (Apple Computer, Inc. build 5370)]'
)
assert cygwin.check_config_h()[0] == cygwin.CONFIG_H_OK
# then it tries to see if it can find "__GNUC__" in pyconfig.h
sys.version = 'something without the *CC word'
# if the file doesn't exist it returns CONFIG_H_UNCERTAIN
assert cygwin.check_config_h()[0] == cygwin.CONFIG_H_UNCERTAIN
# if it exists but does not contain __GNUC__, it returns CONFIG_H_NOTOK
self.write_file(self.python_h, 'xxx')
assert cygwin.check_config_h()[0] == cygwin.CONFIG_H_NOTOK
# and CONFIG_H_OK if __GNUC__ is found
self.write_file(self.python_h, 'xxx __GNUC__ xxx')
assert cygwin.check_config_h()[0] == cygwin.CONFIG_H_OK
def test_get_msvcr(self):
assert cygwin.get_msvcr() == []
@pytest.mark.skipif('sys.platform != "cygwin"')
def test_dll_libraries_not_none(self):
from distutils.cygwinccompiler import CygwinCCompiler
compiler = CygwinCCompiler()
assert compiler.dll_libraries is not None
@@ -1,48 +0,0 @@
from distutils import sysconfig
from distutils.errors import DistutilsPlatformError
from distutils.util import is_mingw, split_quoted
import pytest
from .. import cygwin, errors
class TestMinGW32Compiler:
@pytest.mark.skipif(not is_mingw(), reason='not on mingw')
def test_compiler_type(self):
compiler = cygwin.MinGW32Compiler()
assert compiler.compiler_type == 'mingw32'
@pytest.mark.skipif(not is_mingw(), reason='not on mingw')
def test_set_executables(self, monkeypatch):
monkeypatch.setenv('CC', 'cc')
monkeypatch.setenv('CXX', 'c++')
compiler = cygwin.MinGW32Compiler()
assert compiler.compiler == split_quoted('cc -O -Wall')
assert compiler.compiler_so == split_quoted('cc -shared -O -Wall')
assert compiler.compiler_cxx == split_quoted('c++ -O -Wall')
assert compiler.linker_exe == split_quoted('cc')
assert compiler.linker_so == split_quoted('cc -shared')
@pytest.mark.skipif(not is_mingw(), reason='not on mingw')
def test_runtime_library_dir_option(self):
compiler = cygwin.MinGW32Compiler()
with pytest.raises(DistutilsPlatformError):
compiler.runtime_library_dir_option('/usr/lib')
@pytest.mark.skipif(not is_mingw(), reason='not on mingw')
def test_cygwincc_error(self, monkeypatch):
monkeypatch.setattr(cygwin, 'is_cygwincc', lambda _: True)
with pytest.raises(errors.Error):
cygwin.MinGW32Compiler()
@pytest.mark.skipif('sys.platform == "cygwin"')
def test_customize_compiler_with_msvc_python(self):
# In case we have an MSVC Python build, but still want to use
# MinGW32Compiler, then customize_compiler() shouldn't fail at least.
# https://github.com/pypa/setuptools/issues/4456
compiler = cygwin.MinGW32Compiler()
sysconfig.customize_compiler(compiler)

Some files were not shown because too many files have changed in this diff Show More