-
Notifications
You must be signed in to change notification settings - Fork 0
Bundle the gen CLI client in the Python wheel #226
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,13 +27,23 @@ jobs: | |
| - name: Install capnp (Windows) | ||
| if: runner.os == 'Windows' | ||
| run: choco install capnproto | ||
| - name: Stage bundled client (macOS) | ||
| if: runner.os == 'macOS' | ||
| run: make stage-python-client | ||
| - name: Stage bundled client (Windows) | ||
| if: runner.os == 'Windows' | ||
| shell: pwsh | ||
| run: | | ||
| $ErrorActionPreference = 'Stop' | ||
| cargo build --release --locked --bin gen | ||
| New-Item -ItemType Directory -Force "gen.gen.data/scripts" | ||
| Copy-Item "target/release/gen.exe" "gen.gen.data/scripts/gen.exe" | ||
| - name: Build wheels (Linux) | ||
| if: runner.os == 'Linux' | ||
| uses: PyO3/maturin-action@v1 | ||
| with: | ||
| command: build | ||
| args: --release --manifest-path gen-python/Cargo.toml --features extension-module --interpreter python${{ matrix.python-version }} --out gen-python/target/wheels | ||
| sccache: true | ||
| manylinux: auto | ||
| container: quay.io/pypa/manylinux_2_28_x86_64:latest | ||
| before-script-linux: | | ||
|
|
@@ -45,6 +55,7 @@ jobs: | |
| mkdir -p gen-capnp-schemas/src/generated | ||
| (cd gen-capnp-schemas && capnp compile -I . -orust:src/generated gen-core.capnp gen-models.capnp gen-schema.capnp) | ||
| ls -la gen-capnp-schemas/src/generated | ||
| make stage-python-client | ||
| working-directory: . | ||
| env: | ||
| CAPNP: /usr/bin/capnp | ||
|
|
@@ -58,6 +69,31 @@ jobs: | |
| args: --release --manifest-path gen-python/Cargo.toml --features extension-module --interpreter ${{ steps.setup-python.outputs.python-path }} --out gen-python/target/wheels | ||
| sccache: true | ||
| working-directory: . | ||
| - name: Verify wheel contents (macOS, Linux) | ||
| if: runner.os != 'Windows' | ||
| run: python gen-python/scripts/verify_wheel.py gen-python/target/wheels/*.whl | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i don't really get the point of a verify_wheel script, what are we trying to keep track of? |
||
| - name: Verify wheel contents (Windows) | ||
| if: runner.os == 'Windows' | ||
| shell: pwsh | ||
| run: | | ||
| $ErrorActionPreference = 'Stop' | ||
| $wheel = Get-ChildItem "gen-python/target/wheels/*.whl" | ||
| python gen-python/scripts/verify_wheel.py "$($wheel.FullName)" | ||
| - name: Smoke test wheel (macOS, Linux) | ||
| if: runner.os != 'Windows' | ||
| run: | | ||
| python -m pip install --force-reinstall --no-deps gen-python/target/wheels/*.whl | ||
| python -c "import gen; print(gen.__version__)" | ||
| gen --version | ||
| - name: Smoke test wheel (Windows) | ||
| if: runner.os == 'Windows' | ||
| shell: pwsh | ||
| run: | | ||
| $ErrorActionPreference = 'Stop' | ||
| $wheel = Get-ChildItem "gen-python/target/wheels/*.whl" | ||
| python -m pip install --force-reinstall --no-deps "$($wheel.FullName)" | ||
| python -c "import gen; print(gen.__version__)" | ||
| gen --version | ||
| - name: Upload wheels to releases | ||
| if: runner.os != 'Windows' | ||
| env: | ||
|
|
||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,17 @@ | ||
| .PHONY: python jupyter r r-test release-check-js clean build clippy-fix docker-build gif | ||
| .PHONY: python python-wheel stage-python-client jupyter r r-test release-check-js clean build clippy-fix docker-build gif | ||
| python: | ||
| @[ -d .venv ] || python -m venv .venv | ||
| @.venv/bin/pip show maturin >/dev/null 2>&1 || .venv/bin/pip install maturin | ||
| .venv/bin/maturin develop --release --manifest-path gen-python/Cargo.toml --features extension-module | ||
| stage-python-client: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why is it called stage-python-client? |
||
| cargo build --release --locked --bin gen | ||
| mkdir -p gen.gen.data/scripts | ||
| cp target/release/gen gen.gen.data/scripts/gen | ||
| chmod +x gen.gen.data/scripts/gen | ||
| python-wheel: stage-python-client | ||
| @[ -d .venv ] || python -m venv .venv | ||
| @.venv/bin/pip show maturin >/dev/null 2>&1 || .venv/bin/pip install maturin | ||
| .venv/bin/maturin build --release --manifest-path gen-python/Cargo.toml --features extension-module | ||
| # The jupyter widget requires a bundled JS file compiled from the TypeScript sources in gen-python/js/. | ||
| # We check in the compiled jupyter_widget.js alongside the TS so npm is not required to build the widget. | ||
| jupyter: python | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,19 +8,33 @@ Gen brings version control to genetic sequences. With it, you can track variants | |
|
|
||
| ## Install | ||
|
|
||
| **Gen client**: prebuilt binaries for macOS and Linux are on the [releases page](https://github.com/genhub-bio/gen/releases): [macOS (.pkg)](https://github.com/genhub-bio/gen/releases/download/nightly/gen.macos.pkg), [Linux x86_64 (.zip)](https://github.com/genhub-bio/gen/releases/download/nightly/gen.linux-x86_64.zip), [Linux arm64 (.zip)](https://github.com/genhub-bio/gen/releases/download/nightly/gen.linux-arm64.zip). Gen is built primarily for Unix-like systems; on Windows, you can install [WSL](https://learn.microsoft.com/en-us/windows/wsl/) to get a Linux environment, then use the Linux binary above from inside it. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i'd prefer to keep the binary stuff at the top -- imo that's the easiest way to install |
||
| Install Gen with pip: | ||
|
|
||
| **Python package**: install on macOS, Linux, or Windows using: | ||
| ```sh | ||
| pip install gen | ||
| ``` | ||
|
|
||
| Install the `jupyter` extra to include an interactive graph widget for Jupyter and other anywidget-compatible notebooks: | ||
| Or install CLI in an isolated environment with uv: | ||
|
|
||
| ```sh | ||
| uv tool install gen | ||
| ``` | ||
|
|
||
| It is also available from crates.io: | ||
|
|
||
| ```sh | ||
| cargo install gen | ||
| ``` | ||
|
|
||
| Prebuilt binaries and installers are available on the [releases page](https://github.com/genhub-bio/gen/releases) for [macOS](https://github.com/genhub-bio/gen/releases/download/nightly/gen.macos.pkg) and Linux ([x64](https://github.com/genhub-bio/gen/releases/download/nightly/gen.linux-x86_64.zip) / [arm64](https://github.com/genhub-bio/gen/releases/download/nightly/gen.linux-arm64.zip)). Gen is built primarily for Unix-like systems; on Windows, you can install [WSL](https://learn.microsoft.com/en-us/windows/wsl/) to get a Linux environment, then use the Linux binary above from inside it. | ||
|
|
||
| **Python package**: Installing Gen with pip also installs the Python package. Install the `jupyter` extra to include an interactive graph widget for Jupyter and other anywidget-compatible notebooks: | ||
|
|
||
| ```sh | ||
| pip install gen[jupyter] | ||
| ``` | ||
|
|
||
| **R package**: install on macOS (Apple silicon) using the `remotes` package: | ||
| **R package**: Install on macOS (Apple silicon) using the `remotes` package: | ||
| ```r | ||
| install.packages("remotes") | ||
| remotes::install_url( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| #!/usr/bin/env python3 | ||
| """Verify that a Gen wheel contains the Python package and bundled client.""" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why do we need this?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this doesn't really do a meaningful test. It's like validating a mock you wrote. Meaningful test is verifying it installs + is importable + is a callable binary. |
||
|
|
||
| import sys | ||
| import zipfile | ||
| from pathlib import Path | ||
|
|
||
|
|
||
| def fail(message: str) -> None: | ||
| raise SystemExit(message) | ||
|
|
||
|
|
||
| def verify_wheel(wheel_path: Path) -> None: | ||
| client_name = "gen.exe" if "-win" in wheel_path.name else "gen" | ||
|
|
||
| with zipfile.ZipFile(wheel_path) as wheel: | ||
| names = {entry.filename for entry in wheel.infolist()} | ||
|
|
||
| if not any(name.endswith(f".data/scripts/{client_name}") for name in names): | ||
| fail(f"{wheel_path} should contain the bundled {client_name}") | ||
|
|
||
| extension_suffixes = (".pyd", ".so") | ||
| if not any( | ||
| name.startswith("gen/") and name.endswith(extension_suffixes) | ||
| for name in names | ||
| ): | ||
| fail(f"{wheel_path} should contain the compiled gen extension") | ||
|
|
||
| if "gen/static/jupyter_widget.js" not in names: | ||
| fail(f"{wheel_path} should contain the Jupyter widget asset") | ||
|
|
||
| print(f"Verified bundled client and Python package in {wheel_path}") | ||
|
|
||
|
|
||
| def main() -> None: | ||
| if len(sys.argv) != 2: | ||
| fail(f"usage: {Path(sys.argv[0]).name} WHEEL") | ||
|
|
||
| wheel_path = Path(sys.argv[1]) | ||
| if not wheel_path.is_file(): | ||
| fail(f"wheel does not exist: {wheel_path}") | ||
|
|
||
| verify_wheel(wheel_path) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() | ||
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.
was this failing?