|
| 1 | +#!/usr/bin/env bash |
| 2 | +# requires: |
| 3 | +# 252_pack_library_old_client.sh — an mcpp that predates library packaging must |
| 4 | +# still BUILD against a package produced by one that has it. |
| 5 | +# |
| 6 | +# That claim is the reason the generated manifest introduces no section and no |
| 7 | +# key: everything in it — `sources`, `include_dirs`, `[modules] exports`, a |
| 8 | +# `cfg(...)` block per leg, `[[runtime.artifacts]]` — was already parsed before |
| 9 | +# this feature existed. An older client reads the package and links it; what it |
| 10 | +# does not do is run the gates, because it has no way to know that |
| 11 | +# `provenance = "mcpp-pack …"` means anything. |
| 12 | +# |
| 13 | +# Two halves, because only one of them can run everywhere: |
| 14 | +# |
| 15 | +# 1. STATIC — the generated manifest's top-level sections are a subset of the |
| 16 | +# vocabulary that predates this feature. Portable, and it is the actual |
| 17 | +# invariant rather than a proxy for it. |
| 18 | +# 2. REAL — consume the package with $MCPP_BOOT, the released mcpp each CI job |
| 19 | +# bootstraps from. Skipped with a loud note when that is not available, |
| 20 | +# never silently. |
| 21 | +set -e |
| 22 | +source "$(dirname "$0")/_host_path.sh" |
| 23 | + |
| 24 | +TMP=$(mktemp -d) |
| 25 | +trap "rm -rf $TMP" EXIT |
| 26 | +cd "$TMP" |
| 27 | + |
| 28 | +mkdir -p mathkit/src |
| 29 | +cat > mathkit/src/mathkit.cppm <<'EOF' |
| 30 | +export module mathkit; |
| 31 | +export namespace mk { int answer(); } |
| 32 | +EOF |
| 33 | +cat > mathkit/src/impl.cpp <<'EOF' |
| 34 | +module mathkit; |
| 35 | +namespace mk { int answer() { return 42; } } |
| 36 | +EOF |
| 37 | +cat > mathkit/mcpp.toml <<'EOF' |
| 38 | +[package] |
| 39 | +name = "mathkit" |
| 40 | +version = "0.1.0" |
| 41 | +[build] |
| 42 | +sources = ["src/*.cppm", "src/*.cpp"] |
| 43 | +[targets.mathkit] |
| 44 | +kind = "lib" |
| 45 | +EOF |
| 46 | + |
| 47 | +cd mathkit |
| 48 | +"$MCPP" pack mathkit > pack.log 2>&1 || { cat pack.log; echo "pack failed"; exit 1; } |
| 49 | +pkg="$TMP/mathkit/$(find target/dist -maxdepth 1 -type d -name 'mathkit-0.1.0-*' | head -1)" |
| 50 | +PKG_HOST="$(host_path "$pkg")" |
| 51 | + |
| 52 | +# ── 1. no section outside the pre-existing vocabulary ────────────────── |
| 53 | +# |
| 54 | +# Listed literally rather than derived: the point is that this set was frozen |
| 55 | +# before the feature, so a new entry has to be added here deliberately — and |
| 56 | +# adding one is exactly the moment to ask whether older clients can still read |
| 57 | +# the package. |
| 58 | +# `\[\[?` covers both a table and an array-of-tables header: `[[runtime.artifacts]]` |
| 59 | +# is the same section as `[runtime]` for this purpose, and the first version of |
| 60 | +# this pattern matched only the single-bracket form — so it flagged the very |
| 61 | +# section the design deliberately reuses. |
| 62 | +known='^\[\[?(package|build|modules|targets\.|target\.|dependencies|dev-dependencies|runtime|profile\.|features|lib|pack|workspace|indices|resources|xlings|capabilities|tools)' |
| 63 | +bad="$(grep -E '^\[' "$pkg/mcpp.toml" | grep -Ev "$known" || true)" |
| 64 | +[[ -z "$bad" ]] || { |
| 65 | + echo "FAIL: the generated manifest uses sections an older mcpp cannot read:" |
| 66 | + printf '%s\n' "$bad" |
| 67 | + echo " Either express the fact with an existing key, or accept that packages" |
| 68 | + echo " need a version floor — and say so in docs/12." |
| 69 | + exit 1; } |
| 70 | + |
| 71 | +# ── 2. the released client actually builds against it ────────────────── |
| 72 | +mkdir -p "$TMP/app/src" |
| 73 | +cat > "$TMP/app/src/main.cpp" <<'EOF' |
| 74 | +#include <cstdio> |
| 75 | +import mathkit; |
| 76 | +int main(){ std::printf("ok=%d\n", mk::answer()); return 0; } |
| 77 | +EOF |
| 78 | +cat > "$TMP/app/mcpp.toml" <<EOF |
| 79 | +[package] |
| 80 | +name = "app" |
| 81 | +version = "0.1.0" |
| 82 | +[dependencies] |
| 83 | +mathkit = { path = "$PKG_HOST" } |
| 84 | +[targets.app] |
| 85 | +kind = "bin" |
| 86 | +main = "src/main.cpp" |
| 87 | +EOF |
| 88 | + |
| 89 | +# Baseline with the PR binary, so a failure below is attributable to the client |
| 90 | +# and not to the package. |
| 91 | +( cd "$TMP/app" && "$MCPP" run > new.log 2>&1 ) \ |
| 92 | + || { cat "$TMP/app/new.log"; echo "the PR binary could not consume its own package"; exit 1; } |
| 93 | +grep -q 'ok=42' "$TMP/app/new.log" || { cat "$TMP/app/new.log"; echo "wrong answer"; exit 1; } |
| 94 | + |
| 95 | +if [[ -n "${MCPP_BOOT:-}" && -x "${MCPP_BOOT}" ]] \ |
| 96 | + && [[ "$("$MCPP_BOOT" --version 2>/dev/null)" != "$("$MCPP" --version 2>/dev/null)" ]]; then |
| 97 | + echo "old client: $("$MCPP_BOOT" --version)" |
| 98 | + rm -rf "$TMP/app/target" |
| 99 | + ( cd "$TMP/app" && "$MCPP_BOOT" run > old.log 2>&1 ) || { |
| 100 | + cat "$TMP/app/old.log" |
| 101 | + echo "FAIL: the released mcpp cannot build against a package this one produced." |
| 102 | + echo " The compatibility claim in docs/12 is then false: such packages" |
| 103 | + echo " need a version floor, and publishing one without it bricks older" |
| 104 | + echo " clients rather than degrading them." |
| 105 | + exit 1; } |
| 106 | + grep -q 'ok=42' "$TMP/app/old.log" || { |
| 107 | + cat "$TMP/app/old.log"; echo "the old client built it but ran it wrong"; exit 1; } |
| 108 | + echo "PASS: a released mcpp builds and runs against a package from this one" |
| 109 | +else |
| 110 | + echo "NOTE: \$MCPP_BOOT is unset or identical to \$MCPP — the real old-client" |
| 111 | + echo " check did not run here. The static section-vocabulary check did." |
| 112 | + echo "PASS: the generated manifest introduces no section an older mcpp cannot read" |
| 113 | +fi |
0 commit comments