|
| 1 | +#!/usr/bin/env bash |
| 2 | +# upgrade_lane.sh <variant> <new-mcpp> <new-xlings> -- a home the previous |
| 3 | +# release made keeps working under this commit's mcpp (design 2026-10-17 D9). |
| 4 | +# |
| 5 | +# Every other lane starts from an empty home, so the installed packaging |
| 6 | +# revision always equals the index's and an upgrade is never executed. A user |
| 7 | +# upgraded to 2026.10.16.3 and every `mcpp run` failed from then on; nothing |
| 8 | +# in CI could see it. This lane builds the home with the PREVIOUS RELEASE -- |
| 9 | +# the newest published release that is not this commit's version, read from |
| 10 | +# GitHub, so it moves forward with every release -- and then runs this |
| 11 | +# commit's mcpp in it, laid out as a release (bin/mcpp, registry/bin/xlings). |
| 12 | +# |
| 13 | +# variant: |
| 14 | +# plain the previous release installs from today's index. |
| 15 | +# snapshot the previous release installs from the xim index of 2026-10-07 |
| 16 | +# (xim-pkgindex ebf1fbb): payloads genuinely of older packaging |
| 17 | +# revisions (llvm revision 0, gcc 16.1.0 before -r1). |
| 18 | +# orphan every payload recorded at another revision, and `c++` active at |
| 19 | +# gcc's version whose registration is gone (a reported home). |
| 20 | +# |
| 21 | +# Asserted, every variant: the first `mcpp new && mcpp run` of the new mcpp |
| 22 | +# succeeds and downloads nothing; a second run likewise; `mcpp self doctor` |
| 23 | +# in the project installs nothing and names the toolchain the build uses. |
| 24 | +# snapshot: `mcpp index update` names the newer packagings, the run after it |
| 25 | +# still downloads nothing, and `mcpp toolchain list` offers the new one. |
| 26 | +# orphan: `mcpp toolchain install` replaces the payload, and the dangling |
| 27 | +# record is reported as repaired instead of refusing the replacement. |
| 28 | +set -uo pipefail |
| 29 | +VARIANT="${1:?variant}" |
| 30 | +NEW_MCPP="${2:?new mcpp binary}" |
| 31 | +NEW_XLINGS="${3:?new xlings binary}" |
| 32 | +SNAPSHOT=ebf1fbb3417ec513b923279044433887b3af8850 |
| 33 | +TC=llvm@23.1.3 |
| 34 | + |
| 35 | +unset MCPP_VENDORED_XLINGS MCPP MCPP_FRESH MCPP_BOOT XLINGS_HOME XLINGS_PROJECT_DIR XLINGS_ACTIVE_SUBOS |
| 36 | +W="${RUNNER_TEMP:-/tmp}/upgrade-lane" |
| 37 | +rm -rf "$W"; mkdir -p "$W" |
| 38 | +export MCPP_HOME="$W/home" |
| 39 | +REG="$MCPP_HOME/registry" |
| 40 | +PY=$(command -v python3 || command -v python) |
| 41 | +FAILED=0 |
| 42 | +fail() { echo "::error::upgrade lane ($VARIANT): $*"; FAILED=1; } |
| 43 | + |
| 44 | +case "$(uname -s)-$(uname -m)" in |
| 45 | + Linux-x86_64) PLAT=linux-x86_64; EXT=tar.gz; EXE= ;; |
| 46 | + Linux-aarch64) PLAT=linux-aarch64; EXT=tar.gz; EXE= ;; |
| 47 | + Darwin-arm64) PLAT=macosx-arm64; EXT=tar.gz; EXE= ;; |
| 48 | + MINGW*|MSYS*) PLAT=windows-x86_64; EXT=zip; EXE=.exe ;; |
| 49 | + *) echo "::error::unknown platform $(uname -a)"; exit 1 ;; |
| 50 | +esac |
| 51 | +IS_LINUX=0; [[ $PLAT == linux-* ]] && IS_LINUX=1 |
| 52 | + |
| 53 | +# ── the previous release ─────────────────────────────────────────────── |
| 54 | +CURRENT=$(sed -n 's/^version *= *"\([^"]*\)".*/\1/p' mcpp.toml | head -1) |
| 55 | +OLD_VER=$(curl -fsSL ${GH_TOKEN:+-H "Authorization: Bearer $GH_TOKEN"} \ |
| 56 | + "https://api.github.com/repos/mcpp-community/mcpp/releases?per_page=20" \ |
| 57 | + | "$PY" -c ' |
| 58 | +import json, sys |
| 59 | +current = sys.argv[1] |
| 60 | +for r in json.load(sys.stdin): |
| 61 | + v = r["tag_name"].lstrip("v") |
| 62 | + if not r["draft"] and not r["prerelease"] and v != current: |
| 63 | + print(v); break |
| 64 | +' "$CURRENT") |
| 65 | +case "$OLD_VER" in ''|*[!0-9.]*) echo "::error::no previous release resolved (got '$OLD_VER')"; exit 1 ;; esac |
| 66 | +echo "previous release: $OLD_VER; this commit: $CURRENT ($PLAT, variant $VARIANT)" |
| 67 | + |
| 68 | +name="mcpp-$OLD_VER-$PLAT.$EXT" |
| 69 | +bash .github/tools/fetch_release.sh \ |
| 70 | + "https://github.com/mcpp-community/mcpp/releases/download/v$OLD_VER/$name" "$W/$name" || exit 1 |
| 71 | +mkdir -p "$W/x" |
| 72 | +if [[ $EXT == zip ]]; then |
| 73 | + "$PY" -c "import zipfile,sys; zipfile.ZipFile(sys.argv[1]).extractall(sys.argv[2])" "$W/$name" "$W/x" |
| 74 | +else |
| 75 | + tar xzf "$W/$name" -C "$W/x" |
| 76 | +fi |
| 77 | +mv "$W/x/mcpp-$OLD_VER-$PLAT" "$W/old" |
| 78 | +OLD="$W/old/bin/mcpp$EXE" |
| 79 | +mkdir -p "$W/new/bin" "$W/new/registry/bin" |
| 80 | +cp "$NEW_MCPP" "$W/new/bin/mcpp$EXE" |
| 81 | +cp "$NEW_XLINGS" "$W/new/registry/bin/xlings$EXE" |
| 82 | +chmod +x "$OLD" "$W/old/registry/bin/xlings$EXE" "$W/new/bin/mcpp$EXE" "$W/new/registry/bin/xlings$EXE" 2>/dev/null |
| 83 | +NEW="$W/new/bin/mcpp$EXE" |
| 84 | +"$OLD" --version; "$NEW" --version |
| 85 | + |
| 86 | +# run <label> <dir> <bin> args...: the log is kept; returns the exit code. |
| 87 | +run() { |
| 88 | + local label=$1 dir=$2 bin=$3; shift 3 |
| 89 | + echo "::group::$label: (cd $dir && mcpp $*)" |
| 90 | + (cd "$dir" && "$bin" "$@") > "$W/$label.log" 2>&1 |
| 91 | + local rc=$? |
| 92 | + sed -e 's/\x1b\[[0-9;]*[A-Za-z]//g' "$W/$label.log" | tr '\r' '\n' | grep -v '^\s*Planning ' | tail -60 |
| 93 | + echo "--> rc=$rc" |
| 94 | + echo "::endgroup::" |
| 95 | + return $rc |
| 96 | +} |
| 97 | +downloads() { grep -o "Downloading [^ ]*" "$W/$1.log" | sort -u | tr '\n' ' '; } |
| 98 | +new_project() { # <dir> <bin> |
| 99 | + (cd "$W" && "$2" new "$1" > /dev/null 2>&1) || mkdir -p "$W/$1" |
| 100 | + printf '\n[toolchain]\ndefault = "%s"\n' "$TC" >> "$W/$1/mcpp.toml" |
| 101 | +} |
| 102 | + |
| 103 | +# ── the previous release builds the home ─────────────────────────────── |
| 104 | +run old-env "$W" "$OLD" self env || fail "the previous release does not start" |
| 105 | +if [ "$VARIANT" = snapshot ]; then |
| 106 | + git clone -q --filter=blob:none https://github.com/openxlings/xim-pkgindex "$W/xpi" \ |
| 107 | + && git -C "$W/xpi" checkout -q "$SNAPSHOT" || { echo "::error::cannot fetch the snapshot index"; exit 1; } |
| 108 | + idx="$REG/data/xim-pkgindex" |
| 109 | + [ -d "$idx" ] || idx=$(find "$MCPP_HOME" -type d -name xim-pkgindex | head -1) |
| 110 | + find "$idx" -mindepth 1 -maxdepth 1 ! -name .xlings-index-version -exec rm -rf {} + |
| 111 | + (cd "$W/xpi" && git archive HEAD) | tar x -C "$idx" |
| 112 | + echo "llvm.lua revisions now: $(grep -o 'revision = [0-9]*' "$idx/pkgs/l/llvm.lua" | sort -u | tr '\n' ' ')" |
| 113 | +fi |
| 114 | +run old-tc "$W" "$OLD" toolchain install "$TC" || fail "the previous release cannot install $TC" |
| 115 | +[ "$VARIANT" = orphan ] && { run old-tc-gcc "$W" "$OLD" toolchain install gcc@16.1.0 || fail "the previous release cannot install gcc"; } |
| 116 | +new_project p-old "$OLD" |
| 117 | +run old-run "$W/p-old" "$OLD" run || fail "the previous release cannot run a new project" |
| 118 | + |
| 119 | +if [ "$VARIANT" = orphan ]; then |
| 120 | + "$PY" - "$REG" <<'PY' || fail "could not shape the orphan home" |
| 121 | +import glob, json, os, sys |
| 122 | +reg = sys.argv[1] |
| 123 | +for f in glob.glob(os.path.join(reg, "data", "xpkgs", "xim-x-*", "*", ".xpkg-install.json")): |
| 124 | + d = json.load(open(f)); r = d.get("revision", 0) |
| 125 | + d["revision"] = 1 if r == 0 else r - 1 |
| 126 | + json.dump(d, open(f, "w"), indent=2) |
| 127 | +wsf = os.path.join(reg, "subos", "default", ".xlings.json") |
| 128 | +wsdoc = json.load(open(wsf)); ws = wsdoc["workspace"] |
| 129 | +dbf = os.path.join(reg, "data", "versions.json") |
| 130 | +src = json.load(open(dbf))["versions"] if os.path.exists(dbf) else json.load(open(os.path.join(reg, ".xlings.json")))["versions"] |
| 131 | +owners = {k: (v.get("bindingGroup") or {}).get("provider") for k, v in src.get("c++", {}).get("versions", {}).items()} |
| 132 | +keys = [k for k, p in owners.items() if p and "gcc" in p] |
| 133 | +if not keys: sys.exit("no gcc-owned c++ registration: %r" % owners) |
| 134 | +key = keys[0] |
| 135 | +entry = ws.get("c++") |
| 136 | +if isinstance(entry, dict): entry["active"] = key |
| 137 | +else: ws["c++"] = key |
| 138 | +json.dump(wsdoc, open(wsf, "w")) |
| 139 | +for path in (dbf, os.path.join(reg, ".xlings.json")): |
| 140 | + if not os.path.exists(path): continue |
| 141 | + doc = json.load(open(path)); c = doc.get("versions", {}).get("c++", {}) |
| 142 | + c.get("versions", {}).pop(key, None) |
| 143 | + for edges in c.get("bindings", {}).values(): edges.pop(key, None) |
| 144 | + json.dump(doc, open(path, "w")) |
| 145 | +print("c++ active at %s, its registration removed" % key) |
| 146 | +PY |
| 147 | +fi |
| 148 | + |
| 149 | +# ── this commit's mcpp in that home ──────────────────────────────────── |
| 150 | +if [ "$VARIANT" = snapshot ]; then |
| 151 | + run new-run-on-old-index "$W/p-old" "$NEW" run || fail "the old project does not run on the old index" |
| 152 | + [ -z "$(downloads new-run-on-old-index)" ] || fail "the run on the old index downloaded: $(downloads new-run-on-old-index)" |
| 153 | + run new-index-update "$W" "$NEW" index update || fail "index update failed" |
| 154 | + grep -q "newer packagings" "$W/new-index-update.log" \ |
| 155 | + || fail "index update does not name the newer packagings of installed payloads" |
| 156 | +fi |
| 157 | +new_project p-new "$NEW" |
| 158 | +run new-run1 "$W/p-new" "$NEW" run || fail "the first run of the new mcpp failed" |
| 159 | +[ -z "$(downloads new-run1)" ] || fail "the first run downloaded: $(downloads new-run1)" |
| 160 | +# A payload replaced names its package (`Updating llvm@23.1.3 packaging |
| 161 | +# revision 0 → 1`); the vendored xlings update is not one. |
| 162 | +grep -Eq "^\s*(Updating|Reinstalling) [^ ]+@" "$W/new-run1.log" && fail "the first run replaced a payload" |
| 163 | +run new-run2 "$W/p-new" "$NEW" run || fail "the second run failed" |
| 164 | +[ -z "$(downloads new-run2)" ] || fail "the second run downloaded: $(downloads new-run2)" |
| 165 | +run new-run-old-project "$W/p-old" "$NEW" run || fail "the project the previous release made does not run" |
| 166 | +[ -z "$(downloads new-run-old-project)" ] || fail "the old project's run downloaded: $(downloads new-run-old-project)" |
| 167 | + |
| 168 | +run new-doctor "$W/p-new" "$NEW" self doctor |
| 169 | +[ -z "$(downloads new-doctor)" ] || fail "doctor downloaded: $(downloads new-doctor)" |
| 170 | +grep -q "what a build here uses" "$W/new-doctor.log" || fail "doctor does not name the toolchain the build uses" |
| 171 | + |
| 172 | +if [ "$VARIANT" = snapshot ]; then |
| 173 | + run new-toolchain-list "$W" "$NEW" toolchain list || fail "toolchain list failed" |
| 174 | + grep -q "packaging revision .* available" "$W/new-toolchain-list.log" \ |
| 175 | + || fail "toolchain list does not offer the newer packaging" |
| 176 | +fi |
| 177 | +if [ "$VARIANT" = orphan ]; then |
| 178 | + run new-provision "$W" "$NEW" toolchain install "$TC" || fail "toolchain install could not replace the payload" |
| 179 | + grep -q "installed asset registration missing" "$W/new-provision.log" \ |
| 180 | + && fail "the replacement was refused by the dangling record" |
| 181 | + grep -qi "deactivated\|dangling" "$W/new-provision.log" \ |
| 182 | + || fail "the repaired record is not reported" |
| 183 | + run new-run-after-provision "$W/p-new" "$NEW" run || fail "the run after the replacement failed" |
| 184 | +fi |
| 185 | + |
| 186 | +{ |
| 187 | + echo "### upgrade lane: $PLAT / $VARIANT ($OLD_VER → $CURRENT)" |
| 188 | + echo |
| 189 | + for l in "$W"/*.log; do |
| 190 | + b=$(basename "$l" .log) |
| 191 | + echo "- \`$b\`: downloads [$(downloads "$b")]" |
| 192 | + done |
| 193 | +} >> "${GITHUB_STEP_SUMMARY:-/dev/null}" |
| 194 | +mkdir -p "${GITHUB_WORKSPACE:-.}/upgrade-lane-logs" |
| 195 | +cp "$W"/*.log "${GITHUB_WORKSPACE:-.}/upgrade-lane-logs/" 2>/dev/null || true |
| 196 | +[ "$FAILED" = 0 ] && echo "PASS: upgrade lane ($VARIANT)" |
| 197 | +exit "$FAILED" |
0 commit comments