|
| 1 | +#!/usr/bin/env bash |
| 2 | +# Ecosystem verification for round 7: one package, one version -- and a rule |
| 3 | +# package that brings its own environment. Against a PUBLISHED mcpp and a |
| 4 | +# PUBLISHED mcpp:plugins. |
| 5 | +# |
| 6 | +# # The sandbox has an EMPTY $HOME and a fresh /tmp, so this file is not |
| 7 | +# # visible from inside it. Pass the script itself in: |
| 8 | +# B64=$(base64 -w0 <this file>) |
| 9 | +# xlings subos use verify-966 --sandbox --cmd \ |
| 10 | +# "echo $B64 | base64 -d > /tmp/v.sh && MCPP_VERIFY_VERSION=2026.9.6.6 bash /tmp/v.sh" |
| 11 | +# |
| 12 | +# mcpp is addressed by its STORE path, which is the one thing the sandbox does |
| 13 | +# share: the xlings data directory. A bare `mcpp` is not on PATH in there. |
| 14 | +# |
| 15 | +# WHY A SANDBOX IS THE RIGHT PLACE FOR THIS ONE. Every criterion below is about |
| 16 | +# what gets INSTALLED. On a machine that has built any of this before, the |
| 17 | +# payload is in the registry already and "one version" reads the same whether |
| 18 | +# the engine unified anything or not. A sandbox has an empty registry, so the |
| 19 | +# question can be asked at all. |
| 20 | +# |
| 21 | +# EVERY CRITERION NAMES THE OBJECT IT SELECTED, and every section that did not |
| 22 | +# run is listed again in the summary. "0 assertions failed" printed by a script |
| 23 | +# that skipped four sections is the failure mode this shape exists to prevent. |
| 24 | +set -u |
| 25 | + |
| 26 | +VER="${MCPP_VERIFY_VERSION:?set MCPP_VERIFY_VERSION}" |
| 27 | +STORE="${MCPP_VERIFY_BIN:-$HOME/.xlings/data/xpkgs/xim-x-mcpp/$VER/bin/mcpp}" |
| 28 | +PLUGINS="${MCPP_VERIFY_PLUGINS:-0.2.4}" |
| 29 | + |
| 30 | +fails=0 |
| 31 | +skipped="" |
| 32 | +fail() { printf 'ASSERT-FAIL: %s\n' "$1"; fails=$((fails + 1)); } |
| 33 | +ok() { printf 'ok: %s\n' "$1"; } |
| 34 | +section() { printf '\n== %s ==\n' "$1"; } |
| 35 | +skip() { printf 'NOT RUN: %s\n' "$1"; skipped="$skipped |
| 36 | + - $1"; } |
| 37 | + |
| 38 | +work=$(mktemp -d) |
| 39 | +trap 'rm -rf "$work"' EXIT |
| 40 | + |
| 41 | +# The store this run installs into, so "how many versions of X are here" is a |
| 42 | +# question about THIS run and not about the machine. |
| 43 | +XPKGS="$HOME/.mcpp/registry/data/xpkgs" |
| 44 | +count_versions() { ls -1 "$XPKGS/xim-x-$1" 2>/dev/null | wc -l | tr -d ' '; } |
| 45 | +list_versions() { ls -1 "$XPKGS/xim-x-$1" 2>/dev/null | sort | tr '\n' ' '; } |
| 46 | + |
| 47 | +# ── A. identity ───────────────────────────────────────────────────────────── |
| 48 | +section "A. identity" |
| 49 | +if [ ! -x "$STORE" ]; then |
| 50 | + fail "no released binary at $STORE" |
| 51 | + printf 'FAIL: nothing further can run\n'; exit 1 |
| 52 | +fi |
| 53 | +got=$("$STORE" --version 2>&1 | head -1) |
| 54 | +if [ "$got" = "mcpp $VER" ]; then ok "$got from $STORE" |
| 55 | +else fail "version is '$got' at $STORE"; fi |
| 56 | + |
| 57 | +"$STORE" self config --mirror "${MCPP_VERIFY_MIRROR:-CN}" >/dev/null 2>&1 || true |
| 58 | + |
| 59 | +# ── B. a range is installed AND answered ──────────────────────────────────── |
| 60 | +# |
| 61 | +# Engine-only: no rule package, no index entry beyond the tool itself. This is |
| 62 | +# the gap that made a floor unusable -- the payload arrived and `xpkg_dir` |
| 63 | +# answered "". |
| 64 | +section "B. a range is installed and answered (engine only)" |
| 65 | +mkdir -p "$work/b/src" |
| 66 | +printf 'int main(){return 0;}\n' > "$work/b/src/main.cpp" |
| 67 | +cat > "$work/b/build.mcpp" <<'EOF' |
| 68 | +#include <cstdio> |
| 69 | +#include <string> |
| 70 | +import mcpp; |
| 71 | +int main() { |
| 72 | + const char* d = mcpp::xpkg_dir("xim", "shaderc"); |
| 73 | + std::string out = std::string(mcpp::manifest_dir()) + "/answered.txt"; |
| 74 | + std::FILE* f = std::fopen(out.c_str(), "w"); |
| 75 | + if (f == nullptr) return 3; |
| 76 | + std::fprintf(f, "%s\n", d == nullptr ? "" : d); |
| 77 | + std::fclose(f); |
| 78 | + return 0; |
| 79 | +} |
| 80 | +EOF |
| 81 | +cat > "$work/b/mcpp.toml" <<'EOF' |
| 82 | +[package] |
| 83 | +name = "rangecheck" |
| 84 | +version = "0.1.0" |
| 85 | +[xlings.workspace] |
| 86 | +"xim:shaderc" = ">=2026.1" |
| 87 | +[build] |
| 88 | +sources = ["src/main.cpp"] |
| 89 | +[targets.rangecheck] |
| 90 | +kind = "bin" |
| 91 | +main = "src/main.cpp" |
| 92 | +EOF |
| 93 | +if ( cd "$work/b" && "$STORE" build >build.log 2>&1 ); then |
| 94 | + if [ -s "$work/b/answered.txt" ] && grep -q 'xim-x-shaderc' "$work/b/answered.txt"; then |
| 95 | + ok "a range was answered: $(cat "$work/b/answered.txt")" |
| 96 | + else |
| 97 | + fail "the range installed but xpkg_dir answered '$(cat "$work/b/answered.txt" 2>/dev/null)'" |
| 98 | + fi |
| 99 | +else |
| 100 | + fail "a project declaring a range did not build" |
| 101 | + tail -5 "$work/b/build.log" |
| 102 | +fi |
| 103 | + |
| 104 | +# …and the other direction, so a build that treats the range as a literal is |
| 105 | +# not read as a pass. |
| 106 | +mkdir -p "$work/b2/src" |
| 107 | +printf 'int main(){return 0;}\n' > "$work/b2/src/main.cpp" |
| 108 | +sed 's/>=2026.1/>=2099.1/' "$work/b/mcpp.toml" > "$work/b2/mcpp.toml" |
| 109 | +if ( cd "$work/b2" && "$STORE" build >build.log 2>&1 ); then |
| 110 | + fail "an unsatisfiable range was accepted" |
| 111 | +else |
| 112 | + if grep -q 'not found in the synced index' "$work/b2/build.log"; then |
| 113 | + ok "an unsatisfiable range is refused, naming it" |
| 114 | + else |
| 115 | + fail "refused, but not for the stated reason: $(grep -m1 error "$work/b2/build.log")" |
| 116 | + fi |
| 117 | +fi |
| 118 | + |
| 119 | +# ── C. one edge, and the rule brings its environment ──────────────────────── |
| 120 | +section "C. one edge, no [xlings.workspace] anywhere in the project" |
| 121 | +mk_spirv_project() { # $1 = dir, $2 = extra manifest text |
| 122 | + mkdir -p "$1/src" "$1/shaders" |
| 123 | + cat > "$1/shaders/scale.comp" <<'EOF' |
| 124 | +#version 450 |
| 125 | +layout(local_size_x = 64) in; |
| 126 | +layout(std430, binding = 0) buffer Data { float v[]; }; |
| 127 | +layout(push_constant) uniform Push { float a; uint n; } push; |
| 128 | +void main() { |
| 129 | + const uint i = gl_GlobalInvocationID.x; |
| 130 | + if (i >= push.n) return; |
| 131 | + v[2u * push.n + i] = push.a * v[i] + v[push.n + i]; |
| 132 | +} |
| 133 | +EOF |
| 134 | + cat > "$1/src/main.cpp" <<'EOF' |
| 135 | +#include <cstdint> |
| 136 | +#include <cstdio> |
| 137 | +#include "scale_comp.h" |
| 138 | +int main() { |
| 139 | + const std::uint32_t magic = scale_comp_spv[0]; |
| 140 | + std::printf("magic=%08x\n", magic); |
| 141 | + return magic == 0x07230203u ? 0 : 1; |
| 142 | +} |
| 143 | +EOF |
| 144 | + cat > "$1/build.mcpp" <<'EOF' |
| 145 | +import std; |
| 146 | +import mcpp; |
| 147 | +import mcpp.rules.spirv; |
| 148 | +int main() { |
| 149 | + mcpp::rerun_if_changed_glob("shaders/**/*.comp"); |
| 150 | + mcpp::rules::spirv::options opt; |
| 151 | + opt.includes = { "shaders" }; |
| 152 | + return mcpp::rules::spirv::compile(opt) ? 0 : 1; |
| 153 | +} |
| 154 | +EOF |
| 155 | + cat > "$1/mcpp.toml" <<EOF |
| 156 | +[package] |
| 157 | +name = "onedge" |
| 158 | +version = "0.1.0" |
| 159 | +accelerators = ["vulkan"] |
| 160 | +
|
| 161 | +[language] |
| 162 | +standard = "c++23" |
| 163 | +modules = true |
| 164 | +import_std = true |
| 165 | +
|
| 166 | +[build-dependencies.mcpp] |
| 167 | +plugins = { version = "$PLUGINS", features = ["rules-spirv"], host-module = true } |
| 168 | +
|
| 169 | +[build] |
| 170 | +accel = "vulkan1.2" |
| 171 | +sources = [ |
| 172 | + "src/*.cpp", |
| 173 | + { glob = "shaders/*.comp", accel = "vulkan1.2" }, |
| 174 | +] |
| 175 | +$2 |
| 176 | +[targets.onedge] |
| 177 | +kind = "bin" |
| 178 | +main = "src/main.cpp" |
| 179 | +EOF |
| 180 | +} |
| 181 | + |
| 182 | +mk_spirv_project "$work/c" "" |
| 183 | +if ( cd "$work/c" && "$STORE" build >build.log 2>&1 ); then |
| 184 | + if grep -q 'entries declared by dependencies' "$work/c/build.log"; then |
| 185 | + ok "the payload came from the rule: $(grep -m1 'entries declared by dependencies' "$work/c/build.log")" |
| 186 | + else |
| 187 | + fail "it built, but nothing was provisioned from the graph -- the rule declared no payload" |
| 188 | + fi |
| 189 | + out=$( cd "$work/c" && "$STORE" run 2>&1 | tail -1 ) |
| 190 | + if [ "$out" = "magic=07230203" ]; then ok "and it runs: $out" |
| 191 | + else fail "ran and printed '$out'"; fi |
| 192 | +else |
| 193 | + # THE SKIP IS RECOGNISED BY THE RESOLUTION ERROR, NOT BY "it failed and the |
| 194 | + # log mentions plugins". `E_NOT_FOUND` with the wire address is a sentence |
| 195 | + # only the resolver writes; a compile or link failure cannot produce it, so |
| 196 | + # a genuine defect is never filed as "not published yet". |
| 197 | + if grep -q "E_NOT_FOUND: package 'mcpp:plugins@$PLUGINS' not found" "$work/c/build.log"; then |
| 198 | + skip "C: mcpp:plugins@$PLUGINS is not in the index yet" |
| 199 | + skip "D: needs C" |
| 200 | + skip "E: needs C" |
| 201 | + else |
| 202 | + fail "a project writing only the plugins edge did not build" |
| 203 | + tail -15 "$work/c/build.log" |
| 204 | + fi |
| 205 | +fi |
| 206 | + |
| 207 | +# ── D. the override: one version, and the nearer declaration wins ─────────── |
| 208 | +if [ -d "$work/c/target" ]; then |
| 209 | + section "D. the project's own pin overrides the rule's, and only one is installed" |
| 210 | + mk_spirv_project "$work/d" ' |
| 211 | +[target.'"'"'cfg(accelerator = "vulkan")'"'"'.xlings.workspace] |
| 212 | +"xim:glslang" = "15.1.0" |
| 213 | +' |
| 214 | + if ( cd "$work/d" && "$STORE" build >build.log 2>&1 ); then |
| 215 | + n=$(count_versions glslang) |
| 216 | + if [ "$n" = "1" ]; then ok "one glslang installed: $(list_versions glslang)" |
| 217 | + else fail "two declarations of one package installed $n versions: $(list_versions glslang)"; fi |
| 218 | + else |
| 219 | + fail "the override did not build" |
| 220 | + tail -10 "$work/d/build.log" |
| 221 | + fi |
| 222 | + |
| 223 | + # ── E. a pin below the rule's floor is refused, naming both ───────────── |
| 224 | + section "E. a pin below the rule's stated floor is refused" |
| 225 | + mk_spirv_project "$work/e" ' |
| 226 | +[target.'"'"'cfg(accelerator = "vulkan")'"'"'.xlings.workspace] |
| 227 | +"xim:glslang" = "1.0.0" |
| 228 | +' |
| 229 | + if ( cd "$work/e" && "$STORE" build >build.log 2>&1 ); then |
| 230 | + fail "a pin below the rule's floor was accepted" |
| 231 | + else |
| 232 | + miss="" |
| 233 | + for needle in 'xim:glslang' '1.0.0' 'mcpp' 'drop the pin'; do |
| 234 | + grep -q -- "$needle" "$work/e/build.log" || miss="$miss $needle" |
| 235 | + done |
| 236 | + if [ -z "$miss" ]; then ok "refused, naming both sides and the way out" |
| 237 | + else fail "refused, but the message omits:$miss"; fi |
| 238 | + fi |
| 239 | +fi |
| 240 | + |
| 241 | +# ── summary ──────────────────────────────────────────────────────────────── |
| 242 | +section "summary" |
| 243 | +printf '%s assertion(s) failed\n' "$fails" |
| 244 | +if [ -n "$skipped" ]; then |
| 245 | + printf 'sections that did NOT run:%s\n' "$skipped" |
| 246 | +fi |
| 247 | +[ "$fails" -eq 0 ] || exit 1 |
0 commit comments