#!/usr/bin/env bash set -euo pipefail cd "$(dirname "$0")" # LinuxCNC source basis: this guard ties native/source-link/WASM build entry # points to LinuxCNC Submakefile source ownership, source manifests, persistent # build signatures, and source-backed WASM artifact ordering. It validates # build policy only; it does not add CNC behavior or expand smoke parsing. linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} linuxcnc_root=$(cd "$linuxcnc_root" && pwd) grep -F 'LIBRS274SRCS := $(addprefix emc/rs274ngc/,' \ "$linuxcnc_root/src/emc/rs274ngc/Submakefile" >/dev/null grep -F 'rs274ngc_pre.cc \' "$linuxcnc_root/src/emc/rs274ngc/Submakefile" >/dev/null grep -F '../lib/libtooldata.so.0' "$linuxcnc_root/src/emc/rs274ngc/Submakefile" >/dev/null grep -F 'LIBTOOLDATA_SRCS += emc/tooldata/tooldata_common.cc' \ "$linuxcnc_root/src/emc/tooldata/Submakefile" >/dev/null grep -F 'LIBTOOLDATA_SRCS += emc/tooldata/tooldata_mmap.cc' \ "$linuxcnc_root/src/emc/tooldata/Submakefile" >/dev/null grep -F 'LIBTOOLDATA_SRCS += emc/tooldata/tooldata_db.cc' \ "$linuxcnc_root/src/emc/tooldata/Submakefile" >/dev/null grep -F 'LIBLCNCINISRCS := emc/ini/inifile.cc' \ "$linuxcnc_root/src/emc/ini/Submakefile" >/dev/null grep -F 'LIBPPSRCS := $(addprefix emc/pythonplugin/,' \ "$linuxcnc_root/src/emc/pythonplugin/Submakefile" >/dev/null grep -F 'GENSERKINSSRCS := emc/kinematics/ugenserkins.c' \ "$linuxcnc_root/src/emc/kinematics/Submakefile" >/dev/null grep -F 'extern int kinematicsInverse(const struct EmcPose * world,' \ "$linuxcnc_root/src/emc/kinematics/kinematics.h" >/dev/null grep -F 'LinuxCNC build source map' docs/linuxcnc-build-source-map.md >/dev/null grep -F 'must not add CNC' docs/linuxcnc-build-source-map.md >/dev/null grep -F 'must not expand the temporary smoke parser' docs/linuxcnc-build-source-map.md >/dev/null grep -F '`src/emc/rs274ngc/Submakefile` defines `LIBRS274SRCS`' \ docs/linuxcnc-build-source-map.md >/dev/null grep -F '`src/emc/tooldata/Submakefile` defines the LinuxCNC tooldata' \ docs/linuxcnc-build-source-map.md >/dev/null grep -F 'Default WASM manifest partition must remain 26 wasm-safe `core` sources and' \ docs/linuxcnc-build-source-map.md >/dev/null grep -F '`build-wasm.sh` must configure with `CNC_SIM_ENABLE_SMOKE_BACKEND=OFF`' \ docs/linuxcnc-build-source-map.md >/dev/null grep -F 'LinuxCNC source basis: the wasm build preflights and compiles the' build-wasm.sh >/dev/null grep -F 'manifest_core_count=$(count_nonempty_lines "$manifest_core_output")' build-wasm.sh >/dev/null grep -F 'manifest_blocked_count=$(count_nonempty_lines "$manifest_blocked_output")' build-wasm.sh >/dev/null grep -F 'manifest_core_count" -ne 26 || "$manifest_blocked_count" -ne 9' build-wasm.sh >/dev/null grep -F 'run_parallel_wasm_probe "LinuxCNC wasm tooldata shim link probe" ./test-linuxcnc-wasm-tooldata-link.sh' build-wasm.sh >/dev/null grep -F 'run_parallel_wasm_probe "LinuxCNC wasm rs274ngc_pre object probe" ./test-linuxcnc-wasm-rs274ngc-pre-object.sh' build-wasm.sh >/dev/null grep -F 'wait_parallel_wasm_probes' build-wasm.sh >/dev/null grep -F 'for required_tool in cmake emcmake emcc node; do' build-wasm.sh >/dev/null grep -F 'CONFIGURE_RULE_VERSION=2' build-wasm.sh >/dev/null grep -F -- '-DCNC_SIM_ENABLE_LINUXCNC_WASM_SAFE_PROBE=ON' build-wasm.sh >/dev/null grep -F -- '-DCNC_SIM_ENABLE_SMOKE_BACKEND=OFF' build-wasm.sh >/dev/null grep -F 'cmake --build build/wasm --target cnc_sim_wasm_runtime_probe cnc_sim_wasm --parallel "$build_jobs"' build-wasm.sh >/dev/null grep -F 'cmp -s build/wasm/cnc_sim.js web/public/cnc_sim.js' build-wasm.sh >/dev/null grep -F 'cmp -s build/wasm/cnc_sim.wasm web/public/cnc_sim.wasm' build-wasm.sh >/dev/null grep -F './test-web-wasm-node-smoke.sh' build-wasm.sh >/dev/null grep -F './test-web-wasm-browser-smoke.sh' build-wasm.sh >/dev/null grep -F 'LinuxCNC source basis: this source-link test compiles the manifest-selected' \ test-linuxcnc-source-link.sh >/dev/null grep -F 'BUILD_RULE_VERSION=1' test-linuxcnc-source-link.sh >/dev/null grep -F 'make --output-sync=target -j"$build_jobs" -f "$source_link_makefile"' \ test-linuxcnc-source-link.sh >/dev/null grep -F 'LinuxCNC source basis: this syntax probe checks the native RS274 manifest' \ test-linuxcnc-source-syntax.sh >/dev/null grep -F 'LinuxCNC source basis: the wasm syntax probe compiles the' \ test-linuxcnc-wasm-source-syntax.sh >/dev/null grep -F "printf 'CONFIGURE_RULE_VERSION=1\n'" \ test-linuxcnc-wasm-cmake-safe-probe.sh >/dev/null grep -F 'cmake --build "$build_dir" --target linuxcnc_rs274_wasm_safe_probe cnc_sim_wasm_runtime_probe --parallel "$build_jobs"' \ test-linuxcnc-wasm-cmake-safe-probe.sh >/dev/null if grep -F 'printf '\''BUILD_JOBS=%s\n'\'' "$build_jobs"' build-wasm.sh >/dev/null; then echo "build-wasm configure signature must not depend on CNC_SIM_BUILD_JOBS" >&2 exit 1 fi for hot_signature_script in \ build-linuxcnc-wasm-link-probe.sh \ build-linuxcnc-wasm-probe-objects.sh \ build-linuxcnc-wasm-standalone-probe.sh \ build-linuxcnc-native-bridge-objects.sh \ test-linuxcnc-api-native.sh \ test-linuxcnc-bridge-native.sh \ test-linuxcnc-rs274-native.sh \ test-linuxcnc-source-link.sh \ test-linuxcnc-source-objects.sh \ test-linuxcnc-source-syntax.sh \ test-native.sh; do if grep -F "sha256sum $hot_signature_script" "$hot_signature_script" >/dev/null; then echo "$hot_signature_script hot object signature must use BUILD_RULE_VERSION instead of the whole script hash" >&2 exit 1 fi done python3 - <<'PY' from pathlib import Path import subprocess source_map = Path("docs/linuxcnc-build-source-map.md").read_text(encoding="utf-8") wasm_manifest = Path("linuxcnc-rs274-wasm-source-files.txt") core_count = len(subprocess.check_output( ["./list-linuxcnc-wasm-manifest-sources.sh", str(wasm_manifest), "core"], text=True, ).splitlines()) blocked_count = len(subprocess.check_output( ["./list-linuxcnc-wasm-manifest-sources.sh", str(wasm_manifest), "blocked"], text=True, ).splitlines()) if core_count != 26 or blocked_count != 9: raise SystemExit(f"unexpected default WASM manifest partition: core={core_count} blocked={blocked_count}") for entry in [ "test-native.sh", "test-linuxcnc-source-link.sh", "test-linuxcnc-source-syntax.sh", "test-linuxcnc-wasm-cmake-safe-probe.sh", "build-wasm.sh", ]: if f"| `{entry}` |" not in source_map: raise SystemExit(f"docs/linuxcnc-build-source-map.md missing build entry: {entry}") for phrase in [ "must not add CNC", "must not expand the temporary smoke parser", "CNC_SIM_BUILD_JOBS", "BUILD_RULE_VERSION", "CONFIGURE_RULE_VERSION", "CNC_SIM_ENABLE_SMOKE_BACKEND=OFF", "Node smoke before browser smoke", ]: if phrase not in source_map: raise SystemExit(f"docs/linuxcnc-build-source-map.md missing boundary phrase: {phrase}") build_wasm = Path("build-wasm.sh").read_text(encoding="utf-8").splitlines() positions = {} for index, line in enumerate(build_wasm, start=1): if 'run_parallel_wasm_probe "LinuxCNC wasm tooldata shim link probe"' in line: positions["parallel_start"] = index if "wait_parallel_wasm_probes" in line and "parallel_start" in positions and "wait" not in positions: positions["wait"] = index if "LinuxCNC wasm rs274ngc_pre link blocker scan" in line: positions["pre_link"] = index if "for required_tool in cmake emcmake emcc node; do" in line: positions["tools"] = index if "emcmake cmake -S core -B build/wasm" in line: positions["configure"] = index if "cmake --build build/wasm --target cnc_sim_wasm_runtime_probe cnc_sim_wasm" in line: positions["build"] = index if "cp build/wasm/cnc_sim.js web/public/cnc_sim.js" in line: positions["cp_js"] = index if "cp build/wasm/cnc_sim.wasm web/public/cnc_sim.wasm" in line: positions["cp_wasm"] = index if "cmp -s build/wasm/cnc_sim.js web/public/cnc_sim.js" in line: positions["cmp_js"] = index if "cmp -s build/wasm/cnc_sim.wasm web/public/cnc_sim.wasm" in line: positions["cmp_wasm"] = index if "./test-web-wasm-node-smoke.sh" in line: positions["node"] = index if "./test-web-wasm-browser-smoke.sh" in line: positions["browser"] = index required = [ "parallel_start", "wait", "pre_link", "tools", "configure", "build", "cp_js", "cp_wasm", "cmp_js", "cmp_wasm", "node", "browser", ] missing = [name for name in required if name not in positions] if missing: raise SystemExit(f"build-wasm.sh missing ordered build anchors: {', '.join(missing)}") if not (positions["parallel_start"] < positions["wait"] < positions["pre_link"]): raise SystemExit("build-wasm.sh must wait for parallel probes before rs274ngc_pre link checks") if not (positions["tools"] < positions["configure"] < positions["build"]): raise SystemExit("build-wasm.sh must check tools, then configure, then build") if not (positions["cp_js"] < positions["cmp_js"] < positions["node"] < positions["browser"]): raise SystemExit("build-wasm.js artifact JS copy/compare or smoke ordering changed") if not (positions["cp_wasm"] < positions["cmp_wasm"] < positions["node"] < positions["browser"]): raise SystemExit("build-wasm.wasm artifact copy/compare or smoke ordering changed") PY