Summary
xy advertises the native Rust core as required and has no compute fallback, but an ordinary source wheel build quietly succeeds as py3-none-any when Cargo is missing or the Rust build fails. Installation appears successful and only fails when compute is first imported/used.
Evidence
- The build hook explicitly defines missing Cargo/build failure as a successful coreless install:
|
- **Source builds** (`pip install .` / `-e .` from a clone) compile the core if |
|
a Rust toolchain is present. **If Rust is absent, the build still succeeds** |
|
but produces a pure-Python install with no native core — and since there is no |
|
NumPy fallback, importing the compute layer then raises a clear, actionable |
|
error (see `xy.kernels`). Install a Rust toolchain, or use a published |
|
wheel, for a working compute backend. |
and
|
require = os.environ.get("XY_REQUIRE_CARGO") == "1" |
|
|
|
native_src = self._provision_native(root, lib_name, dest, require) |
|
|
|
if native_src is not None: |
|
# Platform wheel carrying the compiled C-ABI core (one per platform, |
|
# every CPython version). |
|
build_data["pure_python"] = False |
|
build_data["tag"] = f"py3-none-{_platform_tag()}" |
|
build_data.setdefault("force_include", {})[str(native_src)] = ( |
|
f"xy/_native_lib/{lib_name}" |
|
) |
|
else: |
|
# No toolchain / build skipped: ship a pure-Python wheel (the JS |
|
# client is included via committed package data). There is no NumPy |
|
# fallback, so this install imports fine but raises a clear error the |
|
# moment compute is needed (xy.kernels). |
|
print( |
|
"xy: building WITHOUT the native Rust core (cargo not " |
|
"found or build skipped). This install has no compute backend " |
|
"and will raise a clear error on first use. Install a prebuilt " |
|
"wheel or a Rust toolchain (https://rustup.rs) for a working " |
|
"install.", |
|
file=sys.stderr, |
|
) |
|
build_data["pure_python"] = True |
|
build_data["tag"] = "py3-none-any" |
- Missing Cargo or a failed build returns
None unless XY_REQUIRE_CARGO=1:
|
if shutil.which("cargo") is None: |
|
if require: |
|
raise RuntimeError( |
|
"XY_REQUIRE_CARGO=1 but cargo is not on PATH — a " |
|
"published wheel must contain the native core." |
|
) |
|
return None # graceful: pure-Python wheel |
|
|
|
cmd = ["cargo", "build", "--release"] |
|
if target: |
|
cmd += ["--target", target] |
|
try: |
|
subprocess.run(cmd, cwd=root, check=True) |
|
except (subprocess.CalledProcessError, OSError) as e: |
|
if require: |
|
raise RuntimeError(f"cargo build failed: {e}") from e |
|
return None |
|
|
|
resolved = _resolve_built(built, target) |
|
if resolved is None: |
|
if require: |
|
raise RuntimeError( |
|
f"cargo build succeeded but {built} is missing, and no " |
|
f"xy_core.* artifact was found in {built.parent}" |
|
) |
|
return None |
- CI and release explicitly preserve and celebrate the coreless-success path:
|
- name: Build and load native core from sdist |
|
shell: bash |
|
env: |
|
XY_REQUIRE_CARGO: "1" |
|
run: | |
|
uv venv smoke-native |
|
uv pip install --no-cache -p smoke-native dist/*.tar.gz numpy anywidget "reflex>=0.9.6" |
|
./smoke-native/bin/python - <<'PY' |
|
import importlib.metadata as metadata |
|
|
|
import reflex_xy |
|
import xy |
|
import xy.kernels as kernels |
|
|
|
version = metadata.version("xy") |
|
assert xy.__version__ == version |
|
assert reflex_xy.__version__ == version |
|
assert kernels.BACKEND == "native", kernels.BACKEND |
|
print("sdist built and loaded the native Rust core") |
|
PY |
|
|
|
- name: Verify coreless sdist imports reflex_xy |
|
shell: bash |
|
env: |
|
XY_SKIP_CARGO: "1" |
|
run: | |
|
uv venv smoke-no-rust |
|
uv pip install --no-cache -p smoke-no-rust dist/*.tar.gz numpy anywidget "reflex>=0.9.6" |
|
./smoke-no-rust/bin/python - <<'PY' |
|
import importlib.metadata as metadata |
|
|
|
import reflex_xy |
|
import xy |
|
|
|
version = metadata.version("xy") |
|
assert xy.__version__ == version |
|
assert reflex_xy.__version__ == version |
|
try: |
|
import xy.kernels # noqa: F401 |
|
except ImportError as err: |
|
msg = str(err) |
|
assert "native Rust core" in msg, msg |
|
assert "rustup.rs" in msg, msg |
|
print("coreless sdist imports reflex_xy and errors clearly on compute") |
|
else: |
|
raise SystemExit("expected ImportError without the native core") |
|
PY |
and
|
### Fixed |
|
- Source-distribution CI and release validation now exercise both installation |
|
contracts independently: a forced Cargo build must load the native backend, |
|
while a cache-isolated coreless build must still import `reflex_xy`, report |
|
the installed version, and raise the documented error only when compute is |
|
requested. |
- The public install page acknowledges that an apparently successful install is unusable for compute:
|
A source build without Rust can finish installing, but it has no compute |
|
backend and fails with an actionable error when a chart first needs native |
|
compute. XY does not silently switch to a slower implementation. Building for |
|
an unsupported operating system or architecture may also require target-specific |
|
Rust tooling beyond the commands above. |
Acceptance criteria
Summary
xyadvertises the native Rust core as required and has no compute fallback, but an ordinary source wheel build quietly succeeds aspy3-none-anywhen Cargo is missing or the Rust build fails. Installation appears successful and only fails when compute is first imported/used.Evidence
xy/hatch_build.py
Lines 8 to 13 in 99eda6d
xy/hatch_build.py
Lines 171 to 197 in 99eda6d
NoneunlessXY_REQUIRE_CARGO=1:xy/hatch_build.py
Lines 306 to 331 in 99eda6d
xy/.github/workflows/ci.yml
Lines 644 to 690 in 99eda6d
xy/CHANGELOG.md
Lines 37 to 42 in 99eda6d
xy/docs/overview/installation.md
Lines 85 to 89 in 99eda6d
Acceptance criteria
py3-none-anyxywheel with no compute backend.