penpot/render-wasm/_build_env
Miguel de Benito Delgado 6f34794f54
🐛 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
2026-10-13 12:54:48 +02:00

237 lines
7.9 KiB
Bash

#!/usr/bin/env bash
export RUST_VERSION=1.99.0
export VERSION_TAG="${VERSION_TAG:-${VERSION:-develop}}";
export RENDER_TARGET="${RENDER_TARGET:-${1:-frontend}}";
case "$RENDER_TARGET" in
frontend|export) ;;
*)
echo "ERROR: unknown render target '$RENDER_TARGET' (expected 'frontend' or 'export')" >&2;
exit 1;
;;
esac
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}";
# 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"}
# Emscripten only: rustc wasm SIMD (native `./test` unchanged). Wasm
# setjmp/longjmp comes from EMCC_CFLAGS (-sSUPPORT_LONGJMP=wasm), which emcc
# appends on compile and link.
if [ "$CARGO_BUILD_TARGET" = "wasm32-unknown-emscripten" ]; then
export RUSTFLAGS="${RUSTFLAGS:-} -C target-feature=+simd128"
fi
# 256 MB of initial heap to perform less
# initial calls to memory grow.
export EM_INITIAL_HEAP=$((256 * 1024 * 1024))
# 1.0 doubles the heap on every growth.
export EM_MEMORY_GROWTH_GEOMETRIC_STEP="0.8"
# Malloc implementation to use.
# - dlmalloc: a powerful general-purpose malloc.
# - emmalloc: a simple and compact malloc designed for emscripten.
# - emmalloc-debug: use emmalloc and add extra assertion checks.
# - emmalloc-memvalidate: use emmalloc with assertions+heap consistency checking.
# - emmalloc-verbose: use emmalloc with assertions + verbose logging.
# - emmalloc-memvalidate-verbose: use emmalloc with assertions + heap consistency checking + verbose logging.
# Default: dlmalloc
export EM_MALLOC="dlmalloc"
# -msimd128: wasm SIMD in the linked module (Skia -simd prebuild must match).
# -fwasm-exceptions / SUPPORT_LONGJMP=wasm: native Wasm EH and setjmp/longjmp
# (Skia -ljmp-wasm prebuild must match).
export EMCC_CFLAGS="--no-entry \
--js-library src/js/wapi.js \
-msimd128 \
-fwasm-exceptions \
-sSUPPORT_LONGJMP=wasm \
-sMALLOC=$EM_MALLOC \
-sINVOKE_RUN=0 \
-sALLOW_TABLE_GROWTH=0 \
-sALLOW_MEMORY_GROWTH=1 \
-sINITIAL_HEAP=$EM_INITIAL_HEAP \
-sMEMORY_GROWTH_GEOMETRIC_STEP=$EM_MEMORY_GROWTH_GEOMETRIC_STEP \
-sERROR_ON_UNDEFINED_SYMBOLS=0 \
-sMAX_WEBGL_VERSION=2 \
-sEXPORT_NAME=createRustSkiaModule \
-sEXPORTED_RUNTIME_METHODS=GL,UTF8ToString,stringToUTF8,HEAPU8,HEAP32,HEAPU32,HEAPF32 \
-sENVIRONMENT=web,worker,node \
-sMODULARIZE=1 \
-sFILESYSTEM=0 \
-sEXPORT_ES6=1";
export EM_CACHE="/tmp/emsdk_cache";
CARGO_ARGS=("${@:2}");
_CARGO_PROFILE_SET=0;
export CARGO_PROFILE_DIR="debug";
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";
;;
--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
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
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
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;
}
function setup_rust {
# Setup default rust version
rustup default $RUST_VERSION
rustup target add wasm32-unknown-emscripten
rustup component add clippy rustfmt --toolchain $RUST_VERSION
}
function setup {
setup_rust
pnpm install;
}
function build {
(
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 {
local DEST="$1";
local SRC;
SRC=$(jq -r '.artifact_dir' -- "$RENDER_WASM_BUILD_INFO")
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";
fi
# 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" \
--log-level=error \
"--outfile=$DEST/worker/render.js" \
--platform=neutral \
--format=iife \
--global-name=WasmModule;
fi
}
function copy_shared_artifact {
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
# consumer reads it.
function copy_target_artifacts {
case "$RENDER_TARGET" in
frontend)
copy_artifacts "$FRONTEND_DEST";
copy_shared_artifact "$FRONTEND_SHARED_DEST";
;;
export)
copy_artifacts "$EXPORT_DEST";
copy_shared_artifact "$EXPORT_SHARED_DEST";
;;
esac
if [ -n "${RENDER_WASM_SHARED_ARTIFACT:-}" ]; then
copy_shared_artifact "$RENDER_WASM_SHARED_ARTIFACT";
fi
}