Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/scripts/id-pr-checks.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ test-shard-3
test-web-unit
test-web-typecheck
roc-test
roc-examples
crash-gate-sampled
crash-gate-sweep
crash-gate-control
Expand Down
5 changes: 3 additions & 2 deletions pkgs/id/apps/tic-tac-toe/program/build.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")"
ROC="${ROC:-roc}"
export ROC="${ROC:-roc}"
source ../../../examples/roc-toolchain.sh
if [ -z "${WASM_TOOLS:-}" ]; then
if command -v wasm-tools >/dev/null 2>&1; then
WASM_TOOLS="wasm-tools"
Expand All @@ -23,7 +24,7 @@ if [ -z "${WASM_OPT:-}" ]; then
if [ -n "$STORE_OPT" ] && [ -x "$STORE_OPT" ]; then
WASM_OPT="$STORE_OPT"
else
WASM_OPT="nix run nixpkgs#binaryen --"
WASM_OPT="nix shell nixpkgs#binaryen --command wasm-opt"
fi
fi
fi
Expand Down
5 changes: 3 additions & 2 deletions pkgs/id/examples/lounge/build.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")"
ROC="${ROC:-roc}"
export ROC="${ROC:-roc}"
source ../roc-toolchain.sh
if [ -z "${WASM_TOOLS:-}" ]; then
if command -v wasm-tools >/dev/null 2>&1; then
WASM_TOOLS="wasm-tools"
Expand All @@ -23,7 +24,7 @@ if [ -z "${WASM_OPT:-}" ]; then
if [ -n "$STORE_OPT" ] && [ -x "$STORE_OPT" ]; then
WASM_OPT="$STORE_OPT"
else
WASM_OPT="nix run nixpkgs#binaryen --"
WASM_OPT="nix shell nixpkgs#binaryen --command wasm-opt"
fi
fi
fi
Expand Down
5 changes: 3 additions & 2 deletions pkgs/id/examples/roc-chat/build.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")"
ROC="${ROC:-roc}"
export ROC="${ROC:-roc}"
source ../roc-toolchain.sh

if [ -z "${WASM_TOOLS:-}" ]; then
if command -v wasm-tools >/dev/null 2>&1; then
Expand All @@ -24,7 +25,7 @@ if [ -z "${WASM_OPT:-}" ]; then
if [ -n "$STORE_OPT" ] && [ -x "$STORE_OPT" ]; then
WASM_OPT="$STORE_OPT"
else
WASM_OPT="nix run nixpkgs#binaryen --"
WASM_OPT="nix shell nixpkgs#binaryen --command wasm-opt"
fi
fi
fi
Expand Down
5 changes: 3 additions & 2 deletions pkgs/id/examples/roc-counter/build.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")"
ROC="${ROC:-roc}"
export ROC="${ROC:-roc}"
source ../roc-toolchain.sh
WASM_TOOLS="${WASM_TOOLS:-nix run nixpkgs#wasm-tools --}"

if [ -z "${WASM_OPT:-}" ]; then
Expand All @@ -12,7 +13,7 @@ if [ -z "${WASM_OPT:-}" ]; then
if [ -n "$STORE_OPT" ] && [ -x "$STORE_OPT" ]; then
WASM_OPT="$STORE_OPT"
else
WASM_OPT="nix run nixpkgs#binaryen --"
WASM_OPT="nix shell nixpkgs#binaryen --command wasm-opt"
fi
fi
fi
Expand Down
5 changes: 3 additions & 2 deletions pkgs/id/examples/roc-grid/build.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")"
ROC="${ROC:-roc}"
export ROC="${ROC:-roc}"
source ../roc-toolchain.sh

if [ -z "${WASM_TOOLS:-}" ]; then
if command -v wasm-tools >/dev/null 2>&1; then
Expand All @@ -24,7 +25,7 @@ if [ -z "${WASM_OPT:-}" ]; then
if [ -n "$STORE_OPT" ] && [ -x "$STORE_OPT" ]; then
WASM_OPT="$STORE_OPT"
else
WASM_OPT="nix run nixpkgs#binaryen --"
WASM_OPT="nix shell nixpkgs#binaryen --command wasm-opt"
fi
fi
fi
Expand Down
Binary file modified pkgs/id/examples/roc-grid/roc-grid.wasm
Binary file not shown.
5 changes: 3 additions & 2 deletions pkgs/id/examples/roc-screen/build.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")"
ROC="${ROC:-roc}"
export ROC="${ROC:-roc}"
source ../roc-toolchain.sh

