This commit is contained in:
ton
2024-10-07 10:13:40 +07:00
parent aa1631742f
commit 3a7d696db6
9729 changed files with 1832837 additions and 161742 deletions

View File

@@ -1,5 +1,5 @@
import sys
import importlib
import sys
__version__, _, _ = sys.version.partition(' ')

View File

@@ -1,194 +0,0 @@
import collections
import functools
import itertools
import operator
# from jaraco.collections 3.5.1
class DictStack(list, collections.abc.Mapping):
"""
A stack of dictionaries that behaves as a view on those dictionaries,
giving preference to the last.
>>> stack = DictStack([dict(a=1, c=2), dict(b=2, a=2)])
>>> stack['a']
2
>>> stack['b']
2
>>> stack['c']
2
>>> len(stack)
3
>>> stack.push(dict(a=3))
>>> stack['a']
3
>>> set(stack.keys()) == set(['a', 'b', 'c'])
True
>>> set(stack.items()) == set([('a', 3), ('b', 2), ('c', 2)])
True
>>> dict(**stack) == dict(stack) == dict(a=3, c=2, b=2)
True
>>> d = stack.pop()
>>> stack['a']
2
>>> d = stack.pop()
>>> stack['a']
1
>>> stack.get('b', None)
>>> 'c' in stack
True
"""
def __iter__(self):
dicts = list.__iter__(self)
return iter(set(itertools.chain.from_iterable(c.keys() for c in dicts)))
def __getitem__(self, key):
for scope in reversed(tuple(list.__iter__(self))):
if key in scope:
return scope[key]
raise KeyError(key)
push = list.append
def __contains__(self, other):
return collections.abc.Mapping.__contains__(self, other)
def __len__(self):
return len(list(iter(self)))
# from jaraco.collections 3.7
class RangeMap(dict):
"""
A dictionary-like object that uses the keys as bounds for a range.
Inclusion of the value for that range is determined by the
key_match_comparator, which defaults to less-than-or-equal.
A value is returned for a key if it is the first key that matches in
the sorted list of keys.
One may supply keyword parameters to be passed to the sort function used
to sort keys (i.e. key, reverse) as sort_params.
Let's create a map that maps 1-3 -> 'a', 4-6 -> 'b'
>>> r = RangeMap({3: 'a', 6: 'b'}) # boy, that was easy
>>> r[1], r[2], r[3], r[4], r[5], r[6]
('a', 'a', 'a', 'b', 'b', 'b')
Even float values should work so long as the comparison operator
supports it.
>>> r[4.5]
'b'
But you'll notice that the way rangemap is defined, it must be open-ended
on one side.
>>> r[0]
'a'
>>> r[-1]
'a'
One can close the open-end of the RangeMap by using undefined_value
>>> r = RangeMap({0: RangeMap.undefined_value, 3: 'a', 6: 'b'})
>>> r[0]
Traceback (most recent call last):
...
KeyError: 0
One can get the first or last elements in the range by using RangeMap.Item
>>> last_item = RangeMap.Item(-1)
>>> r[last_item]
'b'
.last_item is a shortcut for Item(-1)
>>> r[RangeMap.last_item]
'b'
Sometimes it's useful to find the bounds for a RangeMap
>>> r.bounds()
(0, 6)
RangeMap supports .get(key, default)
>>> r.get(0, 'not found')
'not found'
>>> r.get(7, 'not found')
'not found'
One often wishes to define the ranges by their left-most values,
which requires use of sort params and a key_match_comparator.
>>> r = RangeMap({1: 'a', 4: 'b'},
... sort_params=dict(reverse=True),
... key_match_comparator=operator.ge)
>>> r[1], r[2], r[3], r[4], r[5], r[6]
('a', 'a', 'a', 'b', 'b', 'b')
That wasn't nearly as easy as before, so an alternate constructor
is provided:
>>> r = RangeMap.left({1: 'a', 4: 'b', 7: RangeMap.undefined_value})
>>> r[1], r[2], r[3], r[4], r[5], r[6]
('a', 'a', 'a', 'b', 'b', 'b')
"""
def __init__(self, source, sort_params={}, key_match_comparator=operator.le):
dict.__init__(self, source)
self.sort_params = sort_params
self.match = key_match_comparator
@classmethod
def left(cls, source):
return cls(
source, sort_params=dict(reverse=True), key_match_comparator=operator.ge
)
def __getitem__(self, item):
sorted_keys = sorted(self.keys(), **self.sort_params)
if isinstance(item, RangeMap.Item):
result = self.__getitem__(sorted_keys[item])
else:
key = self._find_first_match_(sorted_keys, item)
result = dict.__getitem__(self, key)
if result is RangeMap.undefined_value:
raise KeyError(key)
return result
def get(self, key, default=None):
"""
Return the value for key if key is in the dictionary, else default.
If default is not given, it defaults to None, so that this method
never raises a KeyError.
"""
try:
return self[key]
except KeyError:
return default
def _find_first_match_(self, keys, item):
is_match = functools.partial(self.match, item)
matches = list(filter(is_match, keys))
if matches:
return matches[0]
raise KeyError(item)
def bounds(self):
sorted_keys = sorted(self.keys(), **self.sort_params)
return (sorted_keys[RangeMap.first_item], sorted_keys[RangeMap.last_item])
# some special values for the RangeMap
undefined_value = type('RangeValueUndefined', (), {})()
class Item(int):
"RangeMap Item"
first_item = Item(0)
last_item = Item(-1)

View File

@@ -1,20 +0,0 @@
import functools
# from jaraco.functools 3.5
def pass_none(func):
"""
Wrap func so it's not called if its first param is None
>>> print_text = pass_none(print)
>>> print_text('text')
text
>>> print_text(None)
"""
@functools.wraps(func)
def wrapper(param, *args, **kwargs):
if param is not None:
return func(param, *args, **kwargs)
return wrapper

View File

@@ -1,4 +1,3 @@
import logging
log = logging.getLogger()

View File

@@ -1,5 +1,5 @@
import sys
import importlib
import sys
def bypass_compiler_fixup(cmd, args):

View File

@@ -0,0 +1,73 @@
"""Timestamp comparison of files and groups of files."""
import functools
import os.path
from jaraco.functools import splat
from .compat.py39 import zip_strict
from .errors import DistutilsFileError
def _newer(source, target):
return not os.path.exists(target) or (
os.path.getmtime(source) > os.path.getmtime(target)
)
def newer(source, target):
"""
Is source modified more recently than target.
Returns True if 'source' is modified more recently than
'target' or if 'target' does not exist.
Raises DistutilsFileError if 'source' does not exist.
"""
if not os.path.exists(source):
raise DistutilsFileError(f"file '{os.path.abspath(source)}' does not exist")
return _newer(source, target)
def newer_pairwise(sources, targets, newer=newer):
"""
Filter filenames where sources are newer than targets.
Walk two filename iterables in parallel, testing if each source is newer
than its corresponding target. Returns a pair of lists (sources,
targets) where source is newer than target, according to the semantics
of 'newer()'.
"""
newer_pairs = filter(splat(newer), zip_strict(sources, targets))
return tuple(map(list, zip(*newer_pairs))) or ([], [])
def newer_group(sources, target, missing='error'):
"""
Is target out-of-date with respect to any file in sources.
Return True if 'target' is out-of-date with respect to any file
listed in 'sources'. In other words, if 'target' exists and is newer
than every file in 'sources', return False; otherwise return True.
``missing`` controls how to handle a missing source file:
- error (default): allow the ``stat()`` call to fail.
- ignore: silently disregard any missing source files.
- newer: treat missing source files as "target out of date". This
mode is handy in "dry-run" mode: it will pretend to carry out
commands that wouldn't work because inputs are missing, but
that doesn't matter because dry-run won't run the commands.
"""
def missing_as_newer(source):
return missing == 'newer' and not os.path.exists(source)
ignored = os.path.exists if missing == 'ignore' else None
return not os.path.exists(target) or any(
missing_as_newer(source) or _newer(source, target)
for source in filter(ignored, sources)
)
newer_pairwise_group = functools.partial(newer_pairwise, newer=newer_group)

View File

