bindings/r: build the C ABI from source for the WebAssembly target (#244)

## Problem
r-universe builds every package to WebAssembly (webR) in addition to the native platforms, calling `./configure --host=wasm32-unknown-emscripten`. `bindings/r/configure` only knew Linux/macOS/Windows and exited with `unsupported OS 'Emscripten'`, so the **WASM job was the single red check** on an otherwise fully-published r-universe build (all native platforms + deploy are green; the package installs fine everywhere).

## Fix
The r-universe wasm build image ships **cargo** (`/usr/local/cargo/bin`) and **emscripten** (`EMSDK` on PATH). So instead of needing a prebuilt wasm lib (which would risk an emscripten ABI/version mismatch), `configure` now detects the Emscripten host and **builds the C ABI staticlib from source** for `wasm32-unknown-emscripten` in-place — compiled with the image's own emscripten, so the ABI always matches. The static `libwickra.a` is linked into the package object (no shared lib, no rpath).

`wickra-core`'s rayon batch needs threads (absent on wasm), so the wasm build drops it via `--no-default-features`. `wickra-c` now takes `wickra-core` as a direct path dep with `default-features = false` plus a default `parallel` feature that re-enables it for native builds (a member-level `default-features = false` is ignored when inheriting a workspace dep — that was the trap). 

## Validated locally
- `cargo build -p wickra-c` (default) → rayon present, builds.
- `cargo build -p wickra-c --no-default-features` (the wasm feature path) → rayon **gone**, builds.
- `cargo build --workspace`, clippy, fmt all clean; `configure` passes `sh -n`.

## Cannot be validated locally
No Rust/emscripten wasm toolchain here. The wasm build only runs in r-universe, and `configure` downloads the matching `v${version}` source tag — so this takes effect from the **first release that includes it** (the tagged source must contain the feature toggle). Open risks: whether the image has the `wasm32-unknown-emscripten` Rust target pre-installed (configure runs `rustup target add` best-effort) and the wasm build time. Worth one r-universe rebuild to confirm.

Not merging — review first.
This commit is contained in:
kingchenc
2026-06-10 01:44:48 +02:00
committed by GitHub
parent 447bbc8220
commit 6433c9b3de
2 changed files with 44 additions and 1 deletions
+14 -1
View File
@@ -42,5 +42,18 @@ cast_sign_loss = "allow"
similar_names = "allow"
float_cmp = "allow"
[features]
# `parallel` (rayon-backed batch in wickra-core) is on by default for native
# builds. The wasm32-unknown-emscripten target has no threads, so the R
# package's wasm build (r-universe / webR) compiles this crate with
# --no-default-features to drop rayon; wickra-core falls back to its serial
# batch path, which is cfg-gated behind the same feature.
default = ["parallel"]
parallel = ["wickra-core/parallel"]
[dependencies]
wickra-core = { workspace = true }
# Direct path dep rather than `workspace = true`: a member-level
# `default-features = false` is ignored when inheriting a workspace dep that
# does not set it, which would leave rayon in the wasm build. wickra-c is
# `publish = false`, so no version pin is needed (and none to keep in sync).
wickra-core = { path = "../../crates/wickra-core", default-features = false }
+30
View File
@@ -13,6 +13,36 @@ set -e
inc=""
lib=""
# WebAssembly (r-universe / webR): there is no prebuilt wasm C ABI to download,
# but the build image ships cargo (/usr/local/cargo/bin) and emscripten
# (EMSDK on PATH), so build the C ABI staticlib from source for
# wasm32-unknown-emscripten right here. Building it in the same image with the
# same emscripten avoids any ABI/version mismatch a prebuilt lib would risk.
# rayon (threads) is dropped via --no-default-features; the indicators are pure
# computation, so the serial path is functionally identical.
if [ "$(uname -s)" = "Emscripten" ]; then
version=$(sed -n 's/^Version:[[:space:]]*//p' DESCRIPTION)
echo "wickra: building C ABI from source for wasm32-unknown-emscripten (v${version})"
build=$(mktemp -d)
url="https://github.com/wickra-lib/wickra/archive/refs/tags/v${version}.tar.gz"
"${R_HOME}/bin/Rscript" -e "download.file('${url}', '${build}/src.tar.gz', mode = 'wb', quiet = TRUE)" \
|| { echo "wickra: failed to download source ${url}"; exit 1; }
"${R_HOME}/bin/Rscript" -e "untar('${build}/src.tar.gz', exdir = '${build}')"
root="${build}/wickra-${version}"
rustup target add wasm32-unknown-emscripten 2>/dev/null || true
( cd "${root}" && cargo build -p wickra-c --release \
--target wasm32-unknown-emscripten --no-default-features ) \
|| { echo "wickra: cargo wasm build failed"; exit 1; }
cp "${root}/bindings/c/include/wickra.h" src/wickra.h
cp "${root}/target/wasm32-unknown-emscripten/release/libwickra.a" src/libwickra.a
rm -rf "${build}"
# The static archive is linked into the package object; no shared lib to
# bundle and no rpath needed.
sed "s|@WICKRA_RPATH@||" src/Makevars.in > src/Makevars
exit 0
fi
if [ -n "${WICKRA_INCLUDE_DIR}" ] && [ -n "${WICKRA_LIB_DIR}" ]; then
echo "wickra: using C ABI from WICKRA_INCLUDE_DIR / WICKRA_LIB_DIR (dev override)"
inc="${WICKRA_INCLUDE_DIR}"