if [ -z "${WASM_TOOLS:-}" ]; then
if command -v wasm-tools >/dev/null 2>&1; then
Expand All @@ -24,7 +25,7 @@ if [ -z "${WASM_OPT:-}" ]; then
if [ -n "$STORE_OPT" ] && [ -x "$STORE_OPT" ]; then
WASM_OPT="$STORE_OPT"
else
WASM_OPT="nix run nixpkgs#binaryen --"
WASM_OPT="nix shell nixpkgs#binaryen --command wasm-opt"
fi
fi
fi
Expand Down
Binary file modified pkgs/id/examples/roc-screen/roc-screen.wasm
Binary file not shown.
21 changes: 21 additions & 0 deletions pkgs/id/examples/roc-toolchain.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env bash
# Shared compiler guard for every checked-in Roc example module.

ID_ROC_VERSION_FILE="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/nix/roc-version"
ID_ROC_EXPECTED_VERSION="$(<"$ID_ROC_VERSION_FILE")"
ID_ROC_EXPECTED_OUTPUT="Roc compiler version $ID_ROC_EXPECTED_VERSION"
ID_ROC_BIN="${ROC:-roc}"

if ! ID_ROC_ACTUAL_OUTPUT="$("$ID_ROC_BIN" version 2>&1)"; then
printf 'Roc toolchain check failed: could not run %s version:\n%s\n' \
"$ID_ROC_BIN" "$ID_ROC_ACTUAL_OUTPUT" >&2
exit 1
fi

if [[ $ID_ROC_ACTUAL_OUTPUT != "$ID_ROC_EXPECTED_OUTPUT" ]]; then
printf 'Roc toolchain mismatch: expected %s from %s, got %s from %s; refusing to build.\n' \
"$ID_ROC_EXPECTED_OUTPUT" "$ID_ROC_VERSION_FILE" "$ID_ROC_ACTUAL_OUTPUT" "$ID_ROC_BIN" >&2
exit 1
fi

echo "Using pinned Roc compiler $ID_ROC_EXPECTED_VERSION"
5 changes: 3 additions & 2 deletions pkgs/id/examples/territory/build.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")"
ROC="${ROC:-roc}"
export ROC="${ROC:-roc}"
source ../roc-toolchain.sh
if [ -z "${WASM_TOOLS:-}" ]; then
if command -v wasm-tools >/dev/null 2>&1; then
WASM_TOOLS="wasm-tools"
Expand All @@ -23,7 +24,7 @@ if [ -z "${WASM_OPT:-}" ]; then
if [ -n "$STORE_OPT" ] && [ -x "$STORE_OPT" ]; then
WASM_OPT="$STORE_OPT"
else
WASM_OPT="nix run nixpkgs#binaryen --"
WASM_OPT="nix shell nixpkgs#binaryen --command wasm-opt"
fi
fi
fi
Expand Down
Binary file modified pkgs/id/examples/territory/territory.wasm
Binary file not shown.
51 changes: 42 additions & 9 deletions pkgs/id/flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,18 @@
'';
};

# Helper to create a check that runs a just command
mkCheck =
name: justCmd:
# Helper to create a check that runs a just command or direct test
# command, with an optional pre-check step and check-specific tools.
mkCheck = name: justCmd: mkCheckWith { inherit name justCmd; };

mkCheckWith =
{
name,
justCmd ? null,
checkCommand ? null,
extraNativeBuildInputs ? [ ],
beforeCheck ? "",
}:
pkgs.stdenv.mkDerivation {
name = "id-${name}";
# Every check builds its own debug test binaries in parallel on a
Expand All @@ -173,10 +182,13 @@
CARGO_PROFILE_DEV_DEBUG = "line-tables-only";
src = ./.;
inherit buildInputs;
nativeBuildInputs = nativeBuildInputs ++ [
bun2nixPkg.hook
pkgs.rustPlatform.cargoSetupHook
];
nativeBuildInputs =
nativeBuildInputs
++ extraNativeBuildInputs
++ [
bun2nixPkg.hook
pkgs.rustPlatform.cargoSetupHook
];
inherit cargoDeps;
inherit (opensslEnv) OPENSSL_DIR;
inherit (opensslEnv) OPENSSL_LIB_DIR;
Expand Down Expand Up @@ -204,7 +216,9 @@

${buildWasmHost}

just ${justCmd}
${beforeCheck}

${if checkCommand != null then checkCommand else "just ${justCmd}"}
'';
installPhase = ''
mkdir -p $out
Expand Down Expand Up @@ -307,7 +321,7 @@
# Uses 'ci' command (read-only, no auto-fix modifications)
# =======================================================================
checks = {
# CI-safe checks (read-only): cargo-fmt-check web-fmt-check clippy-lint web-lint test-sandbox test-web-unit test-web-typecheck roc-test doc build release
# CI-safe checks (read-only): cargo-fmt-check web-fmt-check clippy-lint web-lint test-sandbox test-web-unit test-web-typecheck roc-test roc-examples doc build release
# NixOS VM integration tests (Linux only): nixos-serve nixos-e2e
default = mkCheck "ci" "ci";

Expand Down Expand Up @@ -336,6 +350,25 @@
# nativeBuildInputs); fails on a missing compiler or a failing
# expect — the world_compile.rs skip path is not reused here.
roc-test = mkCheck "roc-test" "roc-test";
# Rebuild all checked-in Roc examples with the pinned compiler, then
# run the sandbox behavior and fuel-budget tests against them.
roc-examples = mkCheckWith {
name = "roc-examples";
checkCommand = ''
cargo test --no-default-features --features sandbox,world --lib sandbox
cargo test --test toolchain_pin
'';
extraNativeBuildInputs = [
pkgs.binaryen
pkgs.wasm-tools
];
beforeCheck = ''
ROC=${rocCompiler}/bin/roc \
WASM_TOOLS=${pkgs.wasm-tools}/bin/wasm-tools \
WASM_OPT=${pkgs.binaryen}/bin/wasm-opt \
bash scripts/rebuild-roc-examples.sh

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Make nested host builds executable in the Nix sandbox

With Nix sandboxing enabled on Linux, this rebuild reaches examples/lounge/build.sh:32, which executes ../roc-world/build-host.sh directly. That script uses #!/usr/bin/env bash, but /usr/bin/env is absent in the sandbox—the existing workaround in nix-common.nix:32–36 explicitly invokes the same script with Bash for this reason. Invoking the outer rebuild script with Bash does not fix the nested invocation, and this check does not patch its shebang before building. Consequently, the new PR-matrix check fails on the first example before reaching the tests. Invoke the nested host builds with Bash or patch their shebangs before rebuilding.

Useful? React with 👍 / 👎.

'';
};
doc = mkCheck "doc" "doc";
cargo-check = mkCheck "cargo-check" "cargo-check";
crash-gate = mkCheck "crash-gate" "crash-gate";
Expand Down
1 change: 1 addition & 0 deletions pkgs/id/nix/roc-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nightly-2026-10-04-130536d
9 changes: 6 additions & 3 deletions pkgs/id/nix/roc.nix
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,18 @@
}:

let
tag = "nightly-2026-10-04-130536d";
# Shared with the example build scripts so the executable guard and Nix
# package cannot silently pin different Roc nightlies.
tag = lib.removeSuffix "\n" (builtins.readFile ./roc-version);
tagSuffix = lib.removePrefix "nightly-" tag;

sources = {
x86_64-linux = {
asset = "roc_nightly-linux_x86_64-2026-10-04-130536d.tar.gz";
asset = "roc_nightly-linux_x86_64-${tagSuffix}.tar.gz";
hash = "sha256-iTyG0toKTDkMvb1CWM5F1obl7BW0Lu64Ef0axEU3cU8=";
};
aarch64-linux = {
asset = "roc_nightly-linux_arm64-2026-10-04-130536d.tar.gz";
asset = "roc_nightly-linux_arm64-${tagSuffix}.tar.gz";
hash = "sha256-9FMtKQyDkg0j8ZHaRlCxmqgwJ0PtNcq06a3IO+rwwds=";
};
};
Expand Down
18 changes: 18 additions & 0 deletions pkgs/id/scripts/rebuild-roc-examples.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")/.."

examples=(
examples/lounge
examples/roc-chat
examples/roc-counter
examples/roc-grid
examples/roc-screen
examples/territory
apps/tic-tac-toe/program
)

for example in "${examples[@]}"; do
echo "=== Rebuilding Roc example: $example ==="
(cd "$example" && bash ./build.sh)
done
83 changes: 83 additions & 0 deletions pkgs/id/tests/toolchain_pin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,17 @@

use std::fs;
use std::path::Path;
use std::process::Command;

const ROC_EXAMPLE_BUILDS: [&str; 7] = [
"examples/lounge/build.sh",
"examples/roc-chat/build.sh",
"examples/roc-counter/build.sh",
"examples/roc-grid/build.sh",
"examples/roc-screen/build.sh",
"examples/territory/build.sh",
"apps/tic-tac-toe/program/build.sh",
];

/// The Nix side of the pin: a literal version, or a value read from the file.
#[derive(Debug, PartialEq, Eq)]
Expand Down Expand Up @@ -70,6 +81,78 @@ fn read_pin_files() -> (String, String) {
(toml, nix)
}

#[test]
fn test_roc_examples_and_nix_package_share_the_compiler_pin() {
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
let pin = fs::read_to_string(root.join("nix/roc-version")).unwrap();
let pinned_version = pin.trim();
assert!(!pinned_version.is_empty(), "nix/roc-version is empty");

let roc_nix = fs::read_to_string(root.join("nix/roc.nix")).unwrap();
assert!(
roc_nix.contains("tag = lib.removeSuffix \"\\n\" (builtins.readFile ./roc-version);")
&& roc_nix.contains("lib.removePrefix \"nightly-\" tag"),
"nix/roc.nix must derive its download tag from nix/roc-version"
);

for build_script in ROC_EXAMPLE_BUILDS {
let script = fs::read_to_string(root.join(build_script)).unwrap();
assert!(
script.contains("roc-toolchain.sh"),
"{build_script} must check the shared Roc compiler pin"
);
}
}

#[cfg(unix)]
#[test]
fn test_example_build_refuses_a_different_roc_before_building() {
use std::os::unix::fs::PermissionsExt;

let root = Path::new(env!("CARGO_MANIFEST_DIR"));
let temp = tempfile::tempdir().unwrap();
let bash = std::env::split_paths(&std::env::var_os("PATH").unwrap())
.map(|directory| directory.join("bash"))
.find(|candidate| candidate.is_file())
.and_then(|candidate| fs::canonicalize(candidate).ok())
.expect("bash is available on PATH");
let roc_stub = temp.path().join("roc-stub");
let build_marker = temp.path().join("build-was-run");
fs::write(
&roc_stub,
format!(
"#!{}\nif [[ \"${{1:-}}\" == version ]]; then\n echo 'Roc compiler version unpinned-test'\nelse\n : > \"$ROC_BUILD_MARKER\"\nfi\n",
bash.display()
),
)
.unwrap();
fs::set_permissions(&roc_stub, fs::Permissions::from_mode(0o755)).unwrap();

let output = Command::new("bash")
.arg("examples/territory/build.sh")
.current_dir(root)
.env("ROC", &roc_stub)
.env("ROC_BUILD_MARKER", &build_marker)
.output()
.unwrap();

assert!(
!output.status.success(),
"an unpinned Roc compiler was accepted"
);
let stderr = String::from_utf8_lossy(&output.stderr);
let pinned_version = fs::read_to_string(root.join("nix/roc-version")).unwrap();
assert!(stderr.contains(pinned_version.trim()), "{stderr}");
assert!(
stderr.contains("Roc compiler version unpinned-test"),
"{stderr}"
);
assert!(
!build_marker.exists(),
"the build command ran before refusal"
);
}

#[test]
fn nix_check_installs_the_channel_pinned_in_rust_toolchain_toml() {
let (toml, nix) = read_pin_files();
Expand Down
Loading