@@ -3,8 +3,7 @@
Contains MSVCCompiler, an implementation of the abstract CCompiler class
for Microsoft Visual Studio 2015.
The module is compatible with VS 2015 and later. You can find legacy support
for older versions in distutils.msvc9compiler and distutils.msvccompiler.
This module requires VS 2015 or later.
"""
# Written by Perry Stoll
@@ -13,27 +12,27 @@ for older versions in distutils.msvc9compiler and distutils.msvccompiler.
# ported to VS 2005 and VS 2008 by Christian Heimes
# ported to VS 2015 by Steve Dower
import contextlib
import os
import subprocess
import contextlib
import warnings
import unittest.mock as mock
import warnings
with contextlib.suppress(ImportError):
import winreg
from itertools import count
from ._log import log
from .ccompiler import CCompiler, gen_lib_options
from .errors import (
CompileError,
DistutilsExecError,
DistutilsPlatformError,
CompileError,
LibError,
LinkError,
)
from .ccompiler import CCompiler, gen_lib_options
from ._log import log
from .util import get_platform
from itertools import count
from .util import get_host_platform, get_platform
def _find_vc2015():
@@ -79,32 +78,40 @@ def _find_vc2017():
if not root:
return None, None
try:
path = subprocess.check_output(
[
os.path.join(
root, "Microsoft Visual Studio", "Installer", "vswhere.exe"
),
"-latest",
"-prerelease",
"-requires",
"Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
"-property",
"installationPath",
"-products",
"*",
],
encoding="mbcs",
errors="strict",
).strip()
except (subprocess.CalledProcessError, OSError, UnicodeDecodeError):
return None, None
variant = 'arm64' if get_platform() == 'win-arm64' else 'x86.x64'
suitable_components = (
f"Microsoft.VisualStudio.Component.VC.Tools.{variant}",
"Microsoft.VisualStudio.Workload.WDExpress",
)
path = os.path.join(path, "VC", "Auxiliary", "Build")
if os.path.isdir(path):
return 15, path
for component in suitable_components:
# Workaround for `-requiresAny` (only available on VS 2017 > 15.6)
with contextlib.suppress(
subprocess.CalledProcessError, OSError, UnicodeDecodeError
):
path = (
subprocess.check_output([
os.path.join(
root, "Microsoft Visual Studio", "Installer", "vswhere.exe"
),
"-latest",
"-prerelease",
"-requires",
component,
"-property",
"installationPath",
"-products",
"*",
])
.decode(encoding="mbcs", errors="strict")
.strip()
)
return None, None
path = os.path.join(path, "VC", "Auxiliary", "Build")
if os.path.isdir(path):
return 15, path
return None, None # no suitable component found
PLAT_SPEC_TO_RUNTIME = {
@@ -140,7 +147,11 @@ def _get_vc_env(plat_spec):
vcvarsall, _ = _find_vcvarsall(plat_spec)
if not vcvarsall:
raise DistutilsPlatformError("Unable to find vcvarsall.bat")
raise DistutilsPlatformError(
'Microsoft Visual C++ 14.0 or greater is required. '
'Get it with "Microsoft C++ Build Tools": '
'https://visualstudio.microsoft.com/visual-cpp-build-tools/'
)
try:
out = subprocess.check_output(
@@ -178,17 +189,43 @@ def _find_exe(exe, paths=None):
return exe
# A map keyed by get_platform() return values to values accepted by
# 'vcvarsall.bat'. Always cross-compile from x86 to work with the
# lighter-weight MSVC installs that do not include native 64-bit tools.
PLAT_TO_VCVARS = {
_vcvars_names = {
'win32': 'x86',
'win-amd64': 'x86_amd64',
'win-arm32': 'x86_arm',
'win-arm64': 'x86_arm64',
'win-amd64': 'amd64',
'win-arm32': 'arm',
'win-arm64': 'arm64',
}
def _get_vcvars_spec(host_platform, platform):
"""
Given a host platform and platform, determine the spec for vcvarsall.
Uses the native MSVC host if the host platform would need expensive
emulation for x86.
>>> _get_vcvars_spec('win-arm64', 'win32')
'arm64_x86'
>>> _get_vcvars_spec('win-arm64', 'win-amd64')
'arm64_amd64'
Otherwise, always cross-compile from x86 to work with the
lighter-weight MSVC installs that do not include native 64-bit tools.
>>> _get_vcvars_spec('win32', 'win32')
'x86'
>>> _get_vcvars_spec('win-arm32', 'win-arm32')
'x86_arm'
>>> _get_vcvars_spec('win-amd64', 'win-arm64')
'x86_arm64'
"""
if host_platform != 'win-arm64':
host_platform = 'win32'
vc_hp = _vcvars_names[host_platform]
vc_plat = _vcvars_names[platform]
return vc_hp if vc_hp == vc_plat else f'{vc_hp}_{vc_plat}'
class MSVCCompiler(CCompiler):
"""Concrete class that implements an interface to Microsoft Visual C++,
as defined by the CCompiler abstract class."""
@@ -218,7 +255,7 @@ class MSVCCompiler(CCompiler):
static_lib_format = shared_lib_format = '%s%s'
exe_extension = '.exe'
def __init__(self, verbose=0, dry_run=0, force=0):
def __init__(self, verbose=False, dry_run=False, force=False):
super().__init__(verbose, dry_run, force)
# target platform (.plat_name is consistent with 'bdist')
self.plat_name = None
@@ -242,18 +279,17 @@ class MSVCCompiler(CCompiler):
if plat_name is None:
plat_name = get_platform()
# sanity check for platforms to prevent obscure errors later.
if plat_name not in PLAT_TO_VCVARS:
if plat_name not in _vcvars_names:
raise DistutilsPlatformError(
f"--plat-name must be one of {tuple(PLAT_TO_VCVARS)}"
f"--plat-name must be one of {tuple(_vcvars_names)}"
)
# Get the vcvarsall.bat spec for the requested platform.
plat_spec = PLAT_TO_VCVARS[plat_name]
plat_spec = _get_vcvars_spec(get_host_platform(), plat_name)
vc_env = _get_vc_env(plat_spec)
if not vc_env:
raise DistutilsPlatformError(
"Unable to find a compatible " "Visual Studio installation."
"Unable to find a compatible Visual Studio installation."
)
self._configure(vc_env)
@@ -334,7 +370,7 @@ class MSVCCompiler(CCompiler):
output_dir=None,
macros=None,
include_dirs=None,
debug=0,
debug=False,
extra_preargs=None,
extra_postargs=None,
depends=None,
@@ -430,7 +466,7 @@ class MSVCCompiler(CCompiler):
return objects
def create_static_lib(
self, objects, output_libname, output_dir=None, debug=0, target_lang=None
self, objects, output_libname, output_dir=None, debug=False, target_lang=None
):
if not self.initialized:
self.initialize()
@@ -459,7 +495,7 @@ class MSVCCompiler(CCompiler):
library_dirs=None,
runtime_library_dirs=None,
export_symbols=None,
debug=0,
debug=False,
extra_preargs=None,
extra_postargs=None,
build_temp=None,
@@ -558,7 +594,7 @@ class MSVCCompiler(CCompiler):
def library_option(self, lib):
return self.library_filename(lib)
def find_library_file(self, dirs, lib, debug=0):
def find_library_file(self, dirs, lib, debug=False):
# Prefer a debugging library if found (and requested), but deal
# with it if we don't have one.
if debug:

View File

@@ -4,8 +4,6 @@ Utility functions for creating archive files (tarballs, zip files,
that sort of thing)."""
import os
from warnings import warn
import sys
try:
import zipfile
@@ -13,10 +11,10 @@ except ImportError:
zipfile = None
from ._log import log
from .dir_util import mkpath
from .errors import DistutilsExecError
from .spawn import spawn
from .dir_util import mkpath
from ._log import log
try:
from pwd import getpwnam
@@ -56,13 +54,18 @@ def _get_uid(name):
def make_tarball(
base_name, base_dir, compress="gzip", verbose=0, dry_run=0, owner=None, group=None
base_name,
base_dir,
compress="gzip",
verbose=False,
dry_run=False,
owner=None,
group=None,
):
"""Create a (possibly compressed) tar file from all the files under
'base_dir'.
'compress' must be "gzip" (the default), "bzip2", "xz", "compress", or
None. ("compress" will be deprecated in Python 3.2)
'compress' must be "gzip" (the default), "bzip2", "xz", or None.
'owner' and 'group' can be used to define an owner and a group for the
archive that is being built. If not provided, the current owner and group
@@ -78,20 +81,17 @@ def make_tarball(
'bzip2': 'bz2',
'xz': 'xz',
None: '',
'compress': '',
}
compress_ext = {'gzip': '.gz', 'bzip2': '.bz2', 'xz': '.xz', 'compress': '.Z'}
compress_ext = {'gzip': '.gz', 'bzip2': '.bz2', 'xz': '.xz'}
# flags for compression program, each element of list will be an argument
if compress is not None and compress not in compress_ext.keys():
raise ValueError(
"bad value for 'compress': must be None, 'gzip', 'bzip2', "
"'xz' or 'compress'"
"bad value for 'compress': must be None, 'gzip', 'bzip2', 'xz'"
)
archive_name = base_name + '.tar'
if compress != 'compress':
archive_name += compress_ext.get(compress, '')
archive_name += compress_ext.get(compress, '')
mkpath(os.path.dirname(archive_name), dry_run=dry_run)
@@ -113,28 +113,16 @@ def make_tarball(
return tarinfo
if not dry_run:
tar = tarfile.open(archive_name, 'w|%s' % tar_compression[compress])
tar = tarfile.open(archive_name, f'w|{tar_compression[compress]}')
try:
tar.add(base_dir, filter=_set_uid_gid)
finally:
tar.close()
# compression using `compress`
if compress == 'compress':
warn("'compress' is deprecated.", DeprecationWarning)
# the option varies depending on the platform
compressed_name = archive_name + compress_ext[compress]
if sys.platform == 'win32':
cmd = [compress, archive_name, compressed_name]
else:
cmd = [compress, '-f', archive_name]
spawn(cmd, dry_run=dry_run)
return compressed_name
return archive_name
def make_zipfile(base_name, base_dir, verbose=0, dry_run=0): # noqa: C901
def make_zipfile(base_name, base_dir, verbose=False, dry_run=False): # noqa: C901
"""Create a zip file from all the files under 'base_dir'.
The output zip file will be named 'base_name' + ".zip". Uses either the
@@ -160,12 +148,9 @@ def make_zipfile(base_name, base_dir, verbose=0, dry_run=0): # noqa: C901
# XXX really should distinguish between "couldn't find
# external 'zip' command" and "zip failed".
raise DistutilsExecError(
(
"unable to create zip file '%s': "
"could neither import the 'zipfile' module nor "
"find a standalone zip utility"
)
% zip_filename
f"unable to create zip file '{zip_filename}': "
"could neither import the 'zipfile' module nor "
"find a standalone zip utility"
)
else:
@@ -224,8 +209,8 @@ def make_archive(
format,
root_dir=None,
base_dir=None,
verbose=0,
dry_run=0,
verbose=False,
dry_run=False,
owner=None,
group=None,
):
@@ -260,11 +245,10 @@ def make_archive(
try:
format_info = ARCHIVE_FORMATS[format]
except KeyError:
raise ValueError("unknown archive format '%s'" % format)
raise ValueError(f"unknown archive format '{format}'")
func = format_info[0]
for arg, val in format_info[1]:
kwargs[arg] = val
kwargs.update(format_info[1])
if format != 'zip':
kwargs['owner'] = owner

View File

@@ -1,401 +0,0 @@
"""distutils.bcppcompiler
Contains BorlandCCompiler, an implementation of the abstract CCompiler class
for the Borland C++ compiler.
"""
# This implementation by Lyle Johnson, based on the original msvccompiler.py
# module and using the directions originally published by Gordon Williams.
# XXX looks like there's a LOT of overlap between these two classes:
# someone should sit down and factor out the common code as
# WindowsCCompiler! --GPW
import os
import warnings
from .errors import (
DistutilsExecError,
CompileError,
LibError,
LinkError,
UnknownFileError,
)
from .ccompiler import CCompiler, gen_preprocess_options
from .file_util import write_file
from .dep_util import newer
from ._log import log
warnings.warn(
"bcppcompiler is deprecated and slated to be removed "
"in the future. Please discontinue use or file an issue "
"with pypa/distutils describing your use case.",
DeprecationWarning,
)
class BCPPCompiler(CCompiler):
"""Concrete class that implements an interface to the Borland C/C++
compiler, as defined by the CCompiler abstract class.
"""
compiler_type = 'bcpp'
# Just set this so CCompiler's constructor doesn't barf. We currently
# don't use the 'set_executables()' bureaucracy provided by CCompiler,
# as it really isn't necessary for this sort of single-compiler class.
# Would be nice to have a consistent interface with UnixCCompiler,
# though, so it's worth thinking about.
executables = {}
# Private class data (need to distinguish C from C++ source for compiler)
_c_extensions = ['.c']
_cpp_extensions = ['.cc', '.cpp', '.cxx']
# Needed for the filename generation methods provided by the
# base class, CCompiler.
src_extensions = _c_extensions + _cpp_extensions
obj_extension = '.obj'
static_lib_extension = '.lib'
shared_lib_extension = '.dll'
static_lib_format = shared_lib_format = '%s%s'
exe_extension = '.exe'
def __init__(self, verbose=0, dry_run=0, force=0):
super().__init__(verbose, dry_run, force)
# These executables are assumed to all be in the path.
# Borland doesn't seem to use any special registry settings to
# indicate their installation locations.
self.cc = "bcc32.exe"
self.linker = "ilink32.exe"
self.lib = "tlib.exe"
self.preprocess_options = None
self.compile_options = ['/tWM', '/O2', '/q', '/g0']
self.compile_options_debug = ['/tWM', '/Od', '/q', '/g0']
self.ldflags_shared = ['/Tpd', '/Gn', '/q', '/x']
self.ldflags_shared_debug = ['/Tpd', '/Gn', '/q', '/x']
self.ldflags_static = []
self.ldflags_exe = ['/Gn', '/q', '/x']
self.ldflags_exe_debug = ['/Gn', '/q', '/x', '/r']
# -- Worker methods ------------------------------------------------
def compile( # noqa: C901
self,
sources,
output_dir=None,
macros=None,
include_dirs=None,
debug=0,
extra_preargs=None,
extra_postargs=None,
depends=None,
):
macros, objects, extra_postargs, pp_opts, build = self._setup_compile(
output_dir, macros, include_dirs, sources, depends, extra_postargs
)
compile_opts = extra_preargs or []
compile_opts.append('-c')
if debug:
compile_opts.extend(self.compile_options_debug)
else:
compile_opts.extend(self.compile_options)
for obj in objects:
try:
src, ext = build[obj]
except KeyError:
continue
# XXX why do the normpath here?
src = os.path.normpath(src)
obj = os.path.normpath(obj)
# XXX _setup_compile() did a mkpath() too but before the normpath.
# Is it possible to skip the normpath?
self.mkpath(os.path.dirname(obj))
if ext == '.res':
# This is already a binary file -- skip it.
continue # the 'for' loop
if ext == '.rc':
# This needs to be compiled to a .res file -- do it now.
try:
self.spawn(["brcc32", "-fo", obj, src])
except DistutilsExecError as msg:
raise CompileError(msg)
continue # the 'for' loop
# The next two are both for the real compiler.
if ext in self._c_extensions:
input_opt = ""
elif ext in self._cpp_extensions:
input_opt = "-P"
else:
# Unknown file type -- no extra options. The compiler
# will probably fail, but let it just in case this is a
# file the compiler recognizes even if we don't.
input_opt = ""
output_opt = "-o" + obj
# Compiler command line syntax is: "bcc32 [options] file(s)".
# Note that the source file names must appear at the end of
# the command line.
try:
self.spawn(
[self.cc]
+ compile_opts
+ pp_opts
+ [input_opt, output_opt]
+ extra_postargs
+ [src]
)
except DistutilsExecError as msg:
raise CompileError(msg)
return objects
# compile ()
def create_static_lib(
self, objects, output_libname, output_dir=None, debug=0, target_lang=None
):
(objects, output_dir) = self._fix_object_args(objects, output_dir)
output_filename = self.library_filename(output_libname, output_dir=output_dir)
if self._need_link(objects, output_filename):
lib_args = [output_filename, '/u'] + objects
if debug:
pass # XXX what goes here?
try:
self.spawn([self.lib] + lib_args)
except DistutilsExecError as msg:
raise LibError(msg)
else:
log.debug("skipping %s (up-to-date)", output_filename)
# create_static_lib ()
def link( # noqa: C901
self,
target_desc,
objects,
output_filename,
output_dir=None,
libraries=None,
library_dirs=None,
runtime_library_dirs=None,
export_symbols=None,
debug=0,
extra_preargs=None,
extra_postargs=None,
build_temp=None,
target_lang=None,
):
# XXX this ignores 'build_temp'! should follow the lead of
# msvccompiler.py
(objects, output_dir) = self._fix_object_args(objects, output_dir)
(libraries, library_dirs, runtime_library_dirs) = self._fix_lib_args(
libraries, library_dirs, runtime_library_dirs
)
if runtime_library_dirs:
log.warning(
"I don't know what to do with 'runtime_library_dirs': %s",
str(runtime_library_dirs),
)
if output_dir is not None:
output_filename = os.path.join(output_dir, output_filename)
if self._need_link(objects, output_filename):
# Figure out linker args based on type of target.
if target_desc == CCompiler.EXECUTABLE:
startup_obj = 'c0w32'
if debug:
ld_args = self.ldflags_exe_debug[:]
else:
ld_args = self.ldflags_exe[:]
else:
startup_obj = 'c0d32'
if debug:
ld_args = self.ldflags_shared_debug[:]
else:
ld_args = self.ldflags_shared[:]
# Create a temporary exports file for use by the linker
if export_symbols is None:
def_file = ''
else:
head, tail = os.path.split(output_filename)
modname, ext = os.path.splitext(tail)
temp_dir = os.path.dirname(objects[0]) # preserve tree structure
def_file = os.path.join(temp_dir, '%s.def' % modname)
contents = ['EXPORTS']
for sym in export_symbols or []:
contents.append(' {}=_{}'.format(sym, sym))
self.execute(write_file, (def_file, contents), "writing %s" % def_file)
# Borland C++ has problems with '/' in paths
objects2 = map(os.path.normpath, objects)
# split objects in .obj and .res files
# Borland C++ needs them at different positions in the command line
objects = [startup_obj]
resources = []
for file in objects2:
(base, ext) = os.path.splitext(os.path.normcase(file))
if ext == '.res':
resources.append(file)
else:
objects.append(file)
for ell in library_dirs:
ld_args.append("/L%s" % os.path.normpath(ell))
ld_args.append("/L.") # we sometimes use relative paths
# list of object files
ld_args.extend(objects)
# XXX the command-line syntax for Borland C++ is a bit wonky;
# certain filenames are jammed together in one big string, but
# comma-delimited. This doesn't mesh too well with the
# Unix-centric attitude (with a DOS/Windows quoting hack) of
# 'spawn()', so constructing the argument list is a bit
# awkward. Note that doing the obvious thing and jamming all
# the filenames and commas into one argument would be wrong,
# because 'spawn()' would quote any filenames with spaces in
# them. Arghghh!. Apparently it works fine as coded...
# name of dll/exe file
ld_args.extend([',', output_filename])
# no map file and start libraries
ld_args.append(',,')
for lib in libraries:
# see if we find it and if there is a bcpp specific lib
# (xxx_bcpp.lib)
libfile = self.find_library_file(library_dirs, lib, debug)
if libfile is None:
ld_args.append(lib)
# probably a BCPP internal library -- don't warn
else:
# full name which prefers bcpp_xxx.lib over xxx.lib
ld_args.append(libfile)
# some default libraries
ld_args.extend(('import32', 'cw32mt'))
# def file for export symbols
ld_args.extend([',', def_file])
# add resource files
ld_args.append(',')
ld_args.extend(resources)
if extra_preargs:
ld_args[:0] = extra_preargs
if extra_postargs:
ld_args.extend(extra_postargs)
self.mkpath(os.path.dirname(output_filename))
try:
self.spawn([self.linker] + ld_args)
except DistutilsExecError as msg:
raise LinkError(msg)
else:
log.debug("skipping %s (up-to-date)", output_filename)
# link ()
# -- Miscellaneous methods -----------------------------------------
def find_library_file(self, dirs, lib, debug=0):
# List of effective library names to try, in order of preference:
# xxx_bcpp.lib is better than xxx.lib
# and xxx_d.lib is better than xxx.lib if debug is set
#
# The "_bcpp" suffix is to handle a Python installation for people
# with multiple compilers (primarily Distutils hackers, I suspect
# ;-). The idea is they'd have one static library for each
# compiler they care about, since (almost?) every Windows compiler
# seems to have a different format for static libraries.
if debug:
dlib = lib + "_d"
try_names = (dlib + "_bcpp", lib + "_bcpp", dlib, lib)
else:
try_names = (lib + "_bcpp", lib)
for dir in dirs:
for name in try_names:
libfile = os.path.join(dir, self.library_filename(name))
if os.path.exists(libfile):
return libfile
else:
# Oops, didn't find it in *any* of 'dirs'
return None
# overwrite the one from CCompiler to support rc and res-files
def object_filenames(self, source_filenames, strip_dir=0, output_dir=''):
if output_dir is None:
output_dir = ''
obj_names = []
for src_name in source_filenames:
# use normcase to make sure '.rc' is really '.rc' and not '.RC'
(base, ext) = os.path.splitext(os.path.normcase(src_name))
if ext not in (self.src_extensions + ['.rc', '.res']):
raise UnknownFileError(
"unknown file type '{}' (from '{}')".format(ext, src_name)
)
if strip_dir:
base = os.path.basename(base)
if ext == '.res':
# these can go unchanged
obj_names.append(os.path.join(output_dir, base + ext))
elif ext == '.rc':
# these need to be compiled to .res-files
obj_names.append(os.path.join(output_dir, base + '.res'))
else:
obj_names.append(os.path.join(output_dir, base + self.obj_extension))
return obj_names
# object_filenames ()
def preprocess(
self,
source,
output_file=None,
macros=None,
include_dirs=None,
extra_preargs=None,
extra_postargs=None,
):
(_, macros, include_dirs) = self._fix_compile_args(None, macros, include_dirs)
pp_opts = gen_preprocess_options(macros, include_dirs)
pp_args = ['cpp32.exe'] + pp_opts
if output_file is not None:
pp_args.append('-o' + output_file)
if extra_preargs:
pp_args[:0] = extra_preargs
if extra_postargs:
pp_args.extend(extra_postargs)
pp_args.append(source)
# We need to preprocess: either we're being forced to, or the
# source file is newer than the target (or the target doesn't
# exist).
if self.force or output_file is None or newer(source, output_file):
if output_file:
self.mkpath(os.path.dirname(output_file))
try:
self.spawn(pp_args)
except DistutilsExecError as msg:
print(msg)
raise CompileError(msg)
# preprocess()

View File

@@ -3,24 +3,27 @@
Contains CCompiler, an abstract base class that defines the interface
for the Distutils compiler abstraction model."""
import sys
import os
import re
import sys
import types
import warnings
from more_itertools import always_iterable
from ._log import log
from ._modified import newer_group
from .dir_util import mkpath
from .errors import (
CompileError,
DistutilsModuleError,
DistutilsPlatformError,
LinkError,
UnknownFileError,
DistutilsPlatformError,
DistutilsModuleError,
)
from .spawn import spawn
from .file_util import move_file
from .dir_util import mkpath
from .dep_util import newer_group
from .util import split_quoted, execute
from ._log import log
from .spawn import spawn
from .util import execute, is_mingw, split_quoted
class CCompiler:
@@ -103,7 +106,7 @@ class CCompiler:
library dirs specific to this compiler class
"""
def __init__(self, verbose=0, dry_run=0, force=0):
def __init__(self, verbose=False, dry_run=False, force=False):
self.dry_run = dry_run
self.force = force
self.verbose = verbose
@@ -168,8 +171,7 @@ class CCompiler:
for key in kwargs:
if key not in self.executables:
raise ValueError(
"unknown executable '%s' for class %s"
% (key, self.__class__.__name__)
f"unknown executable '{key}' for class {self.__class__.__name__}"
)
self.set_executable(key, kwargs[key])
@@ -188,24 +190,28 @@ class CCompiler:
return None
def _check_macro_definitions(self, definitions):
"""Ensures that every element of 'definitions' is a valid macro
definition, ie. either (name,value) 2-tuple or a (name,) tuple. Do
nothing if all definitions are OK, raise TypeError otherwise.
"""
"""Ensure that every element of 'definitions' is valid."""
for defn in definitions:
if not (
isinstance(defn, tuple)
and (
len(defn) in (1, 2)
and (isinstance(defn[1], str) or defn[1] is None)
)
and isinstance(defn[0], str)
):
raise TypeError(
("invalid macro definition '%s': " % defn)
+ "must be tuple (string,), (string, string), or "
+ "(string, None)"
)
self._check_macro_definition(*defn)
def _check_macro_definition(self, defn):
"""
Raise a TypeError if defn is not valid.
A valid definition is either a (name, value) 2-tuple or a (name,) tuple.
"""
if not isinstance(defn, tuple) or not self._is_valid_macro(*defn):
raise TypeError(
f"invalid macro definition '{defn}': "
"must be tuple (string,), (string, string), or (string, None)"
)
@staticmethod
def _is_valid_macro(name, value=None):
"""
A valid macro is a ``name : str`` and a ``value : str | None``.
"""
return isinstance(name, str) and isinstance(value, (str, types.NoneType))
# -- Bookkeeping methods -------------------------------------------
@@ -342,7 +348,7 @@ class CCompiler:
extra = []
# Get the list of expected output (object) files
objects = self.object_filenames(sources, strip_dir=0, output_dir=outdir)
objects = self.object_filenames(sources, strip_dir=False, output_dir=outdir)
assert len(objects) == len(sources)
pp_opts = gen_preprocess_options(macros, incdirs)
@@ -382,7 +388,7 @@ class CCompiler:
raise TypeError("'output_dir' must be a string or None")
if macros is None:
macros = self.macros
macros = list(self.macros)
elif isinstance(macros, list):
macros = macros + (self.macros or [])
else:
@@ -441,14 +447,14 @@ class CCompiler:
fixed versions of all arguments.
"""
if libraries is None:
libraries = self.libraries
libraries = list(self.libraries)
elif isinstance(libraries, (list, tuple)):
libraries = list(libraries) + (self.libraries or [])
else:
raise TypeError("'libraries' (if supplied) must be a list of strings")
if library_dirs is None:
library_dirs = self.library_dirs
library_dirs = list(self.library_dirs)
elif isinstance(library_dirs, (list, tuple)):
library_dirs = list(library_dirs) + (self.library_dirs or [])
else:
@@ -458,14 +464,14 @@ class CCompiler:
library_dirs += self.__class__.library_dirs
if runtime_library_dirs is None:
runtime_library_dirs = self.runtime_library_dirs
runtime_library_dirs = list(self.runtime_library_dirs)
elif isinstance(runtime_library_dirs, (list, tuple)):
runtime_library_dirs = list(runtime_library_dirs) + (
self.runtime_library_dirs or []
)
else:
raise TypeError(
"'runtime_library_dirs' (if supplied) " "must be a list of strings"
"'runtime_library_dirs' (if supplied) must be a list of strings"
)
return (libraries, library_dirs, runtime_library_dirs)
@@ -532,7 +538,7 @@ class CCompiler:
output_dir=None,
macros=None,
include_dirs=None,
debug=0,
debug=False,
extra_preargs=None,
extra_postargs=None,
depends=None,
@@ -609,7 +615,7 @@ class CCompiler:
pass
def create_static_lib(
self, objects, output_libname, output_dir=None, debug=0, target_lang=None
self, objects, output_libname, output_dir=None, debug=False, target_lang=None
):
"""Link a bunch of stuff together to create a static library file.
The "bunch of stuff" consists of the list of object files supplied
@@ -650,7 +656,7 @@ class CCompiler:
library_dirs=None,
runtime_library_dirs=None,
export_symbols=None,
debug=0,
debug=False,
extra_preargs=None,
extra_postargs=None,
build_temp=None,
@@ -712,7 +718,7 @@ class CCompiler:
library_dirs=None,
runtime_library_dirs=None,
export_symbols=None,
debug=0,
debug=False,
extra_preargs=None,
extra_postargs=None,
build_temp=None,
@@ -743,7 +749,7 @@ class CCompiler:
library_dirs=None,
runtime_library_dirs=None,
export_symbols=None,
debug=0,
debug=False,
extra_preargs=None,
extra_postargs=None,
build_temp=None,
@@ -773,7 +779,7 @@ class CCompiler:
libraries=None,
library_dirs=None,
runtime_library_dirs=None,
debug=0,
debug=False,
extra_preargs=None,
extra_postargs=None,
target_lang=None,
@@ -857,10 +863,9 @@ class CCompiler:
if library_dirs is None:
library_dirs = []
fd, fname = tempfile.mkstemp(".c", funcname, text=True)
f = os.fdopen(fd, "w")
try:
with os.fdopen(fd, "w", encoding='utf-8') as f:
for incl in includes:
f.write("""#include "%s"\n""" % incl)
f.write(f"""#include "{incl}"\n""")
if not includes:
# Use "char func(void);" as the prototype to follow
# what autoconf does. This prototype does not match
@@ -870,25 +875,22 @@ class CCompiler:
# know the exact argument types, and the has_function
# interface does not provide that level of information.
f.write(
"""\
f"""\
#ifdef __cplusplus
extern "C"
#endif
char %s(void);
char {funcname}(void);
"""
% funcname
)
f.write(
"""\
int main (int argc, char **argv) {
%s();
f"""\
int main (int argc, char **argv) {{
{funcname}();
return 0;
}
}}
"""
% funcname
)
finally:
f.close()
try:
objects = self.compile([fname], include_dirs=include_dirs)
except CompileError:
@@ -911,7 +913,7 @@ int main (int argc, char **argv) {
os.remove(fn)
return True
def find_library_file(self, dirs, lib, debug=0):
def find_library_file(self, dirs, lib, debug=False):
"""Search the specified list of directories for a static or shared
library file 'lib' and return the full path to that file. If
'debug' true, look for a debugging version (if that makes sense on
@@ -954,7 +956,7 @@ int main (int argc, char **argv) {
# * exe_extension -
# extension for executable files, eg. '' or '.exe'
def object_filenames(self, source_filenames, strip_dir=0, output_dir=''):
def object_filenames(self, source_filenames, strip_dir=False, output_dir=''):
if output_dir is None:
output_dir = ''
return list(
@@ -972,9 +974,7 @@ int main (int argc, char **argv) {
try:
new_ext = self.out_extensions[ext]
except LookupError:
raise UnknownFileError(
"unknown file type '{}' (from '{}')".format(ext, src_name)
)
raise UnknownFileError(f"unknown file type '{ext}' (from '{src_name}')")
if strip_dir:
base = os.path.basename(base)
return os.path.join(output_dir, base + new_ext)
@@ -991,20 +991,24 @@ int main (int argc, char **argv) {
# If abs, chop off leading /
return no_drive[os.path.isabs(no_drive) :]
def shared_object_filename(self, basename, strip_dir=0, output_dir=''):
def shared_object_filename(self, basename, strip_dir=False, output_dir=''):
assert output_dir is not None
if strip_dir:
basename = os.path.basename(basename)
return os.path.join(output_dir, basename + self.shared_lib_extension)
def executable_filename(self, basename, strip_dir=0, output_dir=''):
def executable_filename(self, basename, strip_dir=False, output_dir=''):
assert output_dir is not None
if strip_dir:
basename = os.path.basename(basename)
return os.path.join(output_dir, basename + (self.exe_extension or ''))
def library_filename(
self, libname, lib_type='static', strip_dir=0, output_dir='' # or 'shared'
self,
libname,
lib_type='static',
strip_dir=False,
output_dir='', # or 'shared'
):
assert output_dir is not None
expected = '"static", "shared", "dylib", "xcode_stub"'
@@ -1032,7 +1036,7 @@ int main (int argc, char **argv) {
print(msg)
def warn(self, msg):
sys.stderr.write("warning: %s\n" % msg)
sys.stderr.write(f"warning: {msg}\n")
def execute(self, func, args, msg=None, level=1):
execute(func, args, msg, self.dry_run)
@@ -1056,6 +1060,7 @@ _default_compilers = (
# on a cygwin built python we can use gcc like an ordinary UNIXish
# compiler
('cygwin.*', 'unix'),
('zos', 'zos'),
# OS name mappings
('posix', 'unix'),
('nt', 'msvc'),
@@ -1076,6 +1081,10 @@ def get_default_compiler(osname=None, platform=None):
osname = os.name
if platform is None:
platform = sys.platform
# Mingw is a special case where sys.platform is 'win32' but we
# want to use the 'mingw32' compiler, so check it first
if is_mingw():
return 'mingw32'
for pattern, compiler in _default_compilers:
if (
re.match(pattern, platform) is not None
@@ -1103,6 +1112,7 @@ compiler_class = {
"Mingw32 port of GNU C Compiler for Win32",
),
'bcpp': ('bcppcompiler', 'BCPPCompiler', "Borland C++ Compiler"),
'zos': ('zosccompiler', 'zOSCCompiler', 'IBM XL C/C++ Compilers'),
}
@@ -1115,15 +1125,15 @@ def show_compilers():
# commands that use it.
from distutils.fancy_getopt import FancyGetopt
compilers = []
for compiler in compiler_class.keys():
compilers.append(("compiler=" + compiler, None, compiler_class[compiler][2]))
compilers.sort()
compilers = sorted(
("compiler=" + compiler, None, compiler_class[compiler][2])
for compiler in compiler_class.keys()
)
pretty_printer = FancyGetopt(compilers)
pretty_printer.print_help("List of available compilers:")
def new_compiler(plat=None, compiler=None, verbose=0, dry_run=0, force=0):
def new_compiler(plat=None, compiler=None, verbose=False, dry_run=False, force=False):
"""Generate an instance of some CCompiler subclass for the supplied
platform/compiler combination. 'plat' defaults to 'os.name'
(eg. 'posix', 'nt'), and 'compiler' defaults to the default compiler
@@ -1143,9 +1153,9 @@ def new_compiler(plat=None, compiler=None, verbose=0, dry_run=0, force=0):
(module_name, class_name, long_description) = compiler_class[compiler]
except KeyError:
msg = "don't know how to compile C/C++ code on platform '%s'" % plat
msg = f"don't know how to compile C/C++ code on platform '{plat}'"
if compiler is not None:
msg = msg + " with '%s' compiler" % compiler
msg = msg + f" with '{compiler}' compiler"
raise DistutilsPlatformError(msg)
try:
@@ -1155,12 +1165,12 @@ def new_compiler(plat=None, compiler=None, verbose=0, dry_run=0, force=0):
klass = vars(module)[class_name]
except ImportError:
raise DistutilsModuleError(
"can't compile C/C++ code: unable to load module '%s'" % module_name
f"can't compile C/C++ code: unable to load module '{module_name}'"
)
except KeyError:
raise DistutilsModuleError(
"can't compile C/C++ code: unable to find class '%s' "
"in module '%s'" % (class_name, module_name)
f"can't compile C/C++ code: unable to find class '{class_name}' "
f"in module '{module_name}'"
)
# XXX The None is necessary to preserve backwards compatibility
@@ -1194,23 +1204,22 @@ def gen_preprocess_options(macros, include_dirs):
for macro in macros:
if not (isinstance(macro, tuple) and 1 <= len(macro) <= 2):
raise TypeError(
"bad macro definition '%s': "
"each element of 'macros' list must be a 1- or 2-tuple" % macro
f"bad macro definition '{macro}': "
"each element of 'macros' list must be a 1- or 2-tuple"
)
if len(macro) == 1: # undefine this macro
pp_opts.append("-U%s" % macro[0])
pp_opts.append(f"-U{macro[0]}")
elif len(macro) == 2:
if macro[1] is None: # define with no explicit value
pp_opts.append("-D%s" % macro[0])
pp_opts.append(f"-D{macro[0]}")
else:
# XXX *don't* need to be clever about quoting the
# macro value here, because we're going to avoid the
# shell at all costs when we spawn the command!
pp_opts.append("-D%s=%s" % macro)
pp_opts.append("-D{}={}".format(*macro))
for dir in include_dirs:
pp_opts.append("-I%s" % dir)
pp_opts.extend(f"-I{dir}" for dir in include_dirs)
return pp_opts
@@ -1221,17 +1230,10 @@ def gen_lib_options(compiler, library_dirs, runtime_library_dirs, libraries):
directories. Returns a list of command-line options suitable for use
with some compiler (depending on the two format strings passed in).
"""
lib_opts = []
for dir in library_dirs:
lib_opts.append(compiler.library_dir_option(dir))
lib_opts = [compiler.library_dir_option(dir) for dir in library_dirs]
for dir in runtime_library_dirs:
opt = compiler.runtime_library_dir_option(dir)
if isinstance(opt, list):
lib_opts = lib_opts + opt
else:
lib_opts.append(opt)
lib_opts.extend(always_iterable(compiler.runtime_library_dir_option(dir)))
# XXX it's important that we *not* remove redundant library mentions!
# sometimes you really do have to say "-lfoo -lbar -lfoo" in order to
@@ -1247,7 +1249,7 @@ def gen_lib_options(compiler, library_dirs, runtime_library_dirs, libraries):
lib_opts.append(lib_file)
else:
compiler.warn(
"no library file corresponding to " "'%s' found (skipping)" % lib
f"no library file corresponding to '{lib}' found (skipping)"
)
else:
lib_opts.append(compiler.library_option(lib))

View File

@@ -4,14 +4,14 @@ Provides the Command class, the base class for the command classes
in the distutils.command package.
"""
import sys
import logging
import os
import re
import logging
import sys
from .errors import DistutilsOptionError
from . import util, dir_util, file_util, archive_util, dep_util
from . import _modified, archive_util, dir_util, file_util, util
from ._log import log
from .errors import DistutilsOptionError
class Command:
@@ -87,13 +87,13 @@ class Command:
# The 'help' flag is just used for command-line parsing, so
# none of that complicated bureaucracy is needed.
self.help = 0
self.help = False
# 'finalized' records whether or not 'finalize_options()' has been
# called. 'finalize_options()' itself should not pay attention to
# this flag: it is the business of 'ensure_finalized()', which
# always calls 'finalize_options()', to respect/update it.
self.finalized = 0
self.finalized = False
# XXX A more explicit way to customize dry_run would be better.
def __getattr__(self, attr):
@@ -109,7 +109,7 @@ class Command:
def ensure_finalized(self):
if not self.finalized:
self.finalize_options()
self.finalized = 1
self.finalized = True
# Subclasses must define:
# initialize_options()
@@ -135,7 +135,7 @@ class Command:
This method must be implemented by all command classes.
"""
raise RuntimeError(
"abstract method -- subclass %s must override" % self.__class__
f"abstract method -- subclass {self.__class__} must override"
)
def finalize_options(self):
@@ -150,14 +150,14 @@ class Command:
This method must be implemented by all command classes.
"""
raise RuntimeError(
"abstract method -- subclass %s must override" % self.__class__
f"abstract method -- subclass {self.__class__} must override"
)
def dump_options(self, header=None, indent=""):
from distutils.fancy_getopt import longopt_xlate
if header is None:
header = "command options for '%s':" % self.get_command_name()
header = f"command options for '{self.get_command_name()}':"
self.announce(indent + header, level=logging.INFO)
indent = indent + " "
for option, _, _ in self.user_options:
@@ -165,7 +165,7 @@ class Command:
if option[-1] == "=":
option = option[:-1]
value = getattr(self, option)
self.announce(indent + "{} = {}".format(option, value), level=logging.INFO)
self.announce(indent + f"{option} = {value}", level=logging.INFO)
def run(self):
"""A command's raison d'etre: carry out the action it exists to
@@ -178,7 +178,7 @@ class Command:
This method must be implemented by all command classes.
"""
raise RuntimeError(
"abstract method -- subclass %s must override" % self.__class__
f"abstract method -- subclass {self.__class__} must override"
)
def announce(self, msg, level=logging.DEBUG):
@@ -213,9 +213,7 @@ class Command:
setattr(self, option, default)
return default
elif not isinstance(val, str):
raise DistutilsOptionError(
"'{}' must be a {} (got `{}`)".format(option, what, val)
)
raise DistutilsOptionError(f"'{option}' must be a {what} (got `{val}`)")
return val
def ensure_string(self, option, default=None):
@@ -242,7 +240,7 @@ class Command:
ok = False
if not ok:
raise DistutilsOptionError(
"'{}' must be a list of strings (got {!r})".format(option, val)
f"'{option}' must be a list of strings (got {val!r})"
)
def _ensure_tested_string(self, option, tester, what, error_fmt, default=None):
@@ -295,7 +293,7 @@ class Command:
if getattr(self, dst_option) is None:
setattr(self, dst_option, getattr(src_cmd_obj, src_option))
def get_finalized_command(self, command, create=1):
def get_finalized_command(self, command, create=True):
"""Wrapper around Distribution's 'get_command_obj()' method: find
(create if necessary and 'create' is true) the command object for
'command', call its 'ensure_finalized()' method, and return the
@@ -307,7 +305,7 @@ class Command:
# XXX rename to 'get_reinitialized_command()'? (should do the
# same in dist.py, if so)
def reinitialize_command(self, command, reinit_subcommands=0):
def reinitialize_command(self, command, reinit_subcommands=False):
return self.distribution.reinitialize_command(command, reinit_subcommands)
def run_command(self, command):
@@ -342,7 +340,13 @@ class Command:
dir_util.mkpath(name, mode, dry_run=self.dry_run)
def copy_file(
self, infile, outfile, preserve_mode=1, preserve_times=1, link=None, level=1
self,
infile,
outfile,
preserve_mode=True,
preserve_times=True,
link=None,
level=1,
):
"""Copy a file respecting verbose, dry-run and force flags. (The
former two default to whatever is in the Distribution object, and
@@ -361,9 +365,9 @@ class Command:
self,
infile,
outfile,
preserve_mode=1,
preserve_times=1,
preserve_symlinks=0,
preserve_mode=True,
preserve_times=True,
preserve_symlinks=False,
level=1,
):
"""Copy an entire directory tree respecting verbose, dry-run,
@@ -383,7 +387,7 @@ class Command:
"""Move a file respecting dry-run flag."""
return file_util.move_file(src, dst, dry_run=self.dry_run)
def spawn(self, cmd, search_path=1, level=1):
def spawn(self, cmd, search_path=True, level=1):
"""Spawn an external command respecting dry-run flag."""
from distutils.spawn import spawn
@@ -414,7 +418,7 @@ class Command:
timestamp checks.
"""
if skip_msg is None:
skip_msg = "skipping %s (inputs unchanged)" % outfile
skip_msg = f"skipping {outfile} (inputs unchanged)"
# Allow 'infiles' to be a single string
if isinstance(infiles, str):
@@ -428,7 +432,7 @@ class Command:
# If 'outfile' must be regenerated (either because it doesn't
# exist, is out-of-date, or the 'force' flag is true) then
# perform the action that presumably regenerates it
if self.force or dep_util.newer_group(infiles, outfile):
if self.force or _modified.newer_group(infiles, outfile):
self.execute(func, args, exec_msg, level)
# Otherwise, print the "skip" message
else:

View File

@@ -3,7 +3,7 @@
Package containing implementation of all the standard Distutils
commands."""
__all__ = [ # noqa: F822
__all__ = [
'build',
'build_py',
'build_ext',
@@ -16,10 +16,8 @@ __all__ = [ # noqa: F822
'install_scripts',
'install_data',
'sdist',
'register',
'bdist',
'bdist_dumb',
'bdist_rpm',
'check',
'upload',
]

View File

@@ -2,15 +2,14 @@
Backward compatibility for homebrew builds on macOS.
"""
import sys
import os
import functools
import os
import subprocess
import sys
import sysconfig
@functools.lru_cache()
@functools.lru_cache
def enabled():
"""
Only enabled for Python 3.9 framework homebrew builds
@@ -38,7 +37,7 @@ schemes = dict(
)
@functools.lru_cache()
@functools.lru_cache
def vars():
if not enabled():
return {}

View File

@@ -7,7 +7,7 @@ import os
import warnings
from ..core import Command
from ..errors import DistutilsPlatformError, DistutilsOptionError
from ..errors import DistutilsOptionError, DistutilsPlatformError
from ..util import get_platform
@@ -15,9 +15,10 @@ def show_formats():
"""Print list of available formats (arguments to "--format" option)."""
from ..fancy_getopt import FancyGetopt
formats = []
for format in bdist.format_commands:
formats.append(("formats=" + format, None, bdist.format_commands[format][1]))
formats = [
("formats=" + format, None, bdist.format_commands[format][1])
for format in bdist.format_commands
]
pretty_printer = FancyGetopt(formats)
pretty_printer.print_help("List of available distribution formats:")
@@ -41,24 +42,24 @@ class bdist(Command):
'plat-name=',
'p',
"platform name to embed in generated filenames "
"(default: %s)" % get_platform(),
f"[default: {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]",
),
]
@@ -76,17 +77,15 @@ class bdist(Command):
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
@@ -96,7 +95,7 @@ class bdist(Command):
self.plat_name = None
self.formats = None
self.dist_dir = None
self.skip_build = 0
self.skip_build = False
self.group = None
self.owner = None
@@ -122,7 +121,7 @@ class bdist(Command):
except KeyError:
raise DistutilsPlatformError(
"don't know how to create built distributions "
"on platform %s" % os.name
f"on platform {os.name}"
)
if self.dist_dir is None:
@@ -135,7 +134,7 @@ class bdist(Command):
try:
commands.append(self.format_commands[format][0])
except KeyError:
raise DistutilsOptionError("invalid format '%s'" % format)
raise DistutilsOptionError(f"invalid format '{format}'")
# Reinitialize and run each command.
for i in range(len(self.formats)):
@@ -152,5 +151,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 = 1
sub_cmd.keep_temp = True
self.run_command(cmd_name)

View File

@@ -5,12 +5,13 @@ distribution -- i.e., just an archive to be unpacked under $prefix or
$exec_prefix)."""
import os
from distutils._log import log
from ..core import Command
from ..util import get_platform
from ..dir_util import remove_tree, ensure_relative
from ..dir_util import ensure_relative, remove_tree
from ..errors import DistutilsPlatformError
from ..sysconfig import get_python_version
from distutils._log import log
from ..util import get_platform
class bdist_dumb(Command):
@@ -22,35 +23,34 @@ class bdist_dumb(Command):
'plat-name=',
'p',
"platform name to embed in generated filenames "
"(default: %s)" % get_platform(),
f"[default: {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]",
),
]
@@ -62,10 +62,10 @@ class bdist_dumb(Command):
self.bdist_dir = None
self.plat_name = None
self.format = None
self.keep_temp = 0
self.keep_temp = False
self.dist_dir = None
self.skip_build = None
self.relative = 0
self.relative = False
self.owner = None
self.group = None
@@ -80,7 +80,7 @@ class bdist_dumb(Command):
except KeyError:
raise DistutilsPlatformError(
"don't know how to create dumb built distributions "
"on platform %s" % os.name
f"on platform {os.name}"
)
self.set_undefined_options(
@@ -94,19 +94,17 @@ class bdist_dumb(Command):
if not self.skip_build:
self.run_command('build')
install = self.reinitialize_command('install', reinit_subcommands=1)
install = self.reinitialize_command('install', reinit_subcommands=True)
install.root = self.bdist_dir
install.skip_build = self.skip_build
install.warn_dir = 0
install.warn_dir = False
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 = "{}.{}".format(
self.distribution.get_fullname(), self.plat_name
)
archive_basename = f"{self.distribution.get_fullname()}.{self.plat_name}"
pseudoinstall_root = os.path.join(self.dist_dir, archive_basename)
if not self.relative:
@@ -117,8 +115,7 @@ class bdist_dumb(Command):
):
raise DistutilsPlatformError(
"can't make a dumb built distribution where "
"base and platbase are different (%s, %s)"
% (repr(install.install_base), repr(install.install_platbase))
f"base and platbase are different ({install.install_base!r}, {install.install_platbase!r})"
)
else:
archive_root = os.path.join(

View File

@@ -3,21 +3,21 @@
Implements the Distutils 'bdist_rpm' command (create RPM source and binary
distributions)."""
import os
import subprocess
import sys
import os
from distutils._log import log
from ..core import Command
from ..debug import DEBUG
from ..file_util import write_file
from ..errors import (
DistutilsExecError,
DistutilsFileError,
DistutilsOptionError,
DistutilsPlatformError,
DistutilsFileError,
DistutilsExecError,
)
from ..file_util import write_file
from ..sysconfig import get_python_version
from distutils._log import log
class bdist_rpm(Command):
@@ -34,13 +34,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',
@@ -75,7 +75,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"),
@@ -187,13 +187,13 @@ class bdist_rpm(Command):
self.build_requires = None
self.obsoletes = None
self.keep_temp = 0
self.use_rpm_opt_flags = 1
self.rpm3_mode = 1
self.no_autoreq = 0
self.keep_temp = False
self.use_rpm_opt_flags = True
self.rpm3_mode = True
self.no_autoreq = False
self.force_arch = None
self.quiet = 0
self.quiet = False
def finalize_options(self):
self.set_undefined_options('bdist', ('bdist_base', 'bdist_base'))
@@ -214,7 +214,7 @@ class bdist_rpm(Command):
if os.name != 'posix':
raise DistutilsPlatformError(
"don't know how to create RPM " "distributions on platform %s" % os.name
f"don't know how to create RPM distributions on platform {os.name}"
)
if self.binary_only and self.source_only:
raise DistutilsOptionError(
@@ -223,7 +223,7 @@ class bdist_rpm(Command):
# don't pass CFLAGS to pure python distributions
if not self.distribution.has_ext_modules():
self.use_rpm_opt_flags = 0
self.use_rpm_opt_flags = False
self.set_undefined_options('bdist', ('dist_dir', 'dist_dir'))
self.finalize_package_data()
@@ -232,8 +232,7 @@ class bdist_rpm(Command):
self.ensure_string('group', "Development/Libraries")
self.ensure_string(
'vendor',
"%s <%s>"
% (self.distribution.get_contact(), self.distribution.get_contact_email()),
f"{self.distribution.get_contact()} <{self.distribution.get_contact_email()}>",
)
self.ensure_string('packager')
self.ensure_string_list('doc_files')
@@ -296,9 +295,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, "%s.spec" % self.distribution.get_name())
spec_path = os.path.join(spec_dir, f"{self.distribution.get_name()}.spec")
self.execute(
write_file, (spec_path, self._make_spec_file()), "writing '%s'" % spec_path
write_file, (spec_path, self._make_spec_file()), f"writing '{spec_path}'"
)
if self.spec_only: # stop if requested
@@ -323,7 +322,7 @@ class bdist_rpm(Command):
if os.path.exists(self.icon):
self.copy_file(self.icon, source_dir)
else:
raise DistutilsFileError("icon file '%s' does not exist" % self.icon)
raise DistutilsFileError(f"icon file '{self.icon}' does not exist")
# build package
log.info("building RPMs")
@@ -335,9 +334,9 @@ class bdist_rpm(Command):
rpm_cmd.append('-bb')
else:
rpm_cmd.append('-ba')
rpm_cmd.extend(['--define', '__python %s' % self.python])
rpm_cmd.extend(['--define', f'__python {self.python}'])
if self.rpm3_mode:
rpm_cmd.extend(['--define', '_topdir %s' % os.path.abspath(self.rpm_base)])
rpm_cmd.extend(['--define', f'_topdir {os.path.abspath(self.rpm_base)}'])
if not self.keep_temp:
rpm_cmd.append('--clean')
@@ -352,11 +351,7 @@ 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 = r"rpm -q --qf '{} {}\n' --specfile '{}'".format(
src_rpm,
non_src_rpm,
spec_path,
)
q_cmd = rf"rpm -q --qf '{src_rpm} {non_src_rpm}\n' --specfile '{spec_path}'"
out = os.popen(q_cmd)
try:
@@ -375,7 +370,7 @@ class bdist_rpm(Command):
status = out.close()
if status:
raise DistutilsExecError("Failed to execute: %s" % repr(q_cmd))
raise DistutilsExecError(f"Failed to execute: {q_cmd!r}")
finally:
out.close()
@@ -401,9 +396,11 @@ 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))
@@ -428,14 +425,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(
[' %s \\' % line.strip() for line in vendor_hook.splitlines()]
)
vendor_hook = '\n'.join([
f' {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 http://bugs.python.org/issue14443')
spec_file.append('# Workaround for https://bugs.python.org/issue14443')
spec_file.append('%define __os_install_post ' + fixed_hook + '\n')
# put locale summaries into spec file
@@ -445,13 +442,11 @@ 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
@@ -461,21 +456,19 @@ 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('BuildArch: %s' % self.force_arch)
spec_file.append(f'BuildArch: {self.force_arch}')
for field in (
'Vendor',
@@ -489,7 +482,7 @@ class bdist_rpm(Command):
if isinstance(val, list):
spec_file.append('{}: {}'.format(field, ' '.join(val)))
elif val is not None:
spec_file.append('{}: {}'.format(field, val))
spec_file.append(f'{field}: {val}')
if self.distribution.get_url():
spec_file.append('Url: ' + self.distribution.get_url())
@@ -506,13 +499,11 @@ 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
@@ -526,8 +517,8 @@ class bdist_rpm(Command):
# rpm scripts
# figure out default build script
def_setup_call = "{} {}".format(self.python, os.path.basename(sys.argv[0]))
def_build = "%s build" % def_setup_call
def_setup_call = f"{self.python} {os.path.basename(sys.argv[0])}"
def_build = f"{def_setup_call} build"
if self.use_rpm_opt_flags:
def_build = 'env CFLAGS="$RPM_OPT_FLAGS" ' + def_build
@@ -537,9 +528,7 @@ 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 = (
'%s install -O1 --root=$RPM_BUILD_ROOT ' '--record=INSTALLED_FILES'
) % def_setup_call
install_cmd = f'{def_setup_call} install -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES'
script_options = [
('prep', 'prep_script', "%setup -n %{name}-%{unmangled_version}"),
@@ -558,12 +547,10 @@ class bdist_rpm(Command):
# 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'))
@@ -571,24 +558,20 @@ 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

View File

@@ -2,8 +2,10 @@
Implements the Distutils 'build' command."""
import sys
import os
import sys
import sysconfig
from ..core import Command
from ..errors import DistutilsOptionError
from ..util import get_platform
@@ -25,16 +27,14 @@ 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',
"platform name to build for, if supported "
"(default: %s)" % get_platform(),
f"platform name to build for, if supported [default: {get_platform()}]",
),
('compiler=', 'c', "specify the compiler type"),
('parallel=', 'j', "number of parallel build jobs"),
@@ -61,7 +61,7 @@ class build(Command):
self.compiler = None
self.plat_name = None
self.debug = None
self.force = 0
self.force = False
self.executable = None
self.parallel = None
@@ -78,7 +78,11 @@ class build(Command):
"using './configure --help' on your platform)"
)
plat_specifier = ".{}-{}".format(self.plat_name, sys.implementation.cache_tag)
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'
# 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

