Skip to content

Make Binder build the native Rust core - #383

Merged
Alek99 merged 7 commits into
mainfrom
codex/fix-binder-configuration-for-native-rust-core
Jul 30, 2026
Merged

Make Binder build the native Rust core#383
Alek99 merged 7 commits into
mainfrom
codex/fix-binder-configuration-for-native-rust-core

Conversation

@FarhanAliRaza

@FarhanAliRaza FarhanAliRaza commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Motivation

  • Binder-built images were installing the repository checkout as a pure-Python package when cargo was absent, leaving notebooks that fail at import time because the native Rust core is missing (examples cannot be run on online jupyter notebook (mybinder) due to Rust core missing #379).
  • The goal is to make hosted Binder images fail early (during image build) when the native core cannot be produced, and to make the examples actually runnable — most notebooks need a scientific stack that neither the checkout install nor repo2docker's base environment provides.

Trade-off, stated explicitly

Binder is linux-64, where published wheels already exist — pip install xy would need no toolchain and carry no build risk. This PR instead takes on a full cargo build --release + npm ci + vite build inside repo2docker (build-timeout and image-size exposure) deliberately, so the notebooks always run against the launched ref rather than the last release. Fallback if the source build breaks: swap the postBuild install to pip install xy (published wheel), losing only launched-ref fidelity. This is recorded in spec/design-dossier.md.

Description

  • .binder/environment.yml provisions only the build toolchain with loose, arch-agnostic pins (rust=1.88.*, nodejs=22.*). Exact conda build strings are deliberately avoided: they are arch-specific (mybinder also schedules aarch64 nodes) and conda-forge rebuilds retire them, which silently rots the file into an unsolvable image.
  • The interpreter carries a loose version-only pin (python=3.13.*): repo2docker's default kernel env resolves to Python 3.10 (observed 3.10.19 on both mybinder.org and CI), below the package's requires-python >=3.11, so without the pin the pip install fails at metadata. pip stays unpinned.
  • The scientific stack imported across examples/**/*.ipynb (matplotlib, pandas, scipy, scikit-learn, seaborn, h5py, requests, plus pysam and gwosc for the real_world series) installs from pip wheels on the postBuild install line rather than through conda — keeping ~200 packages out of the mamba solve makes the build faster and sidesteps a reproducible mamba extraction failure observed on a mybinder builder node ("Cannot find a valid extracted directory cache").
  • .binder/postBuild (executable) exports XY_REQUIRE_CARGO=1 so a missing toolchain fails the image build instead of shipping a coreless wheel, disables Playwright's browser download (PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1), installs the checkout + notebook stack with pip --no-cache-dir, and removes the build-only caches (node_modules, target, cargo's crate downloads, npm's cache).
  • tests/test_binder_config.py asserts properties of the parsed config rather than transcribing its contents: contract toolchain versions present, no arch-specific build strings, interpreter pinned at or above the requires-python floor, both env exports ordered before the pip install, notebook stack on the install line, cache removal after it, script executable (skipped on Windows checkouts). It importorskips PyYAML so the python-floor CI job (bare package + pytest by design) skips it cleanly.
  • New .github/workflows/binder.yml runs a pinned jupyter-repo2docker==2026.4.0 --no-run . on changes to .binder/**, pyproject.toml, or hatch_build.py — nothing else in CI builds a Binder image, so this is the only check that exercises the config end to end (it caught the Python-floor conflict on this very PR).
  • README gains a Binder launch badge and docs/integrations/notebooks.md a "Run the Examples on Binder" section, so the deployment is discoverable. Both are explicit that the badge launches main.
  • Binder source-build contract, pin policy, and fallback documented in spec/design-dossier.md.

Testing

  • End to end on mybinder.org: the branch image built and launched, and the examples cannot be run on online jupyter notebook (mybinder) due to Rust core missing #379 notebook (04_dukascopy_fx_ticks.ipynb) opens with the native core present.
  • .github/workflows/binder.yml (full repo2docker image build) green on this PR.
  • uv run pytest -q tests/test_binder_config.py, pre-commit, ruff check/format — all pass; the missing-PyYAML floor environment was simulated locally and skips cleanly.

Fixes #379

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Binder now provisions a pinned Python and scientific environment, installs the project from source with native-build checks, removes build artifacts, validates the setup in tests and CI, and adds Binder access documentation and links.

Changes

Binder source build

Layer / File(s) Summary
Binder environment provisioning
.binder/environment.yml
Adds a Python 3.13 release-line pin while retaining loose Rust and Node.js specifications and the scientific notebook stack.
Source build and configuration validation
.binder/postBuild, tests/test_binder_config.py, pyproject.toml
Enforces native compilation, skips Playwright downloads, installs from source, removes build artifacts, and structurally tests the Binder configuration.
Binder workflow and access documentation
.github/workflows/binder.yml, spec/design-dossier.md, README.md, docs/integrations/notebooks.md
Adds conditional Binder image construction in CI and documents and links hosted example notebooks.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Repo2Docker
  participant Environment
  participant PostBuild
  participant Pip
  Repo2Docker->>Environment: Provision Python, Rust, Node.js, and notebook dependencies
  Environment->>PostBuild: Run strict post-build script
  PostBuild->>Pip: Install project from source with native core required
  PostBuild->>PostBuild: Remove node_modules and target
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR adds Binder toolchain setup and a source install path so the native Rust core is built during image creation, matching #379.
Out of Scope Changes check ✅ Passed The added workflow, docs, tests, and badge are all supporting Binder changes and do not appear unrelated to the issue.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: Binder is updated to build the native Rust core during image creation.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Fix failing CI checks
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-binder-configuration-for-native-rust-core

Comment @coderabbitai help to get the list of available commands.

@codspeed-hq

codspeed-hq Bot commented Jul 30, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 103 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing codex/fix-binder-configuration-for-native-rust-core (02b6bcd) with main (bd1d36e)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
tests/test_binder_config.py (1)

6-16: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Validate parsed configuration instead of substrings.

These assertions can pass when required tokens appear in comments or unrelated commands, and they do not verify dependency-list membership, environment-variable ordering, or cleanup behavior. Parse the YAML or use anchored entry checks, then validate the relevant script structure or execute it against a temporary fixture.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_binder_config.py` around lines 6 - 16, The
test_binder_source_build_requires_native_core currently relies on unanchored
substring assertions that can match comments or unrelated content. Parse
environment.yml or use anchored dependency-entry checks, and validate
postBuild’s required environment assignments, command ordering, and cleanup
behavior using structured script checks or execution against a temporary
fixture.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.binder/environment.yml:
- Around line 6-9: Update the Binder environment specification to pin Python,
Node.js, Rust, and pip to exact reproducible builds rather than broad
major/minor versions. Use exact build-level package pins or replace the
dependency list with a committed conda lock file, preserving the existing
toolchain components.

In @.binder/postBuild:
- Around line 11-13: Align the cleanup contract with the postBuild behavior:
either extend .binder/postBuild to remove the specified Cargo registry/git and
npm caches after installation, or update the relevant spec/ documentation to
claim only node_modules and target are removed. Ensure the spec/ directory
reflects the chosen behavior and remains current.

---

Nitpick comments:
In `@tests/test_binder_config.py`:
- Around line 6-16: The test_binder_source_build_requires_native_core currently
relies on unanchored substring assertions that can match comments or unrelated
content. Parse environment.yml or use anchored dependency-entry checks, and
validate postBuild’s required environment assignments, command ordering, and
cleanup behavior using structured script checks or execution against a temporary
fixture.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bf8cc38e-baf6-4ab3-bf3c-fa44abbbaba0

📥 Commits

Reviewing files that changed from the base of the PR and between bd1d36e and c849610.

📒 Files selected for processing (4)
  • .binder/environment.yml
  • .binder/postBuild
  • spec/design-dossier.md
  • tests/test_binder_config.py

Comment thread .binder/environment.yml Outdated
Comment thread .binder/postBuild Outdated

@Alek99 Alek99 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The diagnosis and the mechanism are right — .binder/ + conda rust/nodejs + a source install under XY_REQUIRE_CARGO=1 is exactly how you make repo2docker produce a native core. But as written the Binder image cannot build: three of the four conda pins do not exist on conda-forge, so the environment solve fails before postBuild ever runs. And with the solve fixed, most examples/ notebooks still won't run.

Green CI is not evidence here — nothing in CI builds a Binder image.

1. Blocking: 3 of 4 conda pins don't exist

Checked each pinned dist against anaconda.org (method validated with a known-good control dist that returns 200):

pin in .binder/environment.yml exists? reality on conda-forge
python=3.13.5=h2b335a9_102_cp313 404 the linux-64 build is hec9711d_102_cp313 (h2b335a9 is the 3.13.8 hash)
nodejs=22.16.0=hb8e1007_0 404 22.16.0 was never packaged — linux-64 goes 22.13.0 → 22.17.0
rust=1.88.0=h3a20983_0 404 the linux-64 build is h1a8d7c4_0
pip=25.1.1=pyh8b19718_0 ✅ noarch

Net effect: mamba env update fails with "nothing provides …", the image build dies, and #379 gets worse — a visitor sees an opaque build log instead of today's actionable ImportError.

2. Blocking for the issue's goal: notebook dependencies are missing

pip install . brings only numpy + anywidget. repo2docker's frozen 3.13 base env has none of the scientific stack (its lock carries only matplotlib-inline, plus requests). Imports across examples/**/*.ipynb:

38 numpy   20 matplotlib   10 sklearn   8 requests   6 pandas
 3 seaborn   2 scipy   2 pysam   2 urllib3   1 h5py   1 gwosc

The specific notebook in #379 (04_dukascopy_fx_ticks) survives because requests/urllib3 happen to be in the base — but the pdsh series, matplotlib_shim.ipynb, and everything touching sklearn/pandas/seaborn/scipy still fail at import. #379 is "examples cannot be run on online jupyter notebook"; fixing the xy import alone doesn't close it. Please add the notebook deps to environment.yml (matplotlib, pandas, scipy, scikit-learn, seaborn, h5py at minimum) and either add or explicitly scope out the exotic ones (pysam, gwosc).

3. Pinning strategy, once the strings are corrected

  • Exact build strings are the wrong granularity here. They are arch-specific (mybinder also runs aarch64 nodes) and conda-forge repackages/removes builds, so this file will silently rot back into an unbuildable image.
  • Pinning Python below repo2docker's base is actively harmful. Its 3.13 lock ships python 3.13.8 and pip 25.2; asking for 3.13.5 / pip 25.1.1 forces mamba to downgrade the interpreter and churn the whole preinstalled Jupyter stack.
  • The wheel is ABI-agnostic (py3-none-<plat>, ctypes C ABI), so nothing here needs a Python pin at all — the 3.14 default that reported the bug would work fine. Suggest dropping python/pip and using nodejs=22.*, rust=1.88.*.
  • The inline comment says 3.13 is "covered by XY's test matrix", but CI's only explicit pin is 3.11 (the floor job) and there is no .python-version. The stated justification doesn't hold.

4. The test locks in the wrong values, and can't detect a structure error

Running the test body on this branch passes — with unbuildable pins. That's the problem: it asserts transcription, not solvability.

  • The dependencies set is built from every - line, so conda-forge (a channel) is asserted as a dependency (tests/test_binder_config.py:18). Swap the channels: and dependencies: keys and the test still passes.
  • Exact commands == matching means any added comment or reorder in postBuild breaks the test, while a semantically broken script that keeps the shape passes it.
  • not line.startswith("#!") is dead code — already covered by startswith("#").
  • stat().st_mode & 0o111 fails on a Windows checkout (CI's pytest is ubuntu-only, so this one is only a local-dev nit).

Better: parse the YAML properly and assert propertiesrust and nodejs present, XY_REQUIRE_CARGO=1 exported before the pip install, the Playwright download disabled, the script executable. The only check with real signal would be a CI job running jupyter-repo2docker --no-run ., which would have caught finding 1 outright. Probably worth the runner minutes, since nothing else in the repo exercises this config.

5. Worth stating in the PR description

Binder is linux-64, where published wheels already exist — pip install xy needs no toolchain and carries no build risk. This PR instead takes on a full cargo build --release + npm ci + vite build inside repo2docker (build-timeout and image-size exposure) purely to keep notebooks on the browsed commit. That's a defensible trade-off, but it should be stated explicitly, ideally with a documented fallback for when the source build fails.

What's correct

  • XY_REQUIRE_CARGO=1 is the right lever — hatch_build.py:171. Without it the hook silently ships a pure-Python wheel, which is precisely the #379 failure mode.
  • nodejs is genuinely required, not incidental: the JS client is a hard requirement of every distribution and is built with npm ci && node js/build.mjs from a clone.
  • PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 is the correct variable and it matters — playwright is in devDependencies, so npm ci would otherwise pull several hundred MB of browsers into the image.
  • rm -rf node_modules target is right for image size; the installed wheel force-includes the lib into site-packages, so nothing at runtime depends on those directories.
  • .binder/postBuild is committed executable (100755), and there is no competing root-level repo2docker config to conflict with .binder/.

One aside: the repo has no Binder references at all (no launch badge, no docs link), so once this works nothing points users at it.

The exact conda build strings pinned three dists that do not exist on
conda-forge (python 3.13.5's hash belongs to 3.13.8, nodejs 22.16.0 was
never packaged, rust 1.88.0's linux-64 build hash was wrong), so the
mamba solve failed before postBuild ever ran. Build strings are also
arch-specific (mybinder schedules aarch64 nodes) and get retired by
conda-forge rebuilds, so replace them with loose pins: rust=1.88.*,
nodejs=22.*. Drop the python/pip pins entirely — the wheel is
ABI-agnostic (py3-none, ctypes C ABI), and pinning below repo2docker's
frozen base environment only forces an interpreter downgrade that churns
the preinstalled Jupyter stack.

Provision the scientific stack the example notebooks import (matplotlib,
pandas, scipy, scikit-learn, seaborn, h5py, requests): the checkout
install brings only numpy + anywidget, so most of examples/ failed at
import even with a working xy. pysam (bioconda-only) and gwosc are
scoped out explicitly for in-notebook install.

Rewrite tests/test_binder_config.py to assert properties of the parsed
config — toolchain and notebook stack present, no arch-specific build
strings, no interpreter pin, XY_REQUIRE_CARGO=1 and the Playwright
download guard exported before the pip install, caches removed after —
instead of transcribing the file contents, which passed verbatim with
the unbuildable pins. PyYAML joins the dev group for the parse.

Add .github/workflows/binder.yml running repo2docker --no-run on
.binder/ changes: nothing else in CI builds a Binder image, and it is
the only check that would have caught the unsolvable pins. Point users
at the deployment with a README launch badge and a docs section, and
record the source-build trade-off (linux-64 wheels exist; the source
build buys browsed-commit fidelity) and its fallback in the dossier.
repo2docker's default kernel env resolves to Python 3.10 (observed
3.10.19 on both mybinder.org and the binder.yml CI run), below the
package's requires-python >=3.11, so the postBuild pip install died at
metadata: "Package 'xy' requires a different Python: 3.10.19 not in
'>=3.11'". Pin python=3.13.* — version only, never an arch-specific
build string — and assert in the test that the pinned version satisfies
the package floor instead of asserting no pin exists.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/integrations/notebooks.md`:
- Around line 110-116: Update the Binder description near the mybinder.org link
to avoid claiming it builds the browsed or current repository commit; state that
Binder builds the main revision resolved by the pinned URL, or replace the URL
with a commit-specific link and retain the matching description.

In `@tests/test_binder_config.py`:
- Around line 32-34: Update the binder configuration test around the
package-name assertions to also validate the required toolchain versions: Rust
must be pinned to 1.88 and Node.js to 22, while preserving the existing package
presence checks for rust, nodejs, and NOTEBOOK_STACK.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4de8b3a4-7c25-4ead-abdd-15bcf99c56f5

📥 Commits

Reviewing files that changed from the base of the PR and between 3144da5 and ab3e45a.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • .binder/environment.yml
  • .github/workflows/binder.yml
  • README.md
  • docs/integrations/notebooks.md
  • pyproject.toml
  • spec/design-dossier.md
  • tests/test_binder_config.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • spec/design-dossier.md

Comment thread docs/integrations/notebooks.md Outdated
Comment thread tests/test_binder_config.py Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed against the latest diff

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

Comment thread docs/integrations/notebooks.md Outdated
Comment thread .github/workflows/binder.yml Outdated
Comment thread .github/workflows/binder.yml
Comment thread .binder/environment.yml Outdated
Comment thread tests/test_binder_config.py
mybinder's bids builder node failed the mamba env update twice at the
same spot — "Cannot find a valid extracted directory cache for
'zipp-...conda' / Package cache error" — while CI built the identical
commit green. The scientific stack does not need conda at all: every
package has manylinux wheels, so install it with pip in postBuild and
leave conda carrying only the toolchain (python, rust, nodejs). That
keeps ~200 packages out of the mamba link phase, which both speeds the
image build and removes the flaky extraction path.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 4 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

Comment thread .binder/postBuild Outdated
The python-floor CI job installs the bare package + pytest (that is its
point), so tests/test_binder_config.py now importorskips yaml the same
way the suite handles pyarrow and pillow, instead of erroring at
collection.

Review fixes: pysam and gwosc join the postBuild pip line — both ship
wheels, so provisioning them beats a dead markdown %pip suggestion and
lets the three real_world genomics/LIGO notebooks run. The rm line also
drops cargo's crate-download cache and npm's package cache, matching
what the dossier promises about build-only caches. The config test now
asserts the contract versions (rust=1.88.*, nodejs=22.*), not just
package names. binder.yml pins jupyter-repo2docker==2026.4.0 for
determinism and also triggers on pyproject.toml/hatch_build.py, whose
contracts (requires-python, the XY_REQUIRE_CARGO hook) the image build
consumes directly. Docs and dossier stop claiming the badge builds the
browsed commit — Binder builds the launched ref, and the badge launches
main.

Declined from review: exact conda build pins / a conda lock (that exact
advice produced the unsolvable image the human review flagged) and
widening the workflow trigger to all source changes (the wheel jobs
already gate src/ and js/; this build is too expensive per PR).

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 6 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

Comment thread .binder/postBuild
Comment thread .github/workflows/binder.yml
Comment thread .github/workflows/binder.yml
@FarhanAliRaza
FarhanAliRaza requested a review from Alek99 July 30, 2026 20:14
@Alek99
Alek99 merged commit 582cf2c into main Jul 30, 2026
30 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

examples cannot be run on online jupyter notebook (mybinder) due to Rust core missing

2 participants