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
@@ -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('#')