-
Notifications
You must be signed in to change notification settings - Fork 4
✨ Ship standalone wk-* binaries on each release #18
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
11 commits
Select commit
Hold shift + click to select a range
c6094c1
:sparkles: Ship standalone wk-* binaries on each release
vanandrew e05341c
:bug: Use manylinux_2_28 for binaries job (glibc 2.17 too old for Nod…
vanandrew 615423d
:bug: Fix Linux + macOS PyInstaller failures in CI
vanandrew 0f7684d
:bug: Exclude packaging/ from setuptools package discovery
vanandrew 142d346
:bug: Explicitly resolve warpkit_cpp via importlib.util.find_spec
vanandrew c55b3ea
:wrench: Build PyInstaller bundles from editable install, drop wheel dep
vanandrew 8044b65
:bug: Trust workspace dir for git in manylinux container
vanandrew 3a1f955
:bug: Don't strip linux .so files (corrupts NumPy's bundled OpenBLAS)
vanandrew 13108b3
:memo: macOS xattr instructions need -r to also strip libraries
vanandrew 9fc8207
:memo: Document standalone binaries + correct wheel matrix in README
vanandrew 1861663
:bug: Address Copilot PR review comments on build_bundle.py
vanandrew 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
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,155 @@ | ||
| """Drive PyInstaller to produce a versioned --onedir bundle + zip for one target. | ||
|
|
||
| Usage (from repo root, inside an env with warpkit + pyinstaller installed): | ||
|
|
||
| python packaging/pyinstaller/build_bundle.py --target linux-x86_64 | ||
|
|
||
| Produces: | ||
| packaging/pyinstaller/dist/warpkit-${VERSION}/ (the bundle) | ||
| packaging/pyinstaller/dist/warpkit-${VERSION}-${TARGET}.zip | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import argparse | ||
| import platform | ||
| import shutil | ||
| import subprocess | ||
| import sys | ||
| from pathlib import Path | ||
|
|
||
| SCRIPTS = [ | ||
| "wk-medic", | ||
| "wk-unwrap-phase", | ||
| "wk-compute-fieldmap", | ||
| "wk-apply-warp", | ||
| "wk-convert-warp", | ||
| "wk-convert-fieldmap", | ||
| "wk-compute-jacobian", | ||
| ] | ||
|
|
||
|
|
||
| def detect_target() -> str: | ||
| system = platform.system().lower() | ||
| machine = platform.machine().lower() | ||
| if system == "linux": | ||
| if machine in ("x86_64", "amd64"): | ||
| return "linux-x86_64" | ||
| if machine in ("aarch64", "arm64"): | ||
| return "linux-aarch64" | ||
| if system == "darwin": | ||
| if machine in ("arm64", "aarch64"): | ||
| return "macos-arm64" | ||
| if machine in ("x86_64", "amd64"): | ||
| return "macos-x86_64" | ||
| raise RuntimeError(f"unsupported target: {system}/{machine}") | ||
|
|
||
|
|
||
| def get_version() -> str: | ||
| from warpkit import __version__ | ||
|
|
||
| return __version__ | ||
|
|
||
|
|
||
| def run_pyinstaller(spec: Path, dist: Path, work: Path) -> None: | ||
| cmd = [ | ||
| sys.executable, | ||
| "-m", | ||
| "PyInstaller", | ||
| "--noconfirm", | ||
| "--clean", | ||
| "--distpath", | ||
| str(dist), | ||
| "--workpath", | ||
| str(work), | ||
| str(spec), | ||
| ] | ||
| subprocess.run(cmd, check=True) | ||
|
|
||
|
|
||
| def adhoc_sign_macos(bundle: Path) -> None: | ||
| if platform.system().lower() != "darwin": | ||
| return | ||
| # Ad-hoc sign every Mach-O in the bundle. `codesign -s -` is sufficient to | ||
| # let users override Gatekeeper after recursively clearing the | ||
| # com.apple.quarantine xattr (see bundle_README.md). | ||
| targets = [bundle / name for name in SCRIPTS] | ||
| targets.extend(p for p in bundle.rglob("*.dylib") if p.is_file()) | ||
| targets.extend(p for p in (bundle / "_internal").rglob("*.so") if p.is_file()) | ||
| failures: list[str] = [] | ||
| for t in targets: | ||
| result = subprocess.run( | ||
| ["codesign", "--force", "--sign", "-", "--timestamp=none", str(t)], | ||
| capture_output=True, | ||
| text=True, | ||
| ) | ||
| if result.returncode != 0: | ||
| failures.append(f"{t} (exit {result.returncode}): {result.stderr.rstrip()}") | ||
| if failures: | ||
| raise RuntimeError( | ||
| "ad-hoc codesign failed for one or more targets:\n " | ||
| + "\n ".join(failures) | ||
| ) | ||
|
|
||
|
|
||
| def write_readme(bundle: Path, version: str, target: str) -> None: | ||
| template = Path(__file__).parent / "bundle_README.md" | ||
| body = ( | ||
| template.read_text(encoding="utf-8") | ||
| .replace("@VERSION@", version) | ||
| .replace("@TARGET@", target) | ||
| ) | ||
| (bundle / "README.md").write_text(body, encoding="utf-8") | ||
|
|
||
|
|
||
| def make_zip(bundle: Path, out_zip: Path) -> None: | ||
| # shutil.make_archive's base_dir keeps a tidy top-level folder inside the zip. | ||
| base_name = str(out_zip.with_suffix("")) | ||
| shutil.make_archive( | ||
| base_name=base_name, | ||
| format="zip", | ||
| root_dir=str(bundle.parent), | ||
| base_dir=bundle.name, | ||
| ) | ||
|
|
||
|
|
||
| def main() -> int: | ||
| parser = argparse.ArgumentParser(description=__doc__) | ||
| parser.add_argument( | ||
| "--target", | ||
| default=None, | ||
| help="target triple (e.g. linux-x86_64); auto-detected by default", | ||
| ) | ||
| args = parser.parse_args() | ||
|
|
||
| here = Path(__file__).parent | ||
| spec = here / "warpkit.spec" | ||
| dist = here / "dist" | ||
| work = here / "build" | ||
| target = args.target or detect_target() | ||
| version = get_version() | ||
|
|
||
| if dist.exists(): | ||
| shutil.rmtree(dist) | ||
| if work.exists(): | ||
| shutil.rmtree(work) | ||
|
|
||
| run_pyinstaller(spec, dist, work) | ||
|
|
||
| raw_bundle = dist / "warpkit" | ||
| if not raw_bundle.is_dir(): | ||
| raise RuntimeError(f"PyInstaller did not produce {raw_bundle}") | ||
| versioned = dist / f"warpkit-{version}" | ||
| raw_bundle.rename(versioned) | ||
|
|
||
| adhoc_sign_macos(versioned) | ||
| write_readme(versioned, version, target) | ||
|
|
||
| out_zip = dist / f"warpkit-{version}-{target}.zip" | ||
| make_zip(versioned, out_zip) | ||
| print(f"wrote {out_zip}") | ||
| return 0 | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| raise SystemExit(main()) | ||
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,49 @@ | ||
| # warpkit @VERSION@ — standalone binaries (@TARGET@) | ||
|
|
||
| This bundle contains the seven `wk-*` CLIs as standalone binaries. No Python | ||
| install or system ITK required — everything is in `_internal/`. | ||
|
|
||
| ## Install | ||
|
|
||
| Extract anywhere and put the bundle directory on your `PATH`: | ||
|
|
||
| ```sh | ||
| # example: install to /opt | ||
| sudo mv warpkit-@VERSION@ /opt/ | ||
| echo 'export PATH=/opt/warpkit-@VERSION@:$PATH' >> ~/.bashrc | ||
| ``` | ||
|
|
||
| Or symlink each `wk-*` onto an existing `PATH` entry — the bootloader resolves | ||
| `_internal/` relative to the real binary, so symlinks work fine: | ||
|
|
||
| ```sh | ||
| for bin in /opt/warpkit-@VERSION@/wk-*; do | ||
| sudo ln -s "$bin" /usr/local/bin/$(basename "$bin") | ||
| done | ||
| ``` | ||
|
|
||
| **Do not separate the binaries from `_internal/`** — they all share the | ||
| embedded interpreter and dependency tree. | ||
|
|
||
| ## macOS Gatekeeper | ||
|
|
||
| Binaries are ad-hoc signed, not Apple-notarized, so on first launch macOS will | ||
| quarantine them — and not just the top-level `wk-*` binaries: every `.dylib` | ||
| and `.so` inside `_internal/` is also flagged. Strip the quarantine attribute | ||
| recursively from the whole bundle: | ||
|
|
||
| ```sh | ||
| xattr -r -d com.apple.quarantine /opt/warpkit-@VERSION@ | ||
| ``` | ||
|
|
||
| ## Available CLIs | ||
|
|
||
| - `wk-medic` — full MEDIC distortion correction pipeline | ||
| - `wk-unwrap-phase` — ROMEO multi-echo phase unwrapping | ||
| - `wk-compute-fieldmap` — compute B0 field map from unwrapped phase | ||
| - `wk-apply-warp` — apply a displacement field to an image | ||
| - `wk-convert-warp` — convert between warp field conventions | ||
| - `wk-convert-fieldmap` — convert between field-map representations | ||
| - `wk-compute-jacobian` — compute the Jacobian determinant of a warp | ||
|
|
||
| Run any of them with `--help` for usage. |
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,15 @@ | ||
| import importlib.util | ||
|
|
||
| # PyInstaller's static analysis usually picks up `warpkit_cpp.cpython-*.so` via | ||
| # the import graph (`from .warpkit_cpp import *` in warpkit/__init__.py), but | ||
| # only when the resolved warpkit package directory has the .so right next to | ||
| # __init__.py — which is the case for an editable install (cmake-build-extension | ||
| # drops the .so into the source tree) and for a wheel install (the .so lives in | ||
| # site-packages/warpkit/). Resolve the .so explicitly via importlib.util so the | ||
| # hook works regardless of install layout. | ||
| binaries = [] | ||
| spec = importlib.util.find_spec("warpkit.warpkit_cpp") | ||
| if spec is not None and spec.origin: | ||
| binaries.append((spec.origin, "warpkit")) | ||
|
|
||
| hiddenimports = ["warpkit.warpkit_cpp"] |
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,7 @@ | ||
| import multiprocessing | ||
|
|
||
| multiprocessing.freeze_support() | ||
|
|
||
| from warpkit.scripts.apply_warp import main # noqa: E402 | ||
|
|
||
| main() |
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,7 @@ | ||
| import multiprocessing | ||
|
|
||
| multiprocessing.freeze_support() | ||
|
|
||
| from warpkit.scripts.compute_fieldmap import main # noqa: E402 | ||
|
|
||
| main() |
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,7 @@ | ||
| import multiprocessing | ||
|
|
||
| multiprocessing.freeze_support() | ||
|
|
||
| from warpkit.scripts.compute_jacobian import main # noqa: E402 | ||
|
|
||
| main() |
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.
Uh oh!
There was an error while loading. Please reload this page.