name: nigig-build (CAD) # Phase 0.3 of the CAD remediation plan. # # Enforces: # 1. Dependency resolution is pinned and reproducible (--locked). # 2. Every git dependency is pinned to a rev, not a branch. # 3. `nigig-build` compiles (lib + tests). # 4. The test suite is green. # # History: the crate had 44 compile errors when this file was added, and # then 17 failing tests once it built. Both are now fixed, so the test step # asserts a plain pass instead of a "no worse than baseline" threshold. on: push: paths: - 'crates/apps/nigig-build/**' - 'crates/apps/doc/**' - 'crates/apps/spreadsheet/**' - 'crates/nigig-core/**' - 'crates/nigig-uikit/**' - 'crates/matrix_client/**' - 'tools/test-cad-coverage.sh' - 'tools/test-doc-workspace-coverage.sh' - 'Cargo.lock' - 'Cargo.toml' - 'rust-toolchain.toml' - '.forgejo/workflows/nigig-build.yml' pull_request: paths: - 'crates/apps/nigig-build/**' - 'crates/apps/doc/**' - 'crates/apps/spreadsheet/**' - 'crates/nigig-core/**' - 'crates/nigig-uikit/**' - 'crates/matrix_client/**' - 'tools/test-cad-coverage.sh' - 'tools/test-doc-workspace-coverage.sh' - 'Cargo.lock' - 'Cargo.toml' - 'rust-toolchain.toml' - '.forgejo/workflows/nigig-build.yml' jobs: supply-chain: runs-on: ubuntu-latest timeout-minutes: 20 steps: - uses: actions/checkout@v4 # A git dependency on a *branch* re-resolves on every build and is a # direct code-execution path into CI if the branch is force-pushed. # Phase 0.1 pinned all 33 manifests to an explicit rev; this keeps # them pinned. - name: Every git dependency must be pinned to a rev run: | set -euo pipefail # Strip commented-out lines before checking; only live # dependency declarations are in scope. if grep -rn 'git = ' --include=Cargo.toml . \ | grep -v ':[0-9]*:[[:space:]]*#' \ | grep -v 'rev = '; then echo echo "ERROR: the git dependencies above are not pinned to a rev." echo "Add rev = \"\" to each." exit 1 fi # ...and the rev must be the full 40-character SHA. An # abbreviated rev resolves only while no other object shares # its prefix; that is a property of the repository's current # object count, not a guarantee. Git's own abbreviation length # grows as a repo grows, so a short pin silently becomes # ambiguous -- and an attacker who can push to the fork can # try to manufacture a colliding prefix. This message has # claimed "full-40-char-sha" since it was written without # actually checking it. if grep -rn 'rev = ' --include=Cargo.toml . \ | grep -v ':[0-9]*:[[:space:]]*#' \ | grep -vE 'rev = "[0-9a-f]{40}"'; then echo echo "ERROR: the rev(s) above are abbreviated. Use the full" echo "40-character SHA so the pin cannot become ambiguous." exit 1 fi echo "OK: all git dependencies are pinned to a full SHA." # A lockfile that changes during CI means the committed one was stale. # An unused dependency is not cosmetic here. robius-sms was declared # by nigig-build (and transitively by nigig-core and nigig-uikit) # and never called once -- zero references in any of their sources. # It dragged in polkit -> gio -> glib, which is the ONLY reason this # crate needed RUSTSEC-2024-0370 and RUSTSEC-2024-0429 exemptions # and carried an unanswered LGPL-2.1 distribution question. Deleting # three manifest lines removed all of it. # # Scoped to the specific packages rather than a blanket # `cargo machete`: five other unused dependencies exist across these # crates (chrono, futures, postcard, rand, serde_json) and a gate # that fails on day one gets switched off. Widen this list as those # are cleared. - name: The removed platform deps must not come back run: | set -euo pipefail bad=0 for manifest in \ crates/apps/nigig-build/Cargo.toml \ crates/apps/nigig-email/Cargo.toml \ crates/nigig-core/Cargo.toml \ crates/nigig-uikit/Cargo.toml; do crate_dir="$(dirname "$manifest")" for dep in robius-sms robius-location; do declared=$(grep -cE "^[[:space:]]*${dep}[[:space:]]*=" "$manifest" || true) [ "$declared" -eq 0 ] && continue underscored="${dep//-/_}" used=$(grep -rl "$underscored" "$crate_dir/src" 2>/dev/null | wc -l) if [ "$used" -eq 0 ]; then echo "ERROR: $manifest declares $dep but $crate_dir/src never uses it." bad=1 fi done done if [ "$bad" -ne 0 ]; then echo echo "These pull polkit/gio/glib into the graph and reintroduce" echo "RUSTSEC-2024-0370, RUSTSEC-2024-0429 and an LGPL-2.1" echo "distribution question, for code that is never called." exit 1 fi echo "OK" - name: Lockfile must be committed and current run: | set -euo pipefail test -f Cargo.lock || { echo "missing Cargo.lock at workspace root"; exit 1; } cargo metadata --locked --format-version 1 > /dev/null git diff --exit-code -- Cargo.lock # Phase 2: these two classes of defect are easy to reintroduce by # copy-paste and invisible in review. - name: No build-time paths used at runtime in the CAD module run: | set -euo pipefail cad=crates/apps/nigig-build/src/construction_frame/pages/workspace/cad # CORE-00: fail closed when the scan target is empty. This tree # was removed (CAD now lives under crates/apps/cad); an # unguarded grep over a missing dir reports "OK" while scanning # nothing. Removal of these stale gates is tracked under BUILD-00. test -d "$cad" || { echo "ERROR: CAD scan root $cad does not exist."; exit 1; } # Rust sources only. ARCHITECTURE.md documents this rule and so # necessarily names the macro; scanning Markdown made the gate # fail on its own documentation. # # Then strip the "file:line:" prefix and drop any line whose code # starts with a comment marker. Doc/inline comments mentioning the # macro are fine; a real call is not. if grep -rn --include='*.rs' 'env!("CARGO_MANIFEST_DIR")' "$cad" \ | sed 's/^[^:]*:[0-9]*://' \ | grep -vE '^[[:space:]]*(//|/\*|\*)'; then echo echo "ERROR: CARGO_MANIFEST_DIR is a BUILD-time path. Using it at" echo "runtime bakes the build machine's source tree into the" echo "binary. Use persistence::cad_data_dir() instead." exit 1 fi echo "OK" - name: No hardcoded network endpoints in the CAD module run: | set -euo pipefail cad=crates/apps/nigig-build/src/construction_frame/pages/workspace/cad # CORE-00: fail closed when the scan target is empty. This tree # was removed (CAD now lives under crates/apps/cad); an # unguarded grep over a missing dir reports "OK" while scanning # nothing. Removal of these stale gates is tracked under BUILD-00. test -d "$cad" || { echo "ERROR: CAD scan root $cad does not exist."; exit 1; } # RFC1918 literals outside comments. constants.rs is excluded # wholesale: its only matches are the endpoint_tests that assert # such addresses are REJECTED. if grep -rnE --include='*.rs' \ '"https?://(10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[01])\.)' "$cad" \ --exclude=constants.rs \ | sed 's/^[^:]*:[0-9]*://' \ | grep -vE '^[[:space:]]*(//|/\*|\*)'; then echo echo "ERROR: a private-network endpoint is hardcoded above." echo "Read it from the environment (see constants::local_openai_url)." exit 1 fi echo "OK" # CadNode::pos/rot/size return Vec3f BY VALUE. `node.pos().x = v` # compiles, mutates a temporary and throws it away. This silently # broke all nine properties-panel inputs and the Extend tool; it # produces no warning and no runtime error, only a control that # does nothing. Mutation must go through set_pos/set_rot/set_size. - name: No writes through the by-value Vec3f getters run: | set -euo pipefail cad=crates/apps/nigig-build/src/construction_frame/pages/workspace/cad # CORE-00: fail closed when the scan target is empty. This tree # was removed (CAD now lives under crates/apps/cad); an # unguarded grep over a missing dir reports "OK" while scanning # nothing. Removal of these stale gates is tracked under BUILD-00. test -d "$cad" || { echo "ERROR: CAD scan root $cad does not exist."; exit 1; } # Exclude the two test modules that demonstrate the trap. if grep -rnE --include='*.rs' \ '\.(pos|rot|size)\(\)\.[xyz][[:space:]]*[-+*/]?=[^=]' "$cad" \ | grep -v 'setters_write_through_but_getters_are_copies' \ | sed 's/^[^:]*:[0-9]*://' \ | grep -vE '^[[:space:]]*(//|/\*|\*)' \ | grep -v 'n\.pos()\.x = 42\.0' \ | grep -v 'n\.rot()\.y = 42\.0' \ | grep -v 'p\.size()\.y = v'; then echo echo "ERROR: the assignment(s) above write to a temporary copy" echo "and are discarded. CadNode::pos/rot/size return Vec3f by" echo "value. Read into a local, mutate it, then call set_pos /" echo "set_rot / set_size." exit 1 fi echo "OK" # eval_cad_script_in_vm is the single chokepoint where user- and # AI-authored source reaches the script VM, and vm.eval is one # blocking call. Without a run budget an unterminated loop wedges the # rebuild worker permanently -- reproduced before the budget existed: # still running after 90 seconds. Losing this line would reintroduce # a hang that no test failure announces, only a frozen app. - name: The CAD script evaluator must set a run budget run: | set -euo pipefail f=crates/apps/nigig-build/src/construction_frame/pages/workspace/cad/script_bindings.rs # CORE-00: fail closed when the scan target is empty (see above). test -f "$f" || { echo "ERROR: CAD scan file $f does not exist."; exit 1; } if ! grep -q 'vm.bx.run_budget = Some(' "$f"; then echo "ERROR: eval_cad_script_in_vm no longer installs a" echo "ScriptRunBudget. An unterminated CAD script would hang the" echo "rebuild worker forever. See CAD_SCRIPT_TIME_BUDGET." exit 1 fi echo "OK" # Phase 0.4. Blocked until Cargo.lock was committed in Phase 0.2, # because cargo-deny resolves the graph from the lockfile. # # Gates security advisories and yanked crates. The licence, bans and # source checks also run, but are configured to reflect what this # graph actually is -- see deny-nigig-build.toml, where every # exception is named and justified rather than blanket-disabled. A # NEW unlicensed crate, or a new advisory, still fails. - name: Dependency audit, licences, bans and sources run: | set -euo pipefail version=0.18.6 url="https://github.com/EmbarkStudios/cargo-deny/releases/download" curl -sSLf -o /tmp/cargo-deny.tar.gz \ "$url/$version/cargo-deny-$version-x86_64-unknown-linux-musl.tar.gz" tar xzf /tmp/cargo-deny.tar.gz -C /tmp install -m 0755 \ "/tmp/cargo-deny-$version-x86_64-unknown-linux-musl/cargo-deny" \ /usr/local/bin/cargo-deny # --config is resolved relative to the manifest, not the working # directory, so it must be absolute. cargo-deny --manifest-path crates/apps/nigig-build/Cargo.toml \ --all-features check --config "$PWD/deny-nigig-build.toml" # A bare identifier in a match pattern that is NOT a known variant # is parsed as a new binding that matches everything. Four such # names (KeyEnter, KeyBackspace, BracketLeft, BracketRight) plus # Digit0-9/Equal/LeftBracket/RightBracket/Apostrophe silently turned # keyboard handlers into catch-alls: the whole direct-distance-entry # feature was unreachable, and typing any digit produced '0'. # # It compiles, and no test catches it. rustc reports it as # `unreachable_pattern` plus an `unused_variables` warning on a # capitalised name -- which is the signature grepped for here. - name: No match arms binding a non-existent enum variant run: | set -euo pipefail # A capitalised "unused variable" is almost always a mistyped # variant used as a pattern. out=$(cargo build --locked -p nigig-build --lib --message-format=short 2>&1 \ | grep -E 'unused variable: `[A-Z]' || true) if [ -n "$out" ]; then echo "$out" echo echo "ERROR: the pattern(s) above bind a new variable instead of" echo "matching an enum variant -- check the spelling against the" echo "enum definition. These silently swallow every input." exit 1 fi echo "OK" # `save_cad_script(..).ok();` compiles, discards a real io::Error, # and used to be followed by an unconditional "Saved" label -- the # user was told their work was safe when the write had failed. A # save path is the one place a dropped Result is data loss. - name: No discarded Results on CAD save/write paths run: | set -euo pipefail cad=crates/apps/nigig-build/src/construction_frame/pages/workspace/cad # CORE-00: fail closed when the scan target is empty. This tree # was removed (CAD now lives under crates/apps/cad); an # unguarded grep over a missing dir reports "OK" while scanning # nothing. Removal of these stale gates is tracked under BUILD-00. test -d "$cad" || { echo "ERROR: CAD scan root $cad does not exist."; exit 1; } if grep -rnE --include='*.rs' \ '(save|write|persist|store|export)[A-Za-z_]*\([^;]*\)\.ok\(\);' \ "$cad" \ | sed 's/^[^:]*:[0-9]*://' \ | grep -vE '^[[:space:]]*(//|/\*|\*)'; then echo echo "ERROR: the call(s) above throw away a Result from a write" echo "path. Surface it -- see save_status_message()." exit 1 fi echo "OK" # A CAD editor holds unsaved work. A panic on a UI path takes the # whole model with it, which is strictly worse than the mistake # being reported. `Command::merge`'s default panicked on a # reachable path -- a command overriding can_merge but not merge -- # while the code five lines away already refused to panic on an # unreachable one. Use debug_assert!, or return. - name: No panicking macros in CAD production code run: | set -euo pipefail cad=crates/apps/nigig-build/src/construction_frame/pages/workspace/cad # CORE-00: fail closed when the scan target is empty. This tree # was removed (CAD now lives under crates/apps/cad); an # unguarded grep over a missing dir reports "OK" while scanning # nothing. Removal of these stale gates is tracked under BUILD-00. test -d "$cad" || { echo "ERROR: CAD scan root $cad does not exist."; exit 1; } hits=0 for f in "$cad"/*.rs; do # Stop at the first #[cfg(test)]: test code may panic freely. out=$(awk '/^#\[cfg\(test\)\]/{exit} /unreachable!\(|panic!\(|todo!\(|unimplemented!\(/{ printf "%d: %s\n", NR, $0 }' "$f" \ | grep -vE '^[0-9]+:[[:space:]]*(//|/\*|\*)' || true) if [ -n "$out" ]; then echo "$f"; echo "$out"; hits=1 fi done if [ "$hits" -ne 0 ]; then echo echo "ERROR: the panicking macro(s) above are in production code." echo "Prefer debug_assert! (loud in dev, survivable in release)" echo "or an early return." exit 1 fi echo "OK" # unwrap()/expect() in CAD production code. A panic here kills the # whole editor and loses unsaved work, so each surviving one must be # a real constructor invariant whose message says which. # # Skips #[cfg(test)] modules by BRACE DEPTH, not by stopping at the # first one. arch_gltf.rs has production code after two test # modules, so a "stop at the first #[cfg(test)]" scan -- which is # what the panicking-macro gate below does -- silently misses it. # The allowlist is deliberate: a bare count drifts upward quietly # and a blanket ban just gets #[allow]-ed. - name: No new unwrap/expect in CAD production code run: | set -euo pipefail python3 - <<'EOF' import re, glob, sys CAD = "crates/apps/nigig-build/src/construction_frame/pages/workspace/cad" # CORE-00: fail closed when the scan target is empty (see above). import os if not os.path.isdir(CAD): print(f"ERROR: CAD scan root {CAD} does not exist.") sys.exit(1) # Known-good, each a documented invariant: # cad_scene.rs x4 -- SceneBuilder::new always inserts the # "default" material and layer (see ~line 850). # arch_gltf.rs x1 -- serde_json::to_vec over a Value built in # this file; it has no non-serialisable branch. ALLOWED = 5 def production(path): depth, td, pending = 0, None, False for i, line in enumerate(open(path), 1): if re.match(r"#\[cfg\(test\)\]", line.strip()): pending = True o, c = line.count("{"), line.count("}") if pending and o: td, pending = depth, False if td is None: yield i, line.rstrip() depth += o - c if td is not None and depth <= td: td = None hits = [] for f in sorted(glob.glob(CAD + "/*.rs")): for n, line in production(f): s = line.strip() if s.startswith(("//", "/*", "*")): continue if ".unwrap()" in line or ".expect(" in line: hits.append(f"{f}:{n}: {s[:100]}") if len(hits) > ALLOWED: print(f"ERROR: {len(hits)} unwrap/expect in CAD production code, allowed {ALLOWED}.") print("A panic on a UI path kills the editor and loses unsaved work.") print("Use Option/Result or an early return. If it really is an") print("invariant, raise ALLOWED here and name the invariant above.") print() for h in hits: print(" " + h) sys.exit(1) if len(hits) < ALLOWED: print(f"NOTE: {len(hits)} found, allowance {ALLOWED}. Lower it to lock the win in.") print(f"OK ({len(hits)}/{ALLOWED})") EOF # BufWriter flushes on drop and DISCARDS any error it hits. Every # CAD export buffered its output, so a write that failed only at # flush time -- full disk, revoked permission, network mount gone -- # returned Ok(()) and the status label said the file was written. # Route file exports through exporters::export_to_file, which # flushes and reports. - name: No unflushed BufWriter in CAD export paths run: | set -euo pipefail cad=crates/apps/nigig-build/src/construction_frame/pages/workspace/cad # CORE-00: fail closed when the scan target is empty. This tree # was removed (CAD now lives under crates/apps/cad); an # unguarded grep over a missing dir reports "OK" while scanning # nothing. Removal of these stale gates is tracked under BUILD-00. test -d "$cad" || { echo "ERROR: CAD scan root $cad does not exist."; exit 1; } # exporters.rs owns the one legitimate BufWriter (inside # export_to_file, which flushes). arch_pdf writes into a Vec, # where flush cannot fail. if grep -rnE --include='*.rs' 'BufWriter::new' "$cad" \ --exclude=exporters.rs --exclude=arch_pdf.rs \ | sed 's/^[^:]*:[0-9]*://' \ | grep -vE '^[[:space:]]*(//|/\*|\*)'; then echo echo "ERROR: the BufWriter(s) above are outside the flushing" echo "helper. Use exporters::export_to_file so a failed flush" echo "is reported instead of silently truncating the file." exit 1 fi echo "OK" - name: Reject whitespace errors run: git diff --check cad-module: runs-on: ubuntu-latest timeout-minutes: 45 steps: - uses: actions/checkout@v4 # Makepad needs a desktop/GL stack even for a check build. - name: Install native dependencies run: | sudo apt-get update -qq sudo apt-get install -y -qq \ pkg-config libwayland-dev libxcursor-dev libxrandr-dev \ libxi-dev libx11-dev libgl1-mesa-dev libasound2-dev \ libpolkit-gobject-1-dev libpolkit-agent-1-dev \ libglib2.0-dev libssl-dev libsqlite3-dev libudev-dev # Named for the whole crate, not "CAD module": `-p nigig-build` # covers doc/, project_management/, cost_estimator/ and tests/ as # well. When this gate was first executed it reported 1,559 diffs # across 89 files, and the three largest were all outside cad/ -- # so the old name sent anyone reading the failure to the wrong # directory. - name: Formatting (nigig-build crate) run: | cargo fmt --version cargo fmt -p nigig-build -- --check \ || { echo "run: cargo fmt -p nigig-build"; exit 1; } full-crate-check: runs-on: ubuntu-latest timeout-minutes: 60 steps: - uses: actions/checkout@v4 # libpulse/libxkbcommon are link-time-only: `cargo check` passes # without them, `cargo test` does not. - name: Install native dependencies run: | sudo apt-get update -qq sudo apt-get install -y -qq \ pkg-config libwayland-dev libxcursor-dev libxrandr-dev \ libxi-dev libx11-dev libgl1-mesa-dev libasound2-dev \ libpolkit-gobject-1-dev libpolkit-agent-1-dev \ libglib2.0-dev libssl-dev libsqlite3-dev libudev-dev \ libpulse-dev libxkbcommon-dev - name: Check run: cargo check --locked -p nigig-build --lib - name: Test (lib) run: cargo test --locked -p nigig-build --lib # Phase 4.7 moved the CAD integration suite out of src/ into its own # test target. `--lib` does not build it, so without this step the # 154 tests it contains would run in no pipeline at all. # # This names the target explicitly rather than running the whole # crate's tests, because `--test cost_estimator` and # `--test cost_estimator_ui` do not currently compile. That is # pre-existing breakage, not this workflow's to hide -- but gating # on it would make this job red for reasons unrelated to the CAD # module, and a step that is always red gets ignored. Add those # targets here the moment they build. - name: Test (CAD integration suite) run: cargo test --locked -p nigig-build --test cad_integration # NOTE: `cargo clippy -- -D warnings` is not enabled yet -- the crate # currently emits ~290 warnings. Enable it once that backlog is # cleared; a step that cannot fail is worse than no step. # --------------------------------------------------------------------- # Source coverage for the CAD engine. # # `cargo test -p nigig-build` needs wayland/X11/GL/alsa/polkit, so the # module had never been run under instrumentation and "well tested" was # an assertion rather than a measurement. tools/test-cad-coverage.sh # copies the twenty pure engine files into a host-only crate with the # same module path, runs them plus tests/cad_integration.rs under # -C instrument-coverage, and enforces a total floor plus a per-file # floor. The per-file floors are the point: losing every test in one # file moves the total by a point or two and a single number would let # that through. # # No native dependencies and no `runs-on` GPU: the harness pulls only # makepad-math and makepad-csg, both dependency-free Rust. It installs # its own toolchain into a temp dir and deletes everything -- toolchain, # cargo home, target dir, profraw data, fetched Makepad tree -- through # a shell trap, so nothing is cached between runs and nothing is left # in the workspace. # # This does NOT cover the widget layer (mod.rs, viewport*.rs, # workspace*.rs, script_bindings.rs, cad_editor_sheet.rs, # code_editor.rs). Those need live_design!, Cx and an event loop; # full-crate-check above is what gates them. `tools.rs` is pure and is # included by the harness; `profile_benchmarks.rs` is included only by # its explicit CAD_BENCH=1 mode. cad-engine-coverage: runs-on: ubuntu-latest timeout-minutes: 45 steps: - uses: actions/checkout@v4 - name: Engine coverage, with floors run: ./tools/test-cad-coverage.sh # --------------------------------------------------------------------- # Source coverage for the document workspace's pure layer. # # Same shape as the CAD gate above: tools/test-doc-workspace-coverage.sh # copies the doc module's dependency-free sources (model, layout, # editing, collaboration, advanced JSON, CRDT bridge, projection layout/ # session, mobile gestures, persistence seams) plus tests_pure.rs into a # host-only crate with a makepad-math shim, runs them under # -C instrument-coverage, and enforces a total floor plus a per-file # floor for every instrumented file. Everything -- toolchain, cargo # home, target dir, fetched Makepad tree, profraw data -- lives in a # mktemp dir removed by a shell trap on every exit path. # # This does NOT cover the widget layer (mod.rs, crdt_widget.rs, # widgets/, render/, projection_renderer.rs): those need live_design!, # Cx and an event loop, and are gated by the full-crate build and test # jobs above. persistence.rs keeps a lower floor on purpose: three # write-path entry points save into the host's real application-data # directory and are covered only through their path-injected seams # (save_doc_state_to / load_saved_doc_state_with); see the doc module's # COVERAGE.md for the honest exclusion list. doc-workspace-coverage: runs-on: ubuntu-latest timeout-minutes: 45 steps: - uses: actions/checkout@v4 - name: Doc workspace coverage, with floors run: ./tools/test-doc-workspace-coverage.sh # --------------------------------------------------------------------- # The CAD widget layer: viewport*.rs, workspace*.rs, mod.rs and # friends. The engine gate above is structurally blind to them, so # "97% covered" has always been a statement about the smaller half. # # Measured when this job was added: 13.25% of 10,637 lines, with # viewport_input.rs, viewport_render.rs, workspace_actions.rs, # cad_editor_sheet.rs, viewport_2d.rs and code_editor.rs at exactly # zero. 9,228 lines that no test has ever executed. # # REPORT-ONLY, deliberately, and this is the whole argument for the # job existing: a floor at 13% reads as a blessing rather than a debt. # What it buys is that the number is printed on every push instead of # being rediscovered in six months. Per Phase 0 of # REVIEWS/REPO_COVERAGE_100_PLAN.md the first real input test should # set a floor behind it -- at measured-minus-one, in this job. # # Unlike the engine harness this builds the real crate, so it needs # Makepad's Linux packages and a toolchain. cad-widget-coverage: runs-on: ubuntu-latest timeout-minutes: 45 steps: - uses: actions/checkout@v4 - name: Install Makepad's native dependencies run: bash tools/makepad-native-libs.sh --install # NOT actions/setup-rust@v1 -- see .forgejo/RUNNER.md. - name: Install the declared toolchain run: | set -e version="$(sed -n 's/^[[:space:]]*channel[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' \ rust-toolchain.toml | head -n 1)" curl --fail --location --proto '=https' --tlsv1.2 https://sh.rustup.rs -o /tmp/rustup-init chmod 700 /tmp/rustup-init /tmp/rustup-init -y --profile minimal --default-toolchain "$version" \ --component llvm-tools-preview --no-modify-path echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" - name: Widget layer coverage (report-only) run: ./tools/test-cad-widget-coverage.sh