View File

@@ -15,10 +15,11 @@ module."""
# cut 'n paste. Sigh.
import os
from distutils._log import log
from ..core import Command
from ..errors import DistutilsSetupError
from ..sysconfig import customize_compiler
from distutils._log import log
def show_compilers():
@@ -56,7 +57,7 @@ class build_clib(Command):
self.define = None
self.undef = None
self.debug = None
self.force = 0
self.force = False
self.compiler = None
def finalize_options(self):
@@ -137,8 +138,8 @@ class build_clib(Command):
if '/' in name or (os.sep != '/' and os.sep in name):
raise DistutilsSetupError(
"bad library name '%s': "
"may not contain directory separators" % lib[0]
f"bad library name '{lib[0]}': "
"may not contain directory separators"
)
if not isinstance(build_info, dict):
@@ -154,7 +155,7 @@ 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
@@ -165,9 +166,9 @@ class build_clib(Command):
sources = build_info.get('sources')
if sources is None or not isinstance(sources, (list, tuple)):
raise DistutilsSetupError(
"in 'libraries' option (library '%s'), "
f"in 'libraries' option (library '{lib_name}'), "
"'sources' must be present and must be "
"a list of source filenames" % lib_name
"a list of source filenames"
)
filenames.extend(sources)
@@ -178,9 +179,9 @@ class build_clib(Command):
sources = build_info.get('sources')
if sources is None or not isinstance(sources, (list, tuple)):
raise DistutilsSetupError(
"in 'libraries' option (library '%s'), "
f"in 'libraries' option (library '{lib_name}'), "
"'sources' must be present and must be "
"a list of source filenames" % lib_name
"a list of source filenames"
)
sources = list(sources)

View File

@@ -8,24 +8,22 @@ import contextlib
import os
import re
import sys
from distutils._log import log
from site import USER_BASE
from .._modified import newer_group
from ..core import Command
from ..errors import (
DistutilsOptionError,
DistutilsSetupError,
CCompilerError,
DistutilsError,
CompileError,
DistutilsError,
DistutilsOptionError,
DistutilsPlatformError,
DistutilsSetupError,
)
from ..sysconfig import customize_compiler, get_python_version
from ..sysconfig import get_config_h_filename
from ..dep_util import newer_group
from ..extension import Extension
from ..util import get_platform
from distutils._log import log
from . import py37compat
from site import USER_BASE
from ..sysconfig import customize_compiler, get_config_h_filename, get_python_version
from ..util import get_platform, is_mingw
# An extension name is just a dot-separated list of Python NAMEs (ie.
# the same as a fully-qualified module name).
@@ -59,7 +57,7 @@ class build_ext(Command):
# takes care of both command-line and client options
# in between initialize_options() and finalize_options())
sep_by = " (separated by '%s')" % os.pathsep
sep_by = f" (separated by '{os.pathsep}')"
user_options = [
('build-lib=', 'b', "directory for compiled extension modules"),
('build-temp=', 't', "directory for temporary files (build by-products)"),
@@ -67,13 +65,13 @@ class build_ext(Command):
'plat-name=',
'p',
"platform name to cross-compile for, if supported "
"(default: %s)" % get_platform(),
f"[default: {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=',
@@ -111,7 +109,7 @@ class build_ext(Command):
self.build_lib = None
self.plat_name = None
self.build_temp = None
self.inplace = 0
self.inplace = False
self.package = None
self.include_dirs = None
@@ -130,6 +128,31 @@ 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): # noqa: C901
from distutils import sysconfig
@@ -152,7 +175,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=1)
plat_py_include = sysconfig.get_python_inc(plat_specific=True)
if self.include_dirs is None:
self.include_dirs = self.distribution.include_dirs or []
if isinstance(self.include_dirs, str):
@@ -189,7 +212,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':
if os.name == 'nt' and not is_mingw():
# 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.
@@ -231,16 +254,7 @@ class build_ext(Command):
# building python standard extensions
self.library_dirs.append('.')
# 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('.')
self.library_dirs.extend(self._python_lib_dir(sysconfig))
# 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
@@ -412,9 +426,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.warning("'def_file' element of build info dict no longer supported")
# Non-trivial stuff: 'macros' split into 'define_macros'
# and 'undef_macros'.
@@ -453,10 +465,7 @@ 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.
outputs = []
for ext in self.extensions:
outputs.append(self.get_ext_fullpath(ext.name))
return outputs
return [self.get_ext_fullpath(ext.name) for ext in self.extensions]
def build_extensions(self):
# First, sanity-check the 'extensions' list
@@ -499,15 +508,15 @@ class build_ext(Command):
except (CCompilerError, DistutilsError, CompileError) as e:
if not ext.optional:
raise
self.warn('building extension "{}" failed: {}'.format(ext.name, e))
self.warn(f'building extension "{ext.name}" failed: {e}')
def build_extension(self, ext):
sources = ext.sources
if sources is None or not isinstance(sources, (list, tuple)):
raise DistutilsSetupError(
"in 'ext_modules' option (extension '%s'), "
f"in 'ext_modules' option (extension '{ext.name}'), "
"'sources' must be present and must be "
"a list of source filenames" % ext.name
"a list of source filenames"
)
# sort to make the resulting .so file build reproducible
sources = sorted(sources)
@@ -629,8 +638,7 @@ class build_ext(Command):
# Do not override commandline arguments
if not self.swig_opts:
for o in extension.swig_opts:
swig_cmd.append(o)
swig_cmd.extend(extension.swig_opts)
for source in swig_sources:
target = swig_targets[source]
@@ -651,7 +659,7 @@ 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("c:\\swig%s" % vers, "swig.exe")
fn = os.path.join(f"c:\\swig{vers}", "swig.exe")
if os.path.isfile(fn):
return fn
else:
@@ -659,7 +667,7 @@ class build_ext(Command):
else:
raise DistutilsPlatformError(
"I don't know how to find (much less run) SWIG "
"on platform '%s'" % os.name
f"on platform '{os.name}'"
)
# -- Name generators -----------------------------------------------
@@ -742,7 +750,7 @@ 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":
if sys.platform == "win32" and not is_mingw():
from .._msvccompiler import MSVCCompiler
if not isinstance(self.compiler, MSVCCompiler):
@@ -772,7 +780,7 @@ class build_ext(Command):
# A native build on an Android device or on Cygwin
if hasattr(sys, 'getandroidapilevel'):
link_libpython = True
elif sys.platform == 'cygwin':
elif sys.platform == 'cygwin' or is_mingw():
link_libpython = True
elif '_PYTHON_HOST_PLATFORM' in os.environ:
# We are cross-compiling for one of the relevant platforms
@@ -785,4 +793,4 @@ class build_ext(Command):
ldversion = get_config_var('LDVERSION')
return ext.libraries + ['python' + ldversion]
return ext.libraries + py37compat.pythonlib()
return ext.libraries

View File

@@ -2,15 +2,15 @@
Implements the Distutils 'build_py' command."""
import os
import importlib.util
import sys
import glob
import importlib.util
import os
import sys
from distutils._log import log
from ..core import Command
from ..errors import DistutilsOptionError, DistutilsFileError
from ..errors import DistutilsFileError, DistutilsOptionError
from ..util import convert_path
from distutils._log import log
class build_py(Command):
@@ -38,7 +38,7 @@ class build_py(Command):
self.package = None
self.package_data = None
self.package_dir = None
self.compile = 0
self.compile = False
self.optimize = 0
self.force = None
@@ -95,7 +95,7 @@ class build_py(Command):
self.build_packages()
self.build_package_data()
self.byte_compile(self.get_outputs(include_bytecode=0))
self.byte_compile(self.get_outputs(include_bytecode=False))
def get_data_files(self):
"""Generate list of '(package,src_dir,build_dir,filenames)' tuples"""
@@ -129,14 +129,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):
"""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))
@@ -191,12 +191,12 @@ class build_py(Command):
if package_dir != "":
if not os.path.exists(package_dir):
raise DistutilsFileError(
"package directory '%s' does not exist" % package_dir
f"package directory '{package_dir}' does not exist"
)
if not os.path.isdir(package_dir):
raise DistutilsFileError(
"supposed package directory '%s' exists, "
"but is not a directory" % package_dir
f"supposed package directory '{package_dir}' exists, "
"but is not a directory"
)
# Directories without __init__.py are namespace packages (PEP 420).
@@ -228,7 +228,7 @@ class build_py(Command):
module = os.path.splitext(os.path.basename(f))[0]
modules.append((package, module, f))
else:
self.debug_print("excluding %s" % setup_script)
self.debug_print(f"excluding {setup_script}")
return modules
def find_modules(self):
@@ -264,7 +264,7 @@ class build_py(Command):
(package_dir, checked) = packages[package]
except KeyError:
package_dir = self.get_package_dir(package)
checked = 0
checked = False
if not checked:
init_py = self.check_package(package, package_dir)
@@ -306,10 +306,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=1):
def get_outputs(self, include_bytecode=True):
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)
@@ -347,7 +347,7 @@ 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=0)
return self.copy_file(module_file, outfile, preserve_mode=False)
def build_modules(self):
modules = self.find_modules()

View File

@@ -4,13 +4,14 @@ Implements the Distutils 'build_scripts' command."""
import os
import re
from stat import ST_MODE
from distutils import sysconfig
from ..core import Command
from ..dep_util import newer
from ..util import convert_path
from distutils._log import log
import tokenize
from distutils import sysconfig
from distutils._log import log
from stat import ST_MODE
from .._modified import newer
from ..core import Command
from ..util import convert_path
shebang_pattern = re.compile('^#!.*python[0-9.]*([ \t].*)?$')
"""
@@ -95,7 +96,7 @@ class build_scripts(Command):
else:
first_line = f.readline()
if not first_line:
self.warn("%s is an empty file (skipping)" % script)
self.warn(f"{script} is an empty file (skipping)")
return
shebang_match = shebang_pattern.match(first_line)
@@ -109,8 +110,7 @@ class build_scripts(Command):
else:
executable = os.path.join(
sysconfig.get_config_var("BINDIR"),
"python%s%s"
% (
"python{}{}".format(
sysconfig.get_config_var("VERSION"),
sysconfig.get_config_var("EXE"),
),
@@ -156,9 +156,7 @@ class build_scripts(Command):
try:
shebang.encode('utf-8')
except UnicodeEncodeError:
raise ValueError(
"The shebang ({!r}) is not encodable " "to utf-8".format(shebang)
)
raise ValueError(f"The shebang ({shebang!r}) is not encodable to utf-8")
# If the script is encoded to a custom encoding (use a
# #coding:xxx cookie), the shebang has to be encodable to
@@ -167,6 +165,6 @@ class build_scripts(Command):
shebang.encode(encoding)
except UnicodeEncodeError:
raise ValueError(
"The shebang ({!r}) is not encodable "
"to the script encoding ({})".format(shebang, encoding)
f"The shebang ({shebang!r}) is not encodable "
f"to the script encoding ({encoding})"
)

View File

@@ -2,16 +2,17 @@
Implements the Distutils 'check' command.
"""
import contextlib
from ..core import Command
from ..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__(
@@ -20,7 +21,7 @@ with contextlib.suppress(ImportError):
report_level,
halt_level,
stream=None,
debug=0,
debug=False,
encoding='ascii',
error_handler='replace',
):
@@ -32,7 +33,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, level=level, type=self.levels[level], *children, **kwargs
message, *children, level=level, type=self.levels[level], **kwargs
)
@@ -57,9 +58,9 @@ class check(Command):
def initialize_options(self):
"""Sets default values for options."""
self.restructuredtext = 0
self.restructuredtext = False
self.metadata = 1
self.strict = 0
self.strict = False
self._warnings = 0
def finalize_options(self):
@@ -99,13 +100,12 @@ class check(Command):
"""
metadata = self.distribution.metadata
missing = []
for attr in 'name', 'version':
if not getattr(metadata, attr, None):
missing.append(attr)
missing = [
attr for attr in ('name', 'version') if not getattr(metadata, attr, None)
]
if missing:
self.warn("missing required meta-data: %s" % ', '.join(missing))
self.warn("missing required meta-data: {}".format(', '.join(missing)))
def check_restructuredtext(self):
"""Checks if the long string fields are reST-compliant."""
@@ -115,7 +115,7 @@ class check(Command):
if line is None:
warning = warning[1]
else:
warning = '{} (line {})'.format(warning[1], line)
warning = f'{warning[1]} (line {line})'
self.warn(warning)
def _check_rst_data(self, data):
@@ -144,8 +144,11 @@ class check(Command):
try:
parser.parse(data, document)
except AttributeError as e:
reporter.messages.append(
(-1, 'Could not finish the parsing: %s.' % e, '', {})
)
reporter.messages.append((
-1,
f'Could not finish the parsing: {e}.',
'',
{},
))
return reporter.messages

View File

@@ -5,25 +5,26 @@ 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 ..core import Command
from ..dir_util import remove_tree
from distutils._log 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"),

View File

@@ -9,13 +9,17 @@ 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 ..core import Command
from ..errors import DistutilsExecError
from ..sysconfig import customize_compiler
from distutils._log import log
LANG_EXT = {"c": ".c", "c++": ".cxx"}
@@ -90,7 +94,7 @@ class config(Command):
if not isinstance(self.compiler, CCompiler):
self.compiler = new_compiler(
compiler=self.compiler, dry_run=self.dry_run, force=1
compiler=self.compiler, dry_run=self.dry_run, force=True
)
customize_compiler(self.compiler)
if self.include_dirs:
@@ -102,10 +106,10 @@ class config(Command):
def _gen_temp_sourcefile(self, body, headers, lang):
filename = "_configtest" + LANG_EXT[lang]
with open(filename, "w") as file:
with open(filename, "w", encoding='utf-8') as file:
if headers:
for header in headers:
file.write("#include <%s>\n" % header)
file.write(f"#include <{header}>\n")
file.write("\n")
file.write(body)
if body[-1] != "\n":
@@ -122,7 +126,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, "compiling '%s':" % src)
dump_file(src, f"compiling '{src}':")
(obj,) = self.compiler.object_filenames([src])
self.temp_files.extend([src, obj])
self.compiler.compile([src], include_dirs=include_dirs)
@@ -199,15 +203,8 @@ class config(Command):
if isinstance(pattern, str):
pattern = re.compile(pattern)
with open(out) as file:
match = False
while True:
line = file.readline()
if line == '':
break
if pattern.search(line):
match = True
break
with open(out, encoding='utf-8') as file:
match = any(pattern.search(line) for line in file)
self._clean()
return match
@@ -295,8 +292,8 @@ class config(Command):
include_dirs=None,
libraries=None,
library_dirs=None,
decl=0,
call=0,
decl=False,
call=False,
):
"""Determine if function 'func' is available by constructing a
source file that refers to 'func', and compiles and links it.
@@ -314,12 +311,12 @@ class config(Command):
self._check_compiler()
body = []
if decl:
body.append("int %s ();" % func)
body.append(f"int {func} ();")
body.append("int main () {")
if call:
body.append(" %s();" % func)
body.append(f" {func}();")
else:
body.append(" %s;" % func)
body.append(f" {func};")
body.append("}")
body = "\n".join(body) + "\n"
@@ -331,7 +328,7 @@ class config(Command):
library_dirs=None,
headers=None,
include_dirs=None,
other_libraries=[],
other_libraries: Sequence[str] = [],
):
"""Determine if 'library' is available to be linked against,
without actually checking that any particular symbols are provided
@@ -346,7 +343,7 @@ class config(Command):
"int main (void) { }",
headers,
include_dirs,
[library] + other_libraries,
[library] + list(other_libraries),
library_dirs,
)
@@ -369,8 +366,4 @@ def dump_file(filename, head=None):
log.info('%s', filename)
else:
log.info(head)
file = open(filename)
try:
log.info(file.read())
finally:
file.close()
log.info(pathlib.Path(filename).read_text(encoding='utf-8'))

View File

@@ -2,25 +2,23 @@
Implements the Distutils 'install' command."""
import sys
import os
import contextlib
import sysconfig
import itertools
import os
import sys
import sysconfig
from distutils._log import log
from site import USER_BASE, USER_SITE
import jaraco.collections
from ..core import Command
from ..debug import DEBUG
from ..sysconfig import get_config_vars
from ..file_util import write_file
from ..util import convert_path, subst_vars, change_root
from ..util import get_platform
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 . import _framework_compat as fw
from .. import _collections
from site import USER_BASE
from site import USER_SITE
HAS_USER_SITE = True
@@ -196,8 +194,7 @@ 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
@@ -214,8 +211,7 @@ 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"),
@@ -245,9 +241,11 @@ class install(Command):
boolean_options = ['compile', 'force', 'skip-build']
if HAS_USER_SITE:
user_options.append(
('user', None, "install in user site-package '%s'" % USER_SITE)
)
user_options.append((
'user',
None,
f"install in user site-package '{USER_SITE}'",
))
boolean_options.append('user')
negative_opt = {'no-compile': 'compile'}
@@ -259,7 +257,7 @@ class install(Command):
self.prefix = None
self.exec_prefix = None
self.home = None
self.user = 0
self.user = False
# These select only the installation base; it's up to the user to
# specify the installation scheme (currently, that means supplying
@@ -294,7 +292,7 @@ class install(Command):
# 'install_path_file' is always true unless some outsider meddles
# with it.
self.extra_path = None
self.install_path_file = 1
self.install_path_file = True
# 'force' forces installation, even if target files are not
# out-of-date. 'skip_build' skips running the "build" command,
@@ -302,9 +300,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 = 0
self.skip_build = 0
self.warn_dir = 1
self.force = False
self.skip_build = False
self.warn_dir = True
# These are only here as a conduit from the 'build' command to the
# 'install_*' commands that do the real work. ('build_base' isn't
@@ -349,8 +347,7 @@ 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):
@@ -432,9 +429,12 @@ class install(Command):
local_vars['userbase'] = self.install_userbase
local_vars['usersite'] = self.install_usersite
self.config_vars = _collections.DictStack(
[fw.vars(), compat_vars, sysconfig.get_config_vars(), local_vars]
)
self.config_vars = jaraco.collections.DictStack([
fw.vars(),
compat_vars,
sysconfig.get_config_vars(),
local_vars,
])
self.expand_basedirs()
@@ -598,7 +598,7 @@ class install(Command):
self.select_scheme(os.name)
except KeyError:
raise DistutilsPlatformError(
"I don't know how to install stuff on '%s'" % os.name
f"I don't know how to install stuff on '{os.name}'"
)
def select_scheme(self, name):
@@ -620,16 +620,14 @@ class install(Command):
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):
"""Call `convert_path` over `names`."""
@@ -683,9 +681,9 @@ class install(Command):
if not self.user:
return
home = convert_path(os.path.expanduser("~"))
for name, path in self.config_vars.items():
for path in self.config_vars.values():
if str(path).startswith(home) and not os.path.isdir(path):
self.debug_print("os.makedirs('%s', 0o700)" % path)
self.debug_print(f"os.makedirs('{path}', 0o700)")
os.makedirs(path, 0o700)
# -- Command execution methods -------------------------------------
@@ -701,7 +699,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():
@@ -720,7 +718,7 @@ class install(Command):
self.execute(
write_file,
(self.record, outputs),
"writing list of installed files to '%s'" % self.record,
f"writing list of installed files to '{self.record}'",
)
sys_path = map(os.path.normpath, sys.path)
@@ -745,10 +743,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]), "creating %s" % filename
write_file, (filename, [self.extra_dirs]), f"creating {filename}"
)
else:
self.warn("path file '%s' not created" % filename)
self.warn(f"path file '{filename}' not created")
# -- Reporting methods ---------------------------------------------

