Skip to content
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

Retag wheels automatically when fusing #215

Merged
merged 21 commits into from
Jun 7, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ rules on making a good Changelog.

- Improved error message for when a MacOS target version is not met.
[#211](https://github.com/matthew-brett/delocate/issues/211)
- Retag wheels automatically when fusing to ensure the wheel name and WHEEL
file tag data is accurate.
[#215](https://github.com/matthew-brett/delocate/pull/215)
dunkmann00 marked this conversation as resolved.
Show resolved Hide resolved

## [0.11.0] - 2024-03-22

Expand Down
20 changes: 11 additions & 9 deletions delocate/cmd/delocate_fuse.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
#!/usr/bin/env python3
"""Fuse two (probably delocated) wheels.

Overwrites the first wheel in-place by default.
Writes to a new wheel with an automatically determined name by default.
"""

# vim: ft=python
from __future__ import absolute_import, division, print_function
from __future__ import annotations

from argparse import ArgumentParser
from os.path import abspath, basename, expanduser
from os.path import join as pjoin
from pathlib import Path

from delocate.cmd.common import common_parser, verbosity_config
from delocate.fuse import fuse_wheels
Expand All @@ -24,18 +23,21 @@
action="store",
type=str,
help="Directory to store delocated wheels"
" (default is to overwrite 1st WHEEL input with 2nd)",
" (default is to store in the same directory as the 1st WHEEL with an"
" automatically determined name).",
)


def main() -> None: # noqa: D103
args = parser.parse_args()
verbosity_config(args)
wheel1, wheel2 = [abspath(expanduser(wheel)) for wheel in args.wheels]
if args.wheel_dir is None:
out_wheel = wheel1
wheel1, wheel2 = [Path(wheel).resolve(strict=True) for wheel in args.wheels]
if args.wheel_dir is not None:
out_wheel = Path(args.wheel_dir).resolve()
if not out_wheel.exists():
out_wheel.mkdir(parents=True)
dunkmann00 marked this conversation as resolved.
Show resolved Hide resolved
else:
out_wheel = pjoin(abspath(expanduser(args.wheel_dir)), basename(wheel1))
out_wheel = None
fuse_wheels(wheel1, wheel2, out_wheel)


Expand Down
22 changes: 14 additions & 8 deletions delocate/delocating.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
tree_libs,
tree_libs_from_directory,
)
from .pkginfo import read_pkg_info, write_pkg_info
from .tmpdirs import TemporaryDirectory
from .tools import (
_is_macho_file,
Expand Down Expand Up @@ -653,6 +654,14 @@ def _get_archs_and_version_from_wheel_name(
raise ValueError(f"Invalid platform tag: {platform_tag.platform}")
major, minor, arch = match.groups()
platform_requirements[arch] = Version(f"{major}.{minor}")
# If we have a wheel name with arm64 and x86_64 we have to convert that to
# universal2
if platform_requirements.keys() == {"arm64", "x86_64"}:
version = platform_requirements["arm64"]
if version == Version("11.0"):
version = platform_requirements["x86_64"]
platform_requirements = {"universal2": version}
dunkmann00 marked this conversation as resolved.
Show resolved Hide resolved

return platform_requirements


Expand Down Expand Up @@ -867,14 +876,11 @@ def _update_wheelfile(wheel_dir: Path, wheel_name: str) -> None:
"""
platform_tag_set = parse_wheel_filename(wheel_name)[-1]
(file_path,) = wheel_dir.glob("*.dist-info/WHEEL")
with file_path.open(encoding="utf-8") as f:
lines = f.readlines()
with file_path.open("w", encoding="utf-8") as f:
for line in lines:
if line.startswith("Tag:"):
f.write(f"Tag: {'.'.join(str(x) for x in platform_tag_set)}\n")
else:
f.write(line)
info = read_pkg_info(file_path)
del info["Tag"]
for tag in platform_tag_set:
info.add_header("Tag", str(tag))
write_pkg_info(file_path, info)


def delocate_wheel(
Expand Down
87 changes: 77 additions & 10 deletions delocate/fuse.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,18 @@
libraries.
"""

from __future__ import annotations

import os
import shutil
from os.path import abspath, exists, relpath, splitext
from os import PathLike
from os.path import exists, relpath, splitext
from os.path import join as pjoin
from pathlib import Path

from packaging.utils import parse_wheel_filename

from .delocating import _check_and_update_wheel_name, _update_wheelfile
from .tmpdirs import InTemporaryDirectory
from .tools import (
chmod_perms,
Expand All @@ -39,6 +46,42 @@ def _copyfile(in_fname, out_fname):
os.chmod(out_fname, perms)


def _retag_wheel(to_wheel: Path, from_wheel: Path, to_tree: Path) -> str:
"""Update the name and dist-info to reflect a univeral2 wheel.

Parameters
----------
to_wheel : Path
The path of the wheel to fuse into.
from_wheel : Path
The path of the wheel to fuse from.
to_tree : Path
The path of the directory tree to fuse into (update into).

Returns
-------
retag_name : str
The new, retagged name the out wheel should be.
"""
to_tree = to_tree.resolve()
dunkmann00 marked this conversation as resolved.
Show resolved Hide resolved
# Add from_wheel platform tags onto to_wheel filename, but make sure to not
# add a tag if it is already there
from_wheel_tags = parse_wheel_filename(from_wheel.name)[-1]
to_wheel_tags = parse_wheel_filename(to_wheel.name)[-1]
add_platform_tags = (
f".{tag.platform}" for tag in from_wheel_tags - to_wheel_tags
)
retag_name = to_wheel.stem + "".join(add_platform_tags) + ".whl"

retag_name = _check_and_update_wheel_name(
Path(retag_name), to_tree, None
).name

_update_wheelfile(to_tree, retag_name)

return retag_name


def fuse_trees(to_tree, from_tree, lib_exts=(".so", ".dylib", ".a")):
"""Fuse path `from_tree` into path `to_tree`.

Expand Down Expand Up @@ -83,24 +126,48 @@ def fuse_trees(to_tree, from_tree, lib_exts=(".so", ".dylib", ".a")):
_copyfile(from_path, to_path)


def fuse_wheels(to_wheel, from_wheel, out_wheel):
def fuse_wheels(
to_wheel: str | PathLike,
from_wheel: str | PathLike,
out_wheel: str | PathLike | None = None,
dunkmann00 marked this conversation as resolved.
Show resolved Hide resolved
) -> Path:
"""Fuse `from_wheel` into `to_wheel`, write to `out_wheel`.

Parameters
----------
to_wheel : str
filename of wheel to fuse into
from_wheel : str
filename of wheel to fuse from
out_wheel : str
filename of new wheel from fusion of `to_wheel` and `from_wheel`
to_wheel : str or Path-like
The path of the wheel to fuse into.
from_wheel : str or Path-like
The path of the wheel to fuse from.
out_wheel : str or Path-like, optional
The path of the new wheel from fusion of `to_wheel` and `from_wheel`. If
a full path is given, (including the filename) it will be used as is. If
a directory is given, the fused wheel will be stored in the directory,
with the name of the wheel automatically determined. If no path is
given, the fused wheel will be stored in the same directory as
`to_wheel`, with the name of the wheel automatically determined.

Returns
-------
out_wheel : Path
The path of the new wheel from fusion of `to_wheel` and `from_wheel`.
"""
to_wheel, from_wheel, out_wheel = [
abspath(w) for w in (to_wheel, from_wheel, out_wheel)
to_wheel, from_wheel = [
Path(w).resolve(strict=True) for w in (to_wheel, from_wheel)
]
dunkmann00 marked this conversation as resolved.
Show resolved Hide resolved
out_wheel = (
to_wheel.parent if out_wheel is None else Path(out_wheel).resolve()
)

with InTemporaryDirectory():
zip2dir(to_wheel, "to_wheel")
dunkmann00 marked this conversation as resolved.
Show resolved Hide resolved
zip2dir(from_wheel, "from_wheel")
fuse_trees("to_wheel", "from_wheel")
if out_wheel.is_dir():
out_wheel_name = _retag_wheel(
to_wheel, from_wheel, Path("to_wheel")
)
out_wheel = out_wheel / out_wheel_name
rewrite_record("to_wheel")
dir2zip("to_wheel", out_wheel)
return out_wheel
Loading