-
Notifications
You must be signed in to change notification settings - Fork 162
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
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
be6617d
feat: adding tags CLI interface
henryiii d6d521f
refactor: custom impl
henryiii bff25a3
refactor: tags run on one wheel each (simpler impl)
henryiii 90a045e
chore: address feedback without CLI change yet
henryiii 7cd6af4
refactor: use +/- mechanism
henryiii 903f363
refactor: using single tag option
henryiii 968690a
docs: adding docs for wheel tags
henryiii a4725c2
fixup! docs: adding docs for wheel tags
henryiii 793358f
fixup! docs: adding docs for wheel tags
henryiii 94ec75e
Update docs/reference/wheel_tags.rst
henryiii 5b9dd22
chore: adjust for pyproject.toml backend
henryiii af574fb
chore: remove manual mypy
henryiii File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,3 +7,4 @@ Reference Guide | |
wheel_convert | ||
wheel_unpack | ||
wheel_pack | ||
wheel_tags |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
from __future__ import annotations | ||
|
||
import itertools | ||
import os | ||
from collections.abc import Iterable | ||
|
||
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]: | ||
"""Add or replace tags. Supports dot-separated tags""" | ||
if new_tags is None: | ||
return set(original_tags) | ||
|
||
if new_tags.startswith("+"): | ||
return {*original_tags, *new_tags[1:].split(".")} | ||
|
||
if new_tags.startswith("-"): | ||
return set(original_tags) - set(new_tags[1:].split(".")) | ||
|
||
return set(new_tags.split(".")) | ||
|
||
|
||
def tags( | ||
wheel: str, | ||
python_tags: str | None = None, | ||
abi_tags: str | None = None, | ||
platform_tags: str | None = None, | ||
build_number: int | None = None, | ||
remove: bool = False, | ||
) -> str: | ||
"""Change the tags on a wheel file. | ||
|
||
The tags are left unchanged if they are not specified. To specify "none", | ||
use ["none"]. To append to the previous tags, a tag should start with a | ||
"+". If a tag starts with "-", it will be removed from existing tags. | ||
Processing is done left to right. | ||
|
||
:param wheel: The paths to the wheels | ||
:param python_tags: The Python tags to set | ||
:param abi_tags: The ABI tags to set | ||
:param platform_tags: The platform tags to set | ||
:param build_number: The build number to set | ||
:param remove: Remove the original wheel | ||
""" | ||
with WheelFile(wheel, "r") as f: | ||
assert f.filename, f"{f.filename} must be available" | ||
|
||
wheel_info = f.read(f.dist_info_path + "/WHEEL") | ||
|
||
original_wheel_name = os.path.basename(f.filename) | ||
namever = f.parsed_filename.group("namever") | ||
build = f.parsed_filename.group("build") | ||
original_python_tags = f.parsed_filename.group("pyver").split(".") | ||
original_abi_tags = f.parsed_filename.group("abi").split(".") | ||
original_plat_tags = f.parsed_filename.group("plat").split(".") | ||
|
||
tags, existing_build_number = read_tags(wheel_info) | ||
|
||
impls = {tag.split("-")[0] for tag in tags} | ||
abivers = {tag.split("-")[1] for tag in tags} | ||
platforms = {tag.split("-")[2] for tag in tags} | ||
|
||
if impls != set(original_python_tags): | ||
msg = f"Wheel internal tags {impls!r} != filename tags {original_python_tags!r}" | ||
raise AssertionError(msg) | ||
|
||
if abivers != set(original_abi_tags): | ||
msg = f"Wheel internal tags {abivers!r} != filename tags {original_abi_tags!r}" | ||
raise AssertionError(msg) | ||
|
||
if platforms != set(original_plat_tags): | ||
msg = ( | ||
f"Wheel internal tags {platforms!r} != filename tags {original_plat_tags!r}" | ||
) | ||
raise AssertionError(msg) | ||
|
||
if existing_build_number != build: | ||
msg = ( | ||
f"Incorrect filename '{build}' " | ||
"& *.dist-info/WHEEL '{existing_build_number}' build numbers" | ||
) | ||
raise AssertionError(msg) | ||
|
||
# Start changing as needed | ||
if build_number is not None: | ||
build = str(build_number) | ||
|
||
final_python_tags = sorted(_compute_tags(original_python_tags, python_tags)) | ||
final_abi_tags = sorted(_compute_tags(original_abi_tags, abi_tags)) | ||
final_plat_tags = sorted(_compute_tags(original_plat_tags, platform_tags)) | ||
|
||
final_tags = [ | ||
namever, | ||
".".join(final_python_tags), | ||
".".join(final_abi_tags), | ||
".".join(final_plat_tags), | ||
] | ||
if build: | ||
final_tags.insert(1, build) | ||
|
||
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 | ||
) | ||
] | ||
|
||
original_wheel_path = os.path.join( | ||
os.path.dirname(f.filename), original_wheel_name | ||
) | ||
final_wheel_path = os.path.join(os.path.dirname(f.filename), final_wheel_name) | ||
|
||
with WheelFile(original_wheel_path, "r") as fin, WheelFile( | ||
final_wheel_path, "w" | ||
) as fout: | ||
fout.comment = fin.comment # preserve the comment | ||
for item in fin.infolist(): | ||
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) | ||
else: | ||
fout.writestr(item, fin.read(item)) | ||
|
||
if remove: | ||
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 |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't these be actual asserts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I'd usually say asserts should only be for things known to be true or internal consistency (since they can be turned off). But this one is tricky - I still think it's serious enough if this is broken, it would be best to report a failure even if asserts were off. Maybe there's a better error class to throw?