View File

@@ -5,7 +5,12 @@ platform-independent data files."""
# contributed by Bastian Kleineidam
from __future__ import annotations
import functools
import os
from typing import Iterable
from ..core import Command
from ..util import change_root, convert_path
@@ -18,7 +23,7 @@ class install_data(Command):
'install-dir=',
'd',
"base directory for installing data files "
"(default: installation base dir)",
"[default: installation base dir]",
),
('root=', None, "install everything relative to this alternate root directory"),
('force', 'f', "force installation (overwrite existing files)"),
@@ -30,9 +35,9 @@ class install_data(Command):
self.install_dir = None
self.outfiles = []
self.root = None
self.force = 0
self.force = False
self.data_files = self.distribution.data_files
self.warn_dir = 1
self.warn_dir = True
def finalize_options(self):
self.set_undefined_options(
@@ -45,36 +50,42 @@ class install_data(Command):
def run(self):
self.mkpath(self.install_dir)
for f in self.data_files:
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)
self._copy(f)
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)
@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)
self.outfiles.append(out)
@_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)
def get_inputs(self):
return self.data_files or []

View File

@@ -6,12 +6,12 @@ a package's PKG-INFO metadata.
"""
import os
import sys
import re
import sys
from ..cmd import Command
from .. import dir_util
from .._log import log
from ..cmd import Command
class install_egg_info(Command):

