aboutsummaryrefslogtreecommitdiff
path: root/tools/wheelmaker.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/wheelmaker.py')
-rw-r--r--tools/wheelmaker.py340
1 files changed, 102 insertions, 238 deletions
diff --git a/tools/wheelmaker.py b/tools/wheelmaker.py
index 3bfaba2..63b833f 100644
--- a/tools/wheelmaker.py
+++ b/tools/wheelmaker.py
@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from __future__ import annotations
-
import argparse
import base64
+import collections
import hashlib
import os
import re
@@ -23,8 +22,6 @@ import sys
import zipfile
from pathlib import Path
-_ZIP_EPOCH = (1980, 1, 1, 0, 0, 0)
-
def commonpath(path1, path2):
ret = []
@@ -36,177 +33,10 @@ def commonpath(path1, path2):
def escape_filename_segment(segment):
- """Escapes a filename segment per https://www.python.org/dev/peps/pep-0427/#escaping-and-unicode
-
- This is a legacy function, kept for backwards compatibility,
- and may be removed in the future. See `escape_filename_distribution_name`
- and `normalize_pep440` for the modern alternatives.
- """
+ """Escapes a filename segment per https://www.python.org/dev/peps/pep-0427/#escaping-and-unicode"""
return re.sub(r"[^\w\d.]+", "_", segment, re.UNICODE)
-def normalize_package_name(name):
- """Normalize a package name according to the Python Packaging User Guide.
-
- See https://packaging.python.org/en/latest/specifications/name-normalization/
- """
- return re.sub(r"[-_.]+", "-", name).lower()
-
-
-def escape_filename_distribution_name(name):
- """Escape the distribution name component of a filename.
-
- See https://packaging.python.org/en/latest/specifications/binary-distribution-format/#escaping-and-unicode
- """
- return normalize_package_name(name).replace("-", "_")
-
-
-def normalize_pep440(version):
- """Normalize version according to PEP 440, with fallback for placeholders.
-
- If there's a placeholder in braces, such as {BUILD_TIMESTAMP},
- replace it with 0. Such placeholders can be used with stamping, in
- which case they would have been resolved already by now; if they
- haven't, we're doing an unstamped build, but we still need to
- produce a valid version. If such replacements are made, the
- original version string, sanitized to dot-separated alphanumerics,
- is appended as a local version segment, so you understand what
- placeholder was involved.
-
- If that still doesn't produce a valid version, use version 0 and
- append the original version string, sanitized to dot-separated
- alphanumerics, as a local version segment.
-
- """
-
- import packaging.version
-
- try:
- return str(packaging.version.Version(version))
- except packaging.version.InvalidVersion:
- pass
-
- sanitized = re.sub(r"[^a-z0-9]+", ".", version.lower()).strip(".")
- substituted = re.sub(r"\{\w+\}", "0", version)
- delimiter = "." if "+" in substituted else "+"
- try:
- return str(packaging.version.Version(f"{substituted}{delimiter}{sanitized}"))
- except packaging.version.InvalidVersion:
- return str(packaging.version.Version(f"0+{sanitized}"))
-
-
-class _WhlFile(zipfile.ZipFile):
- def __init__(
- self,
- filename,
- *,
- mode,
- distinfo_dir: str | Path,
- strip_path_prefixes=None,
- compression=zipfile.ZIP_DEFLATED,
- **kwargs,
- ):
- self._distinfo_dir: str = Path(distinfo_dir).name
- self._strip_path_prefixes = strip_path_prefixes or []
- # Entries for the RECORD file as (filename, hash, size) tuples.
- self._record = []
-
- super().__init__(filename, mode=mode, compression=compression, **kwargs)
-
- def distinfo_path(self, basename):
- return f"{self._distinfo_dir}/{basename}"
-
- def add_file(self, package_filename, real_filename):
- """Add given file to the distribution."""
-
- def arcname_from(name):
- # Always use unix path separators.
- normalized_arcname = name.replace(os.path.sep, "/")
- # Don't manipulate names filenames in the .distinfo directory.
- if normalized_arcname.startswith(self._distinfo_dir):
- return normalized_arcname
- for prefix in self._strip_path_prefixes:
- if normalized_arcname.startswith(prefix):
- return normalized_arcname[len(prefix) :]
-
- return normalized_arcname
-
- if os.path.isdir(real_filename):
- directory_contents = os.listdir(real_filename)
- for file_ in directory_contents:
- self.add_file(
- "{}/{}".format(package_filename, file_),
- "{}/{}".format(real_filename, file_),
- )
- return
-
- arcname = arcname_from(package_filename)
- zinfo = self._zipinfo(arcname)
-
- # Write file to the zip archive while computing the hash and length
- hash = hashlib.sha256()
- size = 0
- with open(real_filename, "rb") as fsrc:
- with self.open(zinfo, "w") as fdst:
- while True:
- block = fsrc.read(2**20)
- if not block:
- break
- fdst.write(block)
- hash.update(block)
- size += len(block)
-
- self._add_to_record(arcname, self._serialize_digest(hash), size)
-
- def add_string(self, filename, contents):
- """Add given 'contents' as filename to the distribution."""
- if isinstance(contents, str):
- contents = contents.encode("utf-8", "surrogateescape")
- zinfo = self._zipinfo(filename)
- self.writestr(zinfo, contents)
- hash = hashlib.sha256()
- hash.update(contents)
- self._add_to_record(filename, self._serialize_digest(hash), len(contents))
-
- def _serialize_digest(self, hash):
- # https://www.python.org/dev/peps/pep-0376/#record
- # "base64.urlsafe_b64encode(digest) with trailing = removed"
- digest = base64.urlsafe_b64encode(hash.digest())
- digest = b"sha256=" + digest.rstrip(b"=")
- return digest
-
- def _add_to_record(self, filename, hash, size):
- size = str(size).encode("ascii")
- self._record.append((filename, hash, size))
-
- def _zipinfo(self, filename):
- """Construct deterministic ZipInfo entry for a file named filename"""
- # Strip leading path separators to mirror ZipInfo.from_file behavior
- separators = os.path.sep
- if os.path.altsep is not None:
- separators += os.path.altsep
- arcname = filename.lstrip(separators)
-
- zinfo = zipfile.ZipInfo(filename=arcname, date_time=_ZIP_EPOCH)
- zinfo.create_system = 3 # ZipInfo entry created on a unix-y system
- zinfo.external_attr = 0o777 << 16 # permissions: rwxrwxrwx
- zinfo.compress_type = self.compression
- return zinfo
-
- def add_recordfile(self):
- """Write RECORD file to the distribution."""
- record_path = self.distinfo_path("RECORD")
- entries = self._record + [(record_path, b"", b"")]
- contents = b""
- for filename, digest, size in entries:
- if isinstance(filename, str):
- filename = filename.lstrip("/").encode("utf-8", "surrogateescape")
- contents += b"%s,%s,%s\n" % (filename, digest, size)
-
- self.add_string(record_path, contents)
- return contents
-
-
class WheelMaker(object):
def __init__(
self,
@@ -218,8 +48,6 @@ class WheelMaker(object):
platform,
outfile=None,
strip_path_prefixes=None,
- incompatible_normalize_name=True,
- incompatible_normalize_version=True,
):
self._name = name
self._version = version
@@ -228,52 +56,32 @@ class WheelMaker(object):
self._abi = abi
self._platform = platform
self._outfile = outfile
- self._strip_path_prefixes = strip_path_prefixes
-
- if incompatible_normalize_version:
- self._version = normalize_pep440(self._version)
- self._escaped_version = self._version
- else:
- self._escaped_version = escape_filename_segment(self._version)
-
- if incompatible_normalize_name:
- escaped_name = escape_filename_distribution_name(self._name)
- self._distinfo_dir = (
- escaped_name + "-" + self._escaped_version + ".dist-info/"
- )
- self._wheelname_fragment_distribution_name = escaped_name
- else:
- # The legacy behavior escapes the distinfo dir but not the
- # wheel name. Enable incompatible_normalize_name to fix it.
- # https://github.com/bazelbuild/rules_python/issues/1132
- self._distinfo_dir = (
- escape_filename_segment(self._name)
- + "-"
- + self._escaped_version
- + ".dist-info/"
- )
- self._wheelname_fragment_distribution_name = self._name
+ self._strip_path_prefixes = (
+ strip_path_prefixes if strip_path_prefixes is not None else []
+ )
- self._whlfile = None
+ self._distinfo_dir = (
+ escape_filename_segment(self._name)
+ + "-"
+ + escape_filename_segment(self._version)
+ + ".dist-info/"
+ )
+ self._zipfile = None
+ # Entries for the RECORD file as (filename, hash, size) tuples.
+ self._record = []
def __enter__(self):
- self._whlfile = _WhlFile(
- self.filename(),
- mode="w",
- distinfo_dir=self._distinfo_dir,
- strip_path_prefixes=self._strip_path_prefixes,
+ self._zipfile = zipfile.ZipFile(
+ self.filename(), mode="w", compression=zipfile.ZIP_DEFLATED
)
return self
def __exit__(self, type, value, traceback):
- self._whlfile.close()
- self._whlfile = None
+ self._zipfile.close()
+ self._zipfile = None
def wheelname(self) -> str:
- components = [
- self._wheelname_fragment_distribution_name,
- self._version,
- ]
+ components = [self._name, self._version]
if self._build_tag:
components.append(self._build_tag)
components += [self._python_tag, self._abi, self._platform]
@@ -288,11 +96,62 @@ class WheelMaker(object):
return ["-".join([self._python_tag, self._abi, self._platform])]
def distinfo_path(self, basename):
- return self._whlfile.distinfo_path(basename)
+ return self._distinfo_dir + basename
+
+ def _serialize_digest(self, hash):
+ # https://www.python.org/dev/peps/pep-0376/#record
+ # "base64.urlsafe_b64encode(digest) with trailing = removed"
+ digest = base64.urlsafe_b64encode(hash.digest())
+ digest = b"sha256=" + digest.rstrip(b"=")
+ return digest
+
+ def add_string(self, filename, contents):
+ """Add given 'contents' as filename to the distribution."""
+ if sys.version_info[0] > 2 and isinstance(contents, str):
+ contents = contents.encode("utf-8", "surrogateescape")
+ self._zipfile.writestr(filename, contents)
+ hash = hashlib.sha256()
+ hash.update(contents)
+ self._add_to_record(filename, self._serialize_digest(hash), len(contents))
def add_file(self, package_filename, real_filename):
"""Add given file to the distribution."""
- self._whlfile.add_file(package_filename, real_filename)
+
+ def arcname_from(name):
+ # Always use unix path separators.
+ normalized_arcname = name.replace(os.path.sep, "/")
+ # Don't manipulate names filenames in the .distinfo directory.
+ if normalized_arcname.startswith(self._distinfo_dir):
+ return normalized_arcname
+ for prefix in self._strip_path_prefixes:
+ if normalized_arcname.startswith(prefix):
+ return normalized_arcname[len(prefix) :]
+
+ return normalized_arcname
+
+ if os.path.isdir(real_filename):
+ directory_contents = os.listdir(real_filename)
+ for file_ in directory_contents:
+ self.add_file(
+ "{}/{}".format(package_filename, file_),
+ "{}/{}".format(real_filename, file_),
+ )
+ return
+
+ arcname = arcname_from(package_filename)
+
+ self._zipfile.write(real_filename, arcname=arcname)
+ # Find the hash and length
+ hash = hashlib.sha256()
+ size = 0
+ with open(real_filename, "rb") as f:
+ while True:
+ block = f.read(2**20)
+ if not block:
+ break
+ hash.update(block)
+ size += len(block)
+ self._add_to_record(arcname, self._serialize_digest(hash), size)
def add_wheelfile(self):
"""Write WHEEL file to the distribution"""
@@ -306,7 +165,7 @@ Root-Is-Purelib: {}
)
for tag in self.disttags():
wheel_contents += "Tag: %s\n" % tag
- self._whlfile.add_string(self.distinfo_path("WHEEL"), wheel_contents)
+ self.add_string(self.distinfo_path("WHEEL"), wheel_contents)
def add_metadata(self, metadata, name, description, version):
"""Write METADATA file to the distribution."""
@@ -318,11 +177,23 @@ Root-Is-Purelib: {}
# provided.
metadata += description if description else "UNKNOWN"
metadata += "\n"
- self._whlfile.add_string(self.distinfo_path("METADATA"), metadata)
+ self.add_string(self.distinfo_path("METADATA"), metadata)
def add_recordfile(self):
"""Write RECORD file to the distribution."""
- self._whlfile.add_recordfile()
+ record_path = self.distinfo_path("RECORD")
+ entries = self._record + [(record_path, b"", b"")]
+ entries.sort()
+ contents = b""
+ for filename, digest, size in entries:
+ if sys.version_info[0] > 2 and isinstance(filename, str):
+ filename = filename.lstrip("/").encode("utf-8", "surrogateescape")
+ contents += b"%s,%s,%s\n" % (filename, digest, size)
+ self.add_string(record_path, contents)
+
+ def _add_to_record(self, filename, hash, size):
+ size = str(size).encode("ascii")
+ self._record.append((filename, hash, size))
def get_files_to_package(input_files):
@@ -459,12 +330,6 @@ def parse_args() -> argparse.Namespace:
help="Pass in the stamp info file for stamping",
)
- feature_group = parser.add_argument_group("Feature flags")
- feature_group.add_argument("--noincompatible_normalize_name", action="store_true")
- feature_group.add_argument(
- "--noincompatible_normalize_version", action="store_true"
- )
-
return parser.parse_args(sys.argv[1:])
@@ -521,8 +386,6 @@ def main() -> None:
platform=arguments.platform,
outfile=arguments.out,
strip_path_prefixes=strip_prefixes,
- incompatible_normalize_name=not arguments.noincompatible_normalize_name,
- incompatible_normalize_version=not arguments.noincompatible_normalize_version,
) as maker:
for package_filename, real_filename in all_files:
maker.add_file(package_filename, real_filename)
@@ -530,24 +393,25 @@ def main() -> None:
description = None
if arguments.description_file:
- with open(
- arguments.description_file, "rt", encoding="utf-8"
- ) as description_file:
- description = description_file.read()
+ if sys.version_info[0] == 2:
+ with open(arguments.description_file, "rt") as description_file:
+ description = description_file.read()
+ else:
+ with open(
+ arguments.description_file, "rt", encoding="utf-8"
+ ) as description_file:
+ description = description_file.read()
metadata = None
- with open(arguments.metadata_file, "rt", encoding="utf-8") as metadata_file:
- metadata = metadata_file.read()
-
- if arguments.noincompatible_normalize_version:
- version_in_metadata = version
+ if sys.version_info[0] == 2:
+ with open(arguments.metadata_file, "rt") as metadata_file:
+ metadata = metadata_file.read()
else:
- version_in_metadata = normalize_pep440(version)
+ with open(arguments.metadata_file, "rt", encoding="utf-8") as metadata_file:
+ metadata = metadata_file.read()
+
maker.add_metadata(
- metadata=metadata,
- name=name,
- description=description,
- version=version_in_metadata,
+ metadata=metadata, name=name, description=description, version=version
)
if arguments.entry_points_file: