🐛 Fix render-wasm build arguments and artifact selection (#12254)

Forward Cargo arguments as an array and honor explicit profiles.
Build and copy each renderer target once under the repository lock,
with watch rebuilds using the same wrapper.

Select this checkout's compiler artifact and matching generated enum
table from Cargo JSON. Record actual features, profile and output paths,
and allow callers to override publication destinations.

Run the existing Cargo parser tests before the renderer's native tests
and stop on parser failures.

AI-assisted-by: gpt-6.1-sol
This commit is contained in:
Miguel de Benito Delgado 2026-10-13 12:54:48 +02:00 committed by GitHub
parent 2965a4c043
commit 6f34794f54
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 589 additions and 57 deletions

View File

@ -2,7 +2,7 @@
export RUST_VERSION=1.99.0
export VERSION_TAG=${VERSION:-develop};
export VERSION_TAG="${VERSION_TAG:-${VERSION:-develop}}";
export RENDER_TARGET="${RENDER_TARGET:-${1:-frontend}}";
case "$RENDER_TARGET" in
@ -13,15 +13,15 @@ case "$RENDER_TARGET" in
;;
esac
if [ "$NODE_ENV" = "production" ]; then
export BUILD_MODE="release";
if [ "${NODE_ENV:-}" = "production" ]; then
export BUILD_MODE="${BUILD_MODE:-release}";
else
export BUILD_MODE=${BUILD_MODE:-debug};
fi
export BUILD_NAME="${BUILD_NAME:-render-wasm}"
export CARGO_BUILD_TARGET=${CARGO_BUILD_TARGET:-"wasm32-unknown-emscripten"};
export CARGO_TARGET_DIR=${CARGO_TARGET_DIR:-"target/$RENDER_TARGET"};
export CARGO_BUILD_TARGET="${CARGO_BUILD_TARGET:-wasm32-unknown-emscripten}";
export CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-target/$RENDER_TARGET}";
# Skia prebuild with wasm SIMD and Wasm EH/SjLj (-ljmp-wasm). Must match
# EMCC_CFLAGS below. Override with SKIA_BINARIES_URL when needed.
export SKIA_BINARIES_URL=${SKIA_BINARIES_URL:-"https://github.com/penpot/skia-binaries/releases/download/0.153.3/skia-binaries-b7f043e0b1e2a850e702-wasm32-unknown-emscripten-gl-svg-textlayout-binary-cache-webp-pdf-simd-ljmp-wasm.tar.gz"}
@ -75,43 +75,82 @@ export EMCC_CFLAGS="--no-entry \
export EM_CACHE="/tmp/emsdk_cache";
export CARGO_PARAMS="${@:2}";
CARGO_ARGS=("${@:2}");
_CARGO_PROFILE_SET=0;
export CARGO_PROFILE_DIR="debug";
if [ "$BUILD_MODE" = "profiling" ]; then
# Optimized like release, but keeps the wasm name section so the Firefox
# profiler can resolve Rust frames (release strips them). `--profiling-funcs`
# preserves function names through wasm-opt without pulling in DWARF.
export CARGO_PARAMS="--profile profiling $CARGO_PARAMS";
export CARGO_PROFILE_DIR="profiling";
export EMCC_CFLAGS="-O3 -sASSERTIONS=0 --profiling-funcs $EMCC_CFLAGS";
elif [ "$BUILD_MODE" = "release" ]; then
case "$RENDER_TARGET" in
frontend)
export CARGO_PARAMS="--release $CARGO_PARAMS";
for (( _cargo_index=0; _cargo_index < ${#CARGO_ARGS[@]}; _cargo_index++ )); do
case "${CARGO_ARGS[_cargo_index]}" in
--release|-r)
_CARGO_PROFILE_SET=1;
export CARGO_PROFILE_DIR="release";
export EMCC_CFLAGS="-O3 -sASSERTIONS=0 $EMCC_CFLAGS";
;;
export)
export CARGO_PARAMS="--profile size $CARGO_PARAMS";
export CARGO_PROFILE_DIR="size";
export EMCC_CFLAGS="-Oz -sASSERTIONS=0 $EMCC_CFLAGS";
--profile)
_CARGO_PROFILE_SET=1;
export CARGO_PROFILE_DIR="${CARGO_ARGS[_cargo_index + 1]}";
;;
--profile=*)
_CARGO_PROFILE_SET=1;
export CARGO_PROFILE_DIR="${CARGO_ARGS[_cargo_index]#--profile=}";
;;
--target-dir)
export CARGO_TARGET_DIR="${CARGO_ARGS[_cargo_index + 1]:-$CARGO_TARGET_DIR}";
;;
--target-dir=*)
export CARGO_TARGET_DIR="${CARGO_ARGS[_cargo_index]#--target-dir=}";
;;
esac
else
# TODO: Extra parameters that could be good to look into:
# -gseparate-dwarf
# -gsplit-dwarf
# -gsource-map
export EMCC_CFLAGS="-g -sASSERTIONS=1 -sVERBOSE=1 $EMCC_CFLAGS"
done
if [ "$_CARGO_PROFILE_SET" = "0" ]; then
if [ "$BUILD_MODE" = "profiling" ]; then
CARGO_ARGS=(--profile profiling "${CARGO_ARGS[@]}");
export CARGO_PROFILE_DIR="profiling";
elif [ "$BUILD_MODE" = "release" ]; then
case "$RENDER_TARGET" in
frontend)
CARGO_ARGS=(--release "${CARGO_ARGS[@]}");
export CARGO_PROFILE_DIR="release";
;;
export)
CARGO_ARGS=(--profile size "${CARGO_ARGS[@]}");
export CARGO_PROFILE_DIR="size";
;;
esac
fi
fi
export FRONTEND_DEST="../frontend/resources/public/js";
export EXPORT_DEST="../exporter/resources/wasm";
case "$CARGO_PROFILE_DIR" in
profiling)
# Keep the wasm name section so profilers can resolve Rust frames.
export EMCC_CFLAGS="-O3 -sASSERTIONS=0 --profiling-funcs $EMCC_CFLAGS";
;;
release)
export EMCC_CFLAGS="-O3 -sASSERTIONS=0 $EMCC_CFLAGS";
;;
size)
export EMCC_CFLAGS="-Oz -sASSERTIONS=0 $EMCC_CFLAGS";
;;
*)
# TODO: Extra parameters that could be good to look into:
# -gseparate-dwarf
# -gsplit-dwarf
# -gsource-map
export EMCC_CFLAGS="-g -sASSERTIONS=1 -sVERBOSE=1 $EMCC_CFLAGS";
;;
esac
export FRONTEND_SHARED_DEST="../frontend/src/app/render_wasm/api/shared.js";
export EXPORT_SHARED_DEST="../exporter/src/app/wasm/shared.js";
if [ "$CARGO_PROFILE_DIR" = "dev" ]; then
export CARGO_PROFILE_DIR="debug";
fi
export RENDER_WASM_BUILD_INFO="${RENDER_WASM_BUILD_INFO:-$CARGO_TARGET_DIR/build-info.json}";
export FRONTEND_DEST="${FRONTEND_DEST:-../frontend/resources/public/js}";
export EXPORT_DEST="${EXPORT_DEST:-../exporter/resources/wasm}";
export FRONTEND_SHARED_DEST="${FRONTEND_SHARED_DEST:-../frontend/src/app/render_wasm/api/shared.js}";
export EXPORT_SHARED_DEST="${EXPORT_SHARED_DEST:-../exporter/src/app/wasm/shared.js}";
function clean {
cargo clean;
@ -130,29 +169,39 @@ function setup {
}
function build {
cargo build $CARGO_PARAMS;
(
set -o pipefail;
cargo build --message-format=json-render-diagnostics "${CARGO_ARGS[@]}" \
| node ./scripts/cargo-build.cjs "$RENDER_WASM_BUILD_INFO" "$PWD/Cargo.toml";
)
}
function copy_artifacts {
DEST=$1;
SRC="$CARGO_TARGET_DIR/$CARGO_BUILD_TARGET/$CARGO_PROFILE_DIR";
local DEST="$1";
local SRC;
SRC=$(jq -r '.artifact_dir' -- "$RENDER_WASM_BUILD_INFO")
mkdir -p -- "$DEST";
mkdir -p $DEST;
cp $SRC/render_wasm.js $DEST/$BUILD_NAME.js;
cp $SRC/render_wasm.wasm $DEST/$BUILD_NAME.wasm;
if [ -f $SRC/render_wasm.wasm.map ]; then
cp $SRC/render_wasm.wasm.map $DEST/$BUILD_NAME.wasm.map;
cp -- "$SRC/render_wasm.js" "$DEST/$BUILD_NAME.js";
cp -- "$SRC/render_wasm.wasm" "$DEST/$BUILD_NAME.wasm";
if [ -f "$SRC/render_wasm.wasm.map" ]; then
cp -- "$SRC/render_wasm.wasm.map" "$DEST/$BUILD_NAME.wasm.map";
fi
sed -i "s/render_wasm.wasm/$BUILD_NAME.wasm?version=$VERSION_TAG/g" $DEST/$BUILD_NAME.js;
# Rewrite the generated JavaScript so it loads the renamed Wasm file with
# a version query, such as render-wasm.wasm?version=develop.
local WASM_URL="$BUILD_NAME.wasm?version=$VERSION_TAG";
WASM_URL=${WASM_URL//\\/\\\\};
WASM_URL=${WASM_URL//&/\\&};
WASM_URL=${WASM_URL//|/\\|};
sed -i "s|render_wasm\\.wasm|$WASM_URL|g" "$DEST/$BUILD_NAME.js";
# The worker bundle is a browser concern; the exporter imports the ESM
# module directly under Node.
if [ "$RENDER_TARGET" = "frontend" ]; then
pnpm exec esbuild $SRC/render_wasm.js \
pnpm exec esbuild "$SRC/render_wasm.js" \
--log-level=error \
--outfile=$DEST/worker/render.js \
"--outfile=$DEST/worker/render.js" \
--platform=neutral \
--format=iife \
--global-name=WasmModule;
@ -160,10 +209,11 @@ function copy_artifacts {
}
function copy_shared_artifact {
DEST=$1;
SHARED_FILE=$(find $CARGO_TARGET_DIR/$CARGO_BUILD_TARGET -name render_wasm_shared.js | head -n 1);
cp $SHARED_FILE $DEST;
local DEST="$1";
local SHARED_FILE;
SHARED_FILE=$(jq -r '.shared_file' -- "$RENDER_WASM_BUILD_INFO")
mkdir -p -- "$(dirname -- "$DEST")";
cp -- "$SHARED_FILE" "$DEST";
}
# Copies whatever the current RENDER_TARGET produced to where that target's
@ -179,4 +229,8 @@ function copy_target_artifacts {
copy_shared_artifact "$EXPORT_SHARED_DEST";
;;
esac
if [ -n "${RENDER_WASM_SHARED_ARTIFACT:-}" ]; then
copy_shared_artifact "$RENDER_WASM_SHARED_ARTIFACT";
fi
}

View File

@ -23,14 +23,14 @@ case "$_RENDER_TARGET" in
;;
esac
set -e
set -e -o pipefail
EMSDK_QUIET=1 . /opt/emsdk/emsdk_env.sh
pushd "$_SCRIPT_DIR" > /dev/null
export RENDER_TARGET="$_RENDER_TARGET"
. ./_build_env "$_RENDER_TARGET"
. ./_build_env "$_RENDER_TARGET" "${@:2}"
. ./_build_lock
acquire_build_lock

View File

@ -0,0 +1,120 @@
// Usage: cargo build --message-format=json-render-diagnostics ... |
// node scripts/cargo-build.cjs <build-info.json> <root Cargo.toml>
const fs = require("node:fs");
const path = require("node:path");
const readline = require("node:readline");
async function main() {
const [infoFile, manifestFile] = process.argv.slice(2);
if (!infoFile || !manifestFile) {
throw new Error(
"Usage: cargo-build.cjs <build-info.json> <root Cargo.toml>",
);
}
const manifest = path.resolve(manifestFile);
// A failed invocation must not leave facts from an earlier build.
fs.rmSync(infoFile, { force: true });
const artifacts = [];
const buildScripts = new Map();
let succeeded = false;
const lines = readline.createInterface({
input: process.stdin,
crlfDelay: Infinity,
});
for await (const line of lines) {
if (!line.trim()) continue;
const message = JSON.parse(line);
if (!message || typeof message !== "object" || Array.isArray(message)) {
throw new Error("Invalid Cargo JSON record");
}
switch (message.reason) {
case "compiler-message":
if (typeof message.message?.rendered === "string") {
process.stderr.write(message.message.rendered);
}
break;
case "compiler-artifact":
if (
message.target?.name === "render_wasm" &&
typeof message.manifest_path === "string" &&
path.resolve(message.manifest_path) === manifest
) {
artifacts.push(message);
}
break;
case "build-script-executed": {
const outDirs = buildScripts.get(message.package_id) || new Set();
outDirs.add(message.out_dir);
buildScripts.set(message.package_id, outDirs);
break;
}
case "build-finished":
succeeded = message.success === true;
break;
}
}
if (!succeeded) throw new Error("Cargo build did not finish successfully");
if (artifacts.length !== 1) {
throw new Error("Expected one root render_wasm compiler-artifact");
}
const artifact = artifacts[0];
if (
typeof artifact.package_id !== "string" ||
!artifact.package_id ||
!Array.isArray(artifact.features) ||
!artifact.features.every((feature) => typeof feature === "string")
) {
throw new Error("Missing root renderer package_id or actual features");
}
if (
!artifact.profile ||
typeof artifact.profile !== "object" ||
Array.isArray(artifact.profile) ||
typeof artifact.profile.opt_level !== "string" ||
typeof artifact.profile.debug_assertions !== "boolean"
) {
throw new Error("Missing or malformed root renderer Cargo profile");
}
const outDirs = [...(buildScripts.get(artifact.package_id) || [])];
if (outDirs.length !== 1 || typeof outDirs[0] !== "string" || !outDirs[0]) {
throw new Error(
"Expected one matching root renderer build-script-executed out_dir",
);
}
const jsFiles = (
Array.isArray(artifact.filenames) ? artifact.filenames : []
).filter(
(file) =>
typeof file === "string" && path.basename(file) === "render_wasm.js",
);
if (jsFiles.length !== 1)
throw new Error("Missing root render_wasm.js artifact");
const outDir = path.resolve(outDirs[0]);
const sharedFile = path.join(outDir, "render_wasm_shared.js");
fs.accessSync(sharedFile, fs.constants.R_OK);
const info = {
package_id: artifact.package_id,
features: artifact.features,
profile: artifact.profile,
out_dir: outDir,
artifact_dir: path.dirname(path.resolve(jsFiles[0])),
shared_file: sharedFile,
};
fs.mkdirSync(path.dirname(infoFile), { recursive: true });
const temporary = `${infoFile}.tmp`;
try {
fs.writeFileSync(temporary, JSON.stringify(info, null, 2) + "\n");
fs.renameSync(temporary, infoFile);
} finally {
fs.rmSync(temporary, { force: true });
}
}
main().catch((error) => {
process.stderr.write(`ERROR: ${error.message}\n`);
process.exitCode = 1;
process.stdin.destroy();
});

View File

@ -0,0 +1,349 @@
const assert = require("node:assert/strict");
const { spawn, spawnSync } = require("node:child_process");
const { once } = require("node:events");
const fs = require("node:fs");
const os = require("node:os");
const path = require("node:path");
const { test } = require("node:test");
const helper = path.join(__dirname, "cargo-build.cjs");
const finished = { reason: "build-finished", success: true };
function fixture(t) {
const root = fs.mkdtempSync(
path.join(os.tmpdir(), "render-wasm-cargo-build-"),
);
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
const manifest = path.join(root, "Cargo.toml");
const artifactDir = path.join(
root,
"target with spaces",
"wasm32-unknown-emscripten",
"release",
);
const outDir = path.join(artifactDir, "build", "render-active", "out");
const staleOutDir = path.join(
artifactDir,
"build",
"000-render-stale",
"out",
);
for (const dir of [outDir, staleOutDir]) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(manifest, '[package]\nname = "render"\n');
fs.writeFileSync(
path.join(artifactDir, "render_wasm.js"),
"export default {};\n",
);
fs.writeFileSync(
path.join(outDir, "render_wasm_shared.js"),
"active enum table\n",
);
fs.writeFileSync(
path.join(staleOutDir, "render_wasm_shared.js"),
"stale enum table\n",
);
const artifact = {
reason: "compiler-artifact",
package_id: `path+file://${root}#render@0.1.0`,
manifest_path: manifest,
target: { name: "render_wasm", kind: ["bin"] },
features: ["default", "profile", "profile-macros", "profile-raf"],
profile: {
opt_level: "3",
debuginfo: 0,
debug_assertions: false,
overflow_checks: false,
test: false,
},
filenames: [path.join(artifactDir, "render_wasm.js")],
fresh: false,
};
const buildScript = {
reason: "build-script-executed",
package_id: artifact.package_id,
out_dir: outDir,
};
const otherScript = {
...buildScript,
package_id:
"registry+https://github.com/rust-lang/crates.io-index#render@0.1.0",
out_dir: staleOutDir,
};
const otherArtifact = {
...artifact,
package_id: otherScript.package_id,
manifest_path: path.join(root, "dependency", "Cargo.toml"),
features: ["stats"],
profile: { ...artifact.profile, opt_level: "0", debug_assertions: true },
};
return {
root,
manifest,
artifactDir,
outDir,
staleOutDir,
artifact,
buildScript,
otherScript,
otherArtifact,
info: path.join(root, "build-info.json"),
};
}
function cargoOutput(records) {
return records.map((record) => JSON.stringify(record)).join("\n") + "\n";
}
function run(f, records) {
return spawnSync(process.execPath, [helper, f.info, f.manifest], {
cwd: f.root,
input: cargoOutput(records),
encoding: "utf8",
});
}
for (const fresh of [false, true]) {
test(`selects the root package's active enum table and actual features (fresh=${fresh})`, (t) => {
const f = fixture(t);
const result = run(f, [
f.otherScript,
f.otherArtifact,
{ ...f.artifact, fresh },
f.buildScript,
{
...f.otherScript,
out_dir: path.join(f.root, "another dependency out"),
},
finished,
]);
assert.equal(result.status, 0, result.stderr);
assert.equal(result.stdout, "");
const info = JSON.parse(fs.readFileSync(f.info, "utf8"));
assert.deepEqual(info, {
package_id: f.artifact.package_id,
features: f.artifact.features,
profile: f.artifact.profile,
out_dir: f.outDir,
artifact_dir: f.artifactDir,
shared_file: path.join(f.outDir, "render_wasm_shared.js"),
});
assert.equal(
fs.readFileSync(info.shared_file, "utf8"),
"active enum table\n",
);
});
}
test("records an empty actual feature set", (t) => {
const f = fixture(t);
const result = run(f, [
f.buildScript,
{ ...f.artifact, features: [] },
finished,
]);
assert.equal(result.status, 0, result.stderr);
assert.deepEqual(JSON.parse(fs.readFileSync(f.info, "utf8")).features, []);
});
for (const profile of [
{
opt_level: "0",
debuginfo: 2,
debug_assertions: true,
overflow_checks: true,
test: false,
},
{
opt_level: "z",
debuginfo: 0,
debug_assertions: false,
overflow_checks: false,
test: false,
},
]) {
test(`records the actual Cargo profile without changing fields (opt_level=${profile.opt_level})`, (t) => {
const f = fixture(t);
const result = run(f, [
f.buildScript,
{ ...f.artifact, profile },
finished,
]);
assert.equal(result.status, 0, result.stderr);
assert.deepEqual(
JSON.parse(fs.readFileSync(f.info, "utf8")).profile,
profile,
);
});
}
test(
"streams rendered compiler messages to stderr before Cargo finishes",
{ timeout: 10000 },
async (t) => {
const f = fixture(t);
const child = spawn(process.execPath, [helper, f.info, f.manifest], {
cwd: f.root,
});
t.after(() => {
if (child.exitCode === null) child.kill();
});
child.stdout.resume();
const closed = once(child, "close");
const diagnostic = "warning: renderer diagnostic\n";
let stderr = "";
const rendered = new Promise((resolve, reject) => {
child.stderr.on("data", (chunk) => {
stderr += chunk;
if (stderr.includes(diagnostic)) resolve();
});
child.on("error", reject);
child.on("close", () => {
if (!stderr.includes(diagnostic))
reject(new Error(stderr || "Missing diagnostic"));
});
});
child.stdin.write(
cargoOutput([
{ reason: "compiler-message", message: { rendered: diagnostic } },
]),
);
await rendered;
assert.equal(child.exitCode, null);
assert.equal(fs.existsSync(f.info), false);
child.stdin.end(cargoOutput([f.buildScript, f.artifact, finished]));
const [code] = await closed;
assert.equal(code, 0, stderr);
assert.equal(stderr, diagnostic);
},
);
const rejected = [
[
"failed Cargo build",
(f) => [f.buildScript, f.artifact, { ...finished, success: false }],
],
["missing build-finished record", (f) => [f.buildScript, f.artifact]],
["missing root artifact", (f) => [f.otherScript, f.otherArtifact, finished]],
[
"missing matching build script",
(f) => [f.otherScript, f.artifact, finished],
],
[
"missing out_dir",
(f) => [{ ...f.buildScript, out_dir: undefined }, f.artifact, finished],
],
[
"missing actual features",
(f) => [f.buildScript, { ...f.artifact, features: undefined }, finished],
],
[
"invalid actual features",
(f) => [f.buildScript, { ...f.artifact, features: [42] }, finished],
],
[
"missing profile",
(f) => [f.buildScript, { ...f.artifact, profile: undefined }, finished],
],
[
"null profile",
(f) => [f.buildScript, { ...f.artifact, profile: null }, finished],
],
[
"non-object profile",
(f) => [f.buildScript, { ...f.artifact, profile: "release" }, finished],
],
[
"array profile",
(f) => [f.buildScript, { ...f.artifact, profile: [] }, finished],
],
[
"missing profile opt_level",
(f) => [
f.buildScript,
{ ...f.artifact, profile: { debug_assertions: false } },
finished,
],
],
[
"invalid profile opt_level",
(f) => [
f.buildScript,
{ ...f.artifact, profile: { opt_level: 3, debug_assertions: false } },
finished,
],
],
[
"missing profile debug_assertions",
(f) => [
f.buildScript,
{ ...f.artifact, profile: { opt_level: "3" } },
finished,
],
],
[
"invalid profile debug_assertions",
(f) => [
f.buildScript,
{ ...f.artifact, profile: { opt_level: "3", debug_assertions: "false" } },
finished,
],
],
[
"missing JS artifact",
(f) => [f.buildScript, { ...f.artifact, filenames: [] }, finished],
],
[
"multiple root artifacts",
(f) => [
f.buildScript,
f.artifact,
{ ...f.artifact, features: [] },
finished,
],
],
[
"ambiguous root out_dir",
(f) => [
f.buildScript,
{ ...f.buildScript, out_dir: f.staleOutDir },
f.artifact,
finished,
],
],
];
for (const [name, records] of rejected) {
test(`rejects ${name} and removes stale build facts`, (t) => {
const f = fixture(t);
fs.writeFileSync(f.info, '{"features":["stale"]}\n');
const result = run(f, records(f));
assert.equal(result.status, 1, result.stderr);
assert.match(result.stderr, /ERROR:/);
assert.equal(fs.existsSync(f.info), false);
});
}
test("rejects a missing active enum table even when a stale table exists", (t) => {
const f = fixture(t);
fs.unlinkSync(path.join(f.outDir, "render_wasm_shared.js"));
const result = run(f, [f.otherScript, f.buildScript, f.artifact, finished]);
assert.equal(result.status, 1, result.stderr);
assert.match(result.stderr, /render_wasm_shared\.js/);
assert.equal(fs.existsSync(f.info), false);
});
test("rejects malformed Cargo JSON and removes stale build facts", (t) => {
const f = fixture(t);
fs.writeFileSync(f.info, '{"features":["stale"]}\n');
const result = spawnSync(process.execPath, [helper, f.info, f.manifest], {
cwd: f.root,
input: "not Cargo JSON\n",
encoding: "utf8",
});
assert.equal(result.status, 1, result.stderr);
assert.match(result.stderr, /ERROR:/);
assert.equal(fs.existsSync(f.info), false);
});

View File

@ -1,5 +1,5 @@
#!/usr/bin/env bash
set -x
set -ex
case "$(uname -m)" in
aarch64|arm64) _ARCH="aarch64" ;;
@ -14,6 +14,8 @@ pushd "$_SCRIPT_DIR" > /dev/null
. ./_build_env frontend
node --test scripts/cargo-build.test.cjs || exit $?
setup
cargo test --bin render_wasm -- --show-output

View File

@ -1,6 +1,6 @@
#!/usr/bin/env bash
# Usage: ./watch [frontend|export]
# Usage: ./watch [frontend|export] [extra cargo params...]
set -e
@ -9,19 +9,26 @@ pushd "$_SCRIPT_DIR" > /dev/null
_RENDER_TARGET=${1:-${RENDER_TARGET:-frontend}}
export RENDER_TARGET="$_RENDER_TARGET"
. ./_build_env "$_RENDER_TARGET" "${@:2}"
_BUILD_COMMAND=("$_SCRIPT_DIR/build" "$RENDER_TARGET" "${@:2}")
# The initial build uses the protected build entry point. Its lock is released
# before cargo watch starts waiting for source changes.
"$_SCRIPT_DIR/build" "$RENDER_TARGET" $CARGO_PARAMS
"${_BUILD_COMMAND[@]}"
# cargo watch runs shell commands; single-quote each original argument for sh.
_WATCH_BUILD_COMMAND=""
for _arg in "${_BUILD_COMMAND[@]}"; do
_arg=${_arg//\'/\'\\\'\'}
_WATCH_BUILD_COMMAND+=" '$_arg'"
done
set -x
cargo watch \
--why \
--postpone \
-i "_tmp*" \
-x "build $CARGO_PARAMS" \
-s "\"$_SCRIPT_DIR/build\" \"$RENDER_TARGET\" $CARGO_PARAMS" \
-s "$_WATCH_BUILD_COMMAND" \
-s "echo 'DONE\n'";
popd > /dev/null