View File

@@ -19,7 +19,7 @@ class install_headers(Command):
def initialize_options(self):
self.install_dir = None
self.force = 0
self.force = False
self.outfiles = []
def finalize_options(self):

View File

@@ -3,14 +3,13 @@
Implements the Distutils 'install_lib' command
(install all Python modules)."""
import os
import importlib.util
import os
import sys
from ..core import Command
from ..errors import DistutilsOptionError
# Extension for Python source files.
PYTHON_SOURCE_EXTENSION = ".py"
@@ -55,7 +54,7 @@ class install_lib(Command):
# let the 'install' command dictate our installation directory
self.install_dir = None
self.build_dir = None
self.force = 0
self.force = False
self.compile = None
self.optimize = None
self.skip_build = None
@@ -82,9 +81,9 @@ class install_lib(Command):
if not isinstance(self.optimize, int):
try:
self.optimize = int(self.optimize)
if self.optimize not in (0, 1, 2):
raise AssertionError
except (ValueError, AssertionError):
except ValueError:
pass
if self.optimize not in (0, 1, 2):
raise DistutilsOptionError("optimize must be 0, 1, or 2")
def run(self):
@@ -115,7 +114,7 @@ class install_lib(Command):
outfiles = self.copy_tree(self.build_dir, self.install_dir)
else:
self.warn(
"'%s' does not exist -- no Python modules to install" % self.build_dir
f"'{self.build_dir}' does not exist -- no Python modules to install"
)
return
return outfiles
@@ -162,9 +161,7 @@ class install_lib(Command):
build_dir = getattr(build_cmd, cmd_option)
prefix_len = len(build_dir) + len(os.sep)
outputs = []
for file in build_files:
outputs.append(os.path.join(output_dir, file[prefix_len:]))
outputs = [os.path.join(output_dir, file[prefix_len:]) for file in build_files]
return outputs

View File

@@ -6,10 +6,11 @@ Python scripts."""
# contributed by Bastian Kleineidam
import os
from ..core import Command
from distutils._log import log
from stat import ST_MODE
from ..core import Command
class install_scripts(Command):
description = "install scripts (Python or otherwise)"
@@ -25,7 +26,7 @@ class install_scripts(Command):
def initialize_options(self):
self.install_dir = None
self.force = 0
self.force = False
self.build_dir = None
self.skip_build = None

