Skip to content

feat: adding tags CLI interface #422

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Mar 12, 2023
Merged
9 changes: 9 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ repos:
hooks:
- id: black

- repo: https://github.com/pre-commit/mirrors-mypy
rev: "v1.1.1"
hooks:
- id: mypy
files: src
args: []
additional_dependencies: [types-setuptools]
stages: [manual]

- repo: https://github.com/codespell-project/codespell
rev: v2.2.2
hooks:
Expand Down
1 change: 1 addition & 0 deletions docs/reference/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ Reference Guide
wheel_convert
wheel_unpack
wheel_pack
wheel_tags
62 changes: 62 additions & 0 deletions docs/reference/wheel_tags.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
wheel tags
==========

Usage
-----

::

wheel tags [-h] [--remove] [--python-tag TAG] [--abi-tag TAG] [--platform-tag TAG] [--build NUMBER] WHEEL [...]

Description
-----------

Make a new wheel with given tags from and existing wheel. Any tags left
unspecified will remain the same. Multiple tags are separated by a "." Starting
with a "+" will append to the existing tags. Starting with a "-" will remove a
tag. Be sure to use the equals syntax on the shell so that it does not get
parsed as an extra option, such as ``--python-tag=-py2``. The original file
will remain unless ``--remove`` is given. The output filename(s) will be
displayed on stdout for further processing.


Options
-------

.. option:: --remove

Remove the original wheel, keeping only the retagged wheel.

.. option:: --python-tag=TAG

Override the python tag (prepend with "+" to append, "-" to remove).
Multiple tags can be separated with a dot.

.. option:: --abi-tag=TAG

Override the abi tag (prepend with "+" to append, "-" to remove).
Multiple tags can be separated with a dot.

.. option:: --platform-tag=TAG

Override the platform tag (prepend with "+" to append, "-" to remove).
Multiple tags can be separated with a dot.

.. option:: --build=NUMBER

Specify a build number.

Examples
--------

* Replace a wheel's Python specific tags with generic tags (if no Python extensions are present, for example)::

$ wheel tags --python-tag=py2.py3 --abi-tag=none cmake-3.20.2-cp39-cp39-win_amd64.whl
cmake-3.20.2-py2.py3-none-win_amd64.whl

* Add compatibility tags for macOS universal wheels and older pips::

$ wheel tags \
--platform-tag=+macosx_10_9_x86_64.macosx_11_0_arm64 \
ninja-1.11.1-py2.py3-none-macosx_10_9_universal2.whl
ninja-1.11.1-py2.py3-none-macosx_10_9_universal2.macosx_10_9_x86_64.macosx_11_0_arm64.whl
7 changes: 7 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,13 @@ ignore = [
target-version = "py37"
src = ["src"]

[tool.mypy]
files = ["src"]

[[tool.mypy.overrides]]
module = ["_manylinux"]
ignore_missing_imports = true

[tool.tox]
legacy_tox_ini = '''
# Tox (http://tox.testrun.org/) is a tool for running tests
Expand Down
49 changes: 49 additions & 0 deletions src/wheel/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,40 @@ def convert_f(args):
convert(args.files, args.dest_dir, args.verbose)


def tags_f(args):
from .tags import tags

names = (
tags(
wheel,
args.python_tag,
args.abi_tag,
args.platform_tag,
args.build,
args.remove,
)
for wheel in args.wheel
)

for name in names:
print(name)


def version_f(args):
from .. import __version__

print("wheel %s" % __version__)


TAGS_HELP = """\
Make a new wheel with given tags. Any tags unspecified will remain the same.
Starting the tags with a "+" will append to the existing tags. Starting with a
"-" will remove a tag (use --option=-TAG syntax). Multiple tags can be
separated by ".". The original file will remain unless --remove is given. The
output filename(s) will be displayed on stdout for further processing.
"""


def parser():
p = argparse.ArgumentParser()
s = p.add_subparsers(help="commands")
Expand Down Expand Up @@ -72,6 +100,27 @@ def parser():
convert_parser.add_argument("--verbose", "-v", action="store_true")
convert_parser.set_defaults(func=convert_f)

tags_parser = s.add_parser(
"tags", help="Add or replace the tags on a wheel", description=TAGS_HELP
)
tags_parser.add_argument("wheel", nargs="*", help="Existing wheel(s) to retag")
tags_parser.add_argument(
"--remove",
action="store_true",
help="Remove the original files, keeping only the renamed ones",
)
tags_parser.add_argument(
"--python-tag", metavar="TAG", help="Specify an interpreter tag(s)"
)
tags_parser.add_argument("--abi-tag", metavar="TAG", help="Specify an ABI tag(s)")
tags_parser.add_argument(
"--platform-tag", metavar="TAG", help="Specify a platform tag(s)"
)
tags_parser.add_argument(
"--build", type=int, metavar="NUMBER", help="Specify a build number"
)
tags_parser.set_defaults(func=tags_f)

version_parser = s.add_parser("version", help="Print version and exit")
version_parser.set_defaults(func=version_f)

Expand Down
76 changes: 55 additions & 21 deletions src/wheel/cli/pack.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,8 @@ def pack(directory: str, dest_dir: str, build_number: str | None):
# 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) as f:
tags = []
for line in f:
if line.startswith("Tag: "):
tags.append(line.split(" ")[1].rstrip())
elif line.startswith("Build: "):
existing_build_number = line.split(" ")[1].rstrip()
with open(wheel_file_path, "rb") as f:
tags, existing_build_number = read_tags(f.read())

if not tags:
raise WheelError(
Expand All @@ -58,28 +53,16 @@ def pack(directory: str, dest_dir: str, build_number: str | None):
name_version += "-" + build_number

if build_number != existing_build_number:
replacement = (
("Build: %s\r\n" % build_number).encode("ascii")
if build_number
else b""
)
with open(wheel_file_path, "rb+") as f:
wheel_file_content = f.read()
wheel_file_content, num_replaced = BUILD_NUM_RE.subn(
replacement, wheel_file_content
)
if not num_replaced:
wheel_file_content += replacement
wheel_file_content = set_build_number(wheel_file_content, build_number)

f.seek(0)
f.truncate()
f.write(wheel_file_content)

# Reassemble the tags for the wheel file
impls = sorted({tag.split("-")[0] for tag in tags})
abivers = sorted({tag.split("-")[1] for tag in tags})
platforms = sorted({tag.split("-")[2] for tag in tags})
tagline = "-".join([".".join(impls), ".".join(abivers), ".".join(platforms)])
tagline = compute_tagline(tags)

# Repack the wheel
wheel_path = os.path.join(dest_dir, f"{name_version}-{tagline}.whl")
Expand All @@ -88,3 +71,54 @@ def pack(directory: str, dest_dir: str, build_number: str | None):
wf.write_files(directory)

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.

:param tags: A list of tags
:return: A tagline
"""
impls = sorted({tag.split("-")[0] for tag in tags})
abivers = sorted({tag.split("-")[1] for tag in tags})
platforms = sorted({tag.split("-")[2] for tag in tags})
return "-".join([".".join(impls), ".".join(abivers), ".".join(platforms)])
Loading