update
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
from __future__ import annotations
|
||||
|
||||
__version__ = "0.41.2"
|
||||
__version__ = "0.44.0"
|
||||
|
||||
Binary file not shown.
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/wheel/__pycache__/_bdist_wheel.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/wheel/__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.
604
.CondaPkg/env/Lib/site-packages/wheel/_bdist_wheel.py
vendored
Normal file
604
.CondaPkg/env/Lib/site-packages/wheel/_bdist_wheel.py
vendored
Normal file
@@ -0,0 +1,604 @@
|
||||
"""
|
||||
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
|
||||
|
||||
import setuptools
|
||||
from setuptools import Command
|
||||
|
||||
from . import __version__ as wheel_version
|
||||
from .metadata import pkginfo_to_metadata
|
||||
from .util import log
|
||||
from .vendored.packaging import tags
|
||||
from .vendored.packaging import version as _packaging_version
|
||||
from .wheelfile import WheelFile
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import types
|
||||
|
||||
|
||||
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(setuptools.__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:
|
||||
from .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: tuple[type[Exception], Exception, types.TracebackType],
|
||||
) -> None:
|
||||
remove_readonly_exc(func, path, excinfo[1])
|
||||
|
||||
|
||||
def remove_readonly_exc(func: Callable[..., object], path: str, exc: Exception) -> 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, "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):
|
||||
self.bdist_dir: str = None
|
||||
self.data_dir = None
|
||||
self.plat_name: str | None = None
|
||||
self.plat_tag = None
|
||||
self.format = "zip"
|
||||
self.keep_temp = False
|
||||
self.dist_dir: str | None = None
|
||||
self.egginfo_dir = None
|
||||
self.root_is_pure: bool | None = None
|
||||
self.skip_build = None
|
||||
self.relative = False
|
||||
self.owner = None
|
||||
self.group = None
|
||||
self.universal: bool = False
|
||||
self.compression: str | int = "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):
|
||||
if self.bdist_dir is None:
|
||||
bdist_base = self.get_finalized_command("bdist").bdist_base
|
||||
self.bdist_dir = os.path.join(bdist_base, "wheel")
|
||||
|
||||
egg_info = 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 = self.plat_name is not None
|
||||
|
||||
try:
|
||||
self.compression = self.supported_compressions[self.compression]
|
||||
except KeyError:
|
||||
raise ValueError(f"Unsupported compression: {self.compression}") from None
|
||||
|
||||
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()
|
||||
)
|
||||
|
||||
if self.py_limited_api and not re.match(
|
||||
PY_LIMITED_API_PATTERN, self.py_limited_api
|
||||
):
|
||||
raise ValueError(f"py-limited-api must match '{PY_LIMITED_API_PATTERN}'")
|
||||
|
||||
# Support legacy [wheel] section for setting universal
|
||||
wheel = self.distribution.get_option_dict("wheel")
|
||||
if "universal" in wheel:
|
||||
# please don't define this in your global configs
|
||||
log.warning(
|
||||
"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.build_number is not None and not self.build_number[:1].isdigit():
|
||||
raise ValueError("Build tag (build-number) must start with a digit.")
|
||||
|
||||
@property
|
||||
def wheel_dist_name(self):
|
||||
"""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 += (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:
|
||||
plat_name = cast(str, 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.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"bdist_wheel ({wheel_version})"
|
||||
):
|
||||
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):
|
||||
"""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)
|
||||
@@ -5,11 +5,11 @@ import logging
|
||||
import sys
|
||||
|
||||
|
||||
def _not_warning(record):
|
||||
def _not_warning(record: logging.LogRecord) -> bool:
|
||||
return record.levelno < logging.WARNING
|
||||
|
||||
|
||||
def configure():
|
||||
def configure() -> None:
|
||||
"""
|
||||
Configure logging to emit warning and above to stderr
|
||||
and everything else to stdout. This behavior is provided
|
||||
|
||||
600
.CondaPkg/env/Lib/site-packages/wheel/bdist_wheel.py
vendored
600
.CondaPkg/env/Lib/site-packages/wheel/bdist_wheel.py
vendored
@@ -1,593 +1,11 @@
|
||||
"""
|
||||
Create a wheel (.whl) distribution.
|
||||
from warnings import warn
|
||||
|
||||
A wheel is a built archive format.
|
||||
"""
|
||||
from ._bdist_wheel import bdist_wheel as bdist_wheel
|
||||
|
||||
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 io import BytesIO
|
||||
from shutil import rmtree
|
||||
from zipfile import ZIP_DEFLATED, ZIP_STORED
|
||||
|
||||
import setuptools
|
||||
from setuptools import Command
|
||||
|
||||
from . import __version__ as wheel_version
|
||||
from .macosx_libfile import calculate_macosx_platform_tag
|
||||
from .metadata import pkginfo_to_metadata
|
||||
from .util import log
|
||||
from .vendored.packaging import tags
|
||||
from .vendored.packaging import version as _packaging_version
|
||||
from .wheelfile import WheelFile
|
||||
|
||||
|
||||
def safe_name(name):
|
||||
"""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):
|
||||
"""
|
||||
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(setuptools.__version__.split(".")[0])
|
||||
|
||||
PY_LIMITED_API_PATTERN = r"cp3\d"
|
||||
|
||||
|
||||
def _is_32bit_interpreter():
|
||||
return struct.calcsize("P") == 4
|
||||
|
||||
|
||||
def python_tag():
|
||||
return f"py{sys.version_info[0]}"
|
||||
|
||||
|
||||
def get_platform(archive_root):
|
||||
"""Return our platform name 'win32', 'linux_x86_64'"""
|
||||
result = sysconfig.get_platform()
|
||||
if result.startswith("macosx") and archive_root is not None:
|
||||
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, fallback, expected=True, warn=True):
|
||||
"""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():
|
||||
"""Return the ABI tag based on SOABI (if available) or emulate SOABI (PyPy2)."""
|
||||
soabi = 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":
|
||||
abi = "cp" + soabi.split("-")[1]
|
||||
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):
|
||||
return safe_name(name).replace("-", "_")
|
||||
|
||||
|
||||
def safer_version(version):
|
||||
return safe_version(version).replace("-", "_")
|
||||
|
||||
|
||||
def remove_readonly(func, path, excinfo):
|
||||
remove_readonly_exc(func, path, excinfo[1])
|
||||
|
||||
|
||||
def remove_readonly_exc(func, path, exc):
|
||||
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 "
|
||||
"(default: %s)" % 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, "make a universal wheel" " (default: false)"),
|
||||
(
|
||||
"compression=",
|
||||
None,
|
||||
"zipfile compression (one of: {})"
|
||||
" (default: 'deflated')".format(", ".join(supported_compressions)),
|
||||
),
|
||||
(
|
||||
"python-tag=",
|
||||
None,
|
||||
"Python implementation compatibility tag"
|
||||
" (default: '%s')" % (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):
|
||||
self.bdist_dir = None
|
||||
self.data_dir = None
|
||||
self.plat_name = None
|
||||
self.plat_tag = None
|
||||
self.format = "zip"
|
||||
self.keep_temp = False
|
||||
self.dist_dir = None
|
||||
self.egginfo_dir = None
|
||||
self.root_is_pure = None
|
||||
self.skip_build = None
|
||||
self.relative = False
|
||||
self.owner = None
|
||||
self.group = None
|
||||
self.universal = False
|
||||
self.compression = "deflated"
|
||||
self.python_tag = python_tag()
|
||||
self.build_number = None
|
||||
self.py_limited_api = False
|
||||
self.plat_name_supplied = False
|
||||
|
||||
def finalize_options(self):
|
||||
if self.bdist_dir is None:
|
||||
bdist_base = self.get_finalized_command("bdist").bdist_base
|
||||
self.bdist_dir = os.path.join(bdist_base, "wheel")
|
||||
|
||||
egg_info = 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 = self.plat_name is not None
|
||||
|
||||
try:
|
||||
self.compression = self.supported_compressions[self.compression]
|
||||
except KeyError:
|
||||
raise ValueError(f"Unsupported compression: {self.compression}") from None
|
||||
|
||||
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()
|
||||
)
|
||||
|
||||
if self.py_limited_api and not re.match(
|
||||
PY_LIMITED_API_PATTERN, self.py_limited_api
|
||||
):
|
||||
raise ValueError("py-limited-api must match '%s'" % PY_LIMITED_API_PATTERN)
|
||||
|
||||
# Support legacy [wheel] section for setting universal
|
||||
wheel = self.distribution.get_option_dict("wheel")
|
||||
if "universal" in wheel:
|
||||
# please don't define this in your global configs
|
||||
log.warning(
|
||||
"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.build_number is not None and not self.build_number[:1].isdigit():
|
||||
raise ValueError("Build tag (build-number) must start with a digit.")
|
||||
|
||||
@property
|
||||
def wheel_dist_name(self):
|
||||
"""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 += (self.build_number,)
|
||||
return "-".join(components)
|
||||
|
||||
def get_tag(self):
|
||||
# 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:
|
||||
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 = "{}-{}.dist-info".format(
|
||||
safer_name(self.distribution.get_name()),
|
||||
safer_version(self.distribution.get_version()),
|
||||
)
|
||||
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.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, generator="bdist_wheel (" + wheel_version + ")"
|
||||
):
|
||||
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}")
|
||||
buffer = BytesIO()
|
||||
BytesGenerator(buffer, maxheaderlen=0).flatten(msg)
|
||||
with open(wheelfile_path, "wb") as f:
|
||||
f.write(buffer.getvalue().replace(b"\r\n", b"\r"))
|
||||
|
||||
def _ensure_relative(self, path):
|
||||
# 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):
|
||||
if setuptools_major_version >= 57:
|
||||
# Setuptools has resolved any patterns to actual file names
|
||||
return self.distribution.metadata.license_files or ()
|
||||
|
||||
files = 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 = 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, distinfo_path):
|
||||
"""Convert an .egg-info directory into a .dist-info directory"""
|
||||
|
||||
def adios(p):
|
||||
"""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
|
||||
pkginfo_path = egginfo_path
|
||||
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)
|
||||
warn(
|
||||
"The 'wheel' package is no longer the canonical location of the 'bdist_wheel' "
|
||||
"command, and will be removed in a future release. Please update to setuptools "
|
||||
"v70.1 or later which contains an integrated version of this command.",
|
||||
DeprecationWarning,
|
||||
stacklevel=1,
|
||||
)
|
||||
|
||||
@@ -14,25 +14,25 @@ class WheelError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def unpack_f(args):
|
||||
def unpack_f(args: argparse.Namespace) -> None:
|
||||
from .unpack import unpack
|
||||
|
||||
unpack(args.wheelfile, args.dest)
|
||||
|
||||
|
||||
def pack_f(args):
|
||||
def pack_f(args: argparse.Namespace) -> None:
|
||||
from .pack import pack
|
||||
|
||||
pack(args.directory, args.dest_dir, args.build_number)
|
||||
|
||||
|
||||
def convert_f(args):
|
||||
def convert_f(args: argparse.Namespace) -> None:
|
||||
from .convert import convert
|
||||
|
||||
convert(args.files, args.dest_dir, args.verbose)
|
||||
|
||||
|
||||
def tags_f(args):
|
||||
def tags_f(args: argparse.Namespace) -> None:
|
||||
from .tags import tags
|
||||
|
||||
names = (
|
||||
@@ -51,14 +51,14 @@ def tags_f(args):
|
||||
print(name)
|
||||
|
||||
|
||||
def version_f(args):
|
||||
def version_f(args: argparse.Namespace) -> None:
|
||||
from .. import __version__
|
||||
|
||||
print("wheel %s" % __version__)
|
||||
print(f"wheel {__version__}")
|
||||
|
||||
|
||||
def parse_build_tag(build_tag: str) -> str:
|
||||
if not build_tag[0].isdigit():
|
||||
if build_tag and not build_tag[0].isdigit():
|
||||
raise ArgumentTypeError("build tag must begin with a digit")
|
||||
elif "-" in build_tag:
|
||||
raise ArgumentTypeError("invalid character ('-') in build tag")
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -7,7 +7,7 @@ import tempfile
|
||||
import zipfile
|
||||
from glob import iglob
|
||||
|
||||
from ..bdist_wheel import bdist_wheel
|
||||
from .._bdist_wheel import bdist_wheel
|
||||
from ..wheelfile import WheelFile
|
||||
from . import WheelError
|
||||
|
||||
@@ -42,7 +42,7 @@ class _bdist_wheel_tag(bdist_wheel):
|
||||
return bdist_wheel.get_tag(self)
|
||||
|
||||
|
||||
def egg2wheel(egg_path: str, dest_dir: str):
|
||||
def egg2wheel(egg_path: str, dest_dir: str) -> None:
|
||||
filename = os.path.basename(egg_path)
|
||||
match = egg_info_re.match(filename)
|
||||
if not match:
|
||||
@@ -96,7 +96,7 @@ def egg2wheel(egg_path: str, dest_dir: str):
|
||||
shutil.rmtree(dir)
|
||||
|
||||
|
||||
def parse_wininst_info(wininfo_name, egginfo_name):
|
||||
def parse_wininst_info(wininfo_name: str, egginfo_name: str | None):
|
||||
"""Extract metadata from filenames.
|
||||
|
||||
Extracts the 4 metadataitems needed (name, version, pyversion, arch) from
|
||||
@@ -167,7 +167,7 @@ def parse_wininst_info(wininfo_name, egginfo_name):
|
||||
return {"name": w_name, "ver": w_ver, "arch": w_arch, "pyver": w_pyver}
|
||||
|
||||
|
||||
def wininst2wheel(path, dest_dir):
|
||||
def wininst2wheel(path: str, dest_dir: str) -> None:
|
||||
with zipfile.ZipFile(path) as bdw:
|
||||
# Search for egg-info in the archive
|
||||
egginfo_name = None
|
||||
@@ -189,11 +189,11 @@ def wininst2wheel(path, dest_dir):
|
||||
paths = {"platlib": ""}
|
||||
|
||||
dist_info = "{name}-{ver}".format(**info)
|
||||
datadir = "%s.data/" % dist_info
|
||||
datadir = f"{dist_info}.data/"
|
||||
|
||||
# rewrite paths to trick ZipFile into extracting an egg
|
||||
# XXX grab wininst .ini - between .exe, padding, and first zip file.
|
||||
members = []
|
||||
members: list[str] = []
|
||||
egginfo_name = ""
|
||||
for zipinfo in bdw.infolist():
|
||||
key, basename = zipinfo.filename.split("/", 1)
|
||||
@@ -246,7 +246,7 @@ def wininst2wheel(path, dest_dir):
|
||||
bw.full_tag_supplied = True
|
||||
bw.full_tag = (pyver, abi, arch)
|
||||
|
||||
dist_info_dir = os.path.join(dir, "%s.dist-info" % dist_info)
|
||||
dist_info_dir = os.path.join(dir, f"{dist_info}.dist-info")
|
||||
bw.egg2dist(os.path.join(dir, egginfo_name), dist_info_dir)
|
||||
bw.write_wheelfile(dist_info_dir, generator="wininst2wheel")
|
||||
|
||||
@@ -257,7 +257,7 @@ def wininst2wheel(path, dest_dir):
|
||||
shutil.rmtree(dir)
|
||||
|
||||
|
||||
def convert(files, dest_dir, verbose):
|
||||
def convert(files: list[str], dest_dir: str, verbose: bool) -> None:
|
||||
for pat in files:
|
||||
for installer in iglob(pat):
|
||||
if os.path.splitext(installer)[1] == ".egg":
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import email.policy
|
||||
import os.path
|
||||
import re
|
||||
from email.generator import BytesGenerator
|
||||
from email.parser import BytesParser
|
||||
|
||||
from wheel.cli import WheelError
|
||||
from wheel.wheelfile import WheelFile
|
||||
|
||||
DIST_INFO_RE = re.compile(r"^(?P<namever>(?P<name>.+?)-(?P<ver>\d.*?))\.dist-info$")
|
||||
BUILD_NUM_RE = re.compile(rb"Build: (\d\w*)$")
|
||||
|
||||
|
||||
def pack(directory: str, dest_dir: str, build_number: str | None):
|
||||
def pack(directory: str, dest_dir: str, build_number: str | None) -> None:
|
||||
"""Repack a previously unpacked wheel directory into a new wheel file.
|
||||
|
||||
The .dist-info/WHEEL file must contain one or more tags so that the target
|
||||
@@ -35,31 +37,29 @@ def pack(directory: str, dest_dir: str, build_number: str | None):
|
||||
name_version = DIST_INFO_RE.match(dist_info_dir).group("namever")
|
||||
|
||||
# Read the tags and the existing build number from .dist-info/WHEEL
|
||||
existing_build_number = None
|
||||
wheel_file_path = os.path.join(directory, dist_info_dir, "WHEEL")
|
||||
with open(wheel_file_path, "rb") as f:
|
||||
tags, existing_build_number = read_tags(f.read())
|
||||
info = BytesParser(policy=email.policy.compat32).parse(f)
|
||||
tags: list[str] = info.get_all("Tag", [])
|
||||
existing_build_number = info.get("Build")
|
||||
|
||||
if not tags:
|
||||
raise WheelError(
|
||||
"No tags present in {}/WHEEL; cannot determine target wheel "
|
||||
"filename".format(dist_info_dir)
|
||||
f"No tags present in {dist_info_dir}/WHEEL; cannot determine target "
|
||||
f"wheel filename"
|
||||
)
|
||||
|
||||
# Set the wheel file name and add/replace/remove the Build tag in .dist-info/WHEEL
|
||||
build_number = build_number if build_number is not None else existing_build_number
|
||||
if build_number is not None:
|
||||
del info["Build"]
|
||||
if build_number:
|
||||
info["Build"] = build_number
|
||||
name_version += "-" + build_number
|
||||
|
||||
if build_number != existing_build_number:
|
||||
with open(wheel_file_path, "rb+") as f:
|
||||
wheel_file_content = f.read()
|
||||
wheel_file_content = set_build_number(wheel_file_content, build_number)
|
||||
|
||||
f.seek(0)
|
||||
f.truncate()
|
||||
f.write(wheel_file_content)
|
||||
with open(wheel_file_path, "wb") as f:
|
||||
BytesGenerator(f, maxheaderlen=0).flatten(info)
|
||||
|
||||
# Reassemble the tags for the wheel file
|
||||
tagline = compute_tagline(tags)
|
||||
@@ -73,45 +73,6 @@ def pack(directory: str, dest_dir: str, build_number: str | None):
|
||||
print("OK")
|
||||
|
||||
|
||||
def read_tags(input_str: bytes) -> tuple[list[str], str | None]:
|
||||
"""Read tags from a string.
|
||||
|
||||
:param input_str: A string containing one or more tags, separated by spaces
|
||||
:return: A list of tags and a list of build tags
|
||||
"""
|
||||
|
||||
tags = []
|
||||
existing_build_number = None
|
||||
for line in input_str.splitlines():
|
||||
if line.startswith(b"Tag: "):
|
||||
tags.append(line.split(b" ")[1].rstrip().decode("ascii"))
|
||||
elif line.startswith(b"Build: "):
|
||||
existing_build_number = line.split(b" ")[1].rstrip().decode("ascii")
|
||||
|
||||
return tags, existing_build_number
|
||||
|
||||
|
||||
def set_build_number(wheel_file_content: bytes, build_number: str | None) -> bytes:
|
||||
"""Compute a build tag and add/replace/remove as necessary.
|
||||
|
||||
:param wheel_file_content: The contents of .dist-info/WHEEL
|
||||
:param build_number: The build tags present in .dist-info/WHEEL
|
||||
:return: The (modified) contents of .dist-info/WHEEL
|
||||
"""
|
||||
replacement = (
|
||||
("Build: %s\r\n" % build_number).encode("ascii") if build_number else b""
|
||||
)
|
||||
|
||||
wheel_file_content, num_replaced = BUILD_NUM_RE.subn(
|
||||
replacement, wheel_file_content
|
||||
)
|
||||
|
||||
if not num_replaced:
|
||||
wheel_file_content += replacement
|
||||
|
||||
return wheel_file_content
|
||||
|
||||
|
||||
def compute_tagline(tags: list[str]) -> str:
|
||||
"""Compute a tagline from a list of tags.
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import email.policy
|
||||
import itertools
|
||||
import os
|
||||
from collections.abc import Iterable
|
||||
from email.parser import BytesParser
|
||||
|
||||
from ..wheelfile import WheelFile
|
||||
from .pack import read_tags, set_build_number
|
||||
|
||||
|
||||
def _compute_tags(original_tags: Iterable[str], new_tags: str | None) -> set[str]:
|
||||
@@ -48,6 +49,7 @@ def tags(
|
||||
assert f.filename, f"{f.filename} must be available"
|
||||
|
||||
wheel_info = f.read(f.dist_info_path + "/WHEEL")
|
||||
info = BytesParser(policy=email.policy.compat32).parsebytes(wheel_info)
|
||||
|
||||
original_wheel_name = os.path.basename(f.filename)
|
||||
namever = f.parsed_filename.group("namever")
|
||||
@@ -56,7 +58,8 @@ def tags(
|
||||
original_abi_tags = f.parsed_filename.group("abi").split(".")
|
||||
original_plat_tags = f.parsed_filename.group("plat").split(".")
|
||||
|
||||
tags, existing_build_tag = read_tags(wheel_info)
|
||||
tags: list[str] = info.get_all("Tag", [])
|
||||
existing_build_tag = info.get("Build")
|
||||
|
||||
impls = {tag.split("-")[0] for tag in tags}
|
||||
abivers = {tag.split("-")[1] for tag in tags}
|
||||
@@ -103,12 +106,13 @@ def tags(
|
||||
final_wheel_name = "-".join(final_tags) + ".whl"
|
||||
|
||||
if original_wheel_name != final_wheel_name:
|
||||
tags = [
|
||||
f"{a}-{b}-{c}"
|
||||
for a, b, c in itertools.product(
|
||||
final_python_tags, final_abi_tags, final_plat_tags
|
||||
)
|
||||
]
|
||||
del info["Tag"], info["Build"]
|
||||
for a, b, c in itertools.product(
|
||||
final_python_tags, final_abi_tags, final_plat_tags
|
||||
):
|
||||
info["Tag"] = f"{a}-{b}-{c}"
|
||||
if build:
|
||||
info["Build"] = build
|
||||
|
||||
original_wheel_path = os.path.join(
|
||||
os.path.dirname(f.filename), original_wheel_name
|
||||
@@ -125,10 +129,7 @@ def tags(
|
||||
if item.filename == f.dist_info_path + "/RECORD":
|
||||
continue
|
||||
if item.filename == f.dist_info_path + "/WHEEL":
|
||||
content = fin.read(item)
|
||||
content = set_tags(content, tags)
|
||||
content = set_build_number(content, build)
|
||||
fout.writestr(item, content)
|
||||
fout.writestr(item, info.as_bytes())
|
||||
else:
|
||||
fout.writestr(item, fin.read(item))
|
||||
|
||||
@@ -136,18 +137,3 @@ def tags(
|
||||
os.remove(original_wheel_path)
|
||||
|
||||
return final_wheel_name
|
||||
|
||||
|
||||
def set_tags(in_string: bytes, tags: Iterable[str]) -> bytes:
|
||||
"""Set the tags in the .dist-info/WHEEL file contents.
|
||||
|
||||
:param in_string: The string to modify.
|
||||
:param tags: The tags to set.
|
||||
"""
|
||||
|
||||
lines = [line for line in in_string.splitlines() if not line.startswith(b"Tag:")]
|
||||
for tag in tags:
|
||||
lines.append(b"Tag: " + tag.encode("ascii"))
|
||||
in_string = b"\r\n".join(lines) + b"\r\n"
|
||||
|
||||
return in_string
|
||||
|
||||
@@ -43,6 +43,13 @@ from __future__ import annotations
|
||||
import ctypes
|
||||
import os
|
||||
import sys
|
||||
from io import BufferedIOBase
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Union
|
||||
|
||||
StrPath = Union[str, os.PathLike[str]]
|
||||
|
||||
"""here the needed const and struct from mach-o header files"""
|
||||
|
||||
@@ -238,7 +245,7 @@ struct build_version_command {
|
||||
"""
|
||||
|
||||
|
||||
def swap32(x):
|
||||
def swap32(x: int) -> int:
|
||||
return (
|
||||
((x << 24) & 0xFF000000)
|
||||
| ((x << 8) & 0x00FF0000)
|
||||
@@ -247,7 +254,10 @@ def swap32(x):
|
||||
)
|
||||
|
||||
|
||||
def get_base_class_and_magic_number(lib_file, seek=None):
|
||||
def get_base_class_and_magic_number(
|
||||
lib_file: BufferedIOBase,
|
||||
seek: int | None = None,
|
||||
) -> tuple[type[ctypes.Structure], int]:
|
||||
if seek is None:
|
||||
seek = lib_file.tell()
|
||||
else:
|
||||
@@ -271,11 +281,11 @@ def get_base_class_and_magic_number(lib_file, seek=None):
|
||||
return BaseClass, magic_number
|
||||
|
||||
|
||||
def read_data(struct_class, lib_file):
|
||||
def read_data(struct_class: type[ctypes.Structure], lib_file: BufferedIOBase):
|
||||
return struct_class.from_buffer_copy(lib_file.read(ctypes.sizeof(struct_class)))
|
||||
|
||||
|
||||
def extract_macosx_min_system_version(path_to_lib):
|
||||
def extract_macosx_min_system_version(path_to_lib: str):
|
||||
with open(path_to_lib, "rb") as lib_file:
|
||||
BaseClass, magic_number = get_base_class_and_magic_number(lib_file, 0)
|
||||
if magic_number not in [FAT_MAGIC, FAT_MAGIC_64, MH_MAGIC, MH_MAGIC_64]:
|
||||
@@ -301,7 +311,7 @@ def extract_macosx_min_system_version(path_to_lib):
|
||||
read_data(FatArch, lib_file) for _ in range(fat_header.nfat_arch)
|
||||
]
|
||||
|
||||
versions_list = []
|
||||
versions_list: list[tuple[int, int, int]] = []
|
||||
for el in fat_arch_list:
|
||||
try:
|
||||
version = read_mach_header(lib_file, el.offset)
|
||||
@@ -333,16 +343,17 @@ def extract_macosx_min_system_version(path_to_lib):
|
||||
return None
|
||||
|
||||
|
||||
def read_mach_header(lib_file, seek=None):
|
||||
def read_mach_header(
|
||||
lib_file: BufferedIOBase,
|
||||
seek: int | None = None,
|
||||
) -> tuple[int, int, int] | None:
|
||||
"""
|
||||
This funcition parse mach-O header and extract
|
||||
information about minimal system version
|
||||
This function parses a Mach-O header and extracts
|
||||
information about the minimal macOS version.
|
||||
|
||||
:param lib_file: reference to opened library file with pointer
|
||||
"""
|
||||
if seek is not None:
|
||||
lib_file.seek(seek)
|
||||
base_class, magic_number = get_base_class_and_magic_number(lib_file)
|
||||
base_class, magic_number = get_base_class_and_magic_number(lib_file, seek)
|
||||
arch = "32" if magic_number == MH_MAGIC else "64"
|
||||
|
||||
class SegmentBase(base_class):
|
||||
@@ -382,14 +393,14 @@ def read_mach_header(lib_file, seek=None):
|
||||
continue
|
||||
|
||||
|
||||
def parse_version(version):
|
||||
def parse_version(version: int) -> tuple[int, int, int]:
|
||||
x = (version & 0xFFFF0000) >> 16
|
||||
y = (version & 0x0000FF00) >> 8
|
||||
z = version & 0x000000FF
|
||||
return x, y, z
|
||||
|
||||
|
||||
def calculate_macosx_platform_tag(archive_root, platform_tag):
|
||||
def calculate_macosx_platform_tag(archive_root: StrPath, platform_tag: str) -> str:
|
||||
"""
|
||||
Calculate proper macosx platform tag basing on files which are included to wheel
|
||||
|
||||
@@ -422,7 +433,7 @@ def calculate_macosx_platform_tag(archive_root, platform_tag):
|
||||
|
||||
assert len(base_version) == 2
|
||||
start_version = base_version
|
||||
versions_dict = {}
|
||||
versions_dict: dict[str, tuple[int, int]] = {}
|
||||
for dirpath, _dirnames, filenames in os.walk(archive_root):
|
||||
for filename in filenames:
|
||||
if filename.endswith(".dylib") or filename.endswith(".so"):
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Tools for converting old- to new-style metadata.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
@@ -10,17 +11,17 @@ import re
|
||||
import textwrap
|
||||
from email.message import Message
|
||||
from email.parser import Parser
|
||||
from typing import Iterator
|
||||
from typing import Generator, Iterable, Iterator, Literal
|
||||
|
||||
from .vendored.packaging.requirements import Requirement
|
||||
|
||||
|
||||
def _nonblank(str):
|
||||
def _nonblank(str: str) -> bool | Literal[""]:
|
||||
return str and not str.startswith("#")
|
||||
|
||||
|
||||
@functools.singledispatch
|
||||
def yield_lines(iterable):
|
||||
def yield_lines(iterable: Iterable[str]) -> Iterator[str]:
|
||||
r"""
|
||||
Yield valid lines of a string or iterable.
|
||||
>>> list(yield_lines(''))
|
||||
@@ -38,11 +39,13 @@ def yield_lines(iterable):
|
||||
|
||||
|
||||
@yield_lines.register(str)
|
||||
def _(text):
|
||||
def _(text: str) -> Iterator[str]:
|
||||
return filter(_nonblank, map(str.strip, text.splitlines()))
|
||||
|
||||
|
||||
def split_sections(s):
|
||||
def split_sections(
|
||||
s: str | Iterator[str],
|
||||
) -> Generator[tuple[str | None, list[str]], None, None]:
|
||||
"""Split a string or iterable thereof into (section, content) pairs
|
||||
Each ``section`` is a stripped version of the section header ("[section]")
|
||||
and each ``content`` is a list of stripped lines excluding blank lines and
|
||||
@@ -50,7 +53,7 @@ def split_sections(s):
|
||||
header, they're returned in a first ``section`` of ``None``.
|
||||
"""
|
||||
section = None
|
||||
content = []
|
||||
content: list[str] = []
|
||||
for line in yield_lines(s):
|
||||
if line.startswith("["):
|
||||
if line.endswith("]"):
|
||||
@@ -67,7 +70,7 @@ def split_sections(s):
|
||||
yield section, content
|
||||
|
||||
|
||||
def safe_extra(extra):
|
||||
def safe_extra(extra: str) -> str:
|
||||
"""Convert an arbitrary string to a standard 'extra' name
|
||||
Any runs of non-alphanumeric characters are replaced with a single '_',
|
||||
and the result is always lowercased.
|
||||
@@ -75,7 +78,7 @@ def safe_extra(extra):
|
||||
return re.sub("[^A-Za-z0-9.-]+", "_", extra).lower()
|
||||
|
||||
|
||||
def safe_name(name):
|
||||
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 '-'.
|
||||
"""
|
||||
@@ -84,10 +87,10 @@ def safe_name(name):
|
||||
|
||||
def requires_to_requires_dist(requirement: Requirement) -> str:
|
||||
"""Return the version specifier for a requirement in PEP 345/566 fashion."""
|
||||
if getattr(requirement, "url", None):
|
||||
if requirement.url:
|
||||
return " @ " + requirement.url
|
||||
|
||||
requires_dist = []
|
||||
requires_dist: list[str] = []
|
||||
for spec in requirement.specifier:
|
||||
requires_dist.append(spec.operator + spec.version)
|
||||
|
||||
@@ -110,7 +113,7 @@ def convert_requirements(requirements: list[str]) -> Iterator[str]:
|
||||
|
||||
|
||||
def generate_requirements(
|
||||
extras_require: dict[str, list[str]]
|
||||
extras_require: dict[str | None, list[str]],
|
||||
) -> Iterator[tuple[str, str]]:
|
||||
"""
|
||||
Convert requirements from a setup()-style dictionary to
|
||||
@@ -130,13 +133,14 @@ def generate_requirements(
|
||||
yield "Provides-Extra", extra
|
||||
if condition:
|
||||
condition = "(" + condition + ") and "
|
||||
condition += "extra == '%s'" % extra
|
||||
condition += f"extra == '{extra}'"
|
||||
|
||||
if condition:
|
||||
condition = " ; " + condition
|
||||
|
||||
for new_req in convert_requirements(depends):
|
||||
yield "Requires-Dist", new_req + condition
|
||||
canonical_req = str(Requirement(new_req + condition))
|
||||
yield "Requires-Dist", canonical_req
|
||||
|
||||
|
||||
def pkginfo_to_metadata(egg_info_path: str, pkginfo_path: str) -> Message:
|
||||
|
||||
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.
@@ -5,7 +5,7 @@ import os
|
||||
import re
|
||||
import sys
|
||||
import warnings
|
||||
from typing import Dict, Generator, Iterator, NamedTuple, Optional, Tuple
|
||||
from typing import Dict, Generator, Iterator, NamedTuple, Optional, Sequence, Tuple
|
||||
|
||||
from ._elffile import EIClass, EIData, ELFFile, EMachine
|
||||
|
||||
@@ -14,6 +14,8 @@ EF_ARM_ABI_VER5 = 0x05000000
|
||||
EF_ARM_ABI_FLOAT_HARD = 0x00000400
|
||||
|
||||
|
||||
# `os.PathLike` not a generic type until Python 3.9, so sticking with `str`
|
||||
# as the type for `path` until then.
|
||||
@contextlib.contextmanager
|
||||
def _parse_elf(path: str) -> Generator[Optional[ELFFile], None, None]:
|
||||
try:
|
||||
@@ -48,12 +50,21 @@ def _is_linux_i686(executable: str) -> bool:
|
||||
)
|
||||
|
||||
|
||||
def _have_compatible_abi(executable: str, arch: str) -> bool:
|
||||
if arch == "armv7l":
|
||||
def _have_compatible_abi(executable: str, archs: Sequence[str]) -> bool:
|
||||
if "armv7l" in archs:
|
||||
return _is_linux_armhf(executable)
|
||||
if arch == "i686":
|
||||
if "i686" in archs:
|
||||
return _is_linux_i686(executable)
|
||||
return arch in {"x86_64", "aarch64", "ppc64", "ppc64le", "s390x"}
|
||||
allowed_archs = {
|
||||
"x86_64",
|
||||
"aarch64",
|
||||
"ppc64",
|
||||
"ppc64le",
|
||||
"s390x",
|
||||
"loongarch64",
|
||||
"riscv64",
|
||||
}
|
||||
return any(arch in allowed_archs for arch in archs)
|
||||
|
||||
|
||||
# If glibc ever changes its major version, we need to know what the last
|
||||
@@ -79,7 +90,7 @@ def _glibc_version_string_confstr() -> Optional[str]:
|
||||
# https://github.com/python/cpython/blob/fcf1d003bf4f0100c/Lib/platform.py#L175-L183
|
||||
try:
|
||||
# Should be a string like "glibc 2.17".
|
||||
version_string: str = getattr(os, "confstr")("CS_GNU_LIBC_VERSION")
|
||||
version_string: Optional[str] = os.confstr("CS_GNU_LIBC_VERSION")
|
||||
assert version_string is not None
|
||||
_, version = version_string.rsplit()
|
||||
except (AssertionError, AttributeError, OSError, ValueError):
|
||||
@@ -156,7 +167,7 @@ def _parse_glibc_version(version_str: str) -> Tuple[int, int]:
|
||||
return int(m.group("major")), int(m.group("minor"))
|
||||
|
||||
|
||||
@functools.lru_cache()
|
||||
@functools.lru_cache
|
||||
def _get_glibc_version() -> Tuple[int, int]:
|
||||
version_str = _glibc_version_string()
|
||||
if version_str is None:
|
||||
@@ -165,13 +176,13 @@ def _get_glibc_version() -> Tuple[int, int]:
|
||||
|
||||
|
||||
# From PEP 513, PEP 600
|
||||
def _is_compatible(name: str, arch: str, version: _GLibCVersion) -> bool:
|
||||
def _is_compatible(arch: str, version: _GLibCVersion) -> bool:
|
||||
sys_glibc = _get_glibc_version()
|
||||
if sys_glibc < version:
|
||||
return False
|
||||
# Check for presence of _manylinux module.
|
||||
try:
|
||||
import _manylinux # noqa
|
||||
import _manylinux
|
||||
except ImportError:
|
||||
return True
|
||||
if hasattr(_manylinux, "manylinux_compatible"):
|
||||
@@ -201,12 +212,22 @@ _LEGACY_MANYLINUX_MAP = {
|
||||
}
|
||||
|
||||
|
||||
def platform_tags(linux: str, arch: str) -> Iterator[str]:
|
||||
if not _have_compatible_abi(sys.executable, arch):
|
||||
def platform_tags(archs: Sequence[str]) -> Iterator[str]:
|
||||
"""Generate manylinux tags compatible to the current platform.
|
||||
|
||||
:param archs: Sequence of compatible architectures.
|
||||
The first one shall be the closest to the actual architecture and be the part of
|
||||
platform tag after the ``linux_`` prefix, e.g. ``x86_64``.
|
||||
The ``linux_`` prefix is assumed as a prerequisite for the current platform to
|
||||
be manylinux-compatible.
|
||||
|
||||
:returns: An iterator of compatible manylinux tags.
|
||||
"""
|
||||
if not _have_compatible_abi(sys.executable, archs):
|
||||
return
|
||||
# Oldest glibc to be supported regardless of architecture is (2, 17).
|
||||
too_old_glibc2 = _GLibCVersion(2, 16)
|
||||
if arch in {"x86_64", "i686"}:
|
||||
if set(archs) & {"x86_64", "i686"}:
|
||||
# On x86/i686 also oldest glibc to be supported is (2, 5).
|
||||
too_old_glibc2 = _GLibCVersion(2, 4)
|
||||
current_glibc = _GLibCVersion(*_get_glibc_version())
|
||||
@@ -220,19 +241,20 @@ def platform_tags(linux: str, arch: str) -> Iterator[str]:
|
||||
for glibc_major in range(current_glibc.major - 1, 1, -1):
|
||||
glibc_minor = _LAST_GLIBC_MINOR[glibc_major]
|
||||
glibc_max_list.append(_GLibCVersion(glibc_major, glibc_minor))
|
||||
for glibc_max in glibc_max_list:
|
||||
if glibc_max.major == too_old_glibc2.major:
|
||||
min_minor = too_old_glibc2.minor
|
||||
else:
|
||||
# For other glibc major versions oldest supported is (x, 0).
|
||||
min_minor = -1
|
||||
for glibc_minor in range(glibc_max.minor, min_minor, -1):
|
||||
glibc_version = _GLibCVersion(glibc_max.major, glibc_minor)
|
||||
tag = "manylinux_{}_{}".format(*glibc_version)
|
||||
if _is_compatible(tag, arch, glibc_version):
|
||||
yield linux.replace("linux", tag)
|
||||
# Handle the legacy manylinux1, manylinux2010, manylinux2014 tags.
|
||||
if glibc_version in _LEGACY_MANYLINUX_MAP:
|
||||
legacy_tag = _LEGACY_MANYLINUX_MAP[glibc_version]
|
||||
if _is_compatible(legacy_tag, arch, glibc_version):
|
||||
yield linux.replace("linux", legacy_tag)
|
||||
for arch in archs:
|
||||
for glibc_max in glibc_max_list:
|
||||
if glibc_max.major == too_old_glibc2.major:
|
||||
min_minor = too_old_glibc2.minor
|
||||
else:
|
||||
# For other glibc major versions oldest supported is (x, 0).
|
||||
min_minor = -1
|
||||
for glibc_minor in range(glibc_max.minor, min_minor, -1):
|
||||
glibc_version = _GLibCVersion(glibc_max.major, glibc_minor)
|
||||
tag = "manylinux_{}_{}".format(*glibc_version)
|
||||
if _is_compatible(arch, glibc_version):
|
||||
yield f"{tag}_{arch}"
|
||||
# Handle the legacy manylinux1, manylinux2010, manylinux2014 tags.
|
||||
if glibc_version in _LEGACY_MANYLINUX_MAP:
|
||||
legacy_tag = _LEGACY_MANYLINUX_MAP[glibc_version]
|
||||
if _is_compatible(arch, glibc_version):
|
||||
yield f"{legacy_tag}_{arch}"
|
||||
|
||||
@@ -8,7 +8,7 @@ import functools
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Iterator, NamedTuple, Optional
|
||||
from typing import Iterator, NamedTuple, Optional, Sequence
|
||||
|
||||
from ._elffile import ELFFile
|
||||
|
||||
@@ -28,7 +28,7 @@ def _parse_musl_version(output: str) -> Optional[_MuslVersion]:
|
||||
return _MuslVersion(major=int(m.group(1)), minor=int(m.group(2)))
|
||||
|
||||
|
||||
@functools.lru_cache()
|
||||
@functools.lru_cache
|
||||
def _get_musl_version(executable: str) -> Optional[_MuslVersion]:
|
||||
"""Detect currently-running musl runtime version.
|
||||
|
||||
@@ -47,24 +47,27 @@ def _get_musl_version(executable: str) -> Optional[_MuslVersion]:
|
||||
return None
|
||||
if ld is None or "musl" not in ld:
|
||||
return None
|
||||
proc = subprocess.run([ld], stderr=subprocess.PIPE, universal_newlines=True)
|
||||
proc = subprocess.run([ld], stderr=subprocess.PIPE, text=True)
|
||||
return _parse_musl_version(proc.stderr)
|
||||
|
||||
|
||||
def platform_tags(arch: str) -> Iterator[str]:
|
||||
def platform_tags(archs: Sequence[str]) -> Iterator[str]:
|
||||
"""Generate musllinux tags compatible to the current platform.
|
||||
|
||||
:param arch: Should be the part of platform tag after the ``linux_``
|
||||
prefix, e.g. ``x86_64``. The ``linux_`` prefix is assumed as a
|
||||
prerequisite for the current platform to be musllinux-compatible.
|
||||
:param archs: Sequence of compatible architectures.
|
||||
The first one shall be the closest to the actual architecture and be the part of
|
||||
platform tag after the ``linux_`` prefix, e.g. ``x86_64``.
|
||||
The ``linux_`` prefix is assumed as a prerequisite for the current platform to
|
||||
be musllinux-compatible.
|
||||
|
||||
:returns: An iterator of compatible musllinux tags.
|
||||
"""
|
||||
sys_musl = _get_musl_version(sys.executable)
|
||||
if sys_musl is None: # Python not dynamically linked against musl.
|
||||
return
|
||||
for minor in range(sys_musl.minor, -1, -1):
|
||||
yield f"musllinux_{sys_musl.major}_{minor}_{arch}"
|
||||
for arch in archs:
|
||||
for minor in range(sys_musl.minor, -1, -1):
|
||||
yield f"musllinux_{sys_musl.major}_{minor}_{arch}"
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Handwritten parser of dependency specifiers.
|
||||
|
||||
The docstring for each __parse_* function contains ENBF-inspired grammar representing
|
||||
The docstring for each __parse_* function contains EBNF-inspired grammar representing
|
||||
the implementation.
|
||||
"""
|
||||
|
||||
@@ -163,7 +163,11 @@ def _parse_extras(tokenizer: Tokenizer) -> List[str]:
|
||||
if not tokenizer.check("LEFT_BRACKET", peek=True):
|
||||
return []
|
||||
|
||||
with tokenizer.enclosing_tokens("LEFT_BRACKET", "RIGHT_BRACKET"):
|
||||
with tokenizer.enclosing_tokens(
|
||||
"LEFT_BRACKET",
|
||||
"RIGHT_BRACKET",
|
||||
around="extras",
|
||||
):
|
||||
tokenizer.consume("WS")
|
||||
extras = _parse_extras_list(tokenizer)
|
||||
tokenizer.consume("WS")
|
||||
@@ -203,7 +207,11 @@ def _parse_specifier(tokenizer: Tokenizer) -> str:
|
||||
specifier = LEFT_PARENTHESIS WS? version_many WS? RIGHT_PARENTHESIS
|
||||
| WS? version_many WS?
|
||||
"""
|
||||
with tokenizer.enclosing_tokens("LEFT_PARENTHESIS", "RIGHT_PARENTHESIS"):
|
||||
with tokenizer.enclosing_tokens(
|
||||
"LEFT_PARENTHESIS",
|
||||
"RIGHT_PARENTHESIS",
|
||||
around="version specifier",
|
||||
):
|
||||
tokenizer.consume("WS")
|
||||
parsed_specifiers = _parse_version_many(tokenizer)
|
||||
tokenizer.consume("WS")
|
||||
@@ -217,7 +225,20 @@ def _parse_version_many(tokenizer: Tokenizer) -> str:
|
||||
"""
|
||||
parsed_specifiers = ""
|
||||
while tokenizer.check("SPECIFIER"):
|
||||
span_start = tokenizer.position
|
||||
parsed_specifiers += tokenizer.read().text
|
||||
if tokenizer.check("VERSION_PREFIX_TRAIL", peek=True):
|
||||
tokenizer.raise_syntax_error(
|
||||
".* suffix can only be used with `==` or `!=` operators",
|
||||
span_start=span_start,
|
||||
span_end=tokenizer.position + 1,
|
||||
)
|
||||
if tokenizer.check("VERSION_LOCAL_LABEL_TRAIL", peek=True):
|
||||
tokenizer.raise_syntax_error(
|
||||
"Local version label can only be used with `==` or `!=` operators",
|
||||
span_start=span_start,
|
||||
span_end=tokenizer.position,
|
||||
)
|
||||
tokenizer.consume("WS")
|
||||
if not tokenizer.check("COMMA"):
|
||||
break
|
||||
@@ -231,7 +252,13 @@ def _parse_version_many(tokenizer: Tokenizer) -> str:
|
||||
# Recursive descent parser for marker expression
|
||||
# --------------------------------------------------------------------------------------
|
||||
def parse_marker(source: str) -> MarkerList:
|
||||
return _parse_marker(Tokenizer(source, rules=DEFAULT_RULES))
|
||||
return _parse_full_marker(Tokenizer(source, rules=DEFAULT_RULES))
|
||||
|
||||
|
||||
def _parse_full_marker(tokenizer: Tokenizer) -> MarkerList:
|
||||
retval = _parse_marker(tokenizer)
|
||||
tokenizer.expect("END", expected="end of marker expression")
|
||||
return retval
|
||||
|
||||
|
||||
def _parse_marker(tokenizer: Tokenizer) -> MarkerList:
|
||||
@@ -254,7 +281,11 @@ def _parse_marker_atom(tokenizer: Tokenizer) -> MarkerAtom:
|
||||
|
||||
tokenizer.consume("WS")
|
||||
if tokenizer.check("LEFT_PARENTHESIS", peek=True):
|
||||
with tokenizer.enclosing_tokens("LEFT_PARENTHESIS", "RIGHT_PARENTHESIS"):
|
||||
with tokenizer.enclosing_tokens(
|
||||
"LEFT_PARENTHESIS",
|
||||
"RIGHT_PARENTHESIS",
|
||||
around="marker expression",
|
||||
):
|
||||
tokenizer.consume("WS")
|
||||
marker: MarkerAtom = _parse_marker(tokenizer)
|
||||
tokenizer.consume("WS")
|
||||
@@ -293,10 +324,7 @@ def _parse_marker_var(tokenizer: Tokenizer) -> MarkerVar:
|
||||
|
||||
|
||||
def process_env_var(env_var: str) -> Variable:
|
||||
if (
|
||||
env_var == "platform_python_implementation"
|
||||
or env_var == "python_implementation"
|
||||
):
|
||||
if env_var in ("platform_python_implementation", "python_implementation"):
|
||||
return Variable("platform_python_implementation")
|
||||
else:
|
||||
return Variable(env_var)
|
||||
|
||||
@@ -78,6 +78,8 @@ DEFAULT_RULES: "Dict[str, Union[str, re.Pattern[str]]]" = {
|
||||
"AT": r"\@",
|
||||
"URL": r"[^ \t]+",
|
||||
"IDENTIFIER": r"\b[a-zA-Z0-9][a-zA-Z0-9._-]*\b",
|
||||
"VERSION_PREFIX_TRAIL": r"\.\*",
|
||||
"VERSION_LOCAL_LABEL_TRAIL": r"\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*",
|
||||
"WS": r"[ \t]+",
|
||||
"END": r"$",
|
||||
}
|
||||
@@ -167,21 +169,23 @@ class Tokenizer:
|
||||
)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def enclosing_tokens(self, open_token: str, close_token: str) -> Iterator[bool]:
|
||||
def enclosing_tokens(
|
||||
self, open_token: str, close_token: str, *, around: str
|
||||
) -> Iterator[None]:
|
||||
if self.check(open_token):
|
||||
open_position = self.position
|
||||
self.read()
|
||||
else:
|
||||
open_position = None
|
||||
|
||||
yield open_position is not None
|
||||
yield
|
||||
|
||||
if open_position is None:
|
||||
return
|
||||
|
||||
if not self.check(close_token):
|
||||
self.raise_syntax_error(
|
||||
f"Expected closing {close_token}",
|
||||
f"Expected matching {close_token} for {open_token}, after {around}",
|
||||
span_start=open_position,
|
||||
)
|
||||
|
||||
|
||||
@@ -8,7 +8,16 @@ import platform
|
||||
import sys
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
||||
|
||||
from ._parser import MarkerAtom, MarkerList, Op, Value, Variable, parse_marker
|
||||
from ._parser import (
|
||||
MarkerAtom,
|
||||
MarkerList,
|
||||
Op,
|
||||
Value,
|
||||
Variable,
|
||||
)
|
||||
from ._parser import (
|
||||
parse_marker as _parse_marker,
|
||||
)
|
||||
from ._tokenizer import ParserSyntaxError
|
||||
from .specifiers import InvalidSpecifier, Specifier
|
||||
from .utils import canonicalize_name
|
||||
@@ -62,7 +71,6 @@ def _normalize_extra_values(results: Any) -> Any:
|
||||
def _format_marker(
|
||||
marker: Union[List[str], MarkerAtom, str], first: Optional[bool] = True
|
||||
) -> str:
|
||||
|
||||
assert isinstance(marker, (list, tuple, str))
|
||||
|
||||
# Sometimes we have a structure like [[...]] which is a single item list
|
||||
@@ -189,7 +197,7 @@ class Marker:
|
||||
# packaging.requirements.Requirement. If any additional logic is
|
||||
# added here, make sure to mirror/adapt Requirement.
|
||||
try:
|
||||
self._markers = _normalize_extra_values(parse_marker(marker))
|
||||
self._markers = _normalize_extra_values(_parse_marker(marker))
|
||||
# The attribute `_markers` can be described in terms of a recursive type:
|
||||
# MarkerList = List[Union[Tuple[Node, ...], str, MarkerList]]
|
||||
#
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
import urllib.parse
|
||||
from typing import Any, List, Optional, Set
|
||||
from typing import Any, Iterator, Optional, Set
|
||||
|
||||
from ._parser import parse_requirement
|
||||
from ._parser import parse_requirement as _parse_requirement
|
||||
from ._tokenizer import ParserSyntaxError
|
||||
from .markers import Marker, _normalize_extra_values
|
||||
from .specifiers import SpecifierSet
|
||||
from .utils import canonicalize_name
|
||||
|
||||
|
||||
class InvalidRequirement(ValueError):
|
||||
@@ -32,62 +32,57 @@ class Requirement:
|
||||
|
||||
def __init__(self, requirement_string: str) -> None:
|
||||
try:
|
||||
parsed = parse_requirement(requirement_string)
|
||||
parsed = _parse_requirement(requirement_string)
|
||||
except ParserSyntaxError as e:
|
||||
raise InvalidRequirement(str(e)) from e
|
||||
|
||||
self.name: str = parsed.name
|
||||
if parsed.url:
|
||||
parsed_url = urllib.parse.urlparse(parsed.url)
|
||||
if parsed_url.scheme == "file":
|
||||
if urllib.parse.urlunparse(parsed_url) != parsed.url:
|
||||
raise InvalidRequirement("Invalid URL given")
|
||||
elif not (parsed_url.scheme and parsed_url.netloc) or (
|
||||
not parsed_url.scheme and not parsed_url.netloc
|
||||
):
|
||||
raise InvalidRequirement(f"Invalid URL: {parsed.url}")
|
||||
self.url: Optional[str] = parsed.url
|
||||
else:
|
||||
self.url = None
|
||||
self.extras: Set[str] = set(parsed.extras if parsed.extras else [])
|
||||
self.url: Optional[str] = parsed.url or None
|
||||
self.extras: Set[str] = set(parsed.extras or [])
|
||||
self.specifier: SpecifierSet = SpecifierSet(parsed.specifier)
|
||||
self.marker: Optional[Marker] = None
|
||||
if parsed.marker is not None:
|
||||
self.marker = Marker.__new__(Marker)
|
||||
self.marker._markers = _normalize_extra_values(parsed.marker)
|
||||
|
||||
def __str__(self) -> str:
|
||||
parts: List[str] = [self.name]
|
||||
def _iter_parts(self, name: str) -> Iterator[str]:
|
||||
yield name
|
||||
|
||||
if self.extras:
|
||||
formatted_extras = ",".join(sorted(self.extras))
|
||||
parts.append(f"[{formatted_extras}]")
|
||||
yield f"[{formatted_extras}]"
|
||||
|
||||
if self.specifier:
|
||||
parts.append(str(self.specifier))
|
||||
yield str(self.specifier)
|
||||
|
||||
if self.url:
|
||||
parts.append(f"@ {self.url}")
|
||||
yield f"@ {self.url}"
|
||||
if self.marker:
|
||||
parts.append(" ")
|
||||
yield " "
|
||||
|
||||
if self.marker:
|
||||
parts.append(f"; {self.marker}")
|
||||
yield f"; {self.marker}"
|
||||
|
||||
return "".join(parts)
|
||||
def __str__(self) -> str:
|
||||
return "".join(self._iter_parts(self.name))
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Requirement('{self}')>"
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((self.__class__.__name__, str(self)))
|
||||
return hash(
|
||||
(
|
||||
self.__class__.__name__,
|
||||
*self._iter_parts(canonicalize_name(self.name)),
|
||||
)
|
||||
)
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
if not isinstance(other, Requirement):
|
||||
return NotImplemented
|
||||
|
||||
return (
|
||||
self.name == other.name
|
||||
canonicalize_name(self.name) == canonicalize_name(other.name)
|
||||
and self.extras == other.extras
|
||||
and self.specifier == other.specifier
|
||||
and self.url == other.url
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
"""
|
||||
@@ -12,17 +11,7 @@
|
||||
import abc
|
||||
import itertools
|
||||
import re
|
||||
from typing import (
|
||||
Callable,
|
||||
Iterable,
|
||||
Iterator,
|
||||
List,
|
||||
Optional,
|
||||
Set,
|
||||
Tuple,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
from typing import Callable, Iterable, Iterator, List, Optional, Tuple, TypeVar, Union
|
||||
|
||||
from .utils import canonicalize_version
|
||||
from .version import Version
|
||||
@@ -253,7 +242,8 @@ class Specifier(BaseSpecifier):
|
||||
# Store whether or not this Specifier should accept prereleases
|
||||
self._prereleases = prereleases
|
||||
|
||||
@property
|
||||
# https://github.com/python/mypy/pull/13475#pullrequestreview-1079784515
|
||||
@property # type: ignore[override]
|
||||
def prereleases(self) -> bool:
|
||||
# If there is an explicit prereleases set for this, then we'll just
|
||||
# blindly use that.
|
||||
@@ -374,7 +364,6 @@ class Specifier(BaseSpecifier):
|
||||
return operator_callable
|
||||
|
||||
def _compare_compatible(self, prospective: Version, spec: str) -> bool:
|
||||
|
||||
# Compatible releases have an equivalent combination of >= and ==. That
|
||||
# is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to
|
||||
# implement this in terms of the other specifiers instead of
|
||||
@@ -383,7 +372,7 @@ class Specifier(BaseSpecifier):
|
||||
|
||||
# We want everything but the last item in the version, but we want to
|
||||
# ignore suffix segments.
|
||||
prefix = ".".join(
|
||||
prefix = _version_join(
|
||||
list(itertools.takewhile(_is_not_suffix, _version_split(spec)))[:-1]
|
||||
)
|
||||
|
||||
@@ -395,20 +384,21 @@ class Specifier(BaseSpecifier):
|
||||
)
|
||||
|
||||
def _compare_equal(self, prospective: Version, spec: str) -> bool:
|
||||
|
||||
# We need special logic to handle prefix matching
|
||||
if spec.endswith(".*"):
|
||||
# In the case of prefix matching we want to ignore local segment.
|
||||
normalized_prospective = canonicalize_version(prospective.public)
|
||||
normalized_prospective = canonicalize_version(
|
||||
prospective.public, strip_trailing_zero=False
|
||||
)
|
||||
# Get the normalized version string ignoring the trailing .*
|
||||
normalized_spec = canonicalize_version(spec[:-2], strip_trailing_zero=False)
|
||||
# Split the spec out by dots, and pretend that there is an implicit
|
||||
# dot in between a release segment and a pre-release segment.
|
||||
# Split the spec out by bangs and dots, and pretend that there is
|
||||
# an implicit dot in between a release segment and a pre-release segment.
|
||||
split_spec = _version_split(normalized_spec)
|
||||
|
||||
# Split the prospective version out by dots, and pretend that there
|
||||
# is an implicit dot in between a release segment and a pre-release
|
||||
# segment.
|
||||
# Split the prospective version out by bangs and dots, and pretend
|
||||
# that there is an implicit dot in between a release segment and
|
||||
# a pre-release segment.
|
||||
split_prospective = _version_split(normalized_prospective)
|
||||
|
||||
# 0-pad the prospective version before shortening it to get the correct
|
||||
@@ -437,21 +427,18 @@ class Specifier(BaseSpecifier):
|
||||
return not self._compare_equal(prospective, spec)
|
||||
|
||||
def _compare_less_than_equal(self, prospective: Version, spec: str) -> bool:
|
||||
|
||||
# NB: Local version identifiers are NOT permitted in the version
|
||||
# specifier, so local version labels can be universally removed from
|
||||
# the prospective version.
|
||||
return Version(prospective.public) <= Version(spec)
|
||||
|
||||
def _compare_greater_than_equal(self, prospective: Version, spec: str) -> bool:
|
||||
|
||||
# NB: Local version identifiers are NOT permitted in the version
|
||||
# specifier, so local version labels can be universally removed from
|
||||
# the prospective version.
|
||||
return Version(prospective.public) >= Version(spec)
|
||||
|
||||
def _compare_less_than(self, prospective: Version, spec_str: str) -> bool:
|
||||
|
||||
# Convert our spec to a Version instance, since we'll want to work with
|
||||
# it as a version.
|
||||
spec = Version(spec_str)
|
||||
@@ -476,7 +463,6 @@ class Specifier(BaseSpecifier):
|
||||
return True
|
||||
|
||||
def _compare_greater_than(self, prospective: Version, spec_str: str) -> bool:
|
||||
|
||||
# Convert our spec to a Version instance, since we'll want to work with
|
||||
# it as a version.
|
||||
spec = Version(spec_str)
|
||||
@@ -642,8 +628,19 @@ _prefix_regex = re.compile(r"^([0-9]+)((?:a|b|c|rc)[0-9]+)$")
|
||||
|
||||
|
||||
def _version_split(version: str) -> List[str]:
|
||||
"""Split version into components.
|
||||
|
||||
The split components are intended for version comparison. The logic does
|
||||
not attempt to retain the original version string, so joining the
|
||||
components back with :func:`_version_join` may not produce the original
|
||||
version string.
|
||||
"""
|
||||
result: List[str] = []
|
||||
for item in version.split("."):
|
||||
|
||||
epoch, _, rest = version.rpartition("!")
|
||||
result.append(epoch or "0")
|
||||
|
||||
for item in rest.split("."):
|
||||
match = _prefix_regex.search(item)
|
||||
if match:
|
||||
result.extend(match.groups())
|
||||
@@ -652,6 +649,17 @@ def _version_split(version: str) -> List[str]:
|
||||
return result
|
||||
|
||||
|
||||
def _version_join(components: List[str]) -> str:
|
||||
"""Join split version components into a version string.
|
||||
|
||||
This function assumes the input came from :func:`_version_split`, where the
|
||||
first component must be the epoch (either empty or numeric), and all other
|
||||
components numeric.
|
||||
"""
|
||||
epoch, *rest = components
|
||||
return f"{epoch}!{'.'.join(rest)}"
|
||||
|
||||
|
||||
def _is_not_suffix(segment: str) -> bool:
|
||||
return not any(
|
||||
segment.startswith(prefix) for prefix in ("dev", "a", "b", "rc", "post")
|
||||
@@ -673,7 +681,10 @@ def _pad_version(left: List[str], right: List[str]) -> Tuple[List[str], List[str
|
||||
left_split.insert(1, ["0"] * max(0, len(right_split[0]) - len(left_split[0])))
|
||||
right_split.insert(1, ["0"] * max(0, len(left_split[0]) - len(right_split[0])))
|
||||
|
||||
return (list(itertools.chain(*left_split)), list(itertools.chain(*right_split)))
|
||||
return (
|
||||
list(itertools.chain.from_iterable(left_split)),
|
||||
list(itertools.chain.from_iterable(right_split)),
|
||||
)
|
||||
|
||||
|
||||
class SpecifierSet(BaseSpecifier):
|
||||
@@ -705,14 +716,8 @@ class SpecifierSet(BaseSpecifier):
|
||||
# strip each item to remove leading/trailing whitespace.
|
||||
split_specifiers = [s.strip() for s in specifiers.split(",") if s.strip()]
|
||||
|
||||
# Parsed each individual specifier, attempting first to make it a
|
||||
# Specifier.
|
||||
parsed: Set[Specifier] = set()
|
||||
for specifier in split_specifiers:
|
||||
parsed.add(Specifier(specifier))
|
||||
|
||||
# Turn our parsed specifiers into a frozen set and save them for later.
|
||||
self._specs = frozenset(parsed)
|
||||
# Make each individual specifier a Specifier and save in a frozen set for later.
|
||||
self._specs = frozenset(map(Specifier, split_specifiers))
|
||||
|
||||
# Store our prereleases value so we can use it later to determine if
|
||||
# we accept prereleases or not.
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
import logging
|
||||
import platform
|
||||
import re
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import sysconfig
|
||||
@@ -37,7 +39,7 @@ INTERPRETER_SHORT_NAMES: Dict[str, str] = {
|
||||
}
|
||||
|
||||
|
||||
_32_BIT_INTERPRETER = sys.maxsize <= 2**32
|
||||
_32_BIT_INTERPRETER = struct.calcsize("P") == 4
|
||||
|
||||
|
||||
class Tag:
|
||||
@@ -111,7 +113,7 @@ def parse_tag(tag: str) -> FrozenSet[Tag]:
|
||||
|
||||
|
||||
def _get_config_var(name: str, warn: bool = False) -> Union[int, str, None]:
|
||||
value = sysconfig.get_config_var(name)
|
||||
value: Union[int, str, None] = sysconfig.get_config_var(name)
|
||||
if value is None and warn:
|
||||
logger.debug(
|
||||
"Config variable '%s' is unset, Python ABI tag may be incorrect", name
|
||||
@@ -120,23 +122,40 @@ def _get_config_var(name: str, warn: bool = False) -> Union[int, str, None]:
|
||||
|
||||
|
||||
def _normalize_string(string: str) -> str:
|
||||
return string.replace(".", "_").replace("-", "_")
|
||||
return string.replace(".", "_").replace("-", "_").replace(" ", "_")
|
||||
|
||||
|
||||
def _abi3_applies(python_version: PythonVersion) -> bool:
|
||||
def _is_threaded_cpython(abis: List[str]) -> bool:
|
||||
"""
|
||||
Determine if the ABI corresponds to a threaded (`--disable-gil`) build.
|
||||
|
||||
The threaded builds are indicated by a "t" in the abiflags.
|
||||
"""
|
||||
if len(abis) == 0:
|
||||
return False
|
||||
# expect e.g., cp313
|
||||
m = re.match(r"cp\d+(.*)", abis[0])
|
||||
if not m:
|
||||
return False
|
||||
abiflags = m.group(1)
|
||||
return "t" in abiflags
|
||||
|
||||
|
||||
def _abi3_applies(python_version: PythonVersion, threading: bool) -> bool:
|
||||
"""
|
||||
Determine if the Python version supports abi3.
|
||||
|
||||
PEP 384 was first implemented in Python 3.2.
|
||||
PEP 384 was first implemented in Python 3.2. The threaded (`--disable-gil`)
|
||||
builds do not support abi3.
|
||||
"""
|
||||
return len(python_version) > 1 and tuple(python_version) >= (3, 2)
|
||||
return len(python_version) > 1 and tuple(python_version) >= (3, 2) and not threading
|
||||
|
||||
|
||||
def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> List[str]:
|
||||
py_version = tuple(py_version) # To allow for version comparison.
|
||||
abis = []
|
||||
version = _version_nodot(py_version[:2])
|
||||
debug = pymalloc = ucs4 = ""
|
||||
threading = debug = pymalloc = ucs4 = ""
|
||||
with_debug = _get_config_var("Py_DEBUG", warn)
|
||||
has_refcount = hasattr(sys, "gettotalrefcount")
|
||||
# Windows doesn't set Py_DEBUG, so checking for support of debug-compiled
|
||||
@@ -145,6 +164,8 @@ def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> List[str]:
|
||||
has_ext = "_d.pyd" in EXTENSION_SUFFIXES
|
||||
if with_debug or (with_debug is None and (has_refcount or has_ext)):
|
||||
debug = "d"
|
||||
if py_version >= (3, 13) and _get_config_var("Py_GIL_DISABLED", warn):
|
||||
threading = "t"
|
||||
if py_version < (3, 8):
|
||||
with_pymalloc = _get_config_var("WITH_PYMALLOC", warn)
|
||||
if with_pymalloc or with_pymalloc is None:
|
||||
@@ -158,13 +179,8 @@ def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> List[str]:
|
||||
elif debug:
|
||||
# Debug builds can also load "normal" extension modules.
|
||||
# We can also assume no UCS-4 or pymalloc requirement.
|
||||
abis.append(f"cp{version}")
|
||||
abis.insert(
|
||||
0,
|
||||
"cp{version}{debug}{pymalloc}{ucs4}".format(
|
||||
version=version, debug=debug, pymalloc=pymalloc, ucs4=ucs4
|
||||
),
|
||||
)
|
||||
abis.append(f"cp{version}{threading}")
|
||||
abis.insert(0, f"cp{version}{threading}{debug}{pymalloc}{ucs4}")
|
||||
return abis
|
||||
|
||||
|
||||
@@ -212,11 +228,14 @@ def cpython_tags(
|
||||
for abi in abis:
|
||||
for platform_ in platforms:
|
||||
yield Tag(interpreter, abi, platform_)
|
||||
if _abi3_applies(python_version):
|
||||
|
||||
threading = _is_threaded_cpython(abis)
|
||||
use_abi3 = _abi3_applies(python_version, threading)
|
||||
if use_abi3:
|
||||
yield from (Tag(interpreter, "abi3", platform_) for platform_ in platforms)
|
||||
yield from (Tag(interpreter, "none", platform_) for platform_ in platforms)
|
||||
|
||||
if _abi3_applies(python_version):
|
||||
if use_abi3:
|
||||
for minor_version in range(python_version[1] - 1, 1, -1):
|
||||
for platform_ in platforms:
|
||||
interpreter = "cp{version}".format(
|
||||
@@ -406,7 +425,7 @@ def mac_platforms(
|
||||
check=True,
|
||||
env={"SYSTEM_VERSION_COMPAT": "0"},
|
||||
stdout=subprocess.PIPE,
|
||||
universal_newlines=True,
|
||||
text=True,
|
||||
).stdout
|
||||
version = cast("MacVersion", tuple(map(int, version_str.split(".")[:2])))
|
||||
else:
|
||||
@@ -469,15 +488,21 @@ def mac_platforms(
|
||||
|
||||
def _linux_platforms(is_32bit: bool = _32_BIT_INTERPRETER) -> Iterator[str]:
|
||||
linux = _normalize_string(sysconfig.get_platform())
|
||||
if not linux.startswith("linux_"):
|
||||
# we should never be here, just yield the sysconfig one and return
|
||||
yield linux
|
||||
return
|
||||
if is_32bit:
|
||||
if linux == "linux_x86_64":
|
||||
linux = "linux_i686"
|
||||
elif linux == "linux_aarch64":
|
||||
linux = "linux_armv7l"
|
||||
linux = "linux_armv8l"
|
||||
_, arch = linux.split("_", 1)
|
||||
yield from _manylinux.platform_tags(linux, arch)
|
||||
yield from _musllinux.platform_tags(arch)
|
||||
yield linux
|
||||
archs = {"armv8l": ["armv8l", "armv7l"]}.get(arch, [arch])
|
||||
yield from _manylinux.platform_tags(archs)
|
||||
yield from _musllinux.platform_tags(archs)
|
||||
for arch in archs:
|
||||
yield f"linux_{arch}"
|
||||
|
||||
|
||||
def _generic_platforms() -> Iterator[str]:
|
||||
|
||||
@@ -12,6 +12,12 @@ BuildTag = Union[Tuple[()], Tuple[int, str]]
|
||||
NormalizedName = NewType("NormalizedName", str)
|
||||
|
||||
|
||||
class InvalidName(ValueError):
|
||||
"""
|
||||
An invalid distribution name; users should refer to the packaging user guide.
|
||||
"""
|
||||
|
||||
|
||||
class InvalidWheelFilename(ValueError):
|
||||
"""
|
||||
An invalid wheel filename was found, users should refer to PEP 427.
|
||||
@@ -24,17 +30,28 @@ class InvalidSdistFilename(ValueError):
|
||||
"""
|
||||
|
||||
|
||||
# Core metadata spec for `Name`
|
||||
_validate_regex = re.compile(
|
||||
r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", re.IGNORECASE
|
||||
)
|
||||
_canonicalize_regex = re.compile(r"[-_.]+")
|
||||
_normalized_regex = re.compile(r"^([a-z0-9]|[a-z0-9]([a-z0-9-](?!--))*[a-z0-9])$")
|
||||
# PEP 427: The build number must start with a digit.
|
||||
_build_tag_regex = re.compile(r"(\d+)(.*)")
|
||||
|
||||
|
||||
def canonicalize_name(name: str) -> NormalizedName:
|
||||
def canonicalize_name(name: str, *, validate: bool = False) -> NormalizedName:
|
||||
if validate and not _validate_regex.match(name):
|
||||
raise InvalidName(f"name is invalid: {name!r}")
|
||||
# This is taken from PEP 503.
|
||||
value = _canonicalize_regex.sub("-", name).lower()
|
||||
return cast(NormalizedName, value)
|
||||
|
||||
|
||||
def is_normalized_name(name: str) -> bool:
|
||||
return _normalized_regex.match(name) is not None
|
||||
|
||||
|
||||
def canonicalize_version(
|
||||
version: Union[Version, str], *, strip_trailing_zero: bool = True
|
||||
) -> str:
|
||||
@@ -100,11 +117,18 @@ def parse_wheel_filename(
|
||||
|
||||
parts = filename.split("-", dashes - 2)
|
||||
name_part = parts[0]
|
||||
# See PEP 427 for the rules on escaping the project name
|
||||
# See PEP 427 for the rules on escaping the project name.
|
||||
if "__" in name_part or re.match(r"^[\w\d._]*$", name_part, re.UNICODE) is None:
|
||||
raise InvalidWheelFilename(f"Invalid project name: {filename}")
|
||||
name = canonicalize_name(name_part)
|
||||
version = Version(parts[1])
|
||||
|
||||
try:
|
||||
version = Version(parts[1])
|
||||
except InvalidVersion as e:
|
||||
raise InvalidWheelFilename(
|
||||
f"Invalid wheel filename (invalid version): {filename}"
|
||||
) from e
|
||||
|
||||
if dashes == 5:
|
||||
build_part = parts[2]
|
||||
build_match = _build_tag_regex.match(build_part)
|
||||
@@ -137,5 +161,12 @@ def parse_sdist_filename(filename: str) -> Tuple[NormalizedName, Version]:
|
||||
raise InvalidSdistFilename(f"Invalid sdist filename: {filename}")
|
||||
|
||||
name = canonicalize_name(name_part)
|
||||
version = Version(version_part)
|
||||
|
||||
try:
|
||||
version = Version(version_part)
|
||||
except InvalidVersion as e:
|
||||
raise InvalidSdistFilename(
|
||||
f"Invalid sdist filename (invalid version): {filename}"
|
||||
) from e
|
||||
|
||||
return (name, version)
|
||||
|
||||
@@ -7,37 +7,39 @@
|
||||
from packaging.version import parse, Version
|
||||
"""
|
||||
|
||||
import collections
|
||||
import itertools
|
||||
import re
|
||||
from typing import Callable, Optional, SupportsInt, Tuple, Union
|
||||
from typing import Any, Callable, NamedTuple, Optional, SupportsInt, Tuple, Union
|
||||
|
||||
from ._structures import Infinity, InfinityType, NegativeInfinity, NegativeInfinityType
|
||||
|
||||
__all__ = ["VERSION_PATTERN", "parse", "Version", "InvalidVersion"]
|
||||
|
||||
InfiniteTypes = Union[InfinityType, NegativeInfinityType]
|
||||
PrePostDevType = Union[InfiniteTypes, Tuple[str, int]]
|
||||
SubLocalType = Union[InfiniteTypes, int, str]
|
||||
LocalType = Union[
|
||||
LocalType = Tuple[Union[int, str], ...]
|
||||
|
||||
CmpPrePostDevType = Union[InfinityType, NegativeInfinityType, Tuple[str, int]]
|
||||
CmpLocalType = Union[
|
||||
NegativeInfinityType,
|
||||
Tuple[
|
||||
Union[
|
||||
SubLocalType,
|
||||
Tuple[SubLocalType, str],
|
||||
Tuple[NegativeInfinityType, SubLocalType],
|
||||
],
|
||||
...,
|
||||
],
|
||||
Tuple[Union[Tuple[int, str], Tuple[NegativeInfinityType, Union[int, str]]], ...],
|
||||
]
|
||||
CmpKey = Tuple[
|
||||
int, Tuple[int, ...], PrePostDevType, PrePostDevType, PrePostDevType, LocalType
|
||||
int,
|
||||
Tuple[int, ...],
|
||||
CmpPrePostDevType,
|
||||
CmpPrePostDevType,
|
||||
CmpPrePostDevType,
|
||||
CmpLocalType,
|
||||
]
|
||||
VersionComparisonMethod = Callable[[CmpKey, CmpKey], bool]
|
||||
|
||||
_Version = collections.namedtuple(
|
||||
"_Version", ["epoch", "release", "dev", "pre", "post", "local"]
|
||||
)
|
||||
|
||||
class _Version(NamedTuple):
|
||||
epoch: int
|
||||
release: Tuple[int, ...]
|
||||
dev: Optional[Tuple[str, int]]
|
||||
pre: Optional[Tuple[str, int]]
|
||||
post: Optional[Tuple[str, int]]
|
||||
local: Optional[LocalType]
|
||||
|
||||
|
||||
def parse(version: str) -> "Version":
|
||||
@@ -63,7 +65,7 @@ class InvalidVersion(ValueError):
|
||||
|
||||
|
||||
class _BaseVersion:
|
||||
_key: CmpKey
|
||||
_key: Tuple[Any, ...]
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self._key)
|
||||
@@ -117,7 +119,7 @@ _VERSION_PATTERN = r"""
|
||||
(?P<release>[0-9]+(?:\.[0-9]+)*) # release segment
|
||||
(?P<pre> # pre-release
|
||||
[-_\.]?
|
||||
(?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview))
|
||||
(?P<pre_l>alpha|a|beta|b|preview|pre|c|rc)
|
||||
[-_\.]?
|
||||
(?P<pre_n>[0-9]+)?
|
||||
)?
|
||||
@@ -179,6 +181,7 @@ class Version(_BaseVersion):
|
||||
"""
|
||||
|
||||
_regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE)
|
||||
_key: CmpKey
|
||||
|
||||
def __init__(self, version: str) -> None:
|
||||
"""Initialize a Version object.
|
||||
@@ -268,8 +271,7 @@ class Version(_BaseVersion):
|
||||
>>> Version("1!2.0.0").epoch
|
||||
1
|
||||
"""
|
||||
_epoch: int = self._version.epoch
|
||||
return _epoch
|
||||
return self._version.epoch
|
||||
|
||||
@property
|
||||
def release(self) -> Tuple[int, ...]:
|
||||
@@ -285,8 +287,7 @@ class Version(_BaseVersion):
|
||||
Includes trailing zeroes but not the epoch or any pre-release / development /
|
||||
post-release suffixes.
|
||||
"""
|
||||
_release: Tuple[int, ...] = self._version.release
|
||||
return _release
|
||||
return self._version.release
|
||||
|
||||
@property
|
||||
def pre(self) -> Optional[Tuple[str, int]]:
|
||||
@@ -301,8 +302,7 @@ class Version(_BaseVersion):
|
||||
>>> Version("1.2.3rc1").pre
|
||||
('rc', 1)
|
||||
"""
|
||||
_pre: Optional[Tuple[str, int]] = self._version.pre
|
||||
return _pre
|
||||
return self._version.pre
|
||||
|
||||
@property
|
||||
def post(self) -> Optional[int]:
|
||||
@@ -450,9 +450,8 @@ class Version(_BaseVersion):
|
||||
|
||||
|
||||
def _parse_letter_version(
|
||||
letter: str, number: Union[str, bytes, SupportsInt]
|
||||
letter: Optional[str], number: Union[str, bytes, SupportsInt, None]
|
||||
) -> Optional[Tuple[str, int]]:
|
||||
|
||||
if letter:
|
||||
# We consider there to be an implicit 0 in a pre-release if there is
|
||||
# not a numeral associated with it.
|
||||
@@ -488,7 +487,7 @@ def _parse_letter_version(
|
||||
_local_version_separators = re.compile(r"[\._-]")
|
||||
|
||||
|
||||
def _parse_local_version(local: str) -> Optional[LocalType]:
|
||||
def _parse_local_version(local: Optional[str]) -> Optional[LocalType]:
|
||||
"""
|
||||
Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
|
||||
"""
|
||||
@@ -506,9 +505,8 @@ def _cmpkey(
|
||||
pre: Optional[Tuple[str, int]],
|
||||
post: Optional[Tuple[str, int]],
|
||||
dev: Optional[Tuple[str, int]],
|
||||
local: Optional[Tuple[SubLocalType]],
|
||||
local: Optional[LocalType],
|
||||
) -> CmpKey:
|
||||
|
||||
# When we compare a release version, we want to compare it with all of the
|
||||
# trailing zeros removed. So we'll use a reverse the list, drop all the now
|
||||
# leading zeros until we come to something non zero, then take the rest
|
||||
@@ -523,7 +521,7 @@ def _cmpkey(
|
||||
# if there is not a pre or a post segment. If we have one of those then
|
||||
# the normal sorting rules will handle this case correctly.
|
||||
if pre is None and post is None and dev is not None:
|
||||
_pre: PrePostDevType = NegativeInfinity
|
||||
_pre: CmpPrePostDevType = NegativeInfinity
|
||||
# Versions without a pre-release (except as noted above) should sort after
|
||||
# those with one.
|
||||
elif pre is None:
|
||||
@@ -533,21 +531,21 @@ def _cmpkey(
|
||||
|
||||
# Versions without a post segment should sort before those with one.
|
||||
if post is None:
|
||||
_post: PrePostDevType = NegativeInfinity
|
||||
_post: CmpPrePostDevType = NegativeInfinity
|
||||
|
||||
else:
|
||||
_post = post
|
||||
|
||||
# Versions without a development segment should sort after those with one.
|
||||
if dev is None:
|
||||
_dev: PrePostDevType = Infinity
|
||||
_dev: CmpPrePostDevType = Infinity
|
||||
|
||||
else:
|
||||
_dev = dev
|
||||
|
||||
if local is None:
|
||||
# Versions without a local segment should sort before those with one.
|
||||
_local: LocalType = NegativeInfinity
|
||||
_local: CmpLocalType = NegativeInfinity
|
||||
else:
|
||||
# Versions with a local segment need that segment parsed to implement
|
||||
# the sorting rules in PEP440.
|
||||
|
||||
@@ -1 +1 @@
|
||||
packaging==23.0
|
||||
packaging==24.0
|
||||
|
||||
@@ -7,11 +7,22 @@ import re
|
||||
import stat
|
||||
import time
|
||||
from io import StringIO, TextIOWrapper
|
||||
from typing import IO, TYPE_CHECKING, Literal
|
||||
from zipfile import ZIP_DEFLATED, ZipFile, ZipInfo
|
||||
|
||||
from wheel.cli import WheelError
|
||||
from wheel.util import log, urlsafe_b64decode, urlsafe_b64encode
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Protocol, Sized, Union
|
||||
|
||||
from typing_extensions import Buffer
|
||||
|
||||
StrPath = Union[str, os.PathLike[str]]
|
||||
|
||||
class SizedBuffer(Sized, Buffer, Protocol): ...
|
||||
|
||||
|
||||
# Non-greedy matching of an optional build number may be too clever (more
|
||||
# invalid wheel filenames will match). Separate regex for .dist-info?
|
||||
WHEEL_INFO_RE = re.compile(
|
||||
@@ -22,7 +33,7 @@ WHEEL_INFO_RE = re.compile(
|
||||
MINIMUM_TIMESTAMP = 315532800 # 1980-01-01 00:00:00 UTC
|
||||
|
||||
|
||||
def get_zipinfo_datetime(timestamp=None):
|
||||
def get_zipinfo_datetime(timestamp: float | None = None):
|
||||
# Some applications need reproducible .whl files, but they can't do this without
|
||||
# forcing the timestamp of the individual ZipInfo objects. See issue #143.
|
||||
timestamp = int(os.environ.get("SOURCE_DATE_EPOCH", timestamp or time.time()))
|
||||
@@ -37,7 +48,12 @@ class WheelFile(ZipFile):
|
||||
|
||||
_default_algorithm = hashlib.sha256
|
||||
|
||||
def __init__(self, file, mode="r", compression=ZIP_DEFLATED):
|
||||
def __init__(
|
||||
self,
|
||||
file: StrPath,
|
||||
mode: Literal["r", "w", "x", "a"] = "r",
|
||||
compression: int = ZIP_DEFLATED,
|
||||
):
|
||||
basename = os.path.basename(file)
|
||||
self.parsed_filename = WHEEL_INFO_RE.match(basename)
|
||||
if not basename.endswith(".whl") or self.parsed_filename is None:
|
||||
@@ -49,7 +65,7 @@ class WheelFile(ZipFile):
|
||||
self.parsed_filename.group("namever")
|
||||
)
|
||||
self.record_path = self.dist_info_path + "/RECORD"
|
||||
self._file_hashes = {}
|
||||
self._file_hashes: dict[str, tuple[None, None] | tuple[int, bytes]] = {}
|
||||
self._file_sizes = {}
|
||||
if mode == "r":
|
||||
# Ignore RECORD and any embedded wheel signatures
|
||||
@@ -81,8 +97,8 @@ class WheelFile(ZipFile):
|
||||
|
||||
if algorithm.lower() in {"md5", "sha1"}:
|
||||
raise WheelError(
|
||||
"Weak hash algorithm ({}) is not permitted by PEP "
|
||||
"427".format(algorithm)
|
||||
f"Weak hash algorithm ({algorithm}) is not permitted by "
|
||||
f"PEP 427"
|
||||
)
|
||||
|
||||
self._file_hashes[path] = (
|
||||
@@ -90,8 +106,13 @@ class WheelFile(ZipFile):
|
||||
urlsafe_b64decode(hash_sum.encode("ascii")),
|
||||
)
|
||||
|
||||
def open(self, name_or_info, mode="r", pwd=None):
|
||||
def _update_crc(newdata):
|
||||
def open(
|
||||
self,
|
||||
name_or_info: str | ZipInfo,
|
||||
mode: Literal["r", "w"] = "r",
|
||||
pwd: bytes | None = None,
|
||||
) -> IO[bytes]:
|
||||
def _update_crc(newdata: bytes) -> None:
|
||||
eof = ef._eof
|
||||
update_crc_orig(newdata)
|
||||
running_hash.update(newdata)
|
||||
@@ -119,9 +140,9 @@ class WheelFile(ZipFile):
|
||||
|
||||
return ef
|
||||
|
||||
def write_files(self, base_dir):
|
||||
def write_files(self, base_dir: str):
|
||||
log.info(f"creating '{self.filename}' and adding '{base_dir}' to it")
|
||||
deferred = []
|
||||
deferred: list[tuple[str, str]] = []
|
||||
for root, dirnames, filenames in os.walk(base_dir):
|
||||
# Sort the directory names so that `os.walk` will walk them in a
|
||||
# defined order on the next iteration.
|
||||
@@ -141,7 +162,12 @@ class WheelFile(ZipFile):
|
||||
for path, arcname in deferred:
|
||||
self.write(path, arcname)
|
||||
|
||||
def write(self, filename, arcname=None, compress_type=None):
|
||||
def write(
|
||||
self,
|
||||
filename: str,
|
||||
arcname: str | None = None,
|
||||
compress_type: int | None = None,
|
||||
) -> None:
|
||||
with open(filename, "rb") as f:
|
||||
st = os.fstat(f.fileno())
|
||||
data = f.read()
|
||||
@@ -153,7 +179,12 @@ class WheelFile(ZipFile):
|
||||
zinfo.compress_type = compress_type or self.compression
|
||||
self.writestr(zinfo, data, compress_type)
|
||||
|
||||
def writestr(self, zinfo_or_arcname, data, compress_type=None):
|
||||
def writestr(
|
||||
self,
|
||||
zinfo_or_arcname: str | ZipInfo,
|
||||
data: SizedBuffer | str,
|
||||
compress_type: int | None = None,
|
||||
):
|
||||
if isinstance(zinfo_or_arcname, str):
|
||||
zinfo_or_arcname = ZipInfo(
|
||||
zinfo_or_arcname, date_time=get_zipinfo_datetime()
|
||||
|
||||
Reference in New Issue
Block a user