View File

@@ -1,31 +0,0 @@
import sys
def _pythonlib_compat():
"""
On Python 3.7 and earlier, distutils would include the Python
library. See pypa/distutils#9.
"""
from distutils import sysconfig
if not sysconfig.get_config_var('Py_ENABLED_SHARED'):
return
yield 'python{}.{}{}'.format(
sys.hexversion >> 24,
(sys.hexversion >> 16) & 0xFF,
sysconfig.get_config_var('ABIFLAGS'),
)
def compose(f1, f2):
return lambda *args, **kwargs: f1(f2(*args, **kwargs))
pythonlib = (
compose(list, _pythonlib_compat)
if sys.version_info < (3, 8)
and sys.platform != 'darwin'
and sys.platform[:3] != 'aix'
else list
)

View File

@@ -1,320 +0,0 @@
"""distutils.command.register
Implements the Distutils 'register' command (register with the repository).
"""
# created 2002/10/21, Richard Jones
import getpass
import io
import logging
import urllib.parse
import urllib.request
from warnings import warn
from ..core import PyPIRCCommand
from distutils._log import log
class register(PyPIRCCommand):
description = "register the distribution with the Python package index"
user_options = PyPIRCCommand.user_options + [
('list-classifiers', None, 'list the valid Trove classifiers'),
(
'strict',
None,
'Will stop the registering if the meta-data are not fully compliant',
),
]
boolean_options = PyPIRCCommand.boolean_options + [
'verify',
'list-classifiers',
'strict',
]
sub_commands = [('check', lambda self: True)]
def initialize_options(self):
PyPIRCCommand.initialize_options(self)
self.list_classifiers = 0
self.strict = 0
def finalize_options(self):
PyPIRCCommand.finalize_options(self)
# setting options for the `check` subcommand
check_options = {
'strict': ('register', self.strict),
'restructuredtext': ('register', 1),
}
self.distribution.command_options['check'] = check_options
def run(self):
self.finalize_options()
self._set_config()
# Run sub commands
for cmd_name in self.get_sub_commands():
self.run_command(cmd_name)
if self.dry_run:
self.verify_metadata()
elif self.list_classifiers:
self.classifiers()
else:
self.send_metadata()
def check_metadata(self):
"""Deprecated API."""
warn(
"distutils.command.register.check_metadata is deprecated; "
"use the check command instead",
DeprecationWarning,
)
check = self.distribution.get_command_obj('check')
check.ensure_finalized()
check.strict = self.strict
check.restructuredtext = 1
check.run()
def _set_config(self):
'''Reads the configuration file and set attributes.'''
config = self._read_pypirc()
if config != {}:
self.username = config['username']
self.password = config['password']
self.repository = config['repository']
self.realm = config['realm']
self.has_config = True
else:
if self.repository not in ('pypi', self.DEFAULT_REPOSITORY):
raise ValueError('%s not found in .pypirc' % self.repository)
if self.repository == 'pypi':
self.repository = self.DEFAULT_REPOSITORY
self.has_config = False
def classifiers(self):
'''Fetch the list of classifiers from the server.'''
url = self.repository + '?:action=list_classifiers'
response = urllib.request.urlopen(url)
log.info(self._read_pypi_response(response))
def verify_metadata(self):
'''Send the metadata to the package index server to be checked.'''
# send the info to the server and report the result
(code, result) = self.post_to_server(self.build_post_data('verify'))
log.info('Server response (%s): %s', code, result)
def send_metadata(self): # noqa: C901
'''Send the metadata to the package index server.
Well, do the following:
1. figure who the user is, and then
2. send the data as a Basic auth'ed POST.
First we try to read the username/password from $HOME/.pypirc,
which is a ConfigParser-formatted file with a section
[distutils] containing username and password entries (both
in clear text). Eg:
[distutils]
index-servers =
pypi
[pypi]
username: fred
password: sekrit
Otherwise, to figure who the user is, we offer the user three
choices:
1. use existing login,
2. register as a new user, or
3. set the password to a random string and email the user.
'''
# see if we can short-cut and get the username/password from the
# config
if self.has_config:
choice = '1'
username = self.username
password = self.password
else:
choice = 'x'
username = password = ''
# get the user's login info
choices = '1 2 3 4'.split()
while choice not in choices:
self.announce(
'''\
We need to know who you are, so please choose either:
1. use your existing login,
2. register as a new user,
3. have the server generate a new password for you (and email it to you), or
4. quit
Your selection [default 1]: ''',
logging.INFO,
)
choice = input()
if not choice:
choice = '1'
elif choice not in choices:
print('Please choose one of the four options!')
if choice == '1':
# get the username and password
while not username:
username = input('Username: ')
while not password:
password = getpass.getpass('Password: ')
# set up the authentication
auth = urllib.request.HTTPPasswordMgr()
host = urllib.parse.urlparse(self.repository)[1]
auth.add_password(self.realm, host, username, password)
# send the info to the server and report the result
code, result = self.post_to_server(self.build_post_data('submit'), auth)
self.announce('Server response ({}): {}'.format(code, result), logging.INFO)
# possibly save the login
if code == 200:
if self.has_config:
# sharing the password in the distribution instance
# so the upload command can reuse it
self.distribution.password = password
else:
self.announce(
(
'I can store your PyPI login so future '
'submissions will be faster.'
),
logging.INFO,
)
self.announce(
'(the login will be stored in %s)' % self._get_rc_file(),
logging.INFO,
)
choice = 'X'
while choice.lower() not in 'yn':
choice = input('Save your login (y/N)?')
if not choice:
choice = 'n'
if choice.lower() == 'y':
self._store_pypirc(username, password)
elif choice == '2':
data = {':action': 'user'}
data['name'] = data['password'] = data['email'] = ''
data['confirm'] = None
while not data['name']:
data['name'] = input('Username: ')
while data['password'] != data['confirm']:
while not data['password']:
data['password'] = getpass.getpass('Password: ')
while not data['confirm']:
data['confirm'] = getpass.getpass(' Confirm: ')
if data['password'] != data['confirm']:
data['password'] = ''
data['confirm'] = None
print("Password and confirm don't match!")
while not data['email']:
data['email'] = input(' EMail: ')
code, result = self.post_to_server(data)
if code != 200:
log.info('Server response (%s): %s', code, result)
else:
log.info('You will receive an email shortly.')
log.info('Follow the instructions in it to ' 'complete registration.')
elif choice == '3':
data = {':action': 'password_reset'}
data['email'] = ''
while not data['email']:
data['email'] = input('Your email address: ')
code, result = self.post_to_server(data)
log.info('Server response (%s): %s', code, result)
def build_post_data(self, action):
# figure the data to send - the metadata plus some additional
# information used by the package server
meta = self.distribution.metadata
data = {
':action': action,
'metadata_version': '1.0',
'name': meta.get_name(),
'version': meta.get_version(),
'summary': meta.get_description(),
'home_page': meta.get_url(),
'author': meta.get_contact(),
'author_email': meta.get_contact_email(),
'license': meta.get_licence(),
'description': meta.get_long_description(),
'keywords': meta.get_keywords(),
'platform': meta.get_platforms(),
'classifiers': meta.get_classifiers(),
'download_url': meta.get_download_url(),
# PEP 314
'provides': meta.get_provides(),
'requires': meta.get_requires(),
'obsoletes': meta.get_obsoletes(),
}
if data['provides'] or data['requires'] or data['obsoletes']:
data['metadata_version'] = '1.1'
return data
def post_to_server(self, data, auth=None): # noqa: C901
'''Post a query to the server, and return a string response.'''
if 'name' in data:
self.announce(
'Registering {} to {}'.format(data['name'], self.repository),
logging.INFO,
)
# Build up the MIME payload for the urllib2 POST data
boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
sep_boundary = '\n--' + boundary
end_boundary = sep_boundary + '--'
body = io.StringIO()
for key, value in data.items():
# handle multiple entries for the same name
if type(value) not in (type([]), type(())):
value = [value]
for value in value:
value = str(value)
body.write(sep_boundary)
body.write('\nContent-Disposition: form-data; name="%s"' % key)
body.write("\n\n")
body.write(value)
if value and value[-1] == '\r':
body.write('\n') # write an extra newline (lurve Macs)
body.write(end_boundary)
body.write("\n")
body = body.getvalue().encode("utf-8")
# build the Request
headers = {
'Content-type': 'multipart/form-data; boundary=%s; charset=utf-8'
% boundary,
'Content-length': str(len(body)),
}
req = urllib.request.Request(self.repository, body, headers)
# handle HTTP and include the Basic Auth handler
opener = urllib.request.build_opener(
urllib.request.HTTPBasicAuthHandler(password_mgr=auth)
)
data = ''
try:
result = opener.open(req)
except urllib.error.HTTPError as e:
if self.show_response:
data = e.fp.read()
result = e.code, e.msg
except urllib.error.URLError as e:
result = 500, str(e)
else:
if self.show_response:
data = self._read_pypi_response(result)
result = 200, 'OK'
if self.show_response:
msg = '\n'.join(('-' * 75, data, '-' * 75))
self.announce(msg, logging.INFO)
return result

View File

@@ -4,31 +4,29 @@ Implements the Distutils 'sdist' command (create a source distribution)."""
import os
import sys
from distutils import archive_util, dir_util, file_util
from distutils._log import log
from glob import glob
from warnings import warn
from itertools import filterfalse
from ..core import Command
from distutils import dir_util
from distutils import file_util
from distutils import archive_util
from ..text_file import TextFile
from ..filelist import FileList
from distutils._log import log
from ..util import convert_path
from ..errors import DistutilsOptionError, DistutilsTemplateError
from ..filelist import FileList
from ..text_file import TextFile
from ..util import convert_path
def show_formats():
"""Print all possible values for the 'formats' option (used by
the "--help-formats" command-line option).
"""
from ..fancy_getopt import FancyGetopt
from ..archive_util import ARCHIVE_FORMATS
from ..fancy_getopt import FancyGetopt
formats = []
for format in ARCHIVE_FORMATS.keys():
formats.append(("formats=" + format, None, ARCHIVE_FORMATS[format][2]))
formats.sort()
formats = sorted(
("formats=" + format, None, ARCHIVE_FORMATS[format][2])
for format in ARCHIVE_FORMATS.keys()
)
FancyGetopt(formats).print_help("List of available source distribution formats:")
@@ -62,7 +60,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',
@@ -79,7 +77,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',
@@ -126,14 +124,14 @@ class sdist(Command):
# 'use_defaults': if true, we will include the default file set
# in the manifest
self.use_defaults = 1
self.prune = 1
self.use_defaults = True
self.prune = True
self.manifest_only = 0
self.force_manifest = 0
self.manifest_only = False
self.force_manifest = False
self.formats = ['gztar']
self.keep_temp = 0
self.keep_temp = False
self.dist_dir = None
self.archive_files = None
@@ -151,7 +149,7 @@ class sdist(Command):
bad_format = archive_util.check_archive_formats(self.formats)
if bad_format:
raise DistutilsOptionError("unknown archive format '%s'" % bad_format)
raise DistutilsOptionError(f"unknown archive format '{bad_format}'")
if self.dist_dir is None:
self.dist_dir = "dist"
@@ -178,17 +176,6 @@ class sdist(Command):
# or zipfile, or whatever.
self.make_distribution()
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
@@ -289,7 +276,7 @@ class sdist(Command):
if self._cs_path_exists(fn):
self.filelist.append(fn)
else:
self.warn("standard file '%s' not found" % fn)
self.warn(f"standard file '{fn}' not found")
def _add_defaults_optional(self):
optional = ['tests/test*.py', 'test/test*.py', 'setup.cfg']
@@ -309,7 +296,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))
@@ -354,12 +341,12 @@ class sdist(Command):
log.info("reading manifest template '%s'", self.template)
template = TextFile(
self.template,
strip_comments=1,
skip_blanks=1,
join_lines=1,
lstrip_ws=1,
rstrip_ws=1,
collapse_join=1,
strip_comments=True,
skip_blanks=True,
join_lines=True,
lstrip_ws=True,
rstrip_ws=True,
collapse_join=True,
)
try:
@@ -392,7 +379,7 @@ class sdist(Command):
build = self.get_finalized_command('build')
base_dir = self.distribution.get_fullname()
self.filelist.exclude_pattern(None, prefix=build.build_base)
self.filelist.exclude_pattern(None, prefix=os.fspath(build.build_base))
self.filelist.exclude_pattern(None, prefix=base_dir)
if sys.platform == 'win32':
@@ -402,7 +389,7 @@ 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=1)
self.filelist.exclude_pattern(vcs_ptrn, is_regex=True)
def write_manifest(self):
"""Write the file list in 'self.filelist' (presumably as filled in
@@ -411,8 +398,7 @@ class sdist(Command):
"""
if self._manifest_is_not_generated():
log.info(
"not writing to manually maintained "
"manifest file '%s'" % self.manifest
f"not writing to manually maintained manifest file '{self.manifest}'"
)
return
@@ -421,7 +407,7 @@ class sdist(Command):
self.execute(
file_util.write_file,
(self.manifest, content),
"writing manifest file '%s'" % self.manifest,
f"writing manifest file '{self.manifest}'",
)
def _manifest_is_not_generated(self):
@@ -429,11 +415,8 @@ class sdist(Command):
if not os.path.isfile(self.manifest):
return False
fp = open(self.manifest)
try:
first_line = fp.readline()
finally:
fp.close()
with open(self.manifest, encoding='utf-8') as fp:
first_line = next(fp)
return first_line != '# file GENERATED by distutils, do NOT edit\n'
def read_manifest(self):
@@ -442,13 +425,11 @@ class sdist(Command):
distribution.
"""
log.info("reading manifest file '%s'", self.manifest)
with open(self.manifest) as manifest:
for line in manifest:
with open(self.manifest, encoding='utf-8') as lines:
self.filelist.extend(
# ignore comments and blank lines
line = line.strip()
if line.startswith('#') or not line:
continue
self.filelist.append(line)
filter(None, filterfalse(is_comment, map(str.strip, lines)))
)
def make_release_tree(self, base_dir, files):
"""Create the directory tree that will become the source
@@ -474,10 +455,10 @@ class sdist(Command):
if hasattr(os, 'link'): # can make hard links on this system
link = 'hard'
msg = "making hard links in %s..." % base_dir
msg = f"making hard links in {base_dir}..."
else: # nope, have to copy
link = None
msg = "copying files to %s..." % base_dir
msg = f"copying files to {base_dir}..."
if not files:
log.warning("no files to distribute -- empty manifest?")
@@ -528,3 +509,7 @@ class sdist(Command):
was run, or None if the command hasn't run yet.
"""
return self.archive_files
def is_comment(line):
return line.startswith('#')

View File

@@ -1,206 +0,0 @@
"""
distutils.command.upload
Implements the Distutils 'upload' subcommand (upload package to a package
index).
"""
import os
import io
import hashlib
import logging
from base64 import standard_b64encode
from urllib.request import urlopen, Request, HTTPError
from urllib.parse import urlparse
from ..errors import DistutilsError, DistutilsOptionError
from ..core import PyPIRCCommand
from ..spawn import spawn
# PyPI Warehouse supports MD5, SHA256, and Blake2 (blake2-256)
# https://bugs.python.org/issue40698
_FILE_CONTENT_DIGESTS = {
"md5_digest": getattr(hashlib, "md5", None),
"sha256_digest": getattr(hashlib, "sha256", None),
"blake2_256_digest": getattr(hashlib, "blake2b", None),
}
class upload(PyPIRCCommand):
description = "upload binary package to PyPI"
user_options = PyPIRCCommand.user_options + [
('sign', 's', 'sign files to upload using gpg'),
('identity=', 'i', 'GPG identity used to sign files'),
]
boolean_options = PyPIRCCommand.boolean_options + ['sign']
def initialize_options(self):
PyPIRCCommand.initialize_options(self)
self.username = ''
self.password = ''
self.show_response = 0
self.sign = False
self.identity = None
def finalize_options(self):
PyPIRCCommand.finalize_options(self)
if self.identity and not self.sign:
raise DistutilsOptionError("Must use --sign for --identity to have meaning")
config = self._read_pypirc()
if config != {}:
self.username = config['username']
self.password = config['password']
self.repository = config['repository']
self.realm = config['realm']
# getting the password from the distribution
# if previously set by the register command
if not self.password and self.distribution.password:
self.password = self.distribution.password
def run(self):
if not self.distribution.dist_files:
msg = (
"Must create and upload files in one command "
"(e.g. setup.py sdist upload)"
)
raise DistutilsOptionError(msg)
for command, pyversion, filename in self.distribution.dist_files:
self.upload_file(command, pyversion, filename)
def upload_file(self, command, pyversion, filename): # noqa: C901
# Makes sure the repository URL is compliant
schema, netloc, url, params, query, fragments = urlparse(self.repository)
if params or query or fragments:
raise AssertionError("Incompatible url %s" % self.repository)
if schema not in ('http', 'https'):
raise AssertionError("unsupported schema " + schema)
# Sign if requested
if self.sign:
gpg_args = ["gpg", "--detach-sign", "-a", filename]
if self.identity:
gpg_args[2:2] = ["--local-user", self.identity]
spawn(gpg_args, dry_run=self.dry_run)
# Fill in the data - send all the meta-data in case we need to
# register a new release
f = open(filename, 'rb')
try:
content = f.read()
finally:
f.close()
meta = self.distribution.metadata
data = {
# action
':action': 'file_upload',
'protocol_version': '1',
# identify release
'name': meta.get_name(),
'version': meta.get_version(),
# file content
'content': (os.path.basename(filename), content),
'filetype': command,
'pyversion': pyversion,
# additional meta-data
'metadata_version': '1.0',
'summary': meta.get_description(),
'home_page': meta.get_url(),
'author': meta.get_contact(),
'author_email': meta.get_contact_email(),
'license': meta.get_licence(),
'description': meta.get_long_description(),
'keywords': meta.get_keywords(),
'platform': meta.get_platforms(),
'classifiers': meta.get_classifiers(),
'download_url': meta.get_download_url(),
# PEP 314
'provides': meta.get_provides(),
'requires': meta.get_requires(),
'obsoletes': meta.get_obsoletes(),
}
data['comment'] = ''
# file content digests
for digest_name, digest_cons in _FILE_CONTENT_DIGESTS.items():
if digest_cons is None:
continue
try:
data[digest_name] = digest_cons(content).hexdigest()
except ValueError:
# hash digest not available or blocked by security policy
pass
if self.sign:
with open(filename + ".asc", "rb") as f:
data['gpg_signature'] = (os.path.basename(filename) + ".asc", f.read())
# set up the authentication
user_pass = (self.username + ":" + self.password).encode('ascii')
# The exact encoding of the authentication string is debated.
# Anyway PyPI only accepts ascii for both username or password.
auth = "Basic " + standard_b64encode(user_pass).decode('ascii')
# Build up the MIME payload for the POST data
boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
sep_boundary = b'\r\n--' + boundary.encode('ascii')
end_boundary = sep_boundary + b'--\r\n'
body = io.BytesIO()
for key, value in data.items():
title = '\r\nContent-Disposition: form-data; name="%s"' % key
# handle multiple entries for the same name
if not isinstance(value, list):
value = [value]
for value in value:
if type(value) is tuple:
title += '; filename="%s"' % value[0]
value = value[1]
else:
value = str(value).encode('utf-8')
body.write(sep_boundary)
body.write(title.encode('utf-8'))
body.write(b"\r\n\r\n")
body.write(value)
body.write(end_boundary)
body = body.getvalue()
msg = "Submitting {} to {}".format(filename, self.repository)
self.announce(msg, logging.INFO)
# build the Request
headers = {
'Content-type': 'multipart/form-data; boundary=%s' % boundary,
'Content-length': str(len(body)),
'Authorization': auth,
}
request = Request(self.repository, data=body, headers=headers)
# send the data
try:
result = urlopen(request)
status = result.getcode()
reason = result.msg
except HTTPError as e:
status = e.code
reason = e.msg
except OSError as e:
self.announce(str(e), logging.ERROR)
raise
if status == 200:
self.announce(
'Server response ({}): {}'.format(status, reason), logging.INFO
)
if self.show_response:
text = self._read_pypi_response(result)
msg = '\n'.join(('-' * 75, text, '-' * 75))
self.announce(msg, logging.INFO)
else:
msg = 'Upload failed ({}): {}'.format(status, reason)
self.announce(msg, logging.ERROR)
raise DistutilsError(msg)

View File

@@ -0,0 +1,15 @@
from __future__ import annotations
from .py38 import removeprefix
def consolidate_linker_args(args: list[str]) -> list[str] | str:
"""
Ensure the return value is a string for backward compatibility.
Retain until at least 2025-04-31. See pypa/distutils#246
"""
if not all(arg.startswith('-Wl,') for arg in args):
return args
return '-Wl,' + ','.join(removeprefix(arg, '-Wl,') for arg in args)

View File

@@ -0,0 +1,34 @@
import sys
if sys.version_info < (3, 9):
def removesuffix(self, suffix):
# suffix='' should not call self[:-0].
if suffix and self.endswith(suffix):
return self[: -len(suffix)]
else:
return self[:]
def removeprefix(self, prefix):
if self.startswith(prefix):
return self[len(prefix) :]
else:
return self[:]
else:
def removesuffix(self, suffix):
return self.removesuffix(suffix)
def removeprefix(self, prefix):
return self.removeprefix(prefix)
def aix_platform(osname, version, release):
try:
import _aix_support # type: ignore
return _aix_support.aix_platform()
except ImportError:
pass
return f"{osname}-{version}.{release}"

View File

@@ -0,0 +1,66 @@
import functools
import itertools
import platform
import sys
def add_ext_suffix_39(vars):
"""
Ensure vars contains 'EXT_SUFFIX'. pypa/distutils#130
"""
import _imp
ext_suffix = _imp.extension_suffixes()[0]
vars.update(
EXT_SUFFIX=ext_suffix,
# sysconfig sets SO to match EXT_SUFFIX, so maintain
# that expectation.
# https://github.com/python/cpython/blob/785cc6770588de087d09e89a69110af2542be208/Lib/sysconfig.py#L671-L673
SO=ext_suffix,
)
needs_ext_suffix = sys.version_info < (3, 10) and platform.system() == 'Windows'
add_ext_suffix = add_ext_suffix_39 if needs_ext_suffix else lambda vars: None
# from more_itertools
class UnequalIterablesError(ValueError):
def __init__(self, details=None):
msg = 'Iterables have different lengths'
if details is not None:
msg += (': index 0 has length {}; index {} has length {}').format(*details)
super().__init__(msg)
# from more_itertools
def _zip_equal_generator(iterables):
_marker = object()
for combo in itertools.zip_longest(*iterables, fillvalue=_marker):
for val in combo:
if val is _marker:
raise UnequalIterablesError()
yield combo
# from more_itertools
def _zip_equal(*iterables):
# Check whether the iterables are all the same size.
try:
first_size = len(iterables[0])
for i, it in enumerate(iterables[1:], 1):
size = len(it)
if size != first_size:
raise UnequalIterablesError(details=(first_size, i, size))
# All sizes are equal, we can use the built-in zip.
return zip(*iterables)
# If any one of the iterables didn't have a length, start reading
# them until one runs out.
except TypeError:
return _zip_equal_generator(iterables)
zip_strict = (
_zip_equal if sys.version_info < (3, 10) else functools.partial(zip, strict=True)
)

View File

@@ -1,139 +0,0 @@
"""distutils.pypirc
Provides the PyPIRCCommand class, the base class for the command classes
that uses .pypirc in the distutils.command package.
"""
import os
from configparser import RawConfigParser
from .cmd import Command
DEFAULT_PYPIRC = """\
[distutils]
index-servers =
pypi
[pypi]
username:%s
password:%s
"""
class PyPIRCCommand(Command):
"""Base command that knows how to handle the .pypirc file"""
DEFAULT_REPOSITORY = 'https://upload.pypi.org/legacy/'
DEFAULT_REALM = 'pypi'
repository = None
realm = None
user_options = [
('repository=', 'r', "url of repository [default: %s]" % DEFAULT_REPOSITORY),
('show-response', None, 'display full response text from server'),
]
boolean_options = ['show-response']
def _get_rc_file(self):
"""Returns rc file path."""
return os.path.join(os.path.expanduser('~'), '.pypirc')
def _store_pypirc(self, username, password):
"""Creates a default .pypirc file."""
rc = self._get_rc_file()
with os.fdopen(os.open(rc, os.O_CREAT | os.O_WRONLY, 0o600), 'w') as f:
f.write(DEFAULT_PYPIRC % (username, password))
def _read_pypirc(self): # noqa: C901
"""Reads the .pypirc file."""
rc = self._get_rc_file()
if os.path.exists(rc):
self.announce('Using PyPI login from %s' % rc)
repository = self.repository or self.DEFAULT_REPOSITORY
config = RawConfigParser()
config.read(rc)
sections = config.sections()
if 'distutils' in sections:
# let's get the list of servers
index_servers = config.get('distutils', 'index-servers')
_servers = [
server.strip()
for server in index_servers.split('\n')
if server.strip() != ''
]
if _servers == []:
# nothing set, let's try to get the default pypi
if 'pypi' in sections:
_servers = ['pypi']
else:
# the file is not properly defined, returning
# an empty dict
return {}
for server in _servers:
current = {'server': server}
current['username'] = config.get(server, 'username')
# optional params
for key, default in (
('repository', self.DEFAULT_REPOSITORY),
('realm', self.DEFAULT_REALM),
('password', None),
):
if config.has_option(server, key):
current[key] = config.get(server, key)
else:
current[key] = default
# work around people having "repository" for the "pypi"
# section of their config set to the HTTP (rather than
# HTTPS) URL
if server == 'pypi' and repository in (
self.DEFAULT_REPOSITORY,
'pypi',
):
current['repository'] = self.DEFAULT_REPOSITORY
return current
if (
current['server'] == repository
or current['repository'] == repository
):
return current
elif 'server-login' in sections:
# old format
server = 'server-login'
if config.has_option(server, 'repository'):
repository = config.get(server, 'repository')
else:
repository = self.DEFAULT_REPOSITORY
return {
'username': config.get(server, 'username'),
'password': config.get(server, 'password'),
'repository': repository,
'server': server,
'realm': self.DEFAULT_REALM,
}
return {}
def _read_pypi_response(self, response):
"""Read and decode a PyPI HTTP response."""
import cgi
content_type = response.getheader('content-type', 'text/plain')
encoding = cgi.parse_header(content_type)[1].get('charset', 'ascii')
return response.read().decode(encoding)
def initialize_options(self):
"""Initialize options."""
self.repository = None
self.realm = None
self.show_response = 0
def finalize_options(self):
"""Finalizes options."""
if self.repository is None:
self.repository = self.DEFAULT_REPOSITORY
if self.realm is None:
self.realm = self.DEFAULT_REALM

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