Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
111 changes: 111 additions & 0 deletions .github/workflows/release-aarch64.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Builds an aarch64-only wheel of mldebug_xdp and publishes it as a GitHub release.
# The aarch64 wheel is produced by post-processing the standard wheel:
# Windows and x86 Linux binaries are stripped out and the wheel is re-tagged
# as py3-none-linux_aarch64.

name: MLDebugger Release (aarch64)

on:
workflow_dispatch:
inputs:
release_tag:
description: 'Release tag (Default: v<run_number>-aarch64)'
required: false
default: ''
release_name:
description: 'Release name (Default: "Release v<run_number> (aarch64)")'
required: false
default: ''

permissions:
contents: write

env:
GIT_MODE: 1

jobs:
build:
runs-on: [ build ]
steps:
- uses: actions/checkout@v4
with:
clean: true
lfs: true

- name: Pull LFS files
run: git lfs pull

- name: Add uv to PATH
run: echo "$(python -m site --user-base)/bin" >> $GITHUB_PATH

- name: "Set up Python"
uses: actions/setup-python@v4
with:
python-version-file: "pyproject.toml"

- name: Create and activate virtual environment
run: |
uv venv
source .venv/bin/activate

- name: Update submodules
run: |
git submodule update --init --recursive
git submodule foreach --recursive 'git lfs pull || true'

- name: Build base wheel with UV
run: |
uv build --wheel .

- name: Build aarch64 wheel
run: |
uv pip install --system wheel
BASE_WHEEL=$(find dist -name "mldebug_xdp-*-py3-none-any.whl" | head -1)
if [ -z "$BASE_WHEEL" ]; then
echo "No base wheel found in dist/" >&2
exit 1
fi
python scripts/build_aarch64_wheel.py "$BASE_WHEEL" --out-dir dist

- name: Store aarch64 wheel path
id: wheel-path
run: |
WHEEL_PATH=$(find dist -name "*-linux_aarch64.whl" | head -1)
if [ -z "$WHEEL_PATH" ]; then
echo "aarch64 wheel not found" >&2
exit 1
fi
echo "wheel_path=$WHEEL_PATH" >> $GITHUB_OUTPUT
echo "wheel_name=$(basename $WHEEL_PATH)" >> $GITHUB_OUTPUT

- name: Resolve release tag and name
id: release_meta
shell: bash
run: |
DEFAULT_TAG="v${{ github.run_number }}-aarch64"
DEFAULT_NAME="Release v${{ github.run_number }} (aarch64)"
TAG="${{ github.event.inputs.release_tag }}"
NAME="${{ github.event.inputs.release_name }}"
TAG="${TAG:-$DEFAULT_TAG}"
NAME="${NAME:-$DEFAULT_NAME}"
echo "tag=$TAG" >> $GITHUB_OUTPUT
echo "name=$NAME" >> $GITHUB_OUTPUT

- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.release_meta.outputs.tag }}
release_name: ${{ steps.release_meta.outputs.name }}
draft: false
prerelease: false

- name: Upload aarch64 wheel to release
run: |
gh release upload "${{ steps.release_meta.outputs.tag }}" \
"${{ steps.wheel-path.outputs.wheel_path }}" \
--clobber
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
129 changes: 129 additions & 0 deletions scripts/build_aarch64_wheel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#!/usr/bin/env python3
"""Post-process a built mldebug_xdp wheel into a linux_aarch64 wheel.

Takes the standard ``py3-none-any`` wheel produced by ``uv build``, removes
the Windows and x86 Linux binaries that are not usable on aarch64, and
re-tags the wheel as ``py3-none-linux_aarch64``.

Usage:
python scripts/build_aarch64_wheel.py dist/mldebug_xdp-0.1.0-py3-none-any.whl
python scripts/build_aarch64_wheel.py dist/ # picks the first .whl in dir
"""

from __future__ import annotations

import argparse
import shutil
import subprocess
import sys
from pathlib import Path

# Files inside the wheel (relative to wheel root) that are NOT needed on aarch64.
EXCLUDE_FILES = [
"mldebug/bin/c++filt.exe",
"mldebug/bin/llvm-objdump.elf",
"mldebug/bin/llvm-objdump.exe",
"mldebug/backend/xrt_backend.cp310-win_amd64.pyd",
"mldebug/backend/xrt_backend.cpython-310-x86_64-linux-gnu.so",
]

PLATFORM_TAG = "linux_aarch64"
PYTHON_TAG = "py3"
ABI_TAG = "none"


def resolve_input_wheel(arg: Path) -> Path:
if arg.is_dir():
wheels = sorted(arg.glob("*.whl"))
if not wheels:
sys.exit(f"no .whl found in {arg}")
return wheels[0]
if not arg.is_file():
sys.exit(f"wheel not found: {arg}")
return arg


def rewrite_wheel_metadata(dist_info: Path) -> None:
wheel_meta = dist_info / "WHEEL"
lines = wheel_meta.read_text().splitlines()
out: list[str] = []
saw_root = False
saw_tag = False
for line in lines:
if line.startswith("Tag:"):
if not saw_tag:
out.append(f"Tag: {PYTHON_TAG}-{ABI_TAG}-{PLATFORM_TAG}")
saw_tag = True
# drop any additional Tag lines from the original wheel
continue
if line.startswith("Root-Is-Purelib:"):
out.append("Root-Is-Purelib: false")
saw_root = True
continue
out.append(line)
if not saw_root:
out.append("Root-Is-Purelib: false")
wheel_meta.write_text("\n".join(out) + "\n")


def main() -> None:
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("wheel", type=Path, help="Path to source wheel or dist directory")
ap.add_argument(
"--out-dir",
type=Path,
default=Path("dist"),
help="Directory to write the aarch64 wheel into (default: dist)",
)
args = ap.parse_args()

src_wheel = resolve_input_wheel(args.wheel)
out_dir = args.out_dir.resolve()
out_dir.mkdir(parents=True, exist_ok=True)

work = out_dir / "_aarch64_unpack"
if work.exists():
shutil.rmtree(work)
work.mkdir()

print(f"unpacking {src_wheel.name}")
subprocess.check_call(
[sys.executable, "-m", "wheel", "unpack", str(src_wheel), "--dest", str(work)],
)

unpacked_dirs = [p for p in work.iterdir() if p.is_dir()]
if len(unpacked_dirs) != 1:
sys.exit(f"expected one unpacked dir under {work}, got {unpacked_dirs}")
unpacked = unpacked_dirs[0]

for rel in EXCLUDE_FILES:
target = unpacked / rel
if target.exists():
target.unlink()
print(f"removed {rel}")
else:
print(f"warning: not found in wheel, skipping: {rel}", file=sys.stderr)

dist_info_dirs = list(unpacked.glob("*.dist-info"))
if len(dist_info_dirs) != 1:
sys.exit(f"expected one *.dist-info under {unpacked}, got {dist_info_dirs}")
rewrite_wheel_metadata(dist_info_dirs[0])

# Re-pack. ``wheel pack`` regenerates RECORD and uses the WHEEL Tag
# entry to pick the output filename, so the result is automatically
# named mldebug_xdp-<ver>-py3-none-linux_aarch64.whl.
print("repacking with aarch64 platform tag")
subprocess.check_call(
[sys.executable, "-m", "wheel", "pack", str(unpacked), "--dest-dir", str(out_dir)],
)

shutil.rmtree(work)

produced = sorted(out_dir.glob(f"*-{PLATFORM_TAG}.whl"))
if not produced:
sys.exit("aarch64 wheel was not produced")
print(f"wrote {produced[-1]}")


if __name__ == "__main__":
main()
Loading