Skip to content

Commit

Permalink
Build: Add build step to deduplicate release wheels by architecture.
Browse files Browse the repository at this point in the history
  • Loading branch information
scoder committed Mar 31, 2024
1 parent 4dc8882 commit 9445e66
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 7 deletions.
26 changes: 19 additions & 7 deletions .github/workflows/wheels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,25 @@ jobs:
path: ./wheelhouse/*.whl
name: wheels-${{ matrix.only }}

merge_wheels:
name: Merge wheel archives
needs: build_wheels
runs-on: ubuntu-latest

steps:
- name: Merge wheels
uses: actions/upload-artifact/merge@v4
with:
name: all_wheels
pattern: wheels-*
delete-merged: true
compression-level: 9

upload_release_assets:
name: Upload packages
needs: [ sdist, build_wheels ]
needs: [ sdist, merge_wheels ]
runs-on: ubuntu-latest
if: github.ref_type == 'tag'

permissions:
contents: write
Expand All @@ -152,13 +167,10 @@ jobs:
- name: List downloaded artifacts
run: ls -la ./dist_downloads

- uses: actions/upload-artifact@694cdabd8bdb0f10b2cea11669e1bf5453eed0a6 # v4.2.0
with:
path: ./dist_downloads/*.whl
name: all_wheels
- name: Deduplicate wheels
run: python3 dedup_wheels.py -d ./dist_downloads

- name: Release
if: github.ref_type == 'tag'
uses: softprops/action-gh-release@v2
with:
files: ./dist_downloads/*.whl
files: ./dist_downloads/*
80 changes: 80 additions & 0 deletions dedup_wheels.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
"""
Split wheels by set of architectures and delete redundant ones.
"""

import argparse
import os
import pathlib
import re
import sys

from collections import defaultdict
from typing import Iterable


def list_wheels(wheel_dir: pathlib.Path):
return (wheel.name for wheel in wheel_dir.glob("*.whl"))


def dedup_wheels(wheel_names: Iterable[str]):
split_wheelname = re.compile(r"(?P<project>\w+-[0-9a-z.]+)-(?P<python>[^-]+-[^-]+)-(?P<archs>(?:[^.]+[.])+)whl").match

keep: set = set()
seen: dict[tuple[str,str],list[set[str]]] = defaultdict(list)
best: dict[tuple[str,str],list[str]] = defaultdict(list)

for wheel in sorted(wheel_names, key=len, reverse=True):
parts = split_wheelname(wheel)
if not parts:
keep.add(wheel)
continue

archs = set(parts['archs'].rstrip('.').split('.'))
key = (parts['project'], parts['python'])
for archs_seen in seen[key]:
if not (archs - archs_seen):
break
else:
seen[key].append(archs)
best[key].append(wheel)

keep.update(wheel for wheel_list in best.values() for wheel in wheel_list)
return sorted(keep)


def print_wheels(wheel_names: Iterable[str]):
for wheel in sorted(wheel_names):
print(f" {wheel}")


def main(wheel_dir: str, delete=True):
wheel_path = pathlib.Path(wheel_dir)
all_wheels = list(list_wheels(wheel_path))
wheels = dedup_wheels(all_wheels)
redundant = set(all_wheels).difference(wheels)
if not redundant:
return

print("Redundant wheels found:")
print_wheels(redundant)

if delete:
for wheel in redundant:
print(f"deleting {wheel}")
os.unlink(wheel_path / wheel)


def parse_args(args):
parser = argparse.ArgumentParser()
parser.add_argument("directory")
parser.add_argument("-d", "--delete", action="store_true")

return parser.parse_args(args)


if __name__ == "__main__":
if len(sys.argv) > 1:
args = parse_args(sys.argv[1:])
main(args.directory, delete=args.delete)
else:
print(f"Usage: {sys.argv[0]} WHEEL_DIR", file=sys.stderr)

0 comments on commit 9445e66

Please sign in to comment.