- Home
- Documentation
- natives
- Natives Build, Release, and Debugging Runbook
Natives Build, Release, and Debugging Runbook
Natives Build, Release, and Debugging Runbook
Section titled “Natives Build, Release, and Debugging Runbook”This runbook describes how the @f5xc-salesdemos/pi-natives build pipeline produces .node addons, how compiled distributions load them, and how to debug loader/build failures.
It follows the architecture terms from docs/natives-architecture.md:
- build-time artifact production (
scripts/build-native.ts) - embedded addon manifest generation (
scripts/embed-native.ts) - runtime addon loading + validation gate (
src/native.ts)
Implementation files
Section titled “Implementation files”packages/natives/scripts/build-native.tspackages/natives/scripts/embed-native.tspackages/natives/package.jsonpackages/natives/src/native.tscrates/pi-natives/Cargo.toml
Build pipeline overview
Section titled “Build pipeline overview”1) Build entrypoints
Section titled “1) Build entrypoints”packages/natives/package.json scripts:
bun scripts/build-native.ts(build) → release buildbun scripts/build-native.ts --dev(dev:native) → debug/dev profile build (same output naming)bun scripts/embed-native.ts(embed:native) → generatesrc/embedded-addon.tsfrom built files
2) Rust artifact build
Section titled “2) Rust artifact build”build-native.ts runs Cargo in crates/pi-natives:
- base command:
cargo build - release mode adds
--releaseunless--devis passed - cross target adds
--target <CROSS_TARGET>
crates/pi-natives/Cargo.toml declares crate-type = ["cdylib"], so Cargo emits a shared library (.so/.dylib/.dll) that is then copied/renamed to a .node addon filename.
3) Artifact discovery and install
Section titled “3) Artifact discovery and install”After Cargo completes, build-native.ts scans candidate output directories in order:
${CARGO_TARGET_DIR}(if set)<repo>/targetcrates/pi-natives/target
For each root it checks profile directories:
- cross build:
<root>/<crossTarget>/<profile>then<root>/<profile> - native build:
<root>/<profile>
Then it looks for one of:
libpi_natives.solibpi_natives.dylibpi_natives.dlllibpi_natives.dll
When found, it atomically installs into packages/natives/native/ with temp-file + rename semantics (Windows fallback handles locked DLL replacement failures explicitly).
Target/variant model and naming conventions
Section titled “Target/variant model and naming conventions”Platform tag
Section titled “Platform tag”Both build and runtime use platform tag:
<platform>-<arch> (example: darwin-arm64, linux-x64)
Variant model (x64 only)
Section titled “Variant model (x64 only)”x64 supports CPU variants:
modern(AVX2-capable path)baseline(fallback)
Non-x64 uses a single default artifact (no variant suffix).
Output filenames
Section titled “Output filenames”Release builds:
- x64:
pi_natives.<platform>-<arch>-modern.nodeor...-baseline.node - non-x64:
pi_natives.<platform>-<arch>.node
Dev build (--dev):
- Uses debug profile flags but keeps standard platform-tagged output naming
Runtime loader candidate order in native.ts:
- release candidates
- compiled mode prepends extracted/cache candidates before package-local files
Environment flags and build options
Section titled “Environment flags and build options”Runtime flags
Section titled “Runtime flags”PI_DEV(loader behavior): enable loader diagnosticsPI_NATIVE_VARIANT(loader behavior, x64 only): forcemodernorbaselineselection at runtimePI_COMPILED(loader behavior): enable compiled-binary candidate/extraction behavior
Build-time flags/options
Section titled “Build-time flags/options”--dev(script arg): build debug profileCROSS_TARGET: passed to Cargo--targetTARGET_PLATFORM: override output platform tag namingTARGET_ARCH: override output arch namingTARGET_VARIANT(x64 only): forcemodernorbaselinefor output filename and RUSTFLAGS policyCARGO_TARGET_DIR: additional root when searching Cargo outputsRUSTFLAGS:- if unset and not cross-compiling, script sets:
- modern:
-C target-cpu=x86-64-v3 - baseline:
-C target-cpu=x86-64-v2 - non-x64 / no variant:
-C target-cpu=native
- modern:
- if already set, script does not override
- if unset and not cross-compiling, script sets:
Build state/lifecycle transitions
Section titled “Build state/lifecycle transitions”Build lifecycle (build-native.ts)
Section titled “Build lifecycle (build-native.ts)”- Init: parse args/env (
--dev, target overrides, cross flags) - Variant resolve:
- non-x64 → no variant
- x64 +
TARGET_VARIANT→ explicit variant - x64 cross-build without
TARGET_VARIANT→ hard error - x64 local build without override → detect host AVX2
- Compile: run Cargo with resolved profile/target
- Locate artifact: scan target roots/profile dirs/library names
- Install: copy + atomic rename into
packages/natives/native - Complete: output addon ready for loader candidates
Failure exits happen at any stage with explicit error text (invalid variant, failed cargo build, missing output library, install/rename failure).
Embed lifecycle (embed-native.ts)
Section titled “Embed lifecycle (embed-native.ts)”- Init: compute platform tag from
TARGET_PLATFORM/TARGET_ARCHor host values - Candidate set:
- x64 expects both
modernandbaseline - non-x64 expects one default file
- x64 expects both
- Validate availability in
packages/natives/native - Generate manifest (
src/embedded-addon.ts) with Bunfileimports and package version - Runtime extraction ready for compiled mode
--reset bypasses validation and writes a null manifest stub (embeddedAddon = null).
Dev workflow vs shipped/compiled behavior
Section titled “Dev workflow vs shipped/compiled behavior”Local development workflow
Section titled “Local development workflow”Typical local loop:
- Build addon:
- release:
bun --cwd=packages/natives run build - debug profile:
bun --cwd=packages/natives run dev:native
- release:
- Set
PI_DEV=1when testing loader diagnostics - Loader in
native.tsresolves package-localnative/(and executable-dir fallback) candidates validateNativeenforces export compatibility before wrappers use the binding
Shipped/compiled binary workflow
Section titled “Shipped/compiled binary workflow”In compiled mode (PI_COMPILED or Bun embedded markers):
- Loader computes versioned cache dir:
<getNativesDir()>/<packageVersion>(operationally~/.xcsh/natives/<version>) - If embedded manifest matches current platform+version, loader may extract selected embedded file into that versioned dir
- Runtime candidate order includes:
- versioned cache dir
- legacy compiled-binary dir (
%LOCALAPPDATA%/xcshon Windows,~/.local/binelsewhere) - package/executable directories
- First successfully loaded addon still must pass
validateNative
This is why packaging + runtime loader expectations must align: filenames, platform tags, and exported symbols must match what native.ts probes and validates.
JS API ↔ Rust export mapping (validation gate subset)
Section titled “JS API ↔ Rust export mapping (validation gate subset)”native.ts requires these JS-visible exports to exist on the loaded addon. They map to Rust N-API exports in crates/pi-natives/src:
JS name required by validateNative | Rust export declaration | Rust source file |
|---|---|---|
glob | #[napi] pub fn glob(...) | crates/pi-natives/src/glob.rs |
grep | #[napi] pub fn grep(...) | crates/pi-natives/src/grep.rs |
search | #[napi] pub fn search(...) | crates/pi-natives/src/grep.rs |
highlightCode | #[napi] pub fn highlight_code(...) | crates/pi-natives/src/highlight.rs |
getSystemInfo | #[napi] pub fn get_system_info(...) | crates/pi-natives/src/system_info.rs |
getWorkProfile | #[napi] pub fn get_work_profile(...) (camel-cased export) | crates/pi-natives/src/prof.rs |
invalidateFsScanCache | #[napi] pub fn invalidate_fs_scan_cache(...) | crates/pi-natives/src/fs_cache.rs |
If any required symbol is missing, loader fails fast with a rebuild hint.
Failure behavior and diagnostics
Section titled “Failure behavior and diagnostics”Build-time failures
Section titled “Build-time failures”- Invalid variant configuration:
TARGET_VARIANTset on non-x64 → immediate error- x64 cross-build without explicit
TARGET_VARIANT→ immediate error
- Cargo build failure:
- script surfaces non-zero exit and stderr
- Artifact not found:
- script prints every checked profile directory
- Install failure:
- explicit message; Windows includes locked-file hint
Runtime loader failures (native.ts)
Section titled “Runtime loader failures (native.ts)”- Unsupported platform tag:
- throws with supported platform list
- No candidate could load:
- throws with full candidate error list and mode-specific remediation hints
- Missing exports:
- throws with exact missing symbol names and rebuild command
- Embedded extraction problems:
- extraction mkdir/write errors recorded and included in final diagnostics
Troubleshooting matrix
Section titled “Troubleshooting matrix”| Symptom | Likely cause | Verify | Fix |
|---|---|---|---|
Native addon missing exports ... Missing: <name> | Stale .node binary, Rust export name mismatch, or wrong binary loaded | Run with PI_DEV=1 to see loaded path; inspect export list for that file | Rebuild build; ensure Rust #[napi] export name (or explicit alias when needed) matches JS key; remove stale cached/versioned files |
| x64 machine loads baseline when modern expected | PI_NATIVE_VARIANT=baseline, no AVX2 detected, or only baseline file present | Check PI_NATIVE_VARIANT; inspect native/ for -modern file | Build modern variant (TARGET_VARIANT=modern ... build) and ensure file is shipped |
| Cross-build produces unusable/wrong-labeled binary | Mismatch between CROSS_TARGET and TARGET_PLATFORM/TARGET_ARCH, or missing TARGET_VARIANT for x64 | Confirm env tuple and output filename | Re-run with consistent env values and explicit x64 TARGET_VARIANT |
| Compiled binary fails after upgrade | Stale extracted cache (~/.xcsh/natives/<old-or-mismatched-version>) or embedded manifest mismatch | Inspect versioned natives dir and loader error list | Delete versioned natives cache for the package version and rerun; regenerate embedded manifest during packaging |
| Loader probes many paths and none work | Platform mismatch or missing release artifact in package native/ | Check platformTag vs actual filename(s) | Ensure built filename exactly matches pi_natives.<platform>-<arch>(-variant).node convention and package includes native/ |
embed:native fails with “Incomplete native addons” | Required variant files not built before embedding | Check expected vs found list in error text | Build required files first (x64: both modern+baseline; non-x64: default), then rerun embed:native |
Operational commands
Section titled “Operational commands”# Release artifact for current hostbun --cwd=packages/natives run build
# Debug profile artifact buildbun --cwd=packages/natives run dev:native
# Build explicit x64 variantsTARGET_VARIANT=modern bun --cwd=packages/natives run buildTARGET_VARIANT=baseline bun --cwd=packages/natives run build
# Generate embedded addon manifest from built native filesbun --cwd=packages/natives run embed:native
# Reset embedded manifest to null stubbun --cwd=packages/natives run embed:native -- --reset