Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -754,7 +754,18 @@ rust_wasm_component_bindgen(

rust_wasm_component_bindgen(
name = "falcon-rate",
crate_features = ["bazel-bindings"],
# `std` is EXPLICIT here (v1.130). The features are now independent:
# bazel-bindings = bindings source, std = std-vs-no_std. Bazel opts into std
# because this target cannot build no_std yet — it would need `lol_alloc` in
# the crate_universe deps and a genuine no_std rules_rust config (without
# them: "cannot find crate `lol_alloc`" + "duplicate lang item `panic_impl`",
# since rules_rust still links std). Until that lands, the WASI-free
# component comes from the cargo-component path and Bazel builds the std
# variant. Making these converge is the follow-up (see docs/OCI-DISTRIBUTION).
crate_features = [
"bazel-bindings",
"std",
],
srcs = ["wasm/cm/rate/src/lib.rs"],
wit = ":falcon_rate_wit",
deps = [":relay-rate"],
Expand Down
43 changes: 43 additions & 0 deletions docs/OCI-DISTRIBUTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,24 @@ cosign verify ghcr.io/pulseengine/falcon-flight:1.127.0 \
already exist and be **public** (step 1) before submitting — an unpullable
entry is rejected. Do this only after a real release has populated the package.

Adding a component to an **existing** namespace auto-merges (no maintainer
review) — only a brand-new namespace is held for review. Registered so far:

| component | wasm.directory | note |
|---|---|---|
| `falcon-flight` | ✅ indexed (#466 → #467) | |
| `falcon-iekf` | filed #474 → PR #478 | |
| `falcon-position` | filed #475 → PR #479 | |
| `falcon-attitude` | filed #476 → PR #480 | |
| `falcon-mixer` | filed #477 → PR #481 | |
| `falcon-rate` | **HELD — do not register yet** | published 1.129.0 is a CORE MODULE, not a component; register after v1.130 republishes it |

**Verify the payload before registering.** The OCI `config.mediaType` reads
`application/vnd.wasm.config.v0+json` whether or not the bytes are actually a
component, so metadata cannot be trusted here — pull the blob and check the
8-byte header (`0061736d0d000100` = component, `0061736d01000000` = core
module). This is how `falcon-rate:1.129.0` was caught.

> Status: **LIVE** — the `pulseengine` namespace was accepted
> ([wasm.directory#466](https://github.com/yoshuawuyts/wasm.directory/issues/466)
> → [PR #467](https://github.com/yoshuawuyts/wasm.directory/pull/467) merged);
Expand All @@ -93,6 +111,31 @@ cosign verify ghcr.io/pulseengine/falcon-flight:1.127.0 \
channel, not a dependency. The durable, signed artifacts remain the GitHub
Release and the ghcr OCI ref.

## Two build paths — and why they still differ

The components are built **twice**, and the paths do not yet agree:

| | Bazel (`rust_wasm_component_bindgen`) | `scripts/build-components.sh` |
|---|---|---|
| used by | the cascade / `wac_plug` / `meld_fuse` / `synth_compile` graph | the **release** (what we publish) |
| variant | **std** (carries WASI imports) | **no_std, WASI-free** |
| on build error | fails the build | fails the build (since v1.130; it used to `|| true`) |

The published artifact is the WASI-free one, and only the script produces it today.
The feature flags are now properly independent — `bazel-bindings` selects the
bindings source, `std` selects std-vs-no_std — but the Bazel target must still
opt into `std`, because building it `no_std` needs two things it does not have:

1. `lol_alloc` in the Bazel `deps` (Bazel does not read `Cargo.toml` deps), and
2. a genuine no_std rules_rust configuration — otherwise std is still linked and
the crate's own `#[panic_handler]` collides ("duplicate lang item `panic_impl`").

**Converging them is the open follow-up**: once Bazel can build the no_std
variant, the release should consume Bazel outputs and this script becomes a thin
bundler. That removes the whole class of bug that shipped `falcon-rate:1.129.0`
as a core module (silent build failure + untyped artifact selection), because a
Bazel action failure cannot be swallowed and its outputs are declared.

## Component metadata contract (what wasm.directory renders)

wasm.directory shows a component's **WIT interface** (with its `///` doc comments)
Expand Down
24 changes: 19 additions & 5 deletions scripts/build-components.sh
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,25 @@ build_component() {
exit 1
fi
# Prefer the wasip2 path; cargo-component <0.20 emits wasip1.
local src
src=$(ls "$wasm_dir"/target/wasm32-wasip2/release/*.wasm 2>/dev/null | head -1 || true)
if [ -z "$src" ]; then
src=$(ls "$wasm_dir"/target/wasm32-wasip1/release/*.wasm 2>/dev/null | head -1 || true)
fi
# Select the artifact BY NAME, never `ls *.wasm | head -1` (v1.130). The old
# glob picked whatever sorted first in the target dir — with a shared
# CARGO_TARGET_DIR (a common CI disk-saving setting) that is a DIFFERENT
# crate's component: building `rate` picked up falcon_flight_component.wasm
# because "flight" < "rate". It would then be published under the wrong name.
# cargo turns - into _ for the artifact filename.
local crate_name artifact src
crate_name=$(grep -m1 '^name *= *"' "$wasm_dir/Cargo.toml" | sed -E 's/.*"(.*)".*/\1/')
artifact="${crate_name//-/_}.wasm"
src=""
for tgt in wasm32-wasip2 wasm32-wasip1; do
if [ -f "$wasm_dir/target/$tgt/release/$artifact" ]; then
src="$wasm_dir/target/$tgt/release/$artifact"; break
fi
# honour a shared CARGO_TARGET_DIR if one is set
if [ -n "${CARGO_TARGET_DIR:-}" ] && [ -f "$CARGO_TARGET_DIR/$tgt/release/$artifact" ]; then
src="$CARGO_TARGET_DIR/$tgt/release/$artifact"; break
fi
done
if [ -z "$src" ]; then
echo " ERROR: no .wasm found" >&2
exit 1
Expand Down
9 changes: 8 additions & 1 deletion wasm/cm/rate/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,12 @@ codegen-units = 1
[workspace]

[features]
default = ["bazel-bindings"]
# Two INDEPENDENT concerns — do not conflate them again (see lib.rs):
# bazel-bindings : where the WIT bindings come from (bazel-generated crate
# vs cargo-component's local `mod bindings`)
# std : link std, or build no_std (WASI-free, lowerable)
# Bazel sets crate_features explicitly and rules_rust does NOT apply Cargo's
# default-features, so a Bazel build gets bazel-bindings WITHOUT std -> no_std.
default = ["bazel-bindings", "std"]
bazel-bindings = []
std = []
18 changes: 13 additions & 5 deletions wasm/cm/rate/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,27 @@
//! comes from `vehicle-state.{wx,wy,wz}` (the gyro reading the EKF component
//! passes through).
//!
//! `no_std` on the cargo-component path so the component imports NO WASI — a
//! `no_std` unless the `std` feature is on, so the component imports NO WASI — a
//! pure `falcon:cascade/types` -> `rate` transformer, which is what lowers to
//! bare metal (synth -> gale on M4/M7/F100) and what a WASI-less wasmtime host
//! can drive directly. (The single-threaded statics replace `thread_local!`,
//! whose `std` dependency was the sole reason WASI was linked.) The Bazel
//! bindings path keeps its existing environment.
//!
//! FEATURE SPLIT (v1.130): `bazel-bindings` selects the BINDINGS SOURCE only;
//! `std` selects std-vs-no_std. These were briefly conflated on one flag, which
//! made the Bazel build (which sets bazel-bindings) silently produce a
//! std/WASI-carrying artifact while the cargo-component build produced the
//! WASI-free one — two build systems, same source, materially different output.
//! rules_rust does not apply Cargo's default-features, so a Bazel build enables
//! ONLY the features it lists: `bazel-bindings` without `std` => no_std.

#![cfg_attr(not(feature = "bazel-bindings"), no_std)]
#![cfg_attr(not(feature = "std"), no_std)]

// no_std (cargo-component path) global allocator — the component ABI's
// cabi_realloc needs one. Single-threaded is sound: a component instance is
// never re-entered concurrently by the runtime.
#[cfg(not(feature = "bazel-bindings"))]
#[cfg(not(feature = "std"))]
#[global_allocator]
static ALLOC: lol_alloc::AssumeSingleThreaded<lol_alloc::FreeListAllocator> =
unsafe { lol_alloc::AssumeSingleThreaded::new(lol_alloc::FreeListAllocator::new()) };
Expand All @@ -31,7 +39,7 @@ static ALLOC: lol_alloc::AssumeSingleThreaded<lol_alloc::FreeListAllocator> =
// `wasm-component-ld` failed with "module does not export a function named
// `cabi_realloc`" — leaving a raw CORE MODULE where a component was expected.
// Export it here, backed by the same single-threaded allocator.
#[cfg(not(feature = "bazel-bindings"))]
#[cfg(not(feature = "std"))]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn cabi_realloc(
old_ptr: *mut u8,
Expand Down Expand Up @@ -114,7 +122,7 @@ bindings::export!(Component with_types_in bindings);

// no_std (cargo-component path) needs a panic handler; the Bazel path (std)
// provides its own.
#[cfg(not(feature = "bazel-bindings"))]
#[cfg(not(feature = "std"))]
#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! {
loop {}
Expand Down
Loading