update
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
from distutils.command.bdist import bdist
|
||||
import sys
|
||||
|
||||
from distutils.command.bdist import bdist
|
||||
|
||||
if 'egg' not in bdist.format_commands:
|
||||
try:
|
||||
bdist.format_commands['egg'] = ('bdist_egg', "Python .egg file")
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/setuptools/command/__pycache__/bdist_wheel.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/setuptools/command/__pycache__/bdist_wheel.cpython-312.pyc
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -6,15 +6,18 @@ The ``requires.txt`` file has an specific format:
|
||||
|
||||
See https://setuptools.pypa.io/en/latest/deprecated/python_eggs.html#requires-txt
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
from collections import defaultdict
|
||||
from itertools import filterfalse
|
||||
from typing import Dict, List, Tuple, Mapping, TypeVar
|
||||
from typing import Dict, Mapping, TypeVar
|
||||
|
||||
from jaraco.text import yield_lines
|
||||
from packaging.requirements import Requirement
|
||||
|
||||
from .. import _reqs
|
||||
from ..extern.jaraco.text import yield_lines
|
||||
from ..extern.packaging.requirements import Requirement
|
||||
|
||||
|
||||
# dict can work as an ordered set
|
||||
_T = TypeVar("_T")
|
||||
@@ -25,7 +28,7 @@ _StrOrIter = _reqs._StrOrIter
|
||||
|
||||
def _prepare(
|
||||
install_requires: _StrOrIter, extras_require: Mapping[str, _StrOrIter]
|
||||
) -> Tuple[List[str], Dict[str, List[str]]]:
|
||||
) -> tuple[list[str], dict[str, list[str]]]:
|
||||
"""Given values for ``install_requires`` and ``extras_require``
|
||||
create modified versions in a way that can be written in ``requires.txt``
|
||||
"""
|
||||
@@ -34,7 +37,7 @@ def _prepare(
|
||||
|
||||
|
||||
def _convert_extras_requirements(
|
||||
extras_require: _StrOrIter,
|
||||
extras_require: Mapping[str, _StrOrIter],
|
||||
) -> Mapping[str, _Ordered[Requirement]]:
|
||||
"""
|
||||
Convert requirements in `extras_require` of the form
|
||||
@@ -53,7 +56,7 @@ def _convert_extras_requirements(
|
||||
|
||||
def _move_install_requirements_markers(
|
||||
install_requires: _StrOrIter, extras_require: Mapping[str, _Ordered[Requirement]]
|
||||
) -> Tuple[List[str], Dict[str, List[str]]]:
|
||||
) -> tuple[list[str], dict[str, list[str]]]:
|
||||
"""
|
||||
The ``requires.txt`` file has an specific format:
|
||||
- Environment markers need to be part of the section headers and
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from distutils.errors import DistutilsOptionError
|
||||
from setuptools.command.setopt import config_file, edit_config, option_base
|
||||
|
||||
from setuptools.command.setopt import edit_config, option_base, config_file
|
||||
from distutils.errors import DistutilsOptionError
|
||||
|
||||
|
||||
def shquote(arg):
|
||||
|
||||
@@ -2,20 +2,21 @@
|
||||
|
||||
Build .egg distributions"""
|
||||
|
||||
from distutils.dir_util import remove_tree, mkpath
|
||||
from distutils import log
|
||||
from types import CodeType
|
||||
import sys
|
||||
import marshal
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import textwrap
|
||||
import marshal
|
||||
from sysconfig import get_path, get_python_version
|
||||
from types import CodeType
|
||||
|
||||
from setuptools.extension import Library
|
||||
from setuptools import Command
|
||||
from setuptools.extension import Library
|
||||
|
||||
from .._path import ensure_directory
|
||||
|
||||
from sysconfig import get_path, get_python_version
|
||||
from distutils import log
|
||||
from distutils.dir_util import mkpath, remove_tree
|
||||
|
||||
|
||||
def _get_purelib():
|
||||
@@ -54,7 +55,7 @@ def write_stub(resource, pyfile):
|
||||
__bootstrap__()
|
||||
"""
|
||||
).lstrip()
|
||||
with open(pyfile, 'w') as f:
|
||||
with open(pyfile, 'w', encoding="utf-8") as f:
|
||||
f.write(_stub_template % resource)
|
||||
|
||||
|
||||
@@ -74,7 +75,7 @@ class bdist_egg(Command):
|
||||
'keep-temp',
|
||||
'k',
|
||||
"keep the pseudo-installation tree around after "
|
||||
+ "creating the distribution archive",
|
||||
"creating the distribution archive",
|
||||
),
|
||||
('dist-dir=', 'd', "directory to put final built distributions in"),
|
||||
('skip-build', None, "skip rebuilding everything (for testing/debugging)"),
|
||||
@@ -85,9 +86,9 @@ class bdist_egg(Command):
|
||||
def initialize_options(self):
|
||||
self.bdist_dir = None
|
||||
self.plat_name = None
|
||||
self.keep_temp = 0
|
||||
self.keep_temp = False
|
||||
self.dist_dir = None
|
||||
self.skip_build = 0
|
||||
self.skip_build = False
|
||||
self.egg_output = None
|
||||
self.exclude_source_files = None
|
||||
|
||||
@@ -136,7 +137,7 @@ class bdist_egg(Command):
|
||||
|
||||
try:
|
||||
log.info("installing package data to %s", self.bdist_dir)
|
||||
self.call_command('install_data', force=0, root=None)
|
||||
self.call_command('install_data', force=False, root=None)
|
||||
finally:
|
||||
self.distribution.data_files = old
|
||||
|
||||
@@ -164,7 +165,7 @@ class bdist_egg(Command):
|
||||
instcmd.root = None
|
||||
if self.distribution.has_c_libraries() and not self.skip_build:
|
||||
self.run_command('build_clib')
|
||||
cmd = self.call_command('install_lib', warn_dir=0)
|
||||
cmd = self.call_command('install_lib', warn_dir=False)
|
||||
instcmd.root = old_root
|
||||
|
||||
all_outputs, ext_outputs = self.get_ext_outputs()
|
||||
@@ -192,7 +193,7 @@ class bdist_egg(Command):
|
||||
if self.distribution.scripts:
|
||||
script_dir = os.path.join(egg_info, 'scripts')
|
||||
log.info("installing scripts to %s", script_dir)
|
||||
self.call_command('install_scripts', install_dir=script_dir, no_ep=1)
|
||||
self.call_command('install_scripts', install_dir=script_dir, no_ep=True)
|
||||
|
||||
self.copy_metadata_to(egg_info)
|
||||
native_libs = os.path.join(egg_info, "native_libs.txt")
|
||||
@@ -200,10 +201,9 @@ class bdist_egg(Command):
|
||||
log.info("writing %s", native_libs)
|
||||
if not self.dry_run:
|
||||
ensure_directory(native_libs)
|
||||
libs_file = open(native_libs, 'wt')
|
||||
libs_file.write('\n'.join(all_outputs))
|
||||
libs_file.write('\n')
|
||||
libs_file.close()
|
||||
with open(native_libs, 'wt', encoding="utf-8") as libs_file:
|
||||
libs_file.write('\n'.join(all_outputs))
|
||||
libs_file.write('\n')
|
||||
elif os.path.isfile(native_libs):
|
||||
log.info("removing %s", native_libs)
|
||||
if not self.dry_run:
|
||||
@@ -232,9 +232,11 @@ class bdist_egg(Command):
|
||||
remove_tree(self.bdist_dir, dry_run=self.dry_run)
|
||||
|
||||
# Add to 'Distribution.dist_files' so that the "upload" command works
|
||||
getattr(self.distribution, 'dist_files', []).append(
|
||||
('bdist_egg', get_python_version(), self.egg_output)
|
||||
)
|
||||
getattr(self.distribution, 'dist_files', []).append((
|
||||
'bdist_egg',
|
||||
get_python_version(),
|
||||
self.egg_output,
|
||||
))
|
||||
|
||||
def zap_pyfiles(self):
|
||||
log.info("Removing .py files from temporary directory")
|
||||
@@ -289,9 +291,11 @@ class bdist_egg(Command):
|
||||
|
||||
paths = {self.bdist_dir: ''}
|
||||
for base, dirs, files in sorted_walk(self.bdist_dir):
|
||||
for filename in files:
|
||||
if os.path.splitext(filename)[1].lower() in NATIVE_EXTENSIONS:
|
||||
all_outputs.append(paths[base] + filename)
|
||||
all_outputs.extend(
|
||||
paths[base] + filename
|
||||
for filename in files
|
||||
if os.path.splitext(filename)[1].lower() in NATIVE_EXTENSIONS
|
||||
)
|
||||
for filename in dirs:
|
||||
paths[os.path.join(base, filename)] = paths[base] + filename + '/'
|
||||
|
||||
@@ -319,8 +323,7 @@ def walk_egg(egg_dir):
|
||||
if 'EGG-INFO' in dirs:
|
||||
dirs.remove('EGG-INFO')
|
||||
yield base, dirs, files
|
||||
for bdf in walker:
|
||||
yield bdf
|
||||
yield from walker
|
||||
|
||||
|
||||
def analyze_egg(egg_dir, stubs):
|
||||
@@ -349,9 +352,8 @@ def write_safety_flag(egg_dir, safe):
|
||||
if safe is None or bool(safe) != flag:
|
||||
os.unlink(fn)
|
||||
elif safe is not None and bool(safe) == flag:
|
||||
f = open(fn, 'wt')
|
||||
f.write('\n')
|
||||
f.close()
|
||||
with open(fn, 'wt', encoding="utf-8") as f:
|
||||
f.write('\n')
|
||||
|
||||
|
||||
safety_flags = {
|
||||
@@ -368,10 +370,7 @@ def scan_module(egg_dir, base, name, stubs):
|
||||
return True # Extension module
|
||||
pkg = base[len(egg_dir) + 1 :].replace(os.sep, '.')
|
||||
module = pkg + (pkg and '.' or '') + os.path.splitext(name)[0]
|
||||
if sys.version_info < (3, 7):
|
||||
skip = 12 # skip magic & date & file size
|
||||
else:
|
||||
skip = 16 # skip magic & reserved? & date & file size
|
||||
skip = 16 # skip magic & reserved? & date & file size
|
||||
f = open(filename, 'rb')
|
||||
f.read(skip)
|
||||
code = marshal.load(f)
|
||||
@@ -386,8 +385,9 @@ def scan_module(egg_dir, base, name, stubs):
|
||||
for bad in [
|
||||
'getsource',
|
||||
'getabsfile',
|
||||
'getfile',
|
||||
'getsourcefile',
|
||||
'getfile' 'getsourcelines',
|
||||
'getsourcelines',
|
||||
'findsource',
|
||||
'getcomments',
|
||||
'getframeinfo',
|
||||
@@ -404,14 +404,12 @@ def scan_module(egg_dir, base, name, stubs):
|
||||
|
||||
def iter_symbols(code):
|
||||
"""Yield names and strings used by `code` and its nested code objects"""
|
||||
for name in code.co_names:
|
||||
yield name
|
||||
yield from code.co_names
|
||||
for const in code.co_consts:
|
||||
if isinstance(const, str):
|
||||
yield const
|
||||
elif isinstance(const, CodeType):
|
||||
for name in iter_symbols(const):
|
||||
yield name
|
||||
yield from iter_symbols(const)
|
||||
|
||||
|
||||
def can_scan():
|
||||
@@ -423,6 +421,7 @@ def can_scan():
|
||||
"Please ask the author to include a 'zip_safe'"
|
||||
" setting (either True or False) in the package's setup.py"
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
# Attribute names of options for commands that might need to be convinced to
|
||||
@@ -431,7 +430,9 @@ def can_scan():
|
||||
INSTALL_DIRECTORY_ATTRS = ['install_lib', 'install_dir', 'install_data', 'install_base']
|
||||
|
||||
|
||||
def make_zipfile(zip_filename, base_dir, verbose=0, dry_run=0, compress=True, mode='w'):
|
||||
def make_zipfile(
|
||||
zip_filename, base_dir, verbose=False, dry_run=False, compress=True, mode='w'
|
||||
):
|
||||
"""Create a zip file from all the files under 'base_dir'. The output
|
||||
zip file will be named 'base_dir' + ".zip". Uses either the "zipfile"
|
||||
Python module (if available) or the InfoZIP "zip" utility (if installed
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import distutils.command.bdist_rpm as orig
|
||||
|
||||
from ..dist import Distribution
|
||||
from ..warnings import SetuptoolsDeprecationWarning
|
||||
|
||||
import distutils.command.bdist_rpm as orig
|
||||
|
||||
|
||||
class bdist_rpm(orig.bdist_rpm):
|
||||
"""
|
||||
@@ -12,6 +13,8 @@ class bdist_rpm(orig.bdist_rpm):
|
||||
disable eggs in RPM distributions.
|
||||
"""
|
||||
|
||||
distribution: Distribution # override distutils.dist.Distribution with setuptools.dist.Distribution
|
||||
|
||||
def run(self):
|
||||
SetuptoolsDeprecationWarning.emit(
|
||||
"Deprecated command",
|
||||
@@ -30,11 +33,10 @@ class bdist_rpm(orig.bdist_rpm):
|
||||
|
||||
def _make_spec_file(self):
|
||||
spec = orig.bdist_rpm._make_spec_file(self)
|
||||
spec = [
|
||||
return [
|
||||
line.replace(
|
||||
"setup.py install ",
|
||||
"setup.py install --single-version-externally-managed ",
|
||||
).replace("%setup", "%setup -n %{name}-%{unmangled_version}")
|
||||
for line in spec
|
||||
]
|
||||
return spec
|
||||
|
||||
635
.CondaPkg/env/Lib/site-packages/setuptools/command/bdist_wheel.py
vendored
Normal file
635
.CondaPkg/env/Lib/site-packages/setuptools/command/bdist_wheel.py
vendored
Normal file
@@ -0,0 +1,635 @@
|
||||
"""
|
||||
Create a wheel (.whl) distribution.
|
||||
|
||||
A wheel is a built archive format.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import stat
|
||||
import struct
|
||||
import sys
|
||||
import sysconfig
|
||||
import warnings
|
||||
from email.generator import BytesGenerator, Generator
|
||||
from email.policy import EmailPolicy
|
||||
from glob import iglob
|
||||
from shutil import rmtree
|
||||
from typing import TYPE_CHECKING, Callable, Iterable, Literal, Sequence, cast
|
||||
from zipfile import ZIP_DEFLATED, ZIP_STORED
|
||||
|
||||
from packaging import tags, version as _packaging_version
|
||||
from wheel.metadata import pkginfo_to_metadata
|
||||
from wheel.wheelfile import WheelFile
|
||||
|
||||
from .. import Command, __version__
|
||||
from ..warnings import SetuptoolsDeprecationWarning
|
||||
from .egg_info import egg_info as egg_info_cls
|
||||
|
||||
from distutils import log
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from _typeshed import ExcInfo
|
||||
|
||||
|
||||
def safe_name(name: str) -> str:
|
||||
"""Convert an arbitrary string to a standard distribution name
|
||||
Any runs of non-alphanumeric/. characters are replaced with a single '-'.
|
||||
"""
|
||||
return re.sub("[^A-Za-z0-9.]+", "-", name)
|
||||
|
||||
|
||||
def safe_version(version: str) -> str:
|
||||
"""
|
||||
Convert an arbitrary string to a standard version string
|
||||
"""
|
||||
try:
|
||||
# normalize the version
|
||||
return str(_packaging_version.Version(version))
|
||||
except _packaging_version.InvalidVersion:
|
||||
version = version.replace(" ", ".")
|
||||
return re.sub("[^A-Za-z0-9.]+", "-", version)
|
||||
|
||||
|
||||
setuptools_major_version = int(__version__.split(".")[0])
|
||||
|
||||
PY_LIMITED_API_PATTERN = r"cp3\d"
|
||||
|
||||
|
||||
def _is_32bit_interpreter() -> bool:
|
||||
return struct.calcsize("P") == 4
|
||||
|
||||
|
||||
def python_tag() -> str:
|
||||
return f"py{sys.version_info[0]}"
|
||||
|
||||
|
||||
def get_platform(archive_root: str | None) -> str:
|
||||
"""Return our platform name 'win32', 'linux_x86_64'"""
|
||||
result = sysconfig.get_platform()
|
||||
if result.startswith("macosx") and archive_root is not None: # pragma: no cover
|
||||
from wheel.macosx_libfile import calculate_macosx_platform_tag
|
||||
|
||||
result = calculate_macosx_platform_tag(archive_root, result)
|
||||
elif _is_32bit_interpreter():
|
||||
if result == "linux-x86_64":
|
||||
# pip pull request #3497
|
||||
result = "linux-i686"
|
||||
elif result == "linux-aarch64":
|
||||
# packaging pull request #234
|
||||
# TODO armv8l, packaging pull request #690 => this did not land
|
||||
# in pip/packaging yet
|
||||
result = "linux-armv7l"
|
||||
|
||||
return result.replace("-", "_")
|
||||
|
||||
|
||||
def get_flag(
|
||||
var: str, fallback: bool, expected: bool = True, warn: bool = True
|
||||
) -> bool:
|
||||
"""Use a fallback value for determining SOABI flags if the needed config
|
||||
var is unset or unavailable."""
|
||||
val = sysconfig.get_config_var(var)
|
||||
if val is None:
|
||||
if warn:
|
||||
warnings.warn(
|
||||
f"Config variable '{var}' is unset, Python ABI tag may be incorrect",
|
||||
RuntimeWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return fallback
|
||||
return val == expected
|
||||
|
||||
|
||||
def get_abi_tag() -> str | None:
|
||||
"""Return the ABI tag based on SOABI (if available) or emulate SOABI (PyPy2)."""
|
||||
soabi: str = sysconfig.get_config_var("SOABI")
|
||||
impl = tags.interpreter_name()
|
||||
if not soabi and impl in ("cp", "pp") and hasattr(sys, "maxunicode"):
|
||||
d = ""
|
||||
m = ""
|
||||
u = ""
|
||||
if get_flag("Py_DEBUG", hasattr(sys, "gettotalrefcount"), warn=(impl == "cp")):
|
||||
d = "d"
|
||||
|
||||
if get_flag(
|
||||
"WITH_PYMALLOC",
|
||||
impl == "cp",
|
||||
warn=(impl == "cp" and sys.version_info < (3, 8)),
|
||||
) and sys.version_info < (3, 8):
|
||||
m = "m"
|
||||
|
||||
abi = f"{impl}{tags.interpreter_version()}{d}{m}{u}"
|
||||
elif soabi and impl == "cp" and soabi.startswith("cpython"):
|
||||
# non-Windows
|
||||
abi = "cp" + soabi.split("-")[1]
|
||||
elif soabi and impl == "cp" and soabi.startswith("cp"):
|
||||
# Windows
|
||||
abi = soabi.split("-")[0]
|
||||
elif soabi and impl == "pp":
|
||||
# we want something like pypy36-pp73
|
||||
abi = "-".join(soabi.split("-")[:2])
|
||||
abi = abi.replace(".", "_").replace("-", "_")
|
||||
elif soabi and impl == "graalpy":
|
||||
abi = "-".join(soabi.split("-")[:3])
|
||||
abi = abi.replace(".", "_").replace("-", "_")
|
||||
elif soabi:
|
||||
abi = soabi.replace(".", "_").replace("-", "_")
|
||||
else:
|
||||
abi = None
|
||||
|
||||
return abi
|
||||
|
||||
|
||||
def safer_name(name: str) -> str:
|
||||
return safe_name(name).replace("-", "_")
|
||||
|
||||
|
||||
def safer_version(version: str) -> str:
|
||||
return safe_version(version).replace("-", "_")
|
||||
|
||||
|
||||
def remove_readonly(
|
||||
func: Callable[..., object],
|
||||
path: str,
|
||||
excinfo: ExcInfo,
|
||||
) -> None:
|
||||
remove_readonly_exc(func, path, excinfo[1])
|
||||
|
||||
|
||||
def remove_readonly_exc(
|
||||
func: Callable[..., object], path: str, exc: BaseException
|
||||
) -> None:
|
||||
os.chmod(path, stat.S_IWRITE)
|
||||
func(path)
|
||||
|
||||
|
||||
class bdist_wheel(Command):
|
||||
description = "create a wheel distribution"
|
||||
|
||||
supported_compressions = {
|
||||
"stored": ZIP_STORED,
|
||||
"deflated": ZIP_DEFLATED,
|
||||
}
|
||||
|
||||
user_options = [
|
||||
("bdist-dir=", "b", "temporary directory for creating the distribution"),
|
||||
(
|
||||
"plat-name=",
|
||||
"p",
|
||||
"platform name to embed in generated filenames "
|
||||
f"[default: {get_platform(None)}]",
|
||||
),
|
||||
(
|
||||
"keep-temp",
|
||||
"k",
|
||||
"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]",
|
||||
),
|
||||
(
|
||||
"owner=",
|
||||
"u",
|
||||
"Owner name used when creating a tar file [default: current user]",
|
||||
),
|
||||
(
|
||||
"group=",
|
||||
"g",
|
||||
"Group name used when creating a tar file [default: current group]",
|
||||
),
|
||||
("universal", None, "*DEPRECATED* make a universal wheel [default: false]"),
|
||||
(
|
||||
"compression=",
|
||||
None,
|
||||
"zipfile compression (one of: {}) [default: 'deflated']".format(
|
||||
", ".join(supported_compressions)
|
||||
),
|
||||
),
|
||||
(
|
||||
"python-tag=",
|
||||
None,
|
||||
f"Python implementation compatibility tag [default: '{python_tag()}']",
|
||||
),
|
||||
(
|
||||
"build-number=",
|
||||
None,
|
||||
"Build number for this particular version. "
|
||||
"As specified in PEP-0427, this must start with a digit. "
|
||||
"[default: None]",
|
||||
),
|
||||
(
|
||||
"py-limited-api=",
|
||||
None,
|
||||
"Python tag (cp32|cp33|cpNN) for abi3 wheel tag [default: false]",
|
||||
),
|
||||
]
|
||||
|
||||
boolean_options = ["keep-temp", "skip-build", "relative", "universal"]
|
||||
|
||||
def initialize_options(self) -> None:
|
||||
self.bdist_dir: str | None = None
|
||||
self.data_dir: str | None = None
|
||||
self.plat_name: str | None = None
|
||||
self.plat_tag: str | None = None
|
||||
self.format = "zip"
|
||||
self.keep_temp = False
|
||||
self.dist_dir: str | None = None
|
||||
self.egginfo_dir: str | None = None
|
||||
self.root_is_pure: bool | None = None
|
||||
self.skip_build = False
|
||||
self.relative = False
|
||||
self.owner = None
|
||||
self.group = None
|
||||
self.universal: bool = False
|
||||
self.compression: int | str = "deflated"
|
||||
self.python_tag: str = python_tag()
|
||||
self.build_number: str | None = None
|
||||
self.py_limited_api: str | Literal[False] = False
|
||||
self.plat_name_supplied = False
|
||||
|
||||
def finalize_options(self) -> None:
|
||||
if not self.bdist_dir:
|
||||
bdist_base = self.get_finalized_command("bdist").bdist_base
|
||||
self.bdist_dir = os.path.join(bdist_base, "wheel")
|
||||
|
||||
egg_info = cast(egg_info_cls, self.distribution.get_command_obj("egg_info"))
|
||||
egg_info.ensure_finalized() # needed for correct `wheel_dist_name`
|
||||
|
||||
self.data_dir = self.wheel_dist_name + ".data"
|
||||
self.plat_name_supplied = bool(self.plat_name)
|
||||
|
||||
need_options = ("dist_dir", "plat_name", "skip_build")
|
||||
|
||||
self.set_undefined_options("bdist", *zip(need_options, need_options))
|
||||
|
||||
self.root_is_pure = not (
|
||||
self.distribution.has_ext_modules() or self.distribution.has_c_libraries()
|
||||
)
|
||||
|
||||
self._validate_py_limited_api()
|
||||
|
||||
# Support legacy [wheel] section for setting universal
|
||||
wheel = self.distribution.get_option_dict("wheel")
|
||||
if "universal" in wheel: # pragma: no cover
|
||||
# please don't define this in your global configs
|
||||
log.warn("The [wheel] section is deprecated. Use [bdist_wheel] instead.")
|
||||
val = wheel["universal"][1].strip()
|
||||
if val.lower() in ("1", "true", "yes"):
|
||||
self.universal = True
|
||||
|
||||
if self.universal:
|
||||
SetuptoolsDeprecationWarning.emit(
|
||||
"bdist_wheel.universal is deprecated",
|
||||
"""
|
||||
With Python 2.7 end-of-life, support for building universal wheels
|
||||
(i.e., wheels that support both Python 2 and Python 3)
|
||||
is being obviated.
|
||||
Please discontinue using this option, or if you still need it,
|
||||
file an issue with pypa/setuptools describing your use case.
|
||||
""",
|
||||
due_date=(2025, 8, 30), # Introduced in 2024-08-30
|
||||
)
|
||||
|
||||
if self.build_number is not None and not self.build_number[:1].isdigit():
|
||||
raise ValueError("Build tag (build-number) must start with a digit.")
|
||||
|
||||
def _validate_py_limited_api(self) -> None:
|
||||
if not self.py_limited_api:
|
||||
return
|
||||
|
||||
if not re.match(PY_LIMITED_API_PATTERN, self.py_limited_api):
|
||||
raise ValueError(f"py-limited-api must match '{PY_LIMITED_API_PATTERN}'")
|
||||
|
||||
if sysconfig.get_config_var("Py_GIL_DISABLED"):
|
||||
raise ValueError(
|
||||
f"`py_limited_api={self.py_limited_api!r}` not supported. "
|
||||
"`Py_LIMITED_API` is currently incompatible with "
|
||||
f"`Py_GIL_DISABLED` ({sys.abiflags=!r}). "
|
||||
"See https://github.com/python/cpython/issues/111506."
|
||||
)
|
||||
|
||||
@property
|
||||
def wheel_dist_name(self) -> str:
|
||||
"""Return distribution full name with - replaced with _"""
|
||||
components = [
|
||||
safer_name(self.distribution.get_name()),
|
||||
safer_version(self.distribution.get_version()),
|
||||
]
|
||||
if self.build_number:
|
||||
components.append(self.build_number)
|
||||
return "-".join(components)
|
||||
|
||||
def get_tag(self) -> tuple[str, str, str]:
|
||||
# bdist sets self.plat_name if unset, we should only use it for purepy
|
||||
# wheels if the user supplied it.
|
||||
if self.plat_name_supplied and self.plat_name:
|
||||
plat_name = self.plat_name
|
||||
elif self.root_is_pure:
|
||||
plat_name = "any"
|
||||
else:
|
||||
# macosx contains system version in platform name so need special handle
|
||||
if self.plat_name and not self.plat_name.startswith("macosx"):
|
||||
plat_name = self.plat_name
|
||||
else:
|
||||
# on macosx always limit the platform name to comply with any
|
||||
# c-extension modules in bdist_dir, since the user can specify
|
||||
# a higher MACOSX_DEPLOYMENT_TARGET via tools like CMake
|
||||
|
||||
# on other platforms, and on macosx if there are no c-extension
|
||||
# modules, use the default platform name.
|
||||
plat_name = get_platform(self.bdist_dir)
|
||||
|
||||
if _is_32bit_interpreter():
|
||||
if plat_name in ("linux-x86_64", "linux_x86_64"):
|
||||
plat_name = "linux_i686"
|
||||
if plat_name in ("linux-aarch64", "linux_aarch64"):
|
||||
# TODO armv8l, packaging pull request #690 => this did not land
|
||||
# in pip/packaging yet
|
||||
plat_name = "linux_armv7l"
|
||||
|
||||
plat_name = (
|
||||
plat_name.lower().replace("-", "_").replace(".", "_").replace(" ", "_")
|
||||
)
|
||||
|
||||
if self.root_is_pure:
|
||||
if self.universal:
|
||||
impl = "py2.py3"
|
||||
else:
|
||||
impl = self.python_tag
|
||||
tag = (impl, "none", plat_name)
|
||||
else:
|
||||
impl_name = tags.interpreter_name()
|
||||
impl_ver = tags.interpreter_version()
|
||||
impl = impl_name + impl_ver
|
||||
# We don't work on CPython 3.1, 3.0.
|
||||
if self.py_limited_api and (impl_name + impl_ver).startswith("cp3"):
|
||||
impl = self.py_limited_api
|
||||
abi_tag = "abi3"
|
||||
else:
|
||||
abi_tag = str(get_abi_tag()).lower()
|
||||
tag = (impl, abi_tag, plat_name)
|
||||
# issue gh-374: allow overriding plat_name
|
||||
supported_tags = [
|
||||
(t.interpreter, t.abi, plat_name) for t in tags.sys_tags()
|
||||
]
|
||||
assert (
|
||||
tag in supported_tags
|
||||
), f"would build wheel with unsupported tag {tag}"
|
||||
return tag
|
||||
|
||||
def run(self):
|
||||
build_scripts = self.reinitialize_command("build_scripts")
|
||||
build_scripts.executable = "python"
|
||||
build_scripts.force = True
|
||||
|
||||
build_ext = self.reinitialize_command("build_ext")
|
||||
build_ext.inplace = False
|
||||
|
||||
if not self.skip_build:
|
||||
self.run_command("build")
|
||||
|
||||
install = self.reinitialize_command("install", reinit_subcommands=True)
|
||||
install.root = self.bdist_dir
|
||||
install.compile = False
|
||||
install.skip_build = self.skip_build
|
||||
install.warn_dir = False
|
||||
|
||||
# A wheel without setuptools scripts is more cross-platform.
|
||||
# Use the (undocumented) `no_ep` option to setuptools'
|
||||
# install_scripts command to avoid creating entry point scripts.
|
||||
install_scripts = self.reinitialize_command("install_scripts")
|
||||
install_scripts.no_ep = True
|
||||
|
||||
# Use a custom scheme for the archive, because we have to decide
|
||||
# at installation time which scheme to use.
|
||||
for key in ("headers", "scripts", "data", "purelib", "platlib"):
|
||||
setattr(install, "install_" + key, os.path.join(self.data_dir, key))
|
||||
|
||||
basedir_observed = ""
|
||||
|
||||
if os.name == "nt":
|
||||
# win32 barfs if any of these are ''; could be '.'?
|
||||
# (distutils.command.install:change_roots bug)
|
||||
basedir_observed = os.path.normpath(os.path.join(self.data_dir, ".."))
|
||||
self.install_libbase = self.install_lib = basedir_observed
|
||||
|
||||
setattr(
|
||||
install,
|
||||
"install_purelib" if self.root_is_pure else "install_platlib",
|
||||
basedir_observed,
|
||||
)
|
||||
|
||||
log.info(f"installing to {self.bdist_dir}")
|
||||
|
||||
self.run_command("install")
|
||||
|
||||
impl_tag, abi_tag, plat_tag = self.get_tag()
|
||||
archive_basename = f"{self.wheel_dist_name}-{impl_tag}-{abi_tag}-{plat_tag}"
|
||||
if not self.relative:
|
||||
archive_root = self.bdist_dir
|
||||
else:
|
||||
archive_root = os.path.join(
|
||||
self.bdist_dir, self._ensure_relative(install.install_base)
|
||||
)
|
||||
|
||||
self.set_undefined_options("install_egg_info", ("target", "egginfo_dir"))
|
||||
distinfo_dirname = (
|
||||
f"{safer_name(self.distribution.get_name())}-"
|
||||
f"{safer_version(self.distribution.get_version())}.dist-info"
|
||||
)
|
||||
distinfo_dir = os.path.join(self.bdist_dir, distinfo_dirname)
|
||||
self.egg2dist(self.egginfo_dir, distinfo_dir)
|
||||
|
||||
self.write_wheelfile(distinfo_dir)
|
||||
|
||||
# Make the archive
|
||||
if not os.path.exists(self.dist_dir):
|
||||
os.makedirs(self.dist_dir)
|
||||
|
||||
wheel_path = os.path.join(self.dist_dir, archive_basename + ".whl")
|
||||
with WheelFile(wheel_path, "w", self._zip_compression()) as wf:
|
||||
wf.write_files(archive_root)
|
||||
|
||||
# Add to 'Distribution.dist_files' so that the "upload" command works
|
||||
getattr(self.distribution, "dist_files", []).append((
|
||||
"bdist_wheel",
|
||||
"{}.{}".format(*sys.version_info[:2]), # like 3.7
|
||||
wheel_path,
|
||||
))
|
||||
|
||||
if not self.keep_temp:
|
||||
log.info(f"removing {self.bdist_dir}")
|
||||
if not self.dry_run:
|
||||
if sys.version_info < (3, 12):
|
||||
rmtree(self.bdist_dir, onerror=remove_readonly)
|
||||
else:
|
||||
rmtree(self.bdist_dir, onexc=remove_readonly_exc)
|
||||
|
||||
def write_wheelfile(
|
||||
self, wheelfile_base: str, generator: str = f"setuptools ({__version__})"
|
||||
) -> None:
|
||||
from email.message import Message
|
||||
|
||||
msg = Message()
|
||||
msg["Wheel-Version"] = "1.0" # of the spec
|
||||
msg["Generator"] = generator
|
||||
msg["Root-Is-Purelib"] = str(self.root_is_pure).lower()
|
||||
if self.build_number is not None:
|
||||
msg["Build"] = self.build_number
|
||||
|
||||
# Doesn't work for bdist_wininst
|
||||
impl_tag, abi_tag, plat_tag = self.get_tag()
|
||||
for impl in impl_tag.split("."):
|
||||
for abi in abi_tag.split("."):
|
||||
for plat in plat_tag.split("."):
|
||||
msg["Tag"] = "-".join((impl, abi, plat))
|
||||
|
||||
wheelfile_path = os.path.join(wheelfile_base, "WHEEL")
|
||||
log.info(f"creating {wheelfile_path}")
|
||||
with open(wheelfile_path, "wb") as f:
|
||||
BytesGenerator(f, maxheaderlen=0).flatten(msg)
|
||||
|
||||
def _ensure_relative(self, path: str) -> str:
|
||||
# copied from dir_util, deleted
|
||||
drive, path = os.path.splitdrive(path)
|
||||
if path[0:1] == os.sep:
|
||||
path = drive + path[1:]
|
||||
return path
|
||||
|
||||
@property
|
||||
def license_paths(self) -> Iterable[str]:
|
||||
if setuptools_major_version >= 57:
|
||||
# Setuptools has resolved any patterns to actual file names
|
||||
return self.distribution.metadata.license_files or ()
|
||||
|
||||
files: set[str] = set()
|
||||
metadata = self.distribution.get_option_dict("metadata")
|
||||
if setuptools_major_version >= 42:
|
||||
# Setuptools recognizes the license_files option but does not do globbing
|
||||
patterns = cast(Sequence[str], self.distribution.metadata.license_files)
|
||||
else:
|
||||
# Prior to those, wheel is entirely responsible for handling license files
|
||||
if "license_files" in metadata:
|
||||
patterns = metadata["license_files"][1].split()
|
||||
else:
|
||||
patterns = ()
|
||||
|
||||
if "license_file" in metadata:
|
||||
warnings.warn(
|
||||
'The "license_file" option is deprecated. Use "license_files" instead.',
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
files.add(metadata["license_file"][1])
|
||||
|
||||
if not files and not patterns and not isinstance(patterns, list):
|
||||
patterns = ("LICEN[CS]E*", "COPYING*", "NOTICE*", "AUTHORS*")
|
||||
|
||||
for pattern in patterns:
|
||||
for path in iglob(pattern):
|
||||
if path.endswith("~"):
|
||||
log.debug(
|
||||
f'ignoring license file "{path}" as it looks like a backup'
|
||||
)
|
||||
continue
|
||||
|
||||
if path not in files and os.path.isfile(path):
|
||||
log.info(
|
||||
f'adding license file "{path}" (matched pattern "{pattern}")'
|
||||
)
|
||||
files.add(path)
|
||||
|
||||
return files
|
||||
|
||||
def egg2dist(self, egginfo_path: str, distinfo_path: str) -> None:
|
||||
"""Convert an .egg-info directory into a .dist-info directory"""
|
||||
|
||||
def adios(p: str) -> None:
|
||||
"""Appropriately delete directory, file or link."""
|
||||
if os.path.exists(p) and not os.path.islink(p) and os.path.isdir(p):
|
||||
shutil.rmtree(p)
|
||||
elif os.path.exists(p):
|
||||
os.unlink(p)
|
||||
|
||||
adios(distinfo_path)
|
||||
|
||||
if not os.path.exists(egginfo_path):
|
||||
# There is no egg-info. This is probably because the egg-info
|
||||
# file/directory is not named matching the distribution name used
|
||||
# to name the archive file. Check for this case and report
|
||||
# accordingly.
|
||||
import glob
|
||||
|
||||
pat = os.path.join(os.path.dirname(egginfo_path), "*.egg-info")
|
||||
possible = glob.glob(pat)
|
||||
err = f"Egg metadata expected at {egginfo_path} but not found"
|
||||
if possible:
|
||||
alt = os.path.basename(possible[0])
|
||||
err += f" ({alt} found - possible misnamed archive file?)"
|
||||
|
||||
raise ValueError(err)
|
||||
|
||||
if os.path.isfile(egginfo_path):
|
||||
# .egg-info is a single file
|
||||
pkg_info = pkginfo_to_metadata(egginfo_path, egginfo_path)
|
||||
os.mkdir(distinfo_path)
|
||||
else:
|
||||
# .egg-info is a directory
|
||||
pkginfo_path = os.path.join(egginfo_path, "PKG-INFO")
|
||||
pkg_info = pkginfo_to_metadata(egginfo_path, pkginfo_path)
|
||||
|
||||
# ignore common egg metadata that is useless to wheel
|
||||
shutil.copytree(
|
||||
egginfo_path,
|
||||
distinfo_path,
|
||||
ignore=lambda x, y: {
|
||||
"PKG-INFO",
|
||||
"requires.txt",
|
||||
"SOURCES.txt",
|
||||
"not-zip-safe",
|
||||
},
|
||||
)
|
||||
|
||||
# delete dependency_links if it is only whitespace
|
||||
dependency_links_path = os.path.join(distinfo_path, "dependency_links.txt")
|
||||
with open(dependency_links_path, encoding="utf-8") as dependency_links_file:
|
||||
dependency_links = dependency_links_file.read().strip()
|
||||
if not dependency_links:
|
||||
adios(dependency_links_path)
|
||||
|
||||
pkg_info_path = os.path.join(distinfo_path, "METADATA")
|
||||
serialization_policy = EmailPolicy(
|
||||
utf8=True,
|
||||
mangle_from_=False,
|
||||
max_line_length=0,
|
||||
)
|
||||
with open(pkg_info_path, "w", encoding="utf-8") as out:
|
||||
Generator(out, policy=serialization_policy).flatten(pkg_info)
|
||||
|
||||
for license_path in self.license_paths:
|
||||
filename = os.path.basename(license_path)
|
||||
shutil.copy(license_path, os.path.join(distinfo_path, filename))
|
||||
|
||||
adios(egginfo_path)
|
||||
|
||||
def _zip_compression(self) -> int:
|
||||
if (
|
||||
isinstance(self.compression, int)
|
||||
and self.compression in self.supported_compressions.values()
|
||||
):
|
||||
return self.compression
|
||||
|
||||
compression = self.supported_compressions.get(str(self.compression))
|
||||
if compression is not None:
|
||||
return compression
|
||||
|
||||
raise ValueError(f"Unsupported compression: {self.compression!r}")
|
||||
@@ -1,40 +1,20 @@
|
||||
import sys
|
||||
from typing import TYPE_CHECKING, List, Dict
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Protocol
|
||||
|
||||
from ..dist import Distribution
|
||||
|
||||
from distutils.command.build import build as _build
|
||||
|
||||
from ..warnings import SetuptoolsDeprecationWarning
|
||||
|
||||
if sys.version_info >= (3, 8):
|
||||
from typing import Protocol
|
||||
elif TYPE_CHECKING:
|
||||
from typing_extensions import Protocol
|
||||
else:
|
||||
from abc import ABC as Protocol
|
||||
|
||||
|
||||
_ORIGINAL_SUBCOMMANDS = {"build_py", "build_clib", "build_ext", "build_scripts"}
|
||||
|
||||
|
||||
class build(_build):
|
||||
distribution: Distribution # override distutils.dist.Distribution with setuptools.dist.Distribution
|
||||
|
||||
# copy to avoid sharing the object with parent class
|
||||
sub_commands = _build.sub_commands[:]
|
||||
|
||||
def get_sub_commands(self):
|
||||
subcommands = {cmd[0] for cmd in _build.sub_commands}
|
||||
if subcommands - _ORIGINAL_SUBCOMMANDS:
|
||||
SetuptoolsDeprecationWarning.emit(
|
||||
"Direct usage of `distutils` commands",
|
||||
"""
|
||||
It seems that you are using `distutils.command.build` to add
|
||||
new subcommands. Using `distutils` directly is considered deprecated,
|
||||
please use `setuptools.command.build`.
|
||||
""",
|
||||
due_date=(2023, 12, 13), # Warning introduced in 13 Jun 2022.
|
||||
see_url="https://peps.python.org/pep-0632/",
|
||||
)
|
||||
self.sub_commands = _build.sub_commands
|
||||
return super().get_sub_commands()
|
||||
|
||||
|
||||
class SubCommand(Protocol):
|
||||
"""In order to support editable installations (see :pep:`660`) all
|
||||
@@ -107,14 +87,17 @@ class SubCommand(Protocol):
|
||||
|
||||
def initialize_options(self):
|
||||
"""(Required by the original :class:`setuptools.Command` interface)"""
|
||||
...
|
||||
|
||||
def finalize_options(self):
|
||||
"""(Required by the original :class:`setuptools.Command` interface)"""
|
||||
...
|
||||
|
||||
def run(self):
|
||||
"""(Required by the original :class:`setuptools.Command` interface)"""
|
||||
...
|
||||
|
||||
def get_source_files(self) -> List[str]:
|
||||
def get_source_files(self) -> list[str]:
|
||||
"""
|
||||
Return a list of all files that are used by the command to create the expected
|
||||
outputs.
|
||||
@@ -124,8 +107,9 @@ class SubCommand(Protocol):
|
||||
with all the files necessary to build the distribution.
|
||||
All files should be strings relative to the project root directory.
|
||||
"""
|
||||
...
|
||||
|
||||
def get_outputs(self) -> List[str]:
|
||||
def get_outputs(self) -> list[str]:
|
||||
"""
|
||||
Return a list of files intended for distribution as they would have been
|
||||
produced by the build.
|
||||
@@ -137,8 +121,9 @@ class SubCommand(Protocol):
|
||||
in ``get_output_mapping()`` plus files that are generated during the build
|
||||
and don't correspond to any source file already present in the project.
|
||||
"""
|
||||
...
|
||||
|
||||
def get_output_mapping(self) -> Dict[str, str]:
|
||||
def get_output_mapping(self) -> dict[str, str]:
|
||||
"""
|
||||
Return a mapping between destination files as they would be produced by the
|
||||
build (dict keys) into the respective existing (source) files (dict values).
|
||||
@@ -147,3 +132,4 @@ class SubCommand(Protocol):
|
||||
Destination files should be strings in the form of
|
||||
``"{build_lib}/destination/file/path"``.
|
||||
"""
|
||||
...
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
from ..dist import Distribution
|
||||
|
||||
import distutils.command.build_clib as orig
|
||||
from distutils.errors import DistutilsSetupError
|
||||
from distutils import log
|
||||
from setuptools.dep_util import newer_pairwise_group
|
||||
from distutils.errors import DistutilsSetupError
|
||||
|
||||
try:
|
||||
from distutils._modified import ( # pyright: ignore[reportMissingImports]
|
||||
newer_pairwise_group,
|
||||
)
|
||||
except ImportError:
|
||||
# fallback for SETUPTOOLS_USE_DISTUTILS=stdlib
|
||||
from .._distutils._modified import newer_pairwise_group
|
||||
|
||||
|
||||
class build_clib(orig.build_clib):
|
||||
@@ -20,6 +29,8 @@ class build_clib(orig.build_clib):
|
||||
the compiler.
|
||||
"""
|
||||
|
||||
distribution: Distribution # override distutils.dist.Distribution with setuptools.dist.Distribution
|
||||
|
||||
def build_libraries(self, libraries):
|
||||
for lib_name, build_info in libraries:
|
||||
sources = build_info.get('sources')
|
||||
|
||||
@@ -1,32 +1,40 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import itertools
|
||||
import os
|
||||
import sys
|
||||
import itertools
|
||||
from importlib.machinery import EXTENSION_SUFFIXES
|
||||
from importlib.util import cache_from_source as _compiled_file_name
|
||||
from typing import Dict, Iterator, List, Tuple
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Iterator
|
||||
|
||||
from distutils.command.build_ext import build_ext as _du_build_ext
|
||||
from distutils.ccompiler import new_compiler
|
||||
from distutils.sysconfig import customize_compiler, get_config_var
|
||||
from distutils import log
|
||||
|
||||
from setuptools.dist import Distribution
|
||||
from setuptools.errors import BaseError
|
||||
from setuptools.extension import Extension, Library
|
||||
|
||||
try:
|
||||
# Attempt to use Cython for building extensions, if available
|
||||
from Cython.Distutils.build_ext import build_ext as _build_ext
|
||||
from distutils import log
|
||||
from distutils.ccompiler import new_compiler
|
||||
from distutils.sysconfig import customize_compiler, get_config_var
|
||||
|
||||
# Additionally, assert that the compiler module will load
|
||||
# also. Ref #1229.
|
||||
__import__('Cython.Compiler.Main')
|
||||
except ImportError:
|
||||
_build_ext = _du_build_ext
|
||||
if TYPE_CHECKING:
|
||||
# Cython not installed on CI tests, causing _build_ext to be `Any`
|
||||
from distutils.command.build_ext import build_ext as _build_ext
|
||||
else:
|
||||
try:
|
||||
# Attempt to use Cython for building extensions, if available
|
||||
from Cython.Distutils.build_ext import build_ext as _build_ext
|
||||
|
||||
# Additionally, assert that the compiler module will load
|
||||
# also. Ref #1229.
|
||||
__import__('Cython.Compiler.Main')
|
||||
except ImportError:
|
||||
from distutils.command.build_ext import build_ext as _build_ext
|
||||
|
||||
# make sure _config_vars is initialized
|
||||
get_config_var("LDSHARED")
|
||||
from distutils.sysconfig import _config_vars as _CONFIG_VARS # noqa
|
||||
# Not publicly exposed in typeshed distutils stubs, but this is done on purpose
|
||||
# See https://github.com/pypa/setuptools/pull/4228#issuecomment-1959856400
|
||||
from distutils.sysconfig import _config_vars as _CONFIG_VARS # noqa: E402
|
||||
|
||||
|
||||
def _customize_compiler_for_shlib(compiler):
|
||||
@@ -37,9 +45,9 @@ def _customize_compiler_for_shlib(compiler):
|
||||
tmp = _CONFIG_VARS.copy()
|
||||
try:
|
||||
# XXX Help! I don't have any idea whether these are right...
|
||||
_CONFIG_VARS[
|
||||
'LDSHARED'
|
||||
] = "gcc -Wl,-x -dynamiclib -undefined dynamic_lookup"
|
||||
_CONFIG_VARS['LDSHARED'] = (
|
||||
"gcc -Wl,-x -dynamiclib -undefined dynamic_lookup"
|
||||
)
|
||||
_CONFIG_VARS['CCSHARED'] = " -dynamiclib"
|
||||
_CONFIG_VARS['SO'] = ".dylib"
|
||||
customize_compiler(compiler)
|
||||
@@ -58,7 +66,7 @@ if sys.platform == "darwin":
|
||||
use_stubs = True
|
||||
elif os.name != 'nt':
|
||||
try:
|
||||
import dl
|
||||
import dl # type: ignore[import-not-found] # https://github.com/python/mypy/issues/13002
|
||||
|
||||
use_stubs = have_rtld = hasattr(dl, 'RTLD_NOW')
|
||||
except ImportError:
|
||||
@@ -76,9 +84,11 @@ def get_abi3_suffix():
|
||||
return suffix
|
||||
elif suffix == '.pyd': # Windows
|
||||
return suffix
|
||||
return None
|
||||
|
||||
|
||||
class build_ext(_build_ext):
|
||||
distribution: Distribution # override distutils.dist.Distribution with setuptools.dist.Distribution
|
||||
editable_mode: bool = False
|
||||
inplace: bool = False
|
||||
|
||||
@@ -90,7 +100,7 @@ class build_ext(_build_ext):
|
||||
if old_inplace:
|
||||
self.copy_extensions_to_source()
|
||||
|
||||
def _get_inplace_equivalent(self, build_py, ext: Extension) -> Tuple[str, str]:
|
||||
def _get_inplace_equivalent(self, build_py, ext: Extension) -> tuple[str, str]:
|
||||
fullname = self.get_ext_fullname(ext.name)
|
||||
filename = self.get_ext_filename(fullname)
|
||||
modpath = fullname.split('.')
|
||||
@@ -122,7 +132,7 @@ class build_ext(_build_ext):
|
||||
_, _, name = ext.name.rpartition(".")
|
||||
return f"{os.path.join(dir_, name)}.py"
|
||||
|
||||
def _get_output_mapping(self) -> Iterator[Tuple[str, str]]:
|
||||
def _get_output_mapping(self) -> Iterator[tuple[str, str]]:
|
||||
if not self.inplace:
|
||||
return
|
||||
|
||||
@@ -147,21 +157,25 @@ class build_ext(_build_ext):
|
||||
output_cache = _compiled_file_name(regular_stub, optimization=opt)
|
||||
yield (output_cache, inplace_cache)
|
||||
|
||||
def get_ext_filename(self, fullname):
|
||||
def get_ext_filename(self, fullname: str) -> str:
|
||||
so_ext = os.getenv('SETUPTOOLS_EXT_SUFFIX')
|
||||
if so_ext:
|
||||
filename = os.path.join(*fullname.split('.')) + so_ext
|
||||
else:
|
||||
filename = _build_ext.get_ext_filename(self, fullname)
|
||||
so_ext = get_config_var('EXT_SUFFIX')
|
||||
ext_suffix = get_config_var('EXT_SUFFIX')
|
||||
if not isinstance(ext_suffix, str):
|
||||
raise OSError(
|
||||
"Configuration variable EXT_SUFFIX not found for this platform "
|
||||
+ "and environment variable SETUPTOOLS_EXT_SUFFIX is missing"
|
||||
)
|
||||
so_ext = ext_suffix
|
||||
|
||||
if fullname in self.ext_map:
|
||||
ext = self.ext_map[fullname]
|
||||
use_abi3 = getattr(ext, 'py_limited_api') and get_abi3_suffix()
|
||||
if use_abi3:
|
||||
filename = filename[: -len(so_ext)]
|
||||
so_ext = get_abi3_suffix()
|
||||
filename = filename + so_ext
|
||||
abi3_suffix = get_abi3_suffix()
|
||||
if ext.py_limited_api and abi3_suffix: # Use abi3
|
||||
filename = filename[: -len(so_ext)] + abi3_suffix
|
||||
if isinstance(ext, Library):
|
||||
fn, ext = os.path.splitext(filename)
|
||||
return self.shlib_compiler.library_filename(fn, libtype)
|
||||
@@ -262,7 +276,7 @@ class build_ext(_build_ext):
|
||||
pkg = '.'.join(ext._full_name.split('.')[:-1] + [''])
|
||||
return any(pkg + libname in libnames for libname in ext.libraries)
|
||||
|
||||
def get_source_files(self) -> List[str]:
|
||||
def get_source_files(self) -> list[str]:
|
||||
return [*_build_ext.get_source_files(self), *self._get_internal_depends()]
|
||||
|
||||
def _get_internal_depends(self) -> Iterator[str]:
|
||||
@@ -303,12 +317,12 @@ class build_ext(_build_ext):
|
||||
|
||||
yield path.as_posix()
|
||||
|
||||
def get_outputs(self) -> List[str]:
|
||||
def get_outputs(self) -> list[str]:
|
||||
if self.inplace:
|
||||
return list(self.get_output_mapping().keys())
|
||||
return sorted(_build_ext.get_outputs(self) + self.__get_stubs_outputs())
|
||||
|
||||
def get_output_mapping(self) -> Dict[str, str]:
|
||||
def get_output_mapping(self) -> dict[str, str]:
|
||||
"""See :class:`setuptools.commands.build.SubCommand`"""
|
||||
mapping = self._get_output_mapping()
|
||||
return dict(sorted(mapping, key=lambda x: x[0]))
|
||||
@@ -339,37 +353,32 @@ class build_ext(_build_ext):
|
||||
if compile and os.path.exists(stub_file):
|
||||
raise BaseError(stub_file + " already exists! Please delete.")
|
||||
if not self.dry_run:
|
||||
f = open(stub_file, 'w')
|
||||
f.write(
|
||||
'\n'.join(
|
||||
[
|
||||
"def __bootstrap__():",
|
||||
" global __bootstrap__, __file__, __loader__",
|
||||
" import sys, os, pkg_resources, importlib.util"
|
||||
+ if_dl(", dl"),
|
||||
" __file__ = pkg_resources.resource_filename"
|
||||
"(__name__,%r)" % os.path.basename(ext._file_name),
|
||||
" del __bootstrap__",
|
||||
" if '__loader__' in globals():",
|
||||
" del __loader__",
|
||||
if_dl(" old_flags = sys.getdlopenflags()"),
|
||||
" old_dir = os.getcwd()",
|
||||
" try:",
|
||||
" os.chdir(os.path.dirname(__file__))",
|
||||
if_dl(" sys.setdlopenflags(dl.RTLD_NOW)"),
|
||||
" spec = importlib.util.spec_from_file_location(",
|
||||
" __name__, __file__)",
|
||||
" mod = importlib.util.module_from_spec(spec)",
|
||||
" spec.loader.exec_module(mod)",
|
||||
" finally:",
|
||||
if_dl(" sys.setdlopenflags(old_flags)"),
|
||||
" os.chdir(old_dir)",
|
||||
"__bootstrap__()",
|
||||
"", # terminal \n
|
||||
]
|
||||
)
|
||||
)
|
||||
f.close()
|
||||
with open(stub_file, 'w', encoding="utf-8") as f:
|
||||
content = '\n'.join([
|
||||
"def __bootstrap__():",
|
||||
" global __bootstrap__, __file__, __loader__",
|
||||
" import sys, os, pkg_resources, importlib.util" + if_dl(", dl"),
|
||||
" __file__ = pkg_resources.resource_filename"
|
||||
"(__name__,%r)" % os.path.basename(ext._file_name),
|
||||
" del __bootstrap__",
|
||||
" if '__loader__' in globals():",
|
||||
" del __loader__",
|
||||
if_dl(" old_flags = sys.getdlopenflags()"),
|
||||
" old_dir = os.getcwd()",
|
||||
" try:",
|
||||
" os.chdir(os.path.dirname(__file__))",
|
||||
if_dl(" sys.setdlopenflags(dl.RTLD_NOW)"),
|
||||
" spec = importlib.util.spec_from_file_location(",
|
||||
" __name__, __file__)",
|
||||
" mod = importlib.util.module_from_spec(spec)",
|
||||
" spec.loader.exec_module(mod)",
|
||||
" finally:",
|
||||
if_dl(" sys.setdlopenflags(old_flags)"),
|
||||
" os.chdir(old_dir)",
|
||||
"__bootstrap__()",
|
||||
"", # terminal \n
|
||||
])
|
||||
f.write(content)
|
||||
if compile:
|
||||
self._compile_and_remove_stub(stub_file)
|
||||
|
||||
@@ -380,7 +389,10 @@ class build_ext(_build_ext):
|
||||
optimize = self.get_finalized_command('install_lib').optimize
|
||||
if optimize > 0:
|
||||
byte_compile(
|
||||
[stub_file], optimize=optimize, force=True, dry_run=self.dry_run
|
||||
[stub_file],
|
||||
optimize=optimize,
|
||||
force=True,
|
||||
dry_run=self.dry_run,
|
||||
)
|
||||
if os.path.exists(stub_file) and not self.dry_run:
|
||||
os.unlink(stub_file)
|
||||
@@ -398,7 +410,7 @@ if use_stubs or os.name == 'nt':
|
||||
library_dirs=None,
|
||||
runtime_library_dirs=None,
|
||||
export_symbols=None,
|
||||
debug=0,
|
||||
debug=False,
|
||||
extra_preargs=None,
|
||||
extra_postargs=None,
|
||||
build_temp=None,
|
||||
@@ -433,7 +445,7 @@ else:
|
||||
library_dirs=None,
|
||||
runtime_library_dirs=None,
|
||||
export_symbols=None,
|
||||
debug=0,
|
||||
debug=False,
|
||||
extra_preargs=None,
|
||||
extra_postargs=None,
|
||||
build_temp=None,
|
||||
|
||||
@@ -1,20 +1,26 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import fnmatch
|
||||
import itertools
|
||||
import os
|
||||
import stat
|
||||
import textwrap
|
||||
from functools import partial
|
||||
from glob import glob
|
||||
from distutils.util import convert_path
|
||||
import distutils.command.build_py as orig
|
||||
import os
|
||||
import fnmatch
|
||||
import textwrap
|
||||
import io
|
||||
import distutils.errors
|
||||
import itertools
|
||||
import stat
|
||||
from pathlib import Path
|
||||
from typing import Dict, Iterable, Iterator, List, Optional, Tuple
|
||||
from typing import Iterable, Iterator
|
||||
|
||||
from ..extern.more_itertools import unique_everseen
|
||||
from more_itertools import unique_everseen
|
||||
|
||||
from ..dist import Distribution
|
||||
from ..warnings import SetuptoolsDeprecationWarning
|
||||
|
||||
import distutils.command.build_py as orig
|
||||
import distutils.errors
|
||||
from distutils.util import convert_path
|
||||
|
||||
_IMPLICIT_DATA_FILES = ('*.pyi', 'py.typed')
|
||||
|
||||
|
||||
def make_writable(target):
|
||||
os.chmod(target, os.stat(target).st_mode | stat.S_IWRITE)
|
||||
@@ -30,8 +36,9 @@ class build_py(orig.build_py):
|
||||
'py_modules' and 'packages' in the same setup operation.
|
||||
"""
|
||||
|
||||
distribution: Distribution # override distutils.dist.Distribution with setuptools.dist.Distribution
|
||||
editable_mode: bool = False
|
||||
existing_egg_info_dir: Optional[str] = None #: Private API, internal use only.
|
||||
existing_egg_info_dir: str | None = None #: Private API, internal use only.
|
||||
|
||||
def finalize_options(self):
|
||||
orig.build_py.finalize_options(self)
|
||||
@@ -42,7 +49,13 @@ class build_py(orig.build_py):
|
||||
self.__updated_files = []
|
||||
|
||||
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,
|
||||
):
|
||||
# Overwrite base class to allow using links
|
||||
if link:
|
||||
@@ -66,9 +79,9 @@ class build_py(orig.build_py):
|
||||
|
||||
# Only compile actual .py files, using our base class' idea of what our
|
||||
# output files are.
|
||||
self.byte_compile(orig.build_py.get_outputs(self, include_bytecode=0))
|
||||
self.byte_compile(orig.build_py.get_outputs(self, include_bytecode=False))
|
||||
|
||||
def __getattr__(self, attr):
|
||||
def __getattr__(self, attr: str):
|
||||
"lazily compute data files"
|
||||
if attr == 'data_files':
|
||||
self.data_files = self._get_data_files()
|
||||
@@ -116,6 +129,7 @@ class build_py(orig.build_py):
|
||||
self.package_data,
|
||||
package,
|
||||
src_dir,
|
||||
extra_patterns=_IMPLICIT_DATA_FILES,
|
||||
)
|
||||
globs_expanded = map(partial(glob, recursive=True), patterns)
|
||||
# flatten the expanded globs into an iterable of matches
|
||||
@@ -127,13 +141,13 @@ class build_py(orig.build_py):
|
||||
)
|
||||
return self.exclude_data_files(package, src_dir, files)
|
||||
|
||||
def get_outputs(self, include_bytecode=1) -> List[str]:
|
||||
def get_outputs(self, include_bytecode=True) -> list[str]:
|
||||
"""See :class:`setuptools.commands.build.SubCommand`"""
|
||||
if self.editable_mode:
|
||||
return list(self.get_output_mapping().keys())
|
||||
return super().get_outputs(include_bytecode)
|
||||
|
||||
def get_output_mapping(self) -> Dict[str, str]:
|
||||
def get_output_mapping(self) -> dict[str, str]:
|
||||
"""See :class:`setuptools.commands.build.SubCommand`"""
|
||||
mapping = itertools.chain(
|
||||
self._get_package_data_output_mapping(),
|
||||
@@ -141,14 +155,14 @@ class build_py(orig.build_py):
|
||||
)
|
||||
return dict(sorted(mapping, key=lambda x: x[0]))
|
||||
|
||||
def _get_module_mapping(self) -> Iterator[Tuple[str, str]]:
|
||||
def _get_module_mapping(self) -> Iterator[tuple[str, str]]:
|
||||
"""Iterate over all modules producing (dest, src) pairs."""
|
||||
for package, module, module_file in self.find_all_modules():
|
||||
package = package.split('.')
|
||||
filename = self.get_module_outfile(self.build_lib, package, module)
|
||||
yield (filename, module_file)
|
||||
|
||||
def _get_package_data_output_mapping(self) -> Iterator[Tuple[str, str]]:
|
||||
def _get_package_data_output_mapping(self) -> Iterator[tuple[str, str]]:
|
||||
"""Iterate over package data producing (dest, src) pairs."""
|
||||
for package, src_dir, build_dir, filenames in self.data_files:
|
||||
for filename in filenames:
|
||||
@@ -245,7 +259,7 @@ class build_py(orig.build_py):
|
||||
else:
|
||||
return init_py
|
||||
|
||||
with io.open(init_py, 'rb') as f:
|
||||
with open(init_py, 'rb') as f:
|
||||
contents = f.read()
|
||||
if b'declare_namespace' not in contents:
|
||||
raise distutils.errors.DistutilsError(
|
||||
@@ -285,7 +299,7 @@ class build_py(orig.build_py):
|
||||
return list(unique_everseen(keepers))
|
||||
|
||||
@staticmethod
|
||||
def _get_platform_patterns(spec, package, src_dir):
|
||||
def _get_platform_patterns(spec, package, src_dir, extra_patterns=()):
|
||||
"""
|
||||
yield platform-specific path patterns (suitable for glob
|
||||
or fn_match) from a glob-based spec (such as
|
||||
@@ -293,6 +307,7 @@ class build_py(orig.build_py):
|
||||
matching package in src_dir.
|
||||
"""
|
||||
raw_patterns = itertools.chain(
|
||||
extra_patterns,
|
||||
spec.get('', []),
|
||||
spec.get(package, []),
|
||||
)
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
from distutils.util import convert_path
|
||||
import glob
|
||||
import os
|
||||
|
||||
import setuptools
|
||||
from setuptools import _normalization, _path, namespaces
|
||||
from setuptools.command.easy_install import easy_install
|
||||
|
||||
from ..unicode_utils import _read_utf8_with_fallback
|
||||
|
||||
from distutils import log
|
||||
from distutils.errors import DistutilsOptionError
|
||||
import os
|
||||
import glob
|
||||
import io
|
||||
|
||||
from setuptools.command.easy_install import easy_install
|
||||
from setuptools import _path
|
||||
from setuptools import namespaces
|
||||
import setuptools
|
||||
from distutils.util import convert_path
|
||||
|
||||
|
||||
class develop(namespaces.DevelopInstaller, easy_install):
|
||||
@@ -53,7 +54,9 @@ class develop(namespaces.DevelopInstaller, easy_install):
|
||||
# pick up setup-dir .egg files only: no .egg-info
|
||||
self.package_index.scan(glob.glob('*.egg'))
|
||||
|
||||
egg_link_fn = ei.egg_name + '.egg-link'
|
||||
egg_link_fn = (
|
||||
_normalization.filename_component_broken(ei.egg_name) + '.egg-link'
|
||||
)
|
||||
self.egg_link = os.path.join(self.install_dir, egg_link_fn)
|
||||
self.egg_base = ei.egg_base
|
||||
if self.egg_path is None:
|
||||
@@ -105,7 +108,7 @@ class develop(namespaces.DevelopInstaller, easy_install):
|
||||
self.run_command('egg_info')
|
||||
|
||||
# Build extensions in-place
|
||||
self.reinitialize_command('build_ext', inplace=1)
|
||||
self.reinitialize_command('build_ext', inplace=True)
|
||||
self.run_command('build_ext')
|
||||
|
||||
if setuptools.bootstrap_install_from:
|
||||
@@ -117,7 +120,7 @@ class develop(namespaces.DevelopInstaller, easy_install):
|
||||
# create an .egg-link in the installation dir, pointing to our egg
|
||||
log.info("Creating %s (link to %s)", self.egg_link, self.egg_base)
|
||||
if not self.dry_run:
|
||||
with open(self.egg_link, "w") as f:
|
||||
with open(self.egg_link, "w", encoding="utf-8") as f:
|
||||
f.write(self.egg_path + "\n" + self.setup_path)
|
||||
# postprocess the installed distro, fixing up .pth, installing scripts,
|
||||
# and handling requirements
|
||||
@@ -126,9 +129,12 @@ class develop(namespaces.DevelopInstaller, easy_install):
|
||||
def uninstall_link(self):
|
||||
if os.path.exists(self.egg_link):
|
||||
log.info("Removing %s (link to %s)", self.egg_link, self.egg_base)
|
||||
egg_link_file = open(self.egg_link)
|
||||
contents = [line.rstrip() for line in egg_link_file]
|
||||
egg_link_file.close()
|
||||
|
||||
contents = [
|
||||
line.rstrip()
|
||||
for line in _read_utf8_with_fallback(self.egg_link).splitlines()
|
||||
]
|
||||
|
||||
if contents not in ([self.egg_path], [self.egg_path, self.setup_path]):
|
||||
log.warn("Link points to %s: uninstall aborted", contents)
|
||||
return
|
||||
@@ -154,10 +160,11 @@ class develop(namespaces.DevelopInstaller, easy_install):
|
||||
for script_name in self.distribution.scripts or []:
|
||||
script_path = os.path.abspath(convert_path(script_name))
|
||||
script_name = os.path.basename(script_path)
|
||||
with io.open(script_path) as strm:
|
||||
script_text = strm.read()
|
||||
script_text = _read_utf8_with_fallback(script_path)
|
||||
self.install_script(dist, script_name, script_text, script_path)
|
||||
|
||||
return None
|
||||
|
||||
def install_wrapper_scripts(self, dist):
|
||||
dist = VersionlessRequirement(dist)
|
||||
return easy_install.install_wrapper_scripts(self, dist)
|
||||
@@ -181,7 +188,7 @@ class VersionlessRequirement:
|
||||
def __init__(self, dist):
|
||||
self.__dist = dist
|
||||
|
||||
def __getattr__(self, name):
|
||||
def __getattr__(self, name: str):
|
||||
return getattr(self.__dist, name)
|
||||
|
||||
def as_requirement(self):
|
||||
|
||||
@@ -5,14 +5,15 @@ As defined in the wheel specification
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
from contextlib import contextmanager
|
||||
from distutils import log
|
||||
from distutils.core import Command
|
||||
from pathlib import Path
|
||||
from typing import cast
|
||||
|
||||
from .. import _normalization
|
||||
from ..warnings import SetuptoolsDeprecationWarning
|
||||
from .egg_info import egg_info as egg_info_cls
|
||||
|
||||
from distutils import log
|
||||
from distutils.core import Command
|
||||
|
||||
|
||||
class dist_info(Command):
|
||||
@@ -24,18 +25,11 @@ class dist_info(Command):
|
||||
description = "DO NOT CALL DIRECTLY, INTERNAL ONLY: create .dist-info directory"
|
||||
|
||||
user_options = [
|
||||
(
|
||||
'egg-base=',
|
||||
'e',
|
||||
"directory containing .egg-info directories"
|
||||
" (default: top of the source tree)"
|
||||
" DEPRECATED: use --output-dir.",
|
||||
),
|
||||
(
|
||||
'output-dir=',
|
||||
'o',
|
||||
"directory inside of which the .dist-info will be"
|
||||
"created (default: top of the source tree)",
|
||||
"created [default: top of the source tree]",
|
||||
),
|
||||
('tag-date', 'd', "Add date stamp (e.g. 20050528) to version number"),
|
||||
('tag-build=', 'b', "Specify explicit tag to add to version number"),
|
||||
@@ -47,7 +41,6 @@ class dist_info(Command):
|
||||
negative_opt = {'no-date': 'tag-date'}
|
||||
|
||||
def initialize_options(self):
|
||||
self.egg_base = None
|
||||
self.output_dir = None
|
||||
self.name = None
|
||||
self.dist_info_dir = None
|
||||
@@ -56,18 +49,11 @@ class dist_info(Command):
|
||||
self.keep_egg_info = False
|
||||
|
||||
def finalize_options(self):
|
||||
if self.egg_base:
|
||||
msg = "--egg-base is deprecated for dist_info command. Use --output-dir."
|
||||
SetuptoolsDeprecationWarning.emit(msg, due_date=(2023, 9, 26))
|
||||
# This command is internal to setuptools, therefore it should be safe
|
||||
# to remove the deprecated support soon.
|
||||
self.output_dir = self.egg_base or self.output_dir
|
||||
|
||||
dist = self.distribution
|
||||
project_dir = dist.src_root or os.curdir
|
||||
self.output_dir = Path(self.output_dir or project_dir)
|
||||
|
||||
egg_info = self.reinitialize_command("egg_info")
|
||||
egg_info = cast(egg_info_cls, self.reinitialize_command("egg_info"))
|
||||
egg_info.egg_base = str(self.output_dir)
|
||||
|
||||
if self.tag_date:
|
||||
@@ -93,7 +79,7 @@ class dist_info(Command):
|
||||
if requires_bkp:
|
||||
bkp_name = f"{dir_path}.__bkp__"
|
||||
_rm(bkp_name, ignore_errors=True)
|
||||
_copy(dir_path, bkp_name, dirs_exist_ok=True, symlinks=True)
|
||||
shutil.copytree(dir_path, bkp_name, dirs_exist_ok=True, symlinks=True)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
@@ -119,9 +105,3 @@ class dist_info(Command):
|
||||
def _rm(dir_name, **opts):
|
||||
if os.path.isdir(dir_name):
|
||||
shutil.rmtree(dir_name, **opts)
|
||||
|
||||
|
||||
def _copy(src, dst, **opts):
|
||||
if sys.version_info < (3, 8):
|
||||
opts.pop("dirs_exist_ok", None)
|
||||
shutil.copytree(src, dst, **opts)
|
||||
|
||||
@@ -10,74 +10,74 @@ __ https://setuptools.pypa.io/en/latest/deprecated/easy_install.html
|
||||
|
||||
"""
|
||||
|
||||
from glob import glob
|
||||
from distutils.util import get_platform
|
||||
from distutils.util import convert_path, subst_vars
|
||||
from distutils.errors import (
|
||||
DistutilsArgError,
|
||||
DistutilsOptionError,
|
||||
DistutilsError,
|
||||
DistutilsPlatformError,
|
||||
)
|
||||
from distutils import log, dir_util
|
||||
from distutils.command.build_scripts import first_line_re
|
||||
from distutils.spawn import find_executable
|
||||
from distutils.command import install
|
||||
import sys
|
||||
from __future__ import annotations
|
||||
|
||||
import configparser
|
||||
import contextlib
|
||||
import io
|
||||
import os
|
||||
import zipimport
|
||||
import shutil
|
||||
import tempfile
|
||||
import zipfile
|
||||
import re
|
||||
import stat
|
||||
import random
|
||||
import re
|
||||
import shlex
|
||||
import shutil
|
||||
import site
|
||||
import stat
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import sysconfig
|
||||
import tempfile
|
||||
import textwrap
|
||||
import warnings
|
||||
import site
|
||||
import struct
|
||||
import contextlib
|
||||
import subprocess
|
||||
import shlex
|
||||
import io
|
||||
import configparser
|
||||
import sysconfig
|
||||
|
||||
|
||||
import zipfile
|
||||
import zipimport
|
||||
from collections.abc import Iterable
|
||||
from glob import glob
|
||||
from sysconfig import get_path
|
||||
from typing import TYPE_CHECKING, Callable, TypeVar
|
||||
|
||||
from setuptools import Command
|
||||
from setuptools.sandbox import run_setup
|
||||
from setuptools.command import setopt
|
||||
from setuptools.archive_util import unpack_archive
|
||||
from setuptools.package_index import (
|
||||
PackageIndex,
|
||||
parse_requirement_arg,
|
||||
URL_SCHEME,
|
||||
)
|
||||
from setuptools.command import bdist_egg, egg_info
|
||||
from setuptools.warnings import SetuptoolsDeprecationWarning, SetuptoolsWarning
|
||||
from setuptools.wheel import Wheel
|
||||
from jaraco.text import yield_lines
|
||||
|
||||
import pkg_resources
|
||||
from pkg_resources import (
|
||||
DEVELOP_DIST,
|
||||
Distribution,
|
||||
DistributionNotFound,
|
||||
EggMetadata,
|
||||
Environment,
|
||||
PathMetadata,
|
||||
Requirement,
|
||||
VersionConflict,
|
||||
WorkingSet,
|
||||
find_distributions,
|
||||
get_distribution,
|
||||
normalize_path,
|
||||
resource_string,
|
||||
get_distribution,
|
||||
find_distributions,
|
||||
Environment,
|
||||
Requirement,
|
||||
Distribution,
|
||||
PathMetadata,
|
||||
EggMetadata,
|
||||
WorkingSet,
|
||||
DistributionNotFound,
|
||||
VersionConflict,
|
||||
DEVELOP_DIST,
|
||||
)
|
||||
import pkg_resources
|
||||
from .. import py312compat
|
||||
from .._path import ensure_directory
|
||||
from ..extern.jaraco.text import yield_lines
|
||||
from setuptools import Command
|
||||
from setuptools.archive_util import unpack_archive
|
||||
from setuptools.command import bdist_egg, egg_info, setopt
|
||||
from setuptools.package_index import URL_SCHEME, PackageIndex, parse_requirement_arg
|
||||
from setuptools.sandbox import run_setup
|
||||
from setuptools.warnings import SetuptoolsDeprecationWarning, SetuptoolsWarning
|
||||
from setuptools.wheel import Wheel
|
||||
|
||||
from .._path import ensure_directory
|
||||
from ..compat import py39, py311, py312
|
||||
|
||||
from distutils import dir_util, log
|
||||
from distutils.command import install
|
||||
from distutils.command.build_scripts import first_line_re
|
||||
from distutils.errors import (
|
||||
DistutilsArgError,
|
||||
DistutilsError,
|
||||
DistutilsOptionError,
|
||||
DistutilsPlatformError,
|
||||
)
|
||||
from distutils.util import convert_path, get_platform, subst_vars
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Self
|
||||
|
||||
# Turn on PEP440Warnings
|
||||
warnings.filterwarnings("default", category=pkg_resources.PEP440Warning)
|
||||
@@ -89,6 +89,8 @@ __all__ = [
|
||||
'get_exe_prefixes',
|
||||
]
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
|
||||
def is_64bit():
|
||||
return struct.calcsize("P") == 8
|
||||
@@ -101,9 +103,9 @@ def _to_bytes(s):
|
||||
def isascii(s):
|
||||
try:
|
||||
s.encode('ascii')
|
||||
return True
|
||||
except UnicodeError:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _one_liner(text):
|
||||
@@ -170,7 +172,7 @@ class easy_install(Command):
|
||||
|
||||
# the --user option seems to be an opt-in one,
|
||||
# so the default should be False.
|
||||
self.user = 0
|
||||
self.user = False
|
||||
self.zip_ok = self.local_snapshots_ok = None
|
||||
self.install_dir = self.script_dir = self.exclude_scripts = None
|
||||
self.index_url = None
|
||||
@@ -236,7 +238,7 @@ class easy_install(Command):
|
||||
dist = get_distribution('setuptools')
|
||||
tmpl = 'setuptools {dist.version} from {dist.location} (Python {ver})'
|
||||
print(tmpl.format(**locals()))
|
||||
raise SystemExit()
|
||||
raise SystemExit
|
||||
|
||||
def finalize_options(self): # noqa: C901 # is too complex (25) # FIXME
|
||||
self.version and self._render_version()
|
||||
@@ -245,31 +247,26 @@ class easy_install(Command):
|
||||
|
||||
self.config_vars = dict(sysconfig.get_config_vars())
|
||||
|
||||
self.config_vars.update(
|
||||
{
|
||||
'dist_name': self.distribution.get_name(),
|
||||
'dist_version': self.distribution.get_version(),
|
||||
'dist_fullname': self.distribution.get_fullname(),
|
||||
'py_version': py_version,
|
||||
'py_version_short': (
|
||||
f'{sys.version_info.major}.{sys.version_info.minor}'
|
||||
),
|
||||
'py_version_nodot': f'{sys.version_info.major}{sys.version_info.minor}',
|
||||
'sys_prefix': self.config_vars['prefix'],
|
||||
'sys_exec_prefix': self.config_vars['exec_prefix'],
|
||||
# Only python 3.2+ has abiflags
|
||||
'abiflags': getattr(sys, 'abiflags', ''),
|
||||
'platlibdir': getattr(sys, 'platlibdir', 'lib'),
|
||||
}
|
||||
)
|
||||
self.config_vars.update({
|
||||
'dist_name': self.distribution.get_name(),
|
||||
'dist_version': self.distribution.get_version(),
|
||||
'dist_fullname': self.distribution.get_fullname(),
|
||||
'py_version': py_version,
|
||||
'py_version_short': f'{sys.version_info.major}.{sys.version_info.minor}',
|
||||
'py_version_nodot': f'{sys.version_info.major}{sys.version_info.minor}',
|
||||
'sys_prefix': self.config_vars['prefix'],
|
||||
'sys_exec_prefix': self.config_vars['exec_prefix'],
|
||||
# Only POSIX systems have abiflags
|
||||
'abiflags': getattr(sys, 'abiflags', ''),
|
||||
# Only python 3.9+ has platlibdir
|
||||
'platlibdir': getattr(sys, 'platlibdir', 'lib'),
|
||||
})
|
||||
with contextlib.suppress(AttributeError):
|
||||
# only for distutils outside stdlib
|
||||
self.config_vars.update(
|
||||
{
|
||||
'implementation_lower': install._get_implementation().lower(),
|
||||
'implementation': install._get_implementation(),
|
||||
}
|
||||
)
|
||||
self.config_vars.update({
|
||||
'implementation_lower': install._get_implementation().lower(),
|
||||
'implementation': install._get_implementation(),
|
||||
})
|
||||
|
||||
# pypa/distutils#113 Python 3.9 compat
|
||||
self.config_vars.setdefault(
|
||||
@@ -471,7 +468,7 @@ class easy_install(Command):
|
||||
def warn_deprecated_options(self):
|
||||
pass
|
||||
|
||||
def check_site_dir(self): # noqa: C901 # is too complex (12) # FIXME
|
||||
def check_site_dir(self): # is too complex (12) # FIXME
|
||||
"""Verify that self.install_dir is .pth-capable dir, if needed"""
|
||||
|
||||
instdir = normalize_path(self.install_dir)
|
||||
@@ -480,7 +477,7 @@ class easy_install(Command):
|
||||
if not os.path.exists(instdir):
|
||||
try:
|
||||
os.makedirs(instdir)
|
||||
except (OSError, IOError):
|
||||
except OSError:
|
||||
self.cant_write_to_target()
|
||||
|
||||
# Is it a configured, PYTHONPATH, implicit, or explicit site dir?
|
||||
@@ -496,9 +493,9 @@ class easy_install(Command):
|
||||
try:
|
||||
if test_exists:
|
||||
os.unlink(testfile)
|
||||
open(testfile, 'w').close()
|
||||
open(testfile, 'wb').close()
|
||||
os.unlink(testfile)
|
||||
except (OSError, IOError):
|
||||
except OSError:
|
||||
self.cant_write_to_target()
|
||||
|
||||
if not is_site_dir and not self.multi_version:
|
||||
@@ -530,7 +527,7 @@ class easy_install(Command):
|
||||
|
||||
%s
|
||||
"""
|
||||
).lstrip() # noqa
|
||||
).lstrip()
|
||||
|
||||
__not_exists_id = textwrap.dedent(
|
||||
"""
|
||||
@@ -538,7 +535,7 @@ class easy_install(Command):
|
||||
choose a different installation directory (using the -d or --install-dir
|
||||
option).
|
||||
"""
|
||||
).lstrip() # noqa
|
||||
).lstrip()
|
||||
|
||||
__access_msg = textwrap.dedent(
|
||||
"""
|
||||
@@ -556,7 +553,7 @@ class easy_install(Command):
|
||||
|
||||
Please make the appropriate changes for your system and try again.
|
||||
"""
|
||||
).lstrip() # noqa
|
||||
).lstrip()
|
||||
|
||||
def cant_write_to_target(self):
|
||||
msg = self.__cant_write_msg % (
|
||||
@@ -570,7 +567,7 @@ class easy_install(Command):
|
||||
msg += '\n' + self.__access_msg
|
||||
raise DistutilsError(msg)
|
||||
|
||||
def check_pth_processing(self):
|
||||
def check_pth_processing(self): # noqa: C901
|
||||
"""Empirically verify whether .pth files are supported in inst. dir"""
|
||||
instdir = self.install_dir
|
||||
log.info("Checking .pth file support in %s", instdir)
|
||||
@@ -581,7 +578,7 @@ class easy_install(Command):
|
||||
_one_liner(
|
||||
"""
|
||||
import os
|
||||
f = open({ok_file!r}, 'w')
|
||||
f = open({ok_file!r}, 'w', encoding="utf-8")
|
||||
f.write('OK')
|
||||
f.close()
|
||||
"""
|
||||
@@ -593,8 +590,10 @@ class easy_install(Command):
|
||||
os.unlink(ok_file)
|
||||
dirname = os.path.dirname(ok_file)
|
||||
os.makedirs(dirname, exist_ok=True)
|
||||
f = open(pth_file, 'w')
|
||||
except (OSError, IOError):
|
||||
f = open(pth_file, 'w', encoding=py312.PTH_ENCODING)
|
||||
# ^-- Python<3.13 require encoding="locale" instead of "utf-8",
|
||||
# see python/cpython#77102.
|
||||
except OSError:
|
||||
self.cant_write_to_target()
|
||||
else:
|
||||
try:
|
||||
@@ -668,7 +667,7 @@ class easy_install(Command):
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _tmpdir(self):
|
||||
tmpdir = tempfile.mkdtemp(prefix=u"easy_install-")
|
||||
tmpdir = tempfile.mkdtemp(prefix="easy_install-")
|
||||
try:
|
||||
# cast to str as workaround for #709 and #710 and #712
|
||||
yield str(tmpdir)
|
||||
@@ -746,6 +745,7 @@ class easy_install(Command):
|
||||
for dist in dists:
|
||||
if dist in spec:
|
||||
return dist
|
||||
return None
|
||||
|
||||
def select_scheme(self, name):
|
||||
try:
|
||||
@@ -876,7 +876,9 @@ class easy_install(Command):
|
||||
ensure_directory(target)
|
||||
if os.path.exists(target):
|
||||
os.unlink(target)
|
||||
with open(target, "w" + mode) as f:
|
||||
|
||||
encoding = None if "b" in mode else "utf-8"
|
||||
with open(target, "w" + mode, encoding=encoding) as f:
|
||||
f.write(contents)
|
||||
chmod(target, 0o777 - mask)
|
||||
|
||||
@@ -939,7 +941,7 @@ class easy_install(Command):
|
||||
return Distribution.from_filename(egg_path, metadata=metadata)
|
||||
|
||||
# FIXME: 'easy_install.install_egg' is too complex (11)
|
||||
def install_egg(self, egg_path, tmpdir): # noqa: C901
|
||||
def install_egg(self, egg_path, tmpdir):
|
||||
destination = os.path.join(
|
||||
self.install_dir,
|
||||
os.path.basename(egg_path),
|
||||
@@ -1020,17 +1022,16 @@ class easy_install(Command):
|
||||
|
||||
# Write EGG-INFO/PKG-INFO
|
||||
if not os.path.exists(pkg_inf):
|
||||
f = open(pkg_inf, 'w')
|
||||
f.write('Metadata-Version: 1.0\n')
|
||||
for k, v in cfg.items('metadata'):
|
||||
if k != 'target_version':
|
||||
f.write('%s: %s\n' % (k.replace('_', '-').title(), v))
|
||||
f.close()
|
||||
with open(pkg_inf, 'w', encoding="utf-8") as f:
|
||||
f.write('Metadata-Version: 1.0\n')
|
||||
for k, v in cfg.items('metadata'):
|
||||
if k != 'target_version':
|
||||
f.write('%s: %s\n' % (k.replace('_', '-').title(), v))
|
||||
script_dir = os.path.join(_egg_info, 'scripts')
|
||||
# delete entry-point scripts to avoid duping
|
||||
self.delete_blockers(
|
||||
[os.path.join(script_dir, args[0]) for args in ScriptWriter.get_args(dist)]
|
||||
)
|
||||
self.delete_blockers([
|
||||
os.path.join(script_dir, args[0]) for args in ScriptWriter.get_args(dist)
|
||||
])
|
||||
# Build .egg file from tmpdir
|
||||
bdist_egg.make_zipfile(
|
||||
egg_path,
|
||||
@@ -1048,7 +1049,7 @@ class easy_install(Command):
|
||||
prefixes = get_exe_prefixes(dist_filename)
|
||||
to_compile = []
|
||||
native_libs = []
|
||||
top_level = {}
|
||||
top_level = set()
|
||||
|
||||
def process(src, dst):
|
||||
s = src.lower()
|
||||
@@ -1060,10 +1061,10 @@ class easy_install(Command):
|
||||
dl = dst.lower()
|
||||
if dl.endswith('.pyd') or dl.endswith('.dll'):
|
||||
parts[-1] = bdist_egg.strip_module(parts[-1])
|
||||
top_level[os.path.splitext(parts[0])[0]] = 1
|
||||
top_level.add([os.path.splitext(parts[0])[0]])
|
||||
native_libs.append(src)
|
||||
elif dl.endswith('.py') and old != 'SCRIPTS/':
|
||||
top_level[os.path.splitext(parts[0])[0]] = 1
|
||||
top_level.add([os.path.splitext(parts[0])[0]])
|
||||
to_compile.append(dst)
|
||||
return dst
|
||||
if not src.endswith('.pth'):
|
||||
@@ -1091,9 +1092,8 @@ class easy_install(Command):
|
||||
if locals()[name]:
|
||||
txt = os.path.join(egg_tmp, 'EGG-INFO', name + '.txt')
|
||||
if not os.path.exists(txt):
|
||||
f = open(txt, 'w')
|
||||
f.write('\n'.join(locals()[name]) + '\n')
|
||||
f.close()
|
||||
with open(txt, 'w', encoding="utf-8") as f:
|
||||
f.write('\n'.join(locals()[name]) + '\n')
|
||||
|
||||
def install_wheel(self, wheel_path, tmpdir):
|
||||
wheel = Wheel(wheel_path)
|
||||
@@ -1133,7 +1133,7 @@ class easy_install(Command):
|
||||
pkg_resources.require("%(name)s==%(version)s") # this exact version
|
||||
pkg_resources.require("%(name)s>=%(version)s") # this version or higher
|
||||
"""
|
||||
).lstrip() # noqa
|
||||
).lstrip()
|
||||
|
||||
__id_warning = textwrap.dedent(
|
||||
"""
|
||||
@@ -1141,7 +1141,7 @@ class easy_install(Command):
|
||||
this to work. (e.g. by being the application's script directory, by being on
|
||||
PYTHONPATH, or by being added to sys.path by your code.)
|
||||
"""
|
||||
) # noqa
|
||||
)
|
||||
|
||||
def installation_report(self, req, dist, what="Installed"):
|
||||
"""Helpful installation message for display to package users"""
|
||||
@@ -1168,7 +1168,7 @@ class easy_install(Command):
|
||||
|
||||
See the setuptools documentation for the "develop" command for more info.
|
||||
"""
|
||||
).lstrip() # noqa
|
||||
).lstrip()
|
||||
|
||||
def report_editable(self, spec, setup_script):
|
||||
dirname = os.path.dirname(setup_script)
|
||||
@@ -1205,10 +1205,11 @@ class easy_install(Command):
|
||||
|
||||
self.run_setup(setup_script, setup_base, args)
|
||||
all_eggs = Environment([dist_dir])
|
||||
eggs = []
|
||||
for key in all_eggs:
|
||||
for dist in all_eggs[key]:
|
||||
eggs.append(self.install_egg(dist.location, setup_base))
|
||||
eggs = [
|
||||
self.install_egg(dist.location, setup_base)
|
||||
for key in all_eggs
|
||||
for dist in all_eggs[key]
|
||||
]
|
||||
if not eggs and not self.dry_run:
|
||||
log.warn("No eggs found in %s (setup script problem?)", dist_dir)
|
||||
return eggs
|
||||
@@ -1281,7 +1282,10 @@ class easy_install(Command):
|
||||
filename = os.path.join(self.install_dir, 'setuptools.pth')
|
||||
if os.path.islink(filename):
|
||||
os.unlink(filename)
|
||||
with open(filename, 'wt') as f:
|
||||
|
||||
with open(filename, 'wt', encoding=py312.PTH_ENCODING) as f:
|
||||
# ^-- Python<3.13 require encoding="locale" instead of "utf-8",
|
||||
# see python/cpython#77102.
|
||||
f.write(self.pth_file.make_relative(dist.location) + '\n')
|
||||
|
||||
def unpack_progress(self, src, dst):
|
||||
@@ -1318,12 +1322,12 @@ class easy_install(Command):
|
||||
# try to make the byte compile messages quieter
|
||||
log.set_verbosity(self.verbose - 1)
|
||||
|
||||
byte_compile(to_compile, optimize=0, force=1, dry_run=self.dry_run)
|
||||
byte_compile(to_compile, optimize=0, force=True, dry_run=self.dry_run)
|
||||
if self.optimize:
|
||||
byte_compile(
|
||||
to_compile,
|
||||
optimize=self.optimize,
|
||||
force=1,
|
||||
force=True,
|
||||
dry_run=self.dry_run,
|
||||
)
|
||||
finally:
|
||||
@@ -1433,24 +1437,20 @@ def get_site_dirs():
|
||||
if sys.platform in ('os2emx', 'riscos'):
|
||||
sitedirs.append(os.path.join(prefix, "Lib", "site-packages"))
|
||||
elif os.sep == '/':
|
||||
sitedirs.extend(
|
||||
[
|
||||
os.path.join(
|
||||
prefix,
|
||||
"lib",
|
||||
"python{}.{}".format(*sys.version_info),
|
||||
"site-packages",
|
||||
),
|
||||
os.path.join(prefix, "lib", "site-python"),
|
||||
]
|
||||
)
|
||||
else:
|
||||
sitedirs.extend(
|
||||
[
|
||||
sitedirs.extend([
|
||||
os.path.join(
|
||||
prefix,
|
||||
os.path.join(prefix, "lib", "site-packages"),
|
||||
]
|
||||
)
|
||||
"lib",
|
||||
"python{}.{}".format(*sys.version_info),
|
||||
"site-packages",
|
||||
),
|
||||
os.path.join(prefix, "lib", "site-python"),
|
||||
])
|
||||
else:
|
||||
sitedirs.extend([
|
||||
prefix,
|
||||
os.path.join(prefix, "lib", "site-packages"),
|
||||
])
|
||||
if sys.platform != 'darwin':
|
||||
continue
|
||||
|
||||
@@ -1482,22 +1482,20 @@ def get_site_dirs():
|
||||
with contextlib.suppress(AttributeError):
|
||||
sitedirs.extend(site.getsitepackages())
|
||||
|
||||
sitedirs = list(map(normalize_path, sitedirs))
|
||||
|
||||
return sitedirs
|
||||
return list(map(normalize_path, sitedirs))
|
||||
|
||||
|
||||
def expand_paths(inputs): # noqa: C901 # is too complex (11) # FIXME
|
||||
"""Yield sys.path directories that might contain "old-style" packages"""
|
||||
|
||||
seen = {}
|
||||
seen = set()
|
||||
|
||||
for dirname in inputs:
|
||||
dirname = normalize_path(dirname)
|
||||
if dirname in seen:
|
||||
continue
|
||||
|
||||
seen[dirname] = 1
|
||||
seen.add(dirname)
|
||||
if not os.path.isdir(dirname):
|
||||
continue
|
||||
|
||||
@@ -1513,9 +1511,8 @@ def expand_paths(inputs): # noqa: C901 # is too complex (11) # FIXME
|
||||
continue
|
||||
|
||||
# Read the .pth file
|
||||
f = open(os.path.join(dirname, name))
|
||||
lines = list(yield_lines(f))
|
||||
f.close()
|
||||
content = _read_pth(os.path.join(dirname, name))
|
||||
lines = list(yield_lines(content))
|
||||
|
||||
# Yield existing non-dupe, non-import directory lines from it
|
||||
for line in lines:
|
||||
@@ -1526,7 +1523,7 @@ def expand_paths(inputs): # noqa: C901 # is too complex (11) # FIXME
|
||||
if line in seen:
|
||||
continue
|
||||
|
||||
seen[line] = 1
|
||||
seen.add(line)
|
||||
if not os.path.isdir(line):
|
||||
continue
|
||||
|
||||
@@ -1628,9 +1625,9 @@ class PthDistributions(Environment):
|
||||
def _load_raw(self):
|
||||
paths = []
|
||||
dirty = saw_import = False
|
||||
seen = dict.fromkeys(self.sitedirs)
|
||||
f = open(self.filename, 'rt')
|
||||
for line in f:
|
||||
seen = set(self.sitedirs)
|
||||
content = _read_pth(self.filename)
|
||||
for line in content.splitlines():
|
||||
path = line.rstrip()
|
||||
# still keep imports and empty/commented lines for formatting
|
||||
paths.append(path)
|
||||
@@ -1648,8 +1645,7 @@ class PthDistributions(Environment):
|
||||
dirty = True
|
||||
paths.pop()
|
||||
continue
|
||||
seen[normalized_path] = 1
|
||||
f.close()
|
||||
seen.add(normalized_path)
|
||||
# remove any trailing empty/blank line
|
||||
while paths and not paths[-1].strip():
|
||||
paths.pop()
|
||||
@@ -1678,9 +1674,11 @@ class PthDistributions(Environment):
|
||||
last_paths.remove(path)
|
||||
# also, re-check that all paths are still valid before saving them
|
||||
for path in self.paths[:]:
|
||||
if path not in last_paths and not path.startswith(
|
||||
('import ', 'from ', '#')
|
||||
):
|
||||
if path not in last_paths and not path.startswith((
|
||||
'import ',
|
||||
'from ',
|
||||
'#',
|
||||
)):
|
||||
absolute_path = os.path.join(self.basedir, path)
|
||||
if not os.path.exists(absolute_path):
|
||||
self.paths.remove(path)
|
||||
@@ -1698,7 +1696,9 @@ class PthDistributions(Environment):
|
||||
data = '\n'.join(lines) + '\n'
|
||||
if os.path.islink(self.filename):
|
||||
os.unlink(self.filename)
|
||||
with open(self.filename, 'wt') as f:
|
||||
with open(self.filename, 'wt', encoding=py312.PTH_ENCODING) as f:
|
||||
# ^-- Python<3.13 require encoding="locale" instead of "utf-8",
|
||||
# see python/cpython#77102.
|
||||
f.write(data)
|
||||
elif os.path.exists(self.filename):
|
||||
log.debug("Deleting empty %s", self.filename)
|
||||
@@ -1751,8 +1751,7 @@ class RewritePthDistributions(PthDistributions):
|
||||
@classmethod
|
||||
def _wrap_lines(cls, lines):
|
||||
yield cls.prelude
|
||||
for line in lines:
|
||||
yield line
|
||||
yield from lines
|
||||
yield cls.postlude
|
||||
|
||||
prelude = _one_liner(
|
||||
@@ -1774,7 +1773,7 @@ class RewritePthDistributions(PthDistributions):
|
||||
|
||||
|
||||
if os.environ.get('SETUPTOOLS_SYS_PATH_TECHNIQUE', 'raw') == 'rewrite':
|
||||
PthDistributions = RewritePthDistributions
|
||||
PthDistributions = RewritePthDistributions # type: ignore[misc] # Overwriting type
|
||||
|
||||
|
||||
def _first_line_re():
|
||||
@@ -1789,13 +1788,14 @@ def _first_line_re():
|
||||
return re.compile(first_line_re.pattern.decode())
|
||||
|
||||
|
||||
def auto_chmod(func, arg, exc):
|
||||
# Must match shutil._OnExcCallback
|
||||
def auto_chmod(func: Callable[..., _T], arg: str, exc: BaseException) -> _T:
|
||||
"""shutils onexc callback to automatically call chmod for certain functions."""
|
||||
# Only retry for scenarios known to have an issue
|
||||
if func in [os.unlink, os.remove] and os.name == 'nt':
|
||||
chmod(arg, stat.S_IWRITE)
|
||||
return func(arg)
|
||||
et, ev, _ = sys.exc_info()
|
||||
# TODO: This code doesn't make sense. What is it trying to do?
|
||||
raise (ev[0], ev[1] + (" %s %s" % (func, arg)))
|
||||
raise exc
|
||||
|
||||
|
||||
def update_dist_caches(dist_path, fix_zipimporter_caches):
|
||||
@@ -1995,9 +1995,9 @@ def is_python(text, filename='<string>'):
|
||||
def is_sh(executable):
|
||||
"""Determine if the specified executable is a .sh (contains a #! line)"""
|
||||
try:
|
||||
with io.open(executable, encoding='latin-1') as fp:
|
||||
with open(executable, encoding='latin-1') as fp:
|
||||
magic = fp.read(2)
|
||||
except (OSError, IOError):
|
||||
except OSError:
|
||||
return executable
|
||||
return magic == '#!'
|
||||
|
||||
@@ -2021,10 +2021,12 @@ def is_python_script(script_text, filename):
|
||||
|
||||
|
||||
try:
|
||||
from os import chmod as _chmod
|
||||
from os import (
|
||||
chmod as _chmod, # pyright: ignore[reportAssignmentType] # Losing type-safety w/ pyright, but that's ok
|
||||
)
|
||||
except ImportError:
|
||||
# Jython compatibility
|
||||
def _chmod(*args):
|
||||
def _chmod(*args: object, **kwargs: object) -> None: # type: ignore[misc] # Mypy reuses the imported definition anyway
|
||||
pass
|
||||
|
||||
|
||||
@@ -2032,7 +2034,7 @@ def chmod(path, mode):
|
||||
log.debug("changing mode of %s to %o", path, mode)
|
||||
try:
|
||||
_chmod(path, mode)
|
||||
except os.error as e:
|
||||
except OSError as e:
|
||||
log.debug("chmod failed: %s", e)
|
||||
|
||||
|
||||
@@ -2042,8 +2044,8 @@ class CommandSpec(list):
|
||||
those passed to Popen.
|
||||
"""
|
||||
|
||||
options = []
|
||||
split_args = dict()
|
||||
options: list[str] = []
|
||||
split_args: dict[str, bool] = dict()
|
||||
|
||||
@classmethod
|
||||
def best(cls):
|
||||
@@ -2058,19 +2060,20 @@ class CommandSpec(list):
|
||||
return os.environ.get('__PYVENV_LAUNCHER__', _default)
|
||||
|
||||
@classmethod
|
||||
def from_param(cls, param):
|
||||
def from_param(cls, param: Self | str | Iterable[str] | None) -> Self:
|
||||
"""
|
||||
Construct a CommandSpec from a parameter to build_scripts, which may
|
||||
be None.
|
||||
"""
|
||||
if isinstance(param, cls):
|
||||
return param
|
||||
if isinstance(param, list):
|
||||
if isinstance(param, str):
|
||||
return cls.from_string(param)
|
||||
if isinstance(param, Iterable):
|
||||
return cls(param)
|
||||
if param is None:
|
||||
return cls.from_environment()
|
||||
# otherwise, assume it's a string.
|
||||
return cls.from_string(param)
|
||||
raise TypeError(f"Argument has an unsupported type {type(param)}")
|
||||
|
||||
@classmethod
|
||||
def from_environment(cls):
|
||||
@@ -2188,8 +2191,7 @@ class ScriptWriter:
|
||||
cls._ensure_safe_name(name)
|
||||
script_text = cls.template % locals()
|
||||
args = cls._get_script_args(type_, name, header, script_text)
|
||||
for res in args:
|
||||
yield res
|
||||
yield from args
|
||||
|
||||
@staticmethod
|
||||
def _ensure_safe_name(name):
|
||||
@@ -2279,7 +2281,7 @@ class WindowsScriptWriter(ScriptWriter):
|
||||
to an executable on the system.
|
||||
"""
|
||||
clean_header = new_header[2:-1].strip('"')
|
||||
return sys.platform != 'win32' or find_executable(clean_header)
|
||||
return sys.platform != 'win32' or shutil.which(clean_header)
|
||||
|
||||
|
||||
class WindowsExecutableLauncherWriter(WindowsScriptWriter):
|
||||
@@ -2339,7 +2341,7 @@ def load_launcher_manifest(name):
|
||||
|
||||
|
||||
def _rmtree(path, ignore_errors=False, onexc=auto_chmod):
|
||||
return py312compat.shutil_rmtree(path, ignore_errors, onexc)
|
||||
return py311.shutil_rmtree(path, ignore_errors, onexc)
|
||||
|
||||
|
||||
def current_umask():
|
||||
@@ -2355,6 +2357,26 @@ def only_strs(values):
|
||||
return filter(lambda val: isinstance(val, str), values)
|
||||
|
||||
|
||||
def _read_pth(fullname: str) -> str:
|
||||
# Python<3.13 require encoding="locale" instead of "utf-8", see python/cpython#77102
|
||||
# In the case old versions of setuptools are producing `pth` files with
|
||||
# different encodings that might be problematic... So we fallback to "locale".
|
||||
|
||||
try:
|
||||
with open(fullname, encoding=py312.PTH_ENCODING) as f:
|
||||
return f.read()
|
||||
except UnicodeDecodeError: # pragma: no cover
|
||||
# This error may only happen for Python >= 3.13
|
||||
# TODO: Possible deprecation warnings to be added in the future:
|
||||
# ``.pth file {fullname!r} is not UTF-8.``
|
||||
# Your environment contain {fullname!r} that cannot be read as UTF-8.
|
||||
# This is likely to have been produced with an old version of setuptools.
|
||||
# Please be mindful that this is deprecated and in the future, non-utf8
|
||||
# .pth files may cause setuptools to fail.
|
||||
with open(fullname, encoding=py39.LOCALE_ENCODING) as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
class EasyInstallDeprecationWarning(SetuptoolsDeprecationWarning):
|
||||
_SUMMARY = "easy_install command is deprecated."
|
||||
_DETAILS = """
|
||||
|
||||
@@ -10,59 +10,41 @@ Create a wheel that, when installed, will make the source package 'editable'
|
||||
*auxiliary build directory* or ``auxiliary_dir``.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import traceback
|
||||
from contextlib import suppress
|
||||
from enum import Enum
|
||||
from inspect import cleandoc
|
||||
from itertools import chain
|
||||
from itertools import chain, starmap
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Dict,
|
||||
Iterable,
|
||||
Iterator,
|
||||
List,
|
||||
Mapping,
|
||||
Optional,
|
||||
Tuple,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
from types import TracebackType
|
||||
from typing import TYPE_CHECKING, Iterable, Iterator, Mapping, Protocol, TypeVar, cast
|
||||
|
||||
from .. import (
|
||||
Command,
|
||||
_normalization,
|
||||
_path,
|
||||
errors,
|
||||
namespaces,
|
||||
)
|
||||
from .. import Command, _normalization, _path, errors, namespaces
|
||||
from .._path import StrPath
|
||||
from ..compat import py312
|
||||
from ..discovery import find_package_path
|
||||
from ..dist import Distribution
|
||||
from ..warnings import (
|
||||
InformationOnly,
|
||||
SetuptoolsDeprecationWarning,
|
||||
SetuptoolsWarning,
|
||||
)
|
||||
from ..warnings import InformationOnly, SetuptoolsDeprecationWarning, SetuptoolsWarning
|
||||
from .build import build as build_cls
|
||||
from .build_py import build_py as build_py_cls
|
||||
from .dist_info import dist_info as dist_info_cls
|
||||
from .egg_info import egg_info as egg_info_cls
|
||||
from .install import install as install_cls
|
||||
from .install_scripts import install_scripts as install_scripts_cls
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from wheel.wheelfile import WheelFile # noqa
|
||||
from typing_extensions import Self
|
||||
|
||||
if sys.version_info >= (3, 8):
|
||||
from typing import Protocol
|
||||
elif TYPE_CHECKING:
|
||||
from typing_extensions import Protocol
|
||||
else:
|
||||
from abc import ABC as Protocol
|
||||
from .._vendor.wheel.wheelfile import WheelFile
|
||||
|
||||
_Path = Union[str, Path]
|
||||
_P = TypeVar("_P", bound=_Path)
|
||||
_P = TypeVar("_P", bound=StrPath)
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -79,7 +61,7 @@ class _EditableMode(Enum):
|
||||
COMPAT = "compat" # TODO: Remove `compat` after Dec/2022.
|
||||
|
||||
@classmethod
|
||||
def convert(cls, mode: Optional[str]) -> "_EditableMode":
|
||||
def convert(cls, mode: str | None) -> _EditableMode:
|
||||
if not mode:
|
||||
return _EditableMode.LENIENT # default
|
||||
|
||||
@@ -156,13 +138,14 @@ class editable_wheel(Command):
|
||||
self._create_wheel_file(bdist_wheel)
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
# TODO: Fix false-positive [attr-defined] in typeshed
|
||||
project = self.distribution.name or self.distribution.get_name()
|
||||
_DebuggingTips.emit(project=project)
|
||||
raise
|
||||
|
||||
def _ensure_dist_info(self):
|
||||
if self.dist_info_dir is None:
|
||||
dist_info = self.reinitialize_command("dist_info")
|
||||
dist_info = cast(dist_info_cls, self.reinitialize_command("dist_info"))
|
||||
dist_info.output_dir = self.dist_dir
|
||||
dist_info.ensure_finalized()
|
||||
dist_info.run()
|
||||
@@ -181,13 +164,13 @@ class editable_wheel(Command):
|
||||
installer = _NamespaceInstaller(dist, installation_dir, pth_prefix, src_root)
|
||||
installer.install_namespaces()
|
||||
|
||||
def _find_egg_info_dir(self) -> Optional[str]:
|
||||
def _find_egg_info_dir(self) -> str | None:
|
||||
parent_dir = Path(self.dist_info_dir).parent if self.dist_info_dir else Path()
|
||||
candidates = map(str, parent_dir.glob("*.egg-info"))
|
||||
return next(candidates, None)
|
||||
|
||||
def _configure_build(
|
||||
self, name: str, unpacked_wheel: _Path, build_lib: _Path, tmp_dir: _Path
|
||||
self, name: str, unpacked_wheel: StrPath, build_lib: StrPath, tmp_dir: StrPath
|
||||
):
|
||||
"""Configure commands to behave in the following ways:
|
||||
|
||||
@@ -209,12 +192,18 @@ class editable_wheel(Command):
|
||||
scripts = str(Path(unpacked_wheel, f"{name}.data", "scripts"))
|
||||
|
||||
# egg-info may be generated again to create a manifest (used for package data)
|
||||
egg_info = dist.reinitialize_command("egg_info", reinit_subcommands=True)
|
||||
egg_info = cast(
|
||||
egg_info_cls, dist.reinitialize_command("egg_info", reinit_subcommands=True)
|
||||
)
|
||||
egg_info.egg_base = str(tmp_dir)
|
||||
egg_info.ignore_egg_info_in_manifest = True
|
||||
|
||||
build = dist.reinitialize_command("build", reinit_subcommands=True)
|
||||
install = dist.reinitialize_command("install", reinit_subcommands=True)
|
||||
build = cast(
|
||||
build_cls, dist.reinitialize_command("build", reinit_subcommands=True)
|
||||
)
|
||||
install = cast(
|
||||
install_cls, dist.reinitialize_command("install", reinit_subcommands=True)
|
||||
)
|
||||
|
||||
build.build_platlib = build.build_purelib = build.build_lib = build_lib
|
||||
install.install_purelib = install.install_platlib = install.install_lib = wheel
|
||||
@@ -222,12 +211,14 @@ class editable_wheel(Command):
|
||||
install.install_headers = headers
|
||||
install.install_data = data
|
||||
|
||||
install_scripts = dist.get_command_obj("install_scripts")
|
||||
install_scripts = cast(
|
||||
install_scripts_cls, dist.get_command_obj("install_scripts")
|
||||
)
|
||||
install_scripts.no_ep = True
|
||||
|
||||
build.build_temp = str(tmp_dir)
|
||||
|
||||
build_py = dist.get_command_obj("build_py")
|
||||
build_py = cast(build_py_cls, dist.get_command_obj("build_py"))
|
||||
build_py.compile = False
|
||||
build_py.existing_egg_info_dir = self._find_egg_info_dir()
|
||||
|
||||
@@ -240,6 +231,7 @@ class editable_wheel(Command):
|
||||
"""Set the ``editable_mode`` flag in the build sub-commands"""
|
||||
dist = self.distribution
|
||||
build = dist.get_command_obj("build")
|
||||
# TODO: Update typeshed distutils stubs to overload non-None return type by default
|
||||
for cmd_name in build.get_sub_commands():
|
||||
cmd = dist.get_command_obj(cmd_name)
|
||||
if hasattr(cmd, "editable_mode"):
|
||||
@@ -247,9 +239,9 @@ class editable_wheel(Command):
|
||||
elif hasattr(cmd, "inplace"):
|
||||
cmd.inplace = True # backward compatibility with distutils
|
||||
|
||||
def _collect_build_outputs(self) -> Tuple[List[str], Dict[str, str]]:
|
||||
files: List[str] = []
|
||||
mapping: Dict[str, str] = {}
|
||||
def _collect_build_outputs(self) -> tuple[list[str], dict[str, str]]:
|
||||
files: list[str] = []
|
||||
mapping: dict[str, str] = {}
|
||||
build = self.get_finalized_command("build")
|
||||
|
||||
for cmd_name in build.get_sub_commands():
|
||||
@@ -262,8 +254,12 @@ class editable_wheel(Command):
|
||||
return files, mapping
|
||||
|
||||
def _run_build_commands(
|
||||
self, dist_name: str, unpacked_wheel: _Path, build_lib: _Path, tmp_dir: _Path
|
||||
) -> Tuple[List[str], Dict[str, str]]:
|
||||
self,
|
||||
dist_name: str,
|
||||
unpacked_wheel: StrPath,
|
||||
build_lib: StrPath,
|
||||
tmp_dir: StrPath,
|
||||
) -> tuple[list[str], dict[str, str]]:
|
||||
self._configure_build(dist_name, unpacked_wheel, build_lib, tmp_dir)
|
||||
self._run_build_subcommands()
|
||||
files, mapping = self._collect_build_outputs()
|
||||
@@ -272,7 +268,7 @@ class editable_wheel(Command):
|
||||
self._run_install("data")
|
||||
return files, mapping
|
||||
|
||||
def _run_build_subcommands(self):
|
||||
def _run_build_subcommands(self) -> None:
|
||||
"""
|
||||
Issue #3501 indicates that some plugins/customizations might rely on:
|
||||
|
||||
@@ -286,10 +282,10 @@ class editable_wheel(Command):
|
||||
# TODO: Once plugins/customisations had the chance to catch up, replace
|
||||
# `self._run_build_subcommands()` with `self.run_command("build")`.
|
||||
# Also remove _safely_run, TestCustomBuildPy. Suggested date: Aug/2023.
|
||||
build: Command = self.get_finalized_command("build")
|
||||
build = self.get_finalized_command("build")
|
||||
for name in build.get_sub_commands():
|
||||
cmd = self.get_finalized_command(name)
|
||||
if name == "build_py" and type(cmd) != build_py_cls:
|
||||
if name == "build_py" and type(cmd) is not build_py_cls:
|
||||
self._safely_run(name)
|
||||
else:
|
||||
self.run_command(name)
|
||||
@@ -360,8 +356,8 @@ class editable_wheel(Command):
|
||||
self,
|
||||
name: str,
|
||||
tag: str,
|
||||
build_lib: _Path,
|
||||
) -> "EditableStrategy":
|
||||
build_lib: StrPath,
|
||||
) -> EditableStrategy:
|
||||
"""Decides which strategy to use to implement an editable installation."""
|
||||
build_name = f"__editable__.{name}-{tag}"
|
||||
project_dir = Path(self.project_dir)
|
||||
@@ -384,28 +380,31 @@ class editable_wheel(Command):
|
||||
|
||||
|
||||
class EditableStrategy(Protocol):
|
||||
def __call__(self, wheel: "WheelFile", files: List[str], mapping: Dict[str, str]):
|
||||
...
|
||||
def __call__(self, wheel: WheelFile, files: list[str], mapping: dict[str, str]): ...
|
||||
|
||||
def __enter__(self):
|
||||
...
|
||||
def __enter__(self) -> Self: ...
|
||||
|
||||
def __exit__(self, _exc_type, _exc_value, _traceback):
|
||||
...
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: type[BaseException] | None,
|
||||
exc_value: BaseException | None,
|
||||
traceback: TracebackType | None,
|
||||
/,
|
||||
) -> object: ...
|
||||
|
||||
|
||||
class _StaticPth:
|
||||
def __init__(self, dist: Distribution, name: str, path_entries: List[Path]):
|
||||
def __init__(self, dist: Distribution, name: str, path_entries: list[Path]):
|
||||
self.dist = dist
|
||||
self.name = name
|
||||
self.path_entries = path_entries
|
||||
|
||||
def __call__(self, wheel: "WheelFile", files: List[str], mapping: Dict[str, str]):
|
||||
entries = "\n".join((str(p.resolve()) for p in self.path_entries))
|
||||
def __call__(self, wheel: WheelFile, files: list[str], mapping: dict[str, str]):
|
||||
entries = "\n".join(str(p.resolve()) for p in self.path_entries)
|
||||
contents = _encode_pth(f"{entries}\n")
|
||||
wheel.writestr(f"__editable__.{self.name}.pth", contents)
|
||||
|
||||
def __enter__(self):
|
||||
def __enter__(self) -> Self:
|
||||
msg = f"""
|
||||
Editable install will be performed using .pth file to extend `sys.path` with:
|
||||
{list(map(os.fspath, self.path_entries))!r}
|
||||
@@ -413,8 +412,13 @@ class _StaticPth:
|
||||
_logger.warning(msg + _LENIENT_WARNING)
|
||||
return self
|
||||
|
||||
def __exit__(self, _exc_type, _exc_value, _traceback):
|
||||
...
|
||||
def __exit__(
|
||||
self,
|
||||
_exc_type: object,
|
||||
_exc_value: object,
|
||||
_traceback: object,
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class _LinkTree(_StaticPth):
|
||||
@@ -432,19 +436,19 @@ class _LinkTree(_StaticPth):
|
||||
self,
|
||||
dist: Distribution,
|
||||
name: str,
|
||||
auxiliary_dir: _Path,
|
||||
build_lib: _Path,
|
||||
auxiliary_dir: StrPath,
|
||||
build_lib: StrPath,
|
||||
):
|
||||
self.auxiliary_dir = Path(auxiliary_dir)
|
||||
self.build_lib = Path(build_lib).resolve()
|
||||
self._file = dist.get_command_obj("build_py").copy_file
|
||||
super().__init__(dist, name, [self.auxiliary_dir])
|
||||
|
||||
def __call__(self, wheel: "WheelFile", files: List[str], mapping: Dict[str, str]):
|
||||
def __call__(self, wheel: WheelFile, files: list[str], mapping: dict[str, str]):
|
||||
self._create_links(files, mapping)
|
||||
super().__call__(wheel, files, mapping)
|
||||
|
||||
def _normalize_output(self, file: str) -> Optional[str]:
|
||||
def _normalize_output(self, file: str) -> str | None:
|
||||
# Files relative to build_lib will be normalized to None
|
||||
with suppress(ValueError):
|
||||
path = Path(file).resolve().relative_to(self.build_lib)
|
||||
@@ -460,8 +464,9 @@ class _LinkTree(_StaticPth):
|
||||
def _create_links(self, outputs, output_mapping):
|
||||
self.auxiliary_dir.mkdir(parents=True, exist_ok=True)
|
||||
link_type = "sym" if _can_symlink_files(self.auxiliary_dir) else "hard"
|
||||
mappings = {self._normalize_output(k): v for k, v in output_mapping.items()}
|
||||
mappings.pop(None, None) # remove files that are not relative to build_lib
|
||||
normalised = ((self._normalize_output(k), v) for k, v in output_mapping.items())
|
||||
# remove files that are not relative to build_lib
|
||||
mappings = {k: v for k, v in normalised if k is not None}
|
||||
|
||||
for output in outputs:
|
||||
relative = self._normalize_output(output)
|
||||
@@ -471,12 +476,17 @@ class _LinkTree(_StaticPth):
|
||||
for relative, src in mappings.items():
|
||||
self._create_file(relative, src, link=link_type)
|
||||
|
||||
def __enter__(self):
|
||||
def __enter__(self) -> Self:
|
||||
msg = "Strict editable install will be performed using a link tree.\n"
|
||||
_logger.warning(msg + _STRICT_WARNING)
|
||||
return self
|
||||
|
||||
def __exit__(self, _exc_type, _exc_value, _traceback):
|
||||
def __exit__(
|
||||
self,
|
||||
_exc_type: object,
|
||||
_exc_value: object,
|
||||
_traceback: object,
|
||||
) -> None:
|
||||
msg = f"""\n
|
||||
Strict editable installation performed using the auxiliary directory:
|
||||
{self.auxiliary_dir}
|
||||
@@ -492,13 +502,13 @@ class _TopLevelFinder:
|
||||
self.dist = dist
|
||||
self.name = name
|
||||
|
||||
def __call__(self, wheel: "WheelFile", files: List[str], mapping: Dict[str, str]):
|
||||
def template_vars(self) -> tuple[str, str, dict[str, str], dict[str, list[str]]]:
|
||||
src_root = self.dist.src_root or os.curdir
|
||||
top_level = chain(_find_packages(self.dist), _find_top_level_modules(self.dist))
|
||||
package_dir = self.dist.package_dir or {}
|
||||
roots = _find_package_roots(top_level, package_dir, src_root)
|
||||
|
||||
namespaces_: Dict[str, List[str]] = dict(
|
||||
namespaces_: dict[str, list[str]] = dict(
|
||||
chain(
|
||||
_find_namespaces(self.dist.packages or [], roots),
|
||||
((ns, []) for ns in _find_virtual_namespaces(roots)),
|
||||
@@ -517,18 +527,32 @@ class _TopLevelFinder:
|
||||
|
||||
name = f"__editable__.{self.name}.finder"
|
||||
finder = _normalization.safe_identifier(name)
|
||||
return finder, name, mapping, namespaces_
|
||||
|
||||
def get_implementation(self) -> Iterator[tuple[str, bytes]]:
|
||||
finder, name, mapping, namespaces_ = self.template_vars()
|
||||
|
||||
content = bytes(_finder_template(name, mapping, namespaces_), "utf-8")
|
||||
wheel.writestr(f"{finder}.py", content)
|
||||
yield (f"{finder}.py", content)
|
||||
|
||||
content = _encode_pth(f"import {finder}; {finder}.install()")
|
||||
wheel.writestr(f"__editable__.{self.name}.pth", content)
|
||||
yield (f"__editable__.{self.name}.pth", content)
|
||||
|
||||
def __enter__(self):
|
||||
def __call__(self, wheel: WheelFile, files: list[str], mapping: dict[str, str]):
|
||||
for file, content in self.get_implementation():
|
||||
wheel.writestr(file, content)
|
||||
|
||||
def __enter__(self) -> Self:
|
||||
msg = "Editable install will be performed using a meta path finder.\n"
|
||||
_logger.warning(msg + _LENIENT_WARNING)
|
||||
return self
|
||||
|
||||
def __exit__(self, _exc_type, _exc_value, _traceback):
|
||||
def __exit__(
|
||||
self,
|
||||
_exc_type: object,
|
||||
_exc_value: object,
|
||||
_traceback: object,
|
||||
) -> None:
|
||||
msg = """\n
|
||||
Please be careful with folders in your working directory with the same
|
||||
name as your package as they may take precedence during imports.
|
||||
@@ -537,17 +561,20 @@ class _TopLevelFinder:
|
||||
|
||||
|
||||
def _encode_pth(content: str) -> bytes:
|
||||
""".pth files are always read with 'locale' encoding, the recommendation
|
||||
"""
|
||||
Prior to Python 3.13 (see https://github.com/python/cpython/issues/77102),
|
||||
.pth files are always read with 'locale' encoding, the recommendation
|
||||
from the cpython core developers is to write them as ``open(path, "w")``
|
||||
and ignore warnings (see python/cpython#77102, pypa/setuptools#3937).
|
||||
This function tries to simulate this behaviour without having to create an
|
||||
actual file, in a way that supports a range of active Python versions.
|
||||
(There seems to be some variety in the way different version of Python handle
|
||||
``encoding=None``, not all of them use ``locale.getpreferredencoding(False)``).
|
||||
``encoding=None``, not all of them use ``locale.getpreferredencoding(False)``
|
||||
or ``locale.getencoding()``).
|
||||
"""
|
||||
encoding = "locale" if sys.version_info >= (3, 10) else None
|
||||
with io.BytesIO() as buffer:
|
||||
wrapper = io.TextIOWrapper(buffer, encoding)
|
||||
wrapper = io.TextIOWrapper(buffer, encoding=py312.PTH_ENCODING)
|
||||
# TODO: Python 3.13 replace the whole function with `bytes(content, "utf-8")`
|
||||
wrapper.write(content)
|
||||
wrapper.flush()
|
||||
buffer.seek(0)
|
||||
@@ -575,7 +602,7 @@ def _can_symlink_files(base_dir: Path) -> bool:
|
||||
|
||||
|
||||
def _simple_layout(
|
||||
packages: Iterable[str], package_dir: Dict[str, str], project_dir: Path
|
||||
packages: Iterable[str], package_dir: dict[str, str], project_dir: StrPath
|
||||
) -> bool:
|
||||
"""Return ``True`` if:
|
||||
- all packages are contained by the same parent directory, **and**
|
||||
@@ -608,7 +635,7 @@ def _simple_layout(
|
||||
layout = {pkg: find_package_path(pkg, package_dir, project_dir) for pkg in packages}
|
||||
if not layout:
|
||||
return set(package_dir) in ({}, {""})
|
||||
parent = os.path.commonpath([_parent_path(k, v) for k, v in layout.items()])
|
||||
parent = os.path.commonpath(starmap(_parent_path, layout.items()))
|
||||
return all(
|
||||
_path.same_path(Path(parent, *key.split('.')), value)
|
||||
for key, value in layout.items()
|
||||
@@ -657,9 +684,9 @@ def _find_top_level_modules(dist: Distribution) -> Iterator[str]:
|
||||
def _find_package_roots(
|
||||
packages: Iterable[str],
|
||||
package_dir: Mapping[str, str],
|
||||
src_root: _Path,
|
||||
) -> Dict[str, str]:
|
||||
pkg_roots: Dict[str, str] = {
|
||||
src_root: StrPath,
|
||||
) -> dict[str, str]:
|
||||
pkg_roots: dict[str, str] = {
|
||||
pkg: _absolute_root(find_package_path(pkg, package_dir, src_root))
|
||||
for pkg in sorted(packages)
|
||||
}
|
||||
@@ -667,7 +694,7 @@ def _find_package_roots(
|
||||
return _remove_nested(pkg_roots)
|
||||
|
||||
|
||||
def _absolute_root(path: _Path) -> str:
|
||||
def _absolute_root(path: StrPath) -> str:
|
||||
"""Works for packages and top-level modules"""
|
||||
path_ = Path(path)
|
||||
parent = path_.parent
|
||||
@@ -678,7 +705,7 @@ def _absolute_root(path: _Path) -> str:
|
||||
return str(parent.resolve() / path_.name)
|
||||
|
||||
|
||||
def _find_virtual_namespaces(pkg_roots: Dict[str, str]) -> Iterator[str]:
|
||||
def _find_virtual_namespaces(pkg_roots: dict[str, str]) -> Iterator[str]:
|
||||
"""By carefully designing ``package_dir``, it is possible to implement the logical
|
||||
structure of PEP 420 in a package without the corresponding directories.
|
||||
|
||||
@@ -703,15 +730,15 @@ def _find_virtual_namespaces(pkg_roots: Dict[str, str]) -> Iterator[str]:
|
||||
|
||||
|
||||
def _find_namespaces(
|
||||
packages: List[str], pkg_roots: Dict[str, str]
|
||||
) -> Iterator[Tuple[str, List[str]]]:
|
||||
packages: list[str], pkg_roots: dict[str, str]
|
||||
) -> Iterator[tuple[str, list[str]]]:
|
||||
for pkg in packages:
|
||||
path = find_package_path(pkg, pkg_roots, "")
|
||||
if Path(path).exists() and not Path(path, "__init__.py").exists():
|
||||
yield (pkg, [path])
|
||||
|
||||
|
||||
def _remove_nested(pkg_roots: Dict[str, str]) -> Dict[str, str]:
|
||||
def _remove_nested(pkg_roots: dict[str, str]) -> dict[str, str]:
|
||||
output = dict(pkg_roots.copy())
|
||||
|
||||
for pkg, path in reversed(list(pkg_roots.items())):
|
||||
@@ -772,6 +799,7 @@ class _NamespaceInstaller(namespaces.Installer):
|
||||
|
||||
|
||||
_FINDER_TEMPLATE = """\
|
||||
from __future__ import annotations
|
||||
import sys
|
||||
from importlib.machinery import ModuleSpec, PathFinder
|
||||
from importlib.machinery import all_suffixes as module_suffixes
|
||||
@@ -779,16 +807,14 @@ from importlib.util import spec_from_file_location
|
||||
from itertools import chain
|
||||
from pathlib import Path
|
||||
|
||||
MAPPING = {mapping!r}
|
||||
NAMESPACES = {namespaces!r}
|
||||
MAPPING: dict[str, str] = {mapping!r}
|
||||
NAMESPACES: dict[str, list[str]] = {namespaces!r}
|
||||
PATH_PLACEHOLDER = {name!r} + ".__path_hook__"
|
||||
|
||||
|
||||
class _EditableFinder: # MetaPathFinder
|
||||
@classmethod
|
||||
def find_spec(cls, fullname, path=None, target=None):
|
||||
extra_path = []
|
||||
|
||||
def find_spec(cls, fullname: str, path=None, target=None) -> ModuleSpec | None: # type: ignore
|
||||
# Top-level packages and modules (we know these exist in the FS)
|
||||
if fullname in MAPPING:
|
||||
pkg_path = MAPPING[fullname]
|
||||
@@ -799,35 +825,42 @@ class _EditableFinder: # MetaPathFinder
|
||||
# to the importlib.machinery implementation.
|
||||
parent, _, child = fullname.rpartition(".")
|
||||
if parent and parent in MAPPING:
|
||||
return PathFinder.find_spec(fullname, path=[MAPPING[parent], *extra_path])
|
||||
return PathFinder.find_spec(fullname, path=[MAPPING[parent]])
|
||||
|
||||
# Other levels of nesting should be handled automatically by importlib
|
||||
# using the parent path.
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def _find_spec(cls, fullname, candidate_path):
|
||||
def _find_spec(cls, fullname: str, candidate_path: Path) -> ModuleSpec | None:
|
||||
init = candidate_path / "__init__.py"
|
||||
candidates = (candidate_path.with_suffix(x) for x in module_suffixes())
|
||||
for candidate in chain([init], candidates):
|
||||
if candidate.exists():
|
||||
return spec_from_file_location(fullname, candidate)
|
||||
return None
|
||||
|
||||
|
||||
class _EditableNamespaceFinder: # PathEntryFinder
|
||||
@classmethod
|
||||
def _path_hook(cls, path):
|
||||
def _path_hook(cls, path) -> type[_EditableNamespaceFinder]:
|
||||
if path == PATH_PLACEHOLDER:
|
||||
return cls
|
||||
raise ImportError
|
||||
|
||||
@classmethod
|
||||
def _paths(cls, fullname):
|
||||
# Ensure __path__ is not empty for the spec to be considered a namespace.
|
||||
return NAMESPACES[fullname] or MAPPING.get(fullname) or [PATH_PLACEHOLDER]
|
||||
def _paths(cls, fullname: str) -> list[str]:
|
||||
paths = NAMESPACES[fullname]
|
||||
if not paths and fullname in MAPPING:
|
||||
paths = [MAPPING[fullname]]
|
||||
# Always add placeholder, for 2 reasons:
|
||||
# 1. __path__ cannot be empty for the spec to be considered namespace.
|
||||
# 2. In the case of nested namespaces, we need to force
|
||||
# import machinery to query _EditableNamespaceFinder again.
|
||||
return [*paths, PATH_PLACEHOLDER]
|
||||
|
||||
@classmethod
|
||||
def find_spec(cls, fullname, target=None):
|
||||
def find_spec(cls, fullname: str, target=None) -> ModuleSpec | None: # type: ignore
|
||||
if fullname in NAMESPACES:
|
||||
spec = ModuleSpec(fullname, None, is_package=True)
|
||||
spec.submodule_search_locations = cls._paths(fullname)
|
||||
@@ -835,7 +868,7 @@ class _EditableNamespaceFinder: # PathEntryFinder
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def find_module(cls, fullname):
|
||||
def find_module(cls, _fullname) -> None:
|
||||
return None
|
||||
|
||||
|
||||
@@ -855,7 +888,7 @@ def install():
|
||||
|
||||
|
||||
def _finder_template(
|
||||
name: str, mapping: Mapping[str, str], namespaces: Dict[str, List[str]]
|
||||
name: str, mapping: Mapping[str, str], namespaces: dict[str, list[str]]
|
||||
) -> str:
|
||||
"""Create a string containing the code for the``MetaPathFinder`` and
|
||||
``PathEntryFinder``.
|
||||
|
||||
@@ -2,34 +2,35 @@
|
||||
|
||||
Create a distribution's .egg-info directory and contents"""
|
||||
|
||||
from distutils.filelist import FileList as _FileList
|
||||
from distutils.errors import DistutilsInternalError
|
||||
from distutils.util import convert_path
|
||||
from distutils import log
|
||||
import distutils.errors
|
||||
import distutils.filelist
|
||||
import collections
|
||||
import functools
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import collections
|
||||
|
||||
from .._importlib import metadata
|
||||
from .. import _entry_points, _normalization
|
||||
from . import _requirestxt
|
||||
import packaging
|
||||
import packaging.requirements
|
||||
import packaging.version
|
||||
|
||||
from setuptools import Command
|
||||
from setuptools.command.sdist import sdist
|
||||
from setuptools.command.sdist import walk_revctrl
|
||||
from setuptools.command.setopt import edit_config
|
||||
from setuptools.command import bdist_egg
|
||||
import setuptools.unicode_utils as unicode_utils
|
||||
from setuptools import Command
|
||||
from setuptools.command import bdist_egg
|
||||
from setuptools.command.sdist import sdist, walk_revctrl
|
||||
from setuptools.command.setopt import edit_config
|
||||
from setuptools.glob import glob
|
||||
|
||||
from setuptools.extern import packaging
|
||||
from .. import _entry_points, _normalization
|
||||
from .._importlib import metadata
|
||||
from ..warnings import SetuptoolsDeprecationWarning
|
||||
from . import _requirestxt
|
||||
|
||||
import distutils.errors
|
||||
import distutils.filelist
|
||||
from distutils import log
|
||||
from distutils.errors import DistutilsInternalError
|
||||
from distutils.filelist import FileList as _FileList
|
||||
from distutils.util import convert_path
|
||||
|
||||
PY_MAJOR = '{}.{}'.format(*sys.version_info)
|
||||
|
||||
@@ -127,7 +128,7 @@ class InfoCommon:
|
||||
|
||||
def tagged_version(self):
|
||||
tagged = self._maybe_tag(self.distribution.get_version())
|
||||
return _normalization.best_effort_version(tagged)
|
||||
return _normalization.safe_version(tagged)
|
||||
|
||||
def _maybe_tag(self, version):
|
||||
"""
|
||||
@@ -148,7 +149,10 @@ class InfoCommon:
|
||||
def _safe_tags(self) -> str:
|
||||
# To implement this we can rely on `safe_version` pretending to be version 0
|
||||
# followed by tags. Then we simply discard the starting 0 (fake version number)
|
||||
return _normalization.best_effort_version(f"0{self.vtags}")[1:]
|
||||
try:
|
||||
return _normalization.safe_version(f"0{self.vtags}")[1:]
|
||||
except packaging.version.InvalidVersion:
|
||||
return _normalization.safe_name(self.vtags.replace(' ', '.'))
|
||||
|
||||
def tags(self) -> str:
|
||||
version = ''
|
||||
@@ -169,7 +173,7 @@ class egg_info(InfoCommon, Command):
|
||||
'egg-base=',
|
||||
'e',
|
||||
"directory containing .egg-info directories"
|
||||
" (default: top of the source tree)",
|
||||
" [default: top of the source tree]",
|
||||
),
|
||||
('tag-date', 'd', "Add date stamp (e.g. 20050528) to version number"),
|
||||
('tag-build=', 'b', "Specify explicit tag to add to version number"),
|
||||
@@ -247,17 +251,6 @@ class egg_info(InfoCommon, Command):
|
||||
#
|
||||
self.distribution.metadata.version = self.egg_version
|
||||
|
||||
# If we bootstrapped around the lack of a PKG-INFO, as might be the
|
||||
# case in a fresh checkout, make sure that any special tags get added
|
||||
# to the version info
|
||||
#
|
||||
pd = self.distribution._patched_dist
|
||||
key = getattr(pd, "key", None) or getattr(pd, "name", None)
|
||||
if pd is not None and key == self.egg_name.lower():
|
||||
pd._version = self.egg_version
|
||||
pd._parsed_version = packaging.version.Version(self.egg_version)
|
||||
self.distribution._patched_dist = None
|
||||
|
||||
def _get_egg_basename(self, py_version=PY_MAJOR, platform=None):
|
||||
"""Compute filename of the output egg. Private API."""
|
||||
return _egg_basename(self.egg_name, self.egg_version, py_version, platform)
|
||||
@@ -360,16 +353,16 @@ class FileList(_FileList):
|
||||
}
|
||||
log_map = {
|
||||
'include': "warning: no files found matching '%s'",
|
||||
'exclude': ("warning: no previously-included files found " "matching '%s'"),
|
||||
'exclude': ("warning: no previously-included files found matching '%s'"),
|
||||
'global-include': (
|
||||
"warning: no files found matching '%s' " "anywhere in distribution"
|
||||
"warning: no files found matching '%s' anywhere in distribution"
|
||||
),
|
||||
'global-exclude': (
|
||||
"warning: no previously-included files matching "
|
||||
"'%s' found anywhere in distribution"
|
||||
),
|
||||
'recursive-include': (
|
||||
"warning: no files found matching '%s' " "under directory '%s'"
|
||||
"warning: no files found matching '%s' under directory '%s'"
|
||||
),
|
||||
'recursive-exclude': (
|
||||
"warning: no previously-included files matching "
|
||||
@@ -382,9 +375,8 @@ class FileList(_FileList):
|
||||
try:
|
||||
process_action = action_map[action]
|
||||
except KeyError:
|
||||
raise DistutilsInternalError(
|
||||
"this cannot happen: invalid action '{action!s}'".format(action=action),
|
||||
)
|
||||
msg = f"Invalid MANIFEST.in: unknown action {action!r} in {line!r}"
|
||||
raise DistutilsInternalError(msg) from None
|
||||
|
||||
# OK, now we know that the action is valid and we have the
|
||||
# right number of words on the line for that action -- so we
|
||||
@@ -532,10 +524,10 @@ class manifest_maker(sdist):
|
||||
template = "MANIFEST.in"
|
||||
|
||||
def initialize_options(self):
|
||||
self.use_defaults = 1
|
||||
self.prune = 1
|
||||
self.manifest_only = 1
|
||||
self.force_manifest = 1
|
||||
self.use_defaults = True
|
||||
self.prune = True
|
||||
self.manifest_only = True
|
||||
self.force_manifest = True
|
||||
self.ignore_egg_info_dir = False
|
||||
|
||||
def finalize_options(self):
|
||||
@@ -614,16 +606,6 @@ class manifest_maker(sdist):
|
||||
log.debug("adding file referenced by config '%s'", rf)
|
||||
self.filelist.extend(referenced)
|
||||
|
||||
def prune_file_list(self):
|
||||
build = self.get_finalized_command('build')
|
||||
base_dir = self.distribution.get_fullname()
|
||||
self.filelist.prune(build.build_base)
|
||||
self.filelist.prune(base_dir)
|
||||
sep = re.escape(os.sep)
|
||||
self.filelist.exclude_pattern(
|
||||
r'(^|' + sep + r')(RCS|CVS|\.svn)' + sep, is_regex=1
|
||||
)
|
||||
|
||||
def _safe_data_files(self, build_py):
|
||||
"""
|
||||
The parent class implementation of this method
|
||||
@@ -697,9 +679,9 @@ write_setup_requirements = _requirestxt.write_setup_requirements
|
||||
|
||||
|
||||
def write_toplevel_names(cmd, basename, filename):
|
||||
pkgs = dict.fromkeys(
|
||||
[k.split('.', 1)[0] for k in cmd.distribution.iter_distribution_names()]
|
||||
)
|
||||
pkgs = dict.fromkeys([
|
||||
k.split('.', 1)[0] for k in cmd.distribution.iter_distribution_names()
|
||||
])
|
||||
cmd.write_file("top-level names", filename, '\n'.join(sorted(pkgs)) + '\n')
|
||||
|
||||
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
from distutils.errors import DistutilsArgError
|
||||
import inspect
|
||||
from __future__ import annotations
|
||||
|
||||
import glob
|
||||
import inspect
|
||||
import platform
|
||||
import distutils.command.install as orig
|
||||
from collections.abc import Callable
|
||||
from typing import Any, ClassVar, cast
|
||||
|
||||
import setuptools
|
||||
|
||||
from ..dist import Distribution
|
||||
from ..warnings import SetuptoolsDeprecationWarning, SetuptoolsWarning
|
||||
from .bdist_egg import bdist_egg as bdist_egg_cls
|
||||
|
||||
import distutils.command.install as orig
|
||||
from distutils.errors import DistutilsArgError
|
||||
|
||||
# Prior to numpy 1.9, NumPy relies on the '_install' name, so provide it for
|
||||
# now. See https://github.com/pypa/setuptools/issues/199/
|
||||
@@ -15,6 +23,8 @@ _install = orig.install
|
||||
class install(orig.install):
|
||||
"""Use easy_install to install the package, w/dependencies"""
|
||||
|
||||
distribution: Distribution # override distutils.dist.Distribution with setuptools.dist.Distribution
|
||||
|
||||
user_options = orig.install.user_options + [
|
||||
('old-and-unmanageable', None, "Try not to use this!"),
|
||||
(
|
||||
@@ -27,7 +37,9 @@ class install(orig.install):
|
||||
'old-and-unmanageable',
|
||||
'single-version-externally-managed',
|
||||
]
|
||||
new_commands = [
|
||||
# Type the same as distutils.command.install.install.sub_commands
|
||||
# Must keep the second tuple item potentially None due to invariance
|
||||
new_commands: ClassVar[list[tuple[str, Callable[[Any], bool] | None]]] = [
|
||||
('install_egg_info', lambda self: True),
|
||||
('install_scripts', lambda self: True),
|
||||
]
|
||||
@@ -47,12 +59,12 @@ class install(orig.install):
|
||||
# and then add a due_date to this warning.
|
||||
)
|
||||
|
||||
orig.install.initialize_options(self)
|
||||
super().initialize_options()
|
||||
self.old_and_unmanageable = None
|
||||
self.single_version_externally_managed = None
|
||||
|
||||
def finalize_options(self):
|
||||
orig.install.finalize_options(self)
|
||||
super().finalize_options()
|
||||
if self.root:
|
||||
self.single_version_externally_managed = True
|
||||
elif self.single_version_externally_managed:
|
||||
@@ -71,18 +83,21 @@ class install(orig.install):
|
||||
# command without --root or --single-version-externally-managed
|
||||
self.path_file = None
|
||||
self.extra_dirs = ''
|
||||
return None
|
||||
|
||||
def run(self):
|
||||
# Explicit request for old-style install? Just do it
|
||||
if self.old_and_unmanageable or self.single_version_externally_managed:
|
||||
return orig.install.run(self)
|
||||
return super().run()
|
||||
|
||||
if not self._called_from_setup(inspect.currentframe()):
|
||||
# Run in backward-compatibility mode to support bdist_* commands.
|
||||
orig.install.run(self)
|
||||
super().run()
|
||||
else:
|
||||
self.do_egg_install()
|
||||
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _called_from_setup(run_frame):
|
||||
"""
|
||||
@@ -114,6 +129,8 @@ class install(orig.install):
|
||||
|
||||
return caller_module == 'distutils.dist' and info.function == 'run_commands'
|
||||
|
||||
return False
|
||||
|
||||
def do_egg_install(self):
|
||||
easy_install = self.distribution.get_command_class('easy_install')
|
||||
|
||||
@@ -130,7 +147,8 @@ class install(orig.install):
|
||||
cmd.package_index.scan(glob.glob('*.egg'))
|
||||
|
||||
self.run_command('bdist_egg')
|
||||
args = [self.distribution.get_command_obj('bdist_egg').egg_output]
|
||||
bdist_egg = cast(bdist_egg_cls, self.distribution.get_command_obj('bdist_egg'))
|
||||
args = [bdist_egg.egg_output]
|
||||
|
||||
if setuptools.bootstrap_install_from:
|
||||
# Bootstrap self-installation of setuptools
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
from distutils import log, dir_util
|
||||
import os
|
||||
|
||||
from setuptools import Command
|
||||
from setuptools import namespaces
|
||||
from setuptools import Command, namespaces
|
||||
from setuptools.archive_util import unpack_archive
|
||||
|
||||
from .._path import ensure_directory
|
||||
|
||||
from distutils import dir_util, log
|
||||
|
||||
|
||||
class install_egg_info(namespaces.Installer, Command):
|
||||
"""Install an .egg-info directory for the package"""
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
from itertools import product, starmap
|
||||
|
||||
from .._path import StrPath
|
||||
from ..dist import Distribution
|
||||
|
||||
import distutils.command.install_lib as orig
|
||||
|
||||
|
||||
class install_lib(orig.install_lib):
|
||||
"""Don't add compiled flags to filenames of non-Python files"""
|
||||
|
||||
distribution: Distribution # override distutils.dist.Distribution with setuptools.dist.Distribution
|
||||
|
||||
def run(self):
|
||||
self.build()
|
||||
outfiles = self.install()
|
||||
@@ -85,13 +93,13 @@ class install_lib(orig.install_lib):
|
||||
|
||||
def copy_tree(
|
||||
self,
|
||||
infile,
|
||||
outfile,
|
||||
preserve_mode=1,
|
||||
preserve_times=1,
|
||||
preserve_symlinks=0,
|
||||
infile: StrPath,
|
||||
outfile: str,
|
||||
preserve_mode=True,
|
||||
preserve_times=True,
|
||||
preserve_symlinks=False,
|
||||
level=1,
|
||||
):
|
||||
) -> list[str]:
|
||||
assert preserve_mode and preserve_times and not preserve_symlinks
|
||||
exclude = self.get_exclusions()
|
||||
|
||||
@@ -101,11 +109,12 @@ class install_lib(orig.install_lib):
|
||||
# Exclude namespace package __init__.py* files from the output
|
||||
|
||||
from setuptools.archive_util import unpack_directory
|
||||
|
||||
from distutils import log
|
||||
|
||||
outfiles = []
|
||||
outfiles: list[str] = []
|
||||
|
||||
def pf(src, dst):
|
||||
def pf(src: str, dst: str):
|
||||
if dst in exclude:
|
||||
log.warn("Skipping installation of %s (namespace package)", dst)
|
||||
return False
|
||||
|
||||
@@ -1,24 +1,30 @@
|
||||
from distutils import log
|
||||
import distutils.command.install_scripts as orig
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from .._path import ensure_directory
|
||||
from ..dist import Distribution
|
||||
|
||||
import distutils.command.install_scripts as orig
|
||||
from distutils import log
|
||||
|
||||
|
||||
class install_scripts(orig.install_scripts):
|
||||
"""Do normal script install, plus any egg_info wrapper scripts"""
|
||||
|
||||
distribution: Distribution # override distutils.dist.Distribution with setuptools.dist.Distribution
|
||||
|
||||
def initialize_options(self):
|
||||
orig.install_scripts.initialize_options(self)
|
||||
self.no_ep = False
|
||||
|
||||
def run(self):
|
||||
def run(self) -> None:
|
||||
self.run_command("egg_info")
|
||||
if self.distribution.scripts:
|
||||
orig.install_scripts.run(self) # run first to set up self.outfiles
|
||||
else:
|
||||
self.outfiles = []
|
||||
self.outfiles: list[str] = []
|
||||
if self.no_ep:
|
||||
# don't install entry point scripts into .egg file!
|
||||
return
|
||||
@@ -27,6 +33,7 @@ class install_scripts(orig.install_scripts):
|
||||
def _install_ep_scripts(self):
|
||||
# Delay import side-effects
|
||||
from pkg_resources import Distribution, PathMetadata
|
||||
|
||||
from . import easy_install as ei
|
||||
|
||||
ei_cmd = self.get_finalized_command("egg_info")
|
||||
@@ -57,10 +64,10 @@ class install_scripts(orig.install_scripts):
|
||||
target = os.path.join(self.install_dir, script_name)
|
||||
self.outfiles.append(target)
|
||||
|
||||
encoding = None if "b" in mode else "utf-8"
|
||||
mask = current_umask()
|
||||
if not self.dry_run:
|
||||
ensure_directory(target)
|
||||
f = open(target, "w" + mode)
|
||||
f.write(contents)
|
||||
f.close()
|
||||
with open(target, "w" + mode, encoding=encoding) as f:
|
||||
f.write(contents)
|
||||
chmod(target, 0o777 - mask)
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
from distutils import log
|
||||
import distutils.command.register as orig
|
||||
|
||||
from setuptools.errors import RemovedCommandError
|
||||
|
||||
|
||||
class register(orig.register):
|
||||
"""Formerly used to register packages on PyPI."""
|
||||
|
||||
def run(self):
|
||||
msg = (
|
||||
"The register command has been removed, use twine to upload "
|
||||
+ "instead (https://pypi.org/p/twine)"
|
||||
)
|
||||
|
||||
self.announce("ERROR: " + msg, log.ERROR)
|
||||
|
||||
raise RemovedCommandError(msg)
|
||||
@@ -1,11 +1,14 @@
|
||||
from distutils.util import convert_path
|
||||
from distutils import log
|
||||
from distutils.errors import DistutilsOptionError
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from setuptools import Command
|
||||
|
||||
from distutils import log
|
||||
from distutils.errors import DistutilsOptionError
|
||||
from distutils.util import convert_path
|
||||
|
||||
|
||||
class rotate(Command):
|
||||
"""Delete older distributions"""
|
||||
@@ -17,7 +20,7 @@ class rotate(Command):
|
||||
('keep=', 'k', "number of matching distributions to keep"),
|
||||
]
|
||||
|
||||
boolean_options = []
|
||||
boolean_options: list[str] = []
|
||||
|
||||
def initialize_options(self):
|
||||
self.match = None
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
from distutils import log
|
||||
import distutils.command.sdist as orig
|
||||
import os
|
||||
import sys
|
||||
import io
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
import re
|
||||
from itertools import chain
|
||||
|
||||
from .._importlib import metadata
|
||||
from ..dist import Distribution
|
||||
from .build import _ORIGINAL_SUBCOMMANDS
|
||||
|
||||
import distutils.command.sdist as orig
|
||||
from distutils import log
|
||||
|
||||
_default_revctrl = list
|
||||
|
||||
|
||||
def walk_revctrl(dirname=''):
|
||||
"""Find all files under revision control"""
|
||||
for ep in metadata.entry_points(group='setuptools.file_finders'):
|
||||
for item in ep.load()(dirname):
|
||||
yield item
|
||||
yield from ep.load()(dirname)
|
||||
|
||||
|
||||
class sdist(orig.sdist):
|
||||
@@ -32,7 +34,7 @@ class sdist(orig.sdist):
|
||||
(
|
||||
'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]",
|
||||
),
|
||||
(
|
||||
'owner=',
|
||||
@@ -46,7 +48,8 @@ class sdist(orig.sdist):
|
||||
),
|
||||
]
|
||||
|
||||
negative_opt = {}
|
||||
distribution: Distribution # override distutils.dist.Distribution with setuptools.dist.Distribution
|
||||
negative_opt: dict[str, str] = {}
|
||||
|
||||
README_EXTENSIONS = ['', '.rst', '.txt', '.md']
|
||||
READMES = tuple('README{0}'.format(ext) for ext in README_EXTENSIONS)
|
||||
@@ -73,14 +76,6 @@ class sdist(orig.sdist):
|
||||
def initialize_options(self):
|
||||
orig.sdist.initialize_options(self)
|
||||
|
||||
self._default_to_gztar()
|
||||
|
||||
def _default_to_gztar(self):
|
||||
# only needed on Python prior to 3.6.
|
||||
if sys.version_info >= (3, 6, 0, 'beta', 1):
|
||||
return
|
||||
self.formats = ['gztar']
|
||||
|
||||
def make_distribution(self):
|
||||
"""
|
||||
Workaround for #516
|
||||
@@ -107,7 +102,7 @@ class sdist(orig.sdist):
|
||||
yield
|
||||
finally:
|
||||
if orig_val is not NoValue:
|
||||
setattr(os, 'link', orig_val)
|
||||
os.link = orig_val
|
||||
|
||||
def add_defaults(self):
|
||||
super().add_defaults()
|
||||
@@ -162,6 +157,12 @@ class sdist(orig.sdist):
|
||||
except TypeError:
|
||||
log.warn("data_files contains unexpected objects")
|
||||
|
||||
def prune_file_list(self):
|
||||
super().prune_file_list()
|
||||
# Prevent accidental inclusion of test-related cache dirs at the project root
|
||||
sep = re.escape(os.sep)
|
||||
self.filelist.exclude_pattern(r"^(\.tox|\.nox|\.venv)" + sep, is_regex=True)
|
||||
|
||||
def check_readme(self):
|
||||
for f in self.READMES:
|
||||
if os.path.exists(f):
|
||||
@@ -189,9 +190,9 @@ class sdist(orig.sdist):
|
||||
if not os.path.isfile(self.manifest):
|
||||
return False
|
||||
|
||||
with io.open(self.manifest, 'rb') as fp:
|
||||
with open(self.manifest, 'rb') as fp:
|
||||
first_line = fp.readline()
|
||||
return first_line != '# file GENERATED by distutils, do NOT edit\n'.encode()
|
||||
return first_line != b'# file GENERATED by distutils, do NOT edit\n'
|
||||
|
||||
def read_manifest(self):
|
||||
"""Read the manifest file (named by 'self.manifest') and use it to
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
from distutils.util import convert_path
|
||||
import configparser
|
||||
import os
|
||||
|
||||
from .. import Command
|
||||
from ..unicode_utils import _cfg_read_utf8_with_fallback
|
||||
|
||||
import distutils
|
||||
from distutils import log
|
||||
from distutils.errors import DistutilsOptionError
|
||||
import distutils
|
||||
import os
|
||||
import configparser
|
||||
|
||||
from setuptools import Command
|
||||
from distutils.util import convert_path
|
||||
|
||||
__all__ = ['config_file', 'edit_config', 'option_base', 'setopt']
|
||||
|
||||
@@ -36,7 +38,8 @@ def edit_config(filename, settings, dry_run=False):
|
||||
log.debug("Reading configuration from %s", filename)
|
||||
opts = configparser.RawConfigParser()
|
||||
opts.optionxform = lambda x: x
|
||||
opts.read([filename])
|
||||
_cfg_read_utf8_with_fallback(opts, filename)
|
||||
|
||||
for section, options in settings.items():
|
||||
if options is None:
|
||||
log.info("Deleting section [%s] from %s", section, filename)
|
||||
@@ -62,7 +65,7 @@ def edit_config(filename, settings, dry_run=False):
|
||||
|
||||
log.info("Writing %s", filename)
|
||||
if not dry_run:
|
||||
with open(filename, 'w') as f:
|
||||
with open(filename, 'w', encoding="utf-8") as f:
|
||||
opts.write(f)
|
||||
|
||||
|
||||
|
||||
@@ -1,82 +1,29 @@
|
||||
import os
|
||||
import operator
|
||||
import sys
|
||||
import contextlib
|
||||
import itertools
|
||||
import unittest
|
||||
from distutils.errors import DistutilsError, DistutilsOptionError
|
||||
from distutils import log
|
||||
from unittest import TestLoader
|
||||
from __future__ import annotations
|
||||
|
||||
from pkg_resources import (
|
||||
resource_listdir,
|
||||
resource_exists,
|
||||
normalize_path,
|
||||
working_set,
|
||||
evaluate_marker,
|
||||
add_activation_listener,
|
||||
require,
|
||||
)
|
||||
from .._importlib import metadata
|
||||
from setuptools import Command
|
||||
from setuptools.extern.more_itertools import unique_everseen
|
||||
from setuptools.extern.jaraco.functools import pass_none
|
||||
from setuptools.warnings import SetuptoolsDeprecationWarning
|
||||
|
||||
|
||||
class ScanningLoader(TestLoader):
|
||||
def __init__(self):
|
||||
TestLoader.__init__(self)
|
||||
self._visited = set()
|
||||
|
||||
def loadTestsFromModule(self, module, pattern=None):
|
||||
"""Return a suite of all tests cases contained in the given module
|
||||
|
||||
If the module is a package, load tests from all the modules in it.
|
||||
If the module has an ``additional_tests`` function, call it and add
|
||||
the return value to the tests.
|
||||
"""
|
||||
if module in self._visited:
|
||||
return None
|
||||
self._visited.add(module)
|
||||
|
||||
tests = []
|
||||
tests.append(TestLoader.loadTestsFromModule(self, module))
|
||||
|
||||
if hasattr(module, "additional_tests"):
|
||||
tests.append(module.additional_tests())
|
||||
|
||||
if hasattr(module, '__path__'):
|
||||
for file in resource_listdir(module.__name__, ''):
|
||||
if file.endswith('.py') and file != '__init__.py':
|
||||
submodule = module.__name__ + '.' + file[:-3]
|
||||
else:
|
||||
if resource_exists(module.__name__, file + '/__init__.py'):
|
||||
submodule = module.__name__ + '.' + file
|
||||
else:
|
||||
continue
|
||||
tests.append(self.loadTestsFromName(submodule))
|
||||
|
||||
if len(tests) != 1:
|
||||
return self.suiteClass(tests)
|
||||
else:
|
||||
return tests[0] # don't create a nested suite for only one return
|
||||
# Would restrict to Literal["test"], but mypy doesn't support it: https://github.com/python/mypy/issues/8203
|
||||
def __getattr__(name: str) -> type[_test]:
|
||||
if name == 'test':
|
||||
SetuptoolsDeprecationWarning.emit(
|
||||
"The test command is disabled and references to it are deprecated.",
|
||||
"Please remove any references to `setuptools.command.test` in all "
|
||||
"supported versions of the affected package.",
|
||||
due_date=(2024, 11, 15),
|
||||
stacklevel=2,
|
||||
)
|
||||
return _test
|
||||
raise AttributeError(name)
|
||||
|
||||
|
||||
# adapted from jaraco.classes.properties:NonDataProperty
|
||||
class NonDataProperty:
|
||||
def __init__(self, fget):
|
||||
self.fget = fget
|
||||
class _test(Command):
|
||||
"""
|
||||
Stub to warn when test command is referenced or used.
|
||||
"""
|
||||
|
||||
def __get__(self, obj, objtype=None):
|
||||
if obj is None:
|
||||
return self
|
||||
return self.fget(obj)
|
||||
|
||||
|
||||
class test(Command):
|
||||
"""Command to run unit tests after in-place build"""
|
||||
|
||||
description = "run unit tests after in-place build (deprecated)"
|
||||
description = "stub for old test command (do not use)"
|
||||
|
||||
user_options = [
|
||||
('test-module=', 'm', "Run 'test_suite' in specified module"),
|
||||
@@ -89,162 +36,10 @@ class test(Command):
|
||||
]
|
||||
|
||||
def initialize_options(self):
|
||||
self.test_suite = None
|
||||
self.test_module = None
|
||||
self.test_loader = None
|
||||
self.test_runner = None
|
||||
pass
|
||||
|
||||
def finalize_options(self):
|
||||
if self.test_suite and self.test_module:
|
||||
msg = "You may specify a module or a suite, but not both"
|
||||
raise DistutilsOptionError(msg)
|
||||
|
||||
if self.test_suite is None:
|
||||
if self.test_module is None:
|
||||
self.test_suite = self.distribution.test_suite
|
||||
else:
|
||||
self.test_suite = self.test_module + ".test_suite"
|
||||
|
||||
if self.test_loader is None:
|
||||
self.test_loader = getattr(self.distribution, 'test_loader', None)
|
||||
if self.test_loader is None:
|
||||
self.test_loader = "setuptools.command.test:ScanningLoader"
|
||||
if self.test_runner is None:
|
||||
self.test_runner = getattr(self.distribution, 'test_runner', None)
|
||||
|
||||
@NonDataProperty
|
||||
def test_args(self):
|
||||
return list(self._test_args())
|
||||
|
||||
def _test_args(self):
|
||||
if not self.test_suite:
|
||||
yield 'discover'
|
||||
if self.verbose:
|
||||
yield '--verbose'
|
||||
if self.test_suite:
|
||||
yield self.test_suite
|
||||
|
||||
def with_project_on_sys_path(self, func):
|
||||
"""
|
||||
Backward compatibility for project_on_sys_path context.
|
||||
"""
|
||||
with self.project_on_sys_path():
|
||||
func()
|
||||
|
||||
@contextlib.contextmanager
|
||||
def project_on_sys_path(self, include_dists=[]):
|
||||
self.run_command('egg_info')
|
||||
|
||||
# Build extensions in-place
|
||||
self.reinitialize_command('build_ext', inplace=1)
|
||||
self.run_command('build_ext')
|
||||
|
||||
ei_cmd = self.get_finalized_command("egg_info")
|
||||
|
||||
old_path = sys.path[:]
|
||||
old_modules = sys.modules.copy()
|
||||
|
||||
try:
|
||||
project_path = normalize_path(ei_cmd.egg_base)
|
||||
sys.path.insert(0, project_path)
|
||||
working_set.__init__()
|
||||
add_activation_listener(lambda dist: dist.activate())
|
||||
require('%s==%s' % (ei_cmd.egg_name, ei_cmd.egg_version))
|
||||
with self.paths_on_pythonpath([project_path]):
|
||||
yield
|
||||
finally:
|
||||
sys.path[:] = old_path
|
||||
sys.modules.clear()
|
||||
sys.modules.update(old_modules)
|
||||
working_set.__init__()
|
||||
|
||||
@staticmethod
|
||||
@contextlib.contextmanager
|
||||
def paths_on_pythonpath(paths):
|
||||
"""
|
||||
Add the indicated paths to the head of the PYTHONPATH environment
|
||||
variable so that subprocesses will also see the packages at
|
||||
these paths.
|
||||
|
||||
Do this in a context that restores the value on exit.
|
||||
"""
|
||||
nothing = object()
|
||||
orig_pythonpath = os.environ.get('PYTHONPATH', nothing)
|
||||
current_pythonpath = os.environ.get('PYTHONPATH', '')
|
||||
try:
|
||||
prefix = os.pathsep.join(unique_everseen(paths))
|
||||
to_join = filter(None, [prefix, current_pythonpath])
|
||||
new_path = os.pathsep.join(to_join)
|
||||
if new_path:
|
||||
os.environ['PYTHONPATH'] = new_path
|
||||
yield
|
||||
finally:
|
||||
if orig_pythonpath is nothing:
|
||||
os.environ.pop('PYTHONPATH', None)
|
||||
else:
|
||||
os.environ['PYTHONPATH'] = orig_pythonpath
|
||||
|
||||
@staticmethod
|
||||
def install_dists(dist):
|
||||
"""
|
||||
Install the requirements indicated by self.distribution and
|
||||
return an iterable of the dists that were built.
|
||||
"""
|
||||
ir_d = dist.fetch_build_eggs(dist.install_requires)
|
||||
tr_d = dist.fetch_build_eggs(dist.tests_require or [])
|
||||
er_d = dist.fetch_build_eggs(
|
||||
v
|
||||
for k, v in dist.extras_require.items()
|
||||
if k.startswith(':') and evaluate_marker(k[1:])
|
||||
)
|
||||
return itertools.chain(ir_d, tr_d, er_d)
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
self.announce(
|
||||
"WARNING: Testing via this command is deprecated and will be "
|
||||
"removed in a future version. Users looking for a generic test "
|
||||
"entry point independent of test runner are encouraged to use "
|
||||
"tox.",
|
||||
log.WARN,
|
||||
)
|
||||
|
||||
installed_dists = self.install_dists(self.distribution)
|
||||
|
||||
cmd = ' '.join(self._argv)
|
||||
if self.dry_run:
|
||||
self.announce('skipping "%s" (dry run)' % cmd)
|
||||
return
|
||||
|
||||
self.announce('running "%s"' % cmd)
|
||||
|
||||
paths = map(operator.attrgetter('location'), installed_dists)
|
||||
with self.paths_on_pythonpath(paths):
|
||||
with self.project_on_sys_path():
|
||||
self.run_tests()
|
||||
|
||||
def run_tests(self):
|
||||
test = unittest.main(
|
||||
None,
|
||||
None,
|
||||
self._argv,
|
||||
testLoader=self._resolve_as_ep(self.test_loader),
|
||||
testRunner=self._resolve_as_ep(self.test_runner),
|
||||
exit=False,
|
||||
)
|
||||
if not test.result.wasSuccessful():
|
||||
msg = 'Test failed: %s' % test.result
|
||||
self.announce(msg, log.ERROR)
|
||||
raise DistutilsError(msg)
|
||||
|
||||
@property
|
||||
def _argv(self):
|
||||
return ['unittest'] + self.test_args
|
||||
|
||||
@staticmethod
|
||||
@pass_none
|
||||
def _resolve_as_ep(val):
|
||||
"""
|
||||
Load the indicated attribute value, called, as a as if it were
|
||||
specified as an entry point.
|
||||
"""
|
||||
return metadata.EntryPoint(value=val, name=None, group=None).load()()
|
||||
raise RuntimeError("Support for the test command was removed in Setuptools 72")
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
from distutils import log
|
||||
from distutils.command import upload as orig
|
||||
|
||||
from setuptools.errors import RemovedCommandError
|
||||
|
||||
|
||||
class upload(orig.upload):
|
||||
"""Formerly used to upload packages to PyPI."""
|
||||
|
||||
def run(self):
|
||||
msg = (
|
||||
"The upload command has been removed, use twine to upload "
|
||||
+ "instead (https://pypi.org/p/twine)"
|
||||
)
|
||||
|
||||
self.announce("ERROR: " + msg, log.ERROR)
|
||||
raise RemovedCommandError(msg)
|
||||
@@ -1,222 +0,0 @@
|
||||
"""upload_docs
|
||||
|
||||
Implements a Distutils 'upload_docs' subcommand (upload documentation to
|
||||
sites other than PyPi such as devpi).
|
||||
"""
|
||||
|
||||
from base64 import standard_b64encode
|
||||
from distutils import log
|
||||
from distutils.errors import DistutilsOptionError
|
||||
import os
|
||||
import socket
|
||||
import zipfile
|
||||
import tempfile
|
||||
import shutil
|
||||
import itertools
|
||||
import functools
|
||||
import http.client
|
||||
import urllib.parse
|
||||
|
||||
from .._importlib import metadata
|
||||
from ..warnings import SetuptoolsDeprecationWarning
|
||||
|
||||
from .upload import upload
|
||||
|
||||
|
||||
def _encode(s):
|
||||
return s.encode('utf-8', 'surrogateescape')
|
||||
|
||||
|
||||
class upload_docs(upload):
|
||||
# override the default repository as upload_docs isn't
|
||||
# supported by Warehouse (and won't be).
|
||||
DEFAULT_REPOSITORY = 'https://pypi.python.org/pypi/'
|
||||
|
||||
description = 'Upload documentation to sites other than PyPi such as devpi'
|
||||
|
||||
user_options = [
|
||||
(
|
||||
'repository=',
|
||||
'r',
|
||||
"url of repository [default: %s]" % upload.DEFAULT_REPOSITORY,
|
||||
),
|
||||
('show-response', None, 'display full response text from server'),
|
||||
('upload-dir=', None, 'directory to upload'),
|
||||
]
|
||||
boolean_options = upload.boolean_options
|
||||
|
||||
def has_sphinx(self):
|
||||
return bool(
|
||||
self.upload_dir is None
|
||||
and metadata.entry_points(group='distutils.commands', name='build_sphinx')
|
||||
)
|
||||
|
||||
sub_commands = [('build_sphinx', has_sphinx)]
|
||||
|
||||
def initialize_options(self):
|
||||
upload.initialize_options(self)
|
||||
self.upload_dir = None
|
||||
self.target_dir = None
|
||||
|
||||
def finalize_options(self):
|
||||
log.warn(
|
||||
"Upload_docs command is deprecated. Use Read the Docs "
|
||||
"(https://readthedocs.org) instead."
|
||||
)
|
||||
upload.finalize_options(self)
|
||||
if self.upload_dir is None:
|
||||
if self.has_sphinx():
|
||||
build_sphinx = self.get_finalized_command('build_sphinx')
|
||||
self.target_dir = dict(build_sphinx.builder_target_dirs)['html']
|
||||
else:
|
||||
build = self.get_finalized_command('build')
|
||||
self.target_dir = os.path.join(build.build_base, 'docs')
|
||||
else:
|
||||
self.ensure_dirname('upload_dir')
|
||||
self.target_dir = self.upload_dir
|
||||
self.announce('Using upload directory %s' % self.target_dir)
|
||||
|
||||
def create_zipfile(self, filename):
|
||||
zip_file = zipfile.ZipFile(filename, "w")
|
||||
try:
|
||||
self.mkpath(self.target_dir) # just in case
|
||||
for root, dirs, files in os.walk(self.target_dir):
|
||||
if root == self.target_dir and not files:
|
||||
tmpl = "no files found in upload directory '%s'"
|
||||
raise DistutilsOptionError(tmpl % self.target_dir)
|
||||
for name in files:
|
||||
full = os.path.join(root, name)
|
||||
relative = root[len(self.target_dir) :].lstrip(os.path.sep)
|
||||
dest = os.path.join(relative, name)
|
||||
zip_file.write(full, dest)
|
||||
finally:
|
||||
zip_file.close()
|
||||
|
||||
def run(self):
|
||||
SetuptoolsDeprecationWarning.emit(
|
||||
"Deprecated command",
|
||||
"""
|
||||
upload_docs is deprecated and will be removed in a future version.
|
||||
Instead, use tools like devpi and Read the Docs; or lower level tools like
|
||||
httpie and curl to interact directly with your hosting service API.
|
||||
""",
|
||||
due_date=(2023, 9, 26), # warning introduced in 27 Jul 2022
|
||||
)
|
||||
|
||||
# Run sub commands
|
||||
for cmd_name in self.get_sub_commands():
|
||||
self.run_command(cmd_name)
|
||||
|
||||
tmp_dir = tempfile.mkdtemp()
|
||||
name = self.distribution.metadata.get_name()
|
||||
zip_file = os.path.join(tmp_dir, "%s.zip" % name)
|
||||
try:
|
||||
self.create_zipfile(zip_file)
|
||||
self.upload_file(zip_file)
|
||||
finally:
|
||||
shutil.rmtree(tmp_dir)
|
||||
|
||||
@staticmethod
|
||||
def _build_part(item, sep_boundary):
|
||||
key, values = item
|
||||
title = '\nContent-Disposition: form-data; name="%s"' % key
|
||||
# handle multiple entries for the same name
|
||||
if not isinstance(values, list):
|
||||
values = [values]
|
||||
for value in values:
|
||||
if isinstance(value, tuple):
|
||||
title += '; filename="%s"' % value[0]
|
||||
value = value[1]
|
||||
else:
|
||||
value = _encode(value)
|
||||
yield sep_boundary
|
||||
yield _encode(title)
|
||||
yield b"\n\n"
|
||||
yield value
|
||||
if value and value[-1:] == b'\r':
|
||||
yield b'\n' # write an extra newline (lurve Macs)
|
||||
|
||||
@classmethod
|
||||
def _build_multipart(cls, data):
|
||||
"""
|
||||
Build up the MIME payload for the POST data
|
||||
"""
|
||||
boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
|
||||
sep_boundary = b'\n--' + boundary.encode('ascii')
|
||||
end_boundary = sep_boundary + b'--'
|
||||
end_items = (
|
||||
end_boundary,
|
||||
b"\n",
|
||||
)
|
||||
builder = functools.partial(
|
||||
cls._build_part,
|
||||
sep_boundary=sep_boundary,
|
||||
)
|
||||
part_groups = map(builder, data.items())
|
||||
parts = itertools.chain.from_iterable(part_groups)
|
||||
body_items = itertools.chain(parts, end_items)
|
||||
content_type = 'multipart/form-data; boundary=%s' % boundary
|
||||
return b''.join(body_items), content_type
|
||||
|
||||
def upload_file(self, filename):
|
||||
with open(filename, 'rb') as f:
|
||||
content = f.read()
|
||||
meta = self.distribution.metadata
|
||||
data = {
|
||||
':action': 'doc_upload',
|
||||
'name': meta.get_name(),
|
||||
'content': (os.path.basename(filename), content),
|
||||
}
|
||||
# set up the authentication
|
||||
credentials = _encode(self.username + ':' + self.password)
|
||||
credentials = standard_b64encode(credentials).decode('ascii')
|
||||
auth = "Basic " + credentials
|
||||
|
||||
body, ct = self._build_multipart(data)
|
||||
|
||||
msg = "Submitting documentation to %s" % (self.repository)
|
||||
self.announce(msg, log.INFO)
|
||||
|
||||
# build the Request
|
||||
# We can't use urllib2 since we need to send the Basic
|
||||
# auth right with the first request
|
||||
schema, netloc, url, params, query, fragments = urllib.parse.urlparse(
|
||||
self.repository
|
||||
)
|
||||
assert not params and not query and not fragments
|
||||
if schema == 'http':
|
||||
conn = http.client.HTTPConnection(netloc)
|
||||
elif schema == 'https':
|
||||
conn = http.client.HTTPSConnection(netloc)
|
||||
else:
|
||||
raise AssertionError("unsupported schema " + schema)
|
||||
|
||||
data = ''
|
||||
try:
|
||||
conn.connect()
|
||||
conn.putrequest("POST", url)
|
||||
content_type = ct
|
||||
conn.putheader('Content-type', content_type)
|
||||
conn.putheader('Content-length', str(len(body)))
|
||||
conn.putheader('Authorization', auth)
|
||||
conn.endheaders()
|
||||
conn.send(body)
|
||||
except socket.error as e:
|
||||
self.announce(str(e), log.ERROR)
|
||||
return
|
||||
|
||||
r = conn.getresponse()
|
||||
if r.status == 200:
|
||||
msg = 'Server response (%s): %s' % (r.status, r.reason)
|
||||
self.announce(msg, log.INFO)
|
||||
elif r.status == 301:
|
||||
location = r.getheader('Location')
|
||||
if location is None:
|
||||
location = 'https://pythonhosted.org/%s/' % meta.get_name()
|
||||
msg = 'Upload successful. Visit %s' % location
|
||||
self.announce(msg, log.INFO)
|
||||
else:
|
||||
msg = 'Upload failed (%s): %s' % (r.status, r.reason)
|
||||
self.announce(msg, log.ERROR)
|
||||
if self.show_response:
|
||||
print('-' * 75, r.read(), '-' * 75)
|
||||
Reference in New Issue
Block a user