Skip to content

Commit df85b5c

Browse files
committed
fix: strip the shipped payload and mirror assets concurrently (2026.7.29.1)
The GitCode mirror leg of `publish-ecosystem` has failed four releases in a row — 0.0.94 / 0.0.97 / 0.0.105 / 2026.7.28.2 — each time on the biggest tarballs, each time costing ~5 minutes of manual `gtc release upload`. The per-asset 180s cap was tuned three times without anyone measuring what it was capping. Probe PR #301 measured it: GitHub US runner -> file.gitcode.com 0.012 MB/s <- the failing path same runner <- file.gitcode.com 3.87 MB/s same runner -> github.com 16 MB/s mainland-CN host -> file.gitcode.com 1.84 MB/s file.gitcode.com is a single Huawei Cloud origin in Beijing, so it is the inbound-to-CN direction that is shaped — not the host (the same runner downloads from it at 3.87 MB/s), not the client (curl and urllib measure identically), and not the runner's egress (16 MB/s to GitHub). The rate also swings ~4.6x run to run, so no fixed per-asset cap can be both safe and useful. At 0.012 MB/s a 34.8MB asset needs ~45 minutes; 180s never had a chance. Two fixes, both measured rather than guessed. S1 — stop shipping 30MB of debug info. Of the 34.8MB linux-x86_64 tarball, almost none was mcpp: the vendored `registry/bin/xlings` shipped at 97.3MB with full debug info (86.9MB on aarch64) against a 4.3MB mcpp. Stripping both takes the tarball to 4.62MB — 7.5x — and the stripped binaries were verified working (`--version`, `new`, `build`, `run`). Two defects made this possible: * the vendored xlings was never stripped at any of the four injection sites, only `cp`'d; * the x86_64 `strip` that DID exist ran before `mcpp pack`, which rebuilds the binary and overwrote it — which is why every release up to and including 2026.7.28.2 shipped an unstripped bin/mcpp while aarch64 (which stages by hand) shipped a stripped one. tools/slim_linux_payload.sh now strips both binaries on the STAGED payload and ASSERTS the result, so a strip that silently stops working fails the release instead of quietly shipping a fat tarball again. The aarch64 leg's best-effort `|| true` strip is now a hard requirement. macOS and Windows are deliberately left alone: their payloads are already 6.1MB / 4.2MB, and stripping a Mach-O invalidates its ad-hoc signature. S2 — mirror a host's assets concurrently, and bound the LEG instead of each asset. The shaping is per-connection: 1/4/8 concurrent 1MB uploads took 76s/80s/93s wall (6.6x aggregate at N=8). Assets within a leg now upload in parallel under MIRROR_MAX_PARALLEL, and MIRROR_UPLOAD_TIMEOUT is replaced by MIRROR_LEG_DEADLINE_GH/GTC. The v0.0.90 rule still holds — nothing is killed and retried inside a round, because the presigned OBS PUT has no multipart/resume (confirmed in #301); exhausting the budget ends the leg and the completeness gate fails loudly as before. Together the gitcode leg goes from ~45 min plus a manual step to ~6 min unattended, and every user's download shrinks 7.5x. Verified locally: slim on the real 2026.7.28.2 payload (101.6MB -> 13.9MB of binaries, 34.81MB -> 4.62MB tarball, stripped mcpp builds and runs an `import std` project); an isolated harness for the concurrent launcher (bounded concurrency, ordered log replay, correct per-asset success/failure); a full real mirror_res.sh run against the existing 2026.7.28.2 tag (all skipped, gate 16/16); and a real parallel-upload run on a throwaway `0.0.0-mirrortest` tag against BOTH hosts (gate 8/8, exit 0), whose artifacts were deleted afterwards with the real release verified intact. NB: release.yml is not exercised by PR CI — it runs on tag/dispatch only — so S1 lands its first real proof at the next release, where the new assertions fail loudly rather than silently.
1 parent f6ef9b9 commit df85b5c

5 files changed

Lines changed: 192 additions & 47 deletions

File tree

.github/tools/mirror_res.sh

Lines changed: 100 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -42,24 +42,39 @@ read -r -a ASSETS <<< "${ASSETS:-$DEFAULT_ASSETS}"
4242

4343
info() { echo "[mirror] $*"; }
4444

45-
# Per-asset upload cap. Exceeding it WARNs and abandons that asset (no retry,
46-
# no delete) so one slow cross-border PUT can't eat the whole job budget — the
47-
# v0.0.94 release lost `publish-ecosystem` to a 30min job timeout with zero
48-
# per-asset visibility, and the two biggest linux tarballs had to be pushed by
49-
# hand afterwards.
45+
# ── Budget: one deadline per HOST LEG, not per asset ────────────────────────
46+
# The old per-asset cap (180s, abandon-on-expiry) failed four releases in a row
47+
# — 0.0.94 / 0.0.97 / 0.0.105 / 2026.7.28.2 — and was tuned three times without
48+
# anyone measuring what it was capping. Measured (probe PR #301):
5049
#
51-
# CAUTION (v0.0.90 postmortem, still binding): a cap that kills a
52-
# slow-but-PROGRESSING upload and then RETRIES it is strictly worse than no cap
53-
# — every restart resumes from byte zero and the mirror never converges. This
54-
# cap is safe only because a capped asset is SKIPPED, never re-uploaded. The
55-
# completeness gate at the bottom is still the pass/fail, so a skipped asset
56-
# fails the release loudly instead of silently shipping a half mirror.
50+
# GitHub US runner -> file.gitcode.com 0.012 MB/s <- the failing path
51+
# same runner <- file.gitcode.com 3.87 MB/s
52+
# same runner -> github.com 16 MB/s
53+
# mainland-CN host -> file.gitcode.com 1.84 MB/s
5754
#
58-
# Sizing: mcpp's largest asset is ~30MB (no package exceeds 100MB). 180s is a
59-
# generous ceiling for that — an upload still running at 3min is not "slow", it
60-
# is stuck, and the right move is to stop paying CI for it and push that one
61-
# asset by hand (the gate below prints the exact command).
62-
: "${MIRROR_UPLOAD_TIMEOUT:=180}"
55+
# file.gitcode.com is a single Huawei Cloud origin in Beijing; it is the
56+
# inbound-to-CN direction that is shaped, not the host and not the client
57+
# (curl and urllib measure identically). At 0.012 MB/s a 34.8MB asset needs
58+
# ~45 MINUTES, so no per-asset value in the 180s neighbourhood was ever going
59+
# to work. The rate also varies ~4.6x run to run (0.011-0.051 MB/s), which
60+
# rules out ANY fixed per-asset cap being simultaneously safe and useful.
61+
#
62+
# So: bound the LEG, and let each upload have whatever is left of it. A leg
63+
# that overruns fails the completeness gate exactly as before.
64+
#
65+
# The v0.0.90 rule still binds: never kill a slow-but-PROGRESSING upload and
66+
# then retry it — a restart resumes from byte zero (the presigned OBS PUT has
67+
# no multipart/resume; see #301). Here nothing is killed and retried within a
68+
# round: the deadline ends the leg.
69+
: "${MIRROR_LEG_DEADLINE_GH:=600}" # github is fast; 10min is already absurd
70+
: "${MIRROR_LEG_DEADLINE_GTC:=2400}" # gitcode: shaped inbound, needs headroom
71+
72+
# Assets are uploaded CONCURRENTLY within a leg. Measured on the same probe:
73+
# the shaping is per-CONNECTION, so concurrency scales almost linearly —
74+
# 1/4/8 concurrent 1MB uploads took 76s/80s/93s wall (0.013/0.050/0.086 MB/s
75+
# aggregate, 6.6x at N=8). Concurrency does raise the error rate (one 502 in
76+
# the N=4 round), which the existing probe-then-reupload rounds absorb.
77+
: "${MIRROR_MAX_PARALLEL:=8}"
6378

6479
DL="$(mktemp -d)"; trap 'rm -rf "$DL"' EXIT
6580

@@ -70,24 +85,34 @@ human_size() { # path → e.g. 31.9MB
7085

7186
host_label() { [[ "$1" == gh ]] && echo github || echo gitcode; }
7287

73-
# Upload one asset under the cap, timing it. 0 = the command returned within
74-
# the cap (NOT proof it landed — gtc's exit code lies both ways, so the probe /
75-
# gate remains the only source of truth); 1 = the cap fired, asset abandoned.
76-
upload_asset() { # kind(gh|gtc) asset → 0 returned / 1 capped
77-
local kind="$1" a="$2" start elapsed rc=0 sz host
88+
# Upload one asset with whatever is left of the leg deadline, timing it.
89+
# 0 = the command returned inside the budget (NOT proof it landed — gtc's exit
90+
# code lies both ways, so the probe / gate remains the only source of truth);
91+
# 1 = the leg deadline fired.
92+
#
93+
# Runs in a SUBSHELL under the parallel launcher, so it must not rely on any
94+
# state surviving the call; everything it produces goes to stdout/stderr, which
95+
# the launcher collects per asset and replays in order.
96+
upload_asset() { # kind(gh|gtc) asset deadline_epoch → 0 returned / 1 out of budget
97+
local kind="$1" a="$2" deadline="$3" start elapsed rc=0 sz host budget
7898
sz=$(human_size "$DL/$a")
7999
host=$(host_label "$kind")
100+
budget=$(( deadline - SECONDS ))
101+
if (( budget <= 0 )); then
102+
info "WARN: $host $a ($sz) not attempted — leg deadline already spent"
103+
return 1
104+
fi
80105
start=$SECONDS
81106
if [[ "$kind" == gh ]]; then
82-
GH_TOKEN="${XLINGS_RES_TOKEN:-}" timeout "$MIRROR_UPLOAD_TIMEOUT" \
107+
GH_TOKEN="${XLINGS_RES_TOKEN:-}" timeout "$budget" \
83108
gh release upload "$VER" "$DL/$a" -R "$GH_DST" --clobber >/dev/null 2>&1 || rc=$?
84109
else
85-
timeout "$MIRROR_UPLOAD_TIMEOUT" gtc release upload "$GTC_DST" "$DL/$a" --tag "$VER" \
110+
timeout "$budget" gtc release upload "$GTC_DST" "$DL/$a" --tag "$VER" \
86111
>/dev/null 2>&1 || rc=$?
87112
fi
88113
elapsed=$((SECONDS - start))
89114
if [[ $rc == 124 || $rc == 137 ]]; then
90-
info "WARN: $host $a ($sz) exceeded the ${MIRROR_UPLOAD_TIMEOUT}s cap after ${elapsed}s — skipping (not retried; the verify gate below decides the release)"
115+
info "WARN: $host $a ($sz) hit the leg deadline after ${elapsed}s — abandoning (the verify gate below decides the release)"
91116
return 1
92117
fi
93118
info "$host $a ($sz) uploaded in ${elapsed}s"
@@ -146,39 +171,73 @@ verify_batch() { # base_url asset... → prints assets still not serving
146171
# - NEVER kill a slow-but-progressing upload AND RETRY IT. v0.0.90 wrapped
147172
# uploads in `timeout 300`, so every cross-border PUT >5min was SIGKILLed
148173
# at 60%% and restarted from byte zero — the 20min job ceiling fell to
149-
# this. MIRROR_UPLOAD_TIMEOUT keeps a cap but ABANDONS the asset instead of
150-
# retrying it, which is what makes the cap safe; the gate then fails the
151-
# release loudly rather than thrashing until the job is killed.
174+
# this. Nothing is killed-and-retried inside a round now: an upload gets
175+
# the remaining LEG budget, and exhausting it ends the leg.
152176
# - gtc's exit code lies both ways (obs_callback flakiness); the download
153-
# probe is the only source of truth.
154-
mirror_host() { # kind(gh|gtc) base_url
155-
local kind="$1" base="$2" try a
177+
# probe is the only source of truth. Measured mechanism (#301): the
178+
# presigned PUT carries an `x-obs-callback` header pointing at
179+
# api.gitcode.com; OBS stores the object and THEN calls back, so a failed
180+
# callback reports `code:400 ... EOF` for an upload that did land.
181+
#
182+
# Assets within a leg upload CONCURRENTLY (the shaping is per-connection —
183+
# see the deadline block at the top). Each upload runs in its own subshell
184+
# writing to its own log, which is replayed in asset order after the wait, so
185+
# concurrent progress lines don't interleave into unreadable soup.
186+
mirror_host() { # kind(gh|gtc) base_url deadline_seconds
187+
local kind="$1" base="$2" budget="$3" try a
156188
local pending failed
157-
local -A capped=()
189+
local -A lost=()
158190
local host_start=$SECONDS
191+
local deadline=$((SECONDS + budget))
159192
local host; host=$(host_label "$kind")
193+
local wdir; wdir=$(mktemp -d)
194+
info "$host leg budget ${budget}s, up to ${MIRROR_MAX_PARALLEL} concurrent uploads"
160195
for try in 1 2 3; do
161-
pending=()
196+
# Step 1: probe first — anything already serving is mirrored and must
197+
# never be re-uploaded.
198+
local todo=()
162199
for a in "${ASSETS[@]}"; do
163-
# A capped asset is never re-attempted (see MIRROR_UPLOAD_TIMEOUT).
164-
[[ -n "${capped[$a]:-}" ]] && continue
165-
# Step 1: already serving? then it's mirrored — never re-upload it.
200+
[[ -n "${lost[$a]:-}" ]] && continue
166201
if probe "${base}/${a}"; then
167202
[[ $try == 1 ]] && info "$host $a already mirrored, skipping"
168203
continue
169204
fi
170-
if upload_asset "$kind" "$a"; then
205+
todo+=("$a")
206+
done
207+
[[ ${#todo[@]} == 0 ]] && break
208+
209+
# Step 2: upload the missing ones concurrently, bounded by a live count of
210+
# running children (not a hand-kept counter — that mis-books as soon as one
211+
# finishes early). `if/else` around upload_asset so the subshell's own
212+
# errexit can't swallow the rc file.
213+
local i=0
214+
for a in "${todo[@]}"; do
215+
while (( $(jobs -rp | wc -l) >= MIRROR_MAX_PARALLEL )); do sleep 1; done
216+
( if upload_asset "$kind" "$a" "$deadline" >"$wdir/$i.log" 2>&1
217+
then echo 0; else echo 1; fi >"$wdir/$i.rc" ) &
218+
i=$((i + 1))
219+
done
220+
wait
221+
222+
pending=()
223+
for i in "${!todo[@]}"; do
224+
a="${todo[$i]}"
225+
cat "$wdir/$i.log" 2>/dev/null || true
226+
if [[ "$(cat "$wdir/$i.rc" 2>/dev/null || echo 1)" == 0 ]]; then
171227
pending+=("$a")
172228
else
173-
capped[$a]=1
229+
lost[$a]=1
174230
fi
231+
rm -f "$wdir/$i.log" "$wdir/$i.rc"
175232
done
233+
176234
[[ ${#pending[@]} == 0 ]] && break
177235
failed=$(verify_batch "$base" "${pending[@]}")
178236
[[ -z "$failed" ]] && break
179237
info "$host not serving after patience (try $try): $failed — re-uploading (no delete)"
180238
done
181-
((${#capped[@]})) && info "WARN: $host abandoned ${#capped[@]} asset(s) at the ${MIRROR_UPLOAD_TIMEOUT}s cap: ${!capped[*]}"
239+
rm -rf "$wdir"
240+
((${#lost[@]})) && info "WARN: $host abandoned ${#lost[@]} asset(s) at the ${budget}s leg deadline: ${!lost[*]}"
182241
info "$host mirror leg finished in $((SECONDS - host_start))s"
183242
return 0 # the completeness gate below is the real pass/fail
184243
}
@@ -205,11 +264,11 @@ fi
205264

206265
GH_PID=""; GTC_PID=""
207266
if [[ "$GH_ENABLED" == 1 ]]; then
208-
mirror_host gh "https://github.com/${GH_DST}/releases/download/${VER}" &
267+
mirror_host gh "https://github.com/${GH_DST}/releases/download/${VER}" "$MIRROR_LEG_DEADLINE_GH" &
209268
GH_PID=$!
210269
fi
211270
if [[ "$GTC_ENABLED" == 1 ]]; then
212-
mirror_host gtc "https://gitcode.com/${GTC_DST}/releases/download/${VER}" &
271+
mirror_host gtc "https://gitcode.com/${GTC_DST}/releases/download/${VER}" "$MIRROR_LEG_DEADLINE_GTC" &
213272
GTC_PID=$!
214273
fi
215274
[[ -n "$GH_PID" ]] && wait "$GH_PID"
@@ -232,7 +291,7 @@ for host in "${hosts[@]}"; do
232291
done
233292
done
234293
if [[ $rc != 0 ]]; then
235-
echo "[mirror] hint: if the asset above was WARNed as capped at ${MIRROR_UPLOAD_TIMEOUT}s, either raise MIRROR_UPLOAD_TIMEOUT for this run or push it by hand:" >&2
294+
echo "[mirror] hint: if the asset above was WARNed at a leg deadline, raise MIRROR_LEG_DEADLINE_GH/GTC for this run, or push it by hand:" >&2
236295
echo "[mirror] gh release download v$VER -R $SRC_REPO -p '<asset>' && gtc release upload $GTC_DST '<asset>' --tag $VER" >&2
237296
fi
238297
[[ $rc == 0 ]] && info "all assets mirrored + verified on ${#hosts[@]} host(s) in ${SECONDS}s"
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#!/usr/bin/env bash
2+
# slim_linux_payload.sh — strip the shipped ELF binaries in a staged release
3+
# wrapper directory, then ASSERT the result.
4+
#
5+
# WHY. The linux tarballs were 34.8MB (x86_64) and 30.2MB (aarch64) while the
6+
# macOS one was 6.1MB and the Windows one 4.2MB. Almost none of that was mcpp:
7+
# the vendored `registry/bin/xlings` shipped at 97.3MB / 86.9MB with full debug
8+
# info, against a 4.3MB / 2.8MB mcpp. Stripping both takes the x86_64 tarball
9+
# from 34.81MB to 4.62MB — 7.5x — with the stripped binaries verified working
10+
# (`--version`, `new`, `build`, `run`).
11+
#
12+
# That size is what makes the GitCode mirror leg fail: measured upload from a
13+
# GitHub-hosted US runner to file.gitcode.com (a single Huawei Cloud origin in
14+
# Beijing) is ~0.012 MB/s in the inbound-to-CN direction — the SAME runner
15+
# downloads from that host at 3.87 MB/s and uploads to GitHub at 16 MB/s, and a
16+
# mainland-CN host reaches 1.84 MB/s to the same endpoint. At 0.012 MB/s a
17+
# 34.8MB asset needs ~45 minutes; at 4.6MB it needs ~6.
18+
#
19+
# ASSERT, don't trust. release.yml already stripped the x86_64 mcpp before
20+
# packing — and shipped it unstripped anyway, because `mcpp pack` rebuilds the
21+
# binary and overwrites the stripped one. A `strip` whose effect is never
22+
# checked is a comment, not a step. Hence: this runs on the STAGED payload
23+
# (after pack, before tar) and fails loudly if anything is still unstripped.
24+
#
25+
# Linux only, on purpose. The macOS and Windows payloads are already small, and
26+
# stripping a Mach-O invalidates its (ad-hoc) code signature — not worth the
27+
# risk for ~2MB when 100% of the mirror problem is the two linux tarballs.
28+
#
29+
# Usage: slim_linux_payload.sh <wrapper-dir> [strip-cmd]
30+
set -euo pipefail
31+
32+
DIR="${1:?usage: slim_linux_payload.sh <wrapper-dir> [strip-cmd]}"
33+
STRIP="${2:-strip}"
34+
35+
[ -d "$DIR" ] || { echo "slim: no such directory: $DIR" >&2; exit 1; }
36+
command -v "$STRIP" >/dev/null 2>&1 || [ -x "$STRIP" ] || {
37+
echo "slim: strip tool not usable: $STRIP" >&2; exit 1; }
38+
39+
size_of() { stat -c %s "$1" 2>/dev/null || stat -f %z "$1"; }
40+
mb() { awk -v b="$1" 'BEGIN{printf "%.1f", b/1048576}'; }
41+
42+
# The two binaries the release actually ships. Both are static ELFs; a missing
43+
# one is not an error (the aarch64 leg skips xlings when the upstream download
44+
# fails, and that has its own handling), but a PRESENT one must end up stripped.
45+
total_before=0 total_after=0 found=0
46+
for rel in bin/mcpp registry/bin/xlings; do
47+
f="$DIR/$rel"
48+
[ -f "$f" ] || { echo "slim: $rel absent, skipping"; continue; }
49+
found=$((found + 1))
50+
before=$(size_of "$f")
51+
"$STRIP" --strip-unneeded "$f" 2>/dev/null || "$STRIP" "$f"
52+
after=$(size_of "$f")
53+
total_before=$((total_before + before))
54+
total_after=$((total_after + after))
55+
echo "slim: $rel $(mb "$before")MB -> $(mb "$after")MB"
56+
57+
# The assertion this script exists for.
58+
if file "$f" | grep -q 'not stripped'; then
59+
file "$f"
60+
echo "slim: FAIL: $rel is still not stripped after running $STRIP" >&2
61+
exit 1
62+
fi
63+
# A stripped binary that no longer runs is worse than a fat one. The callers
64+
# smoke-test the packaged tarball too, but catch it here where the failure
65+
# names the file.
66+
[ -x "$f" ] || { echo "slim: FAIL: $rel lost its exec bit" >&2; exit 1; }
67+
done
68+
69+
[ "$found" -gt 0 ] || { echo "slim: FAIL: no shippable binary found under $DIR" >&2; exit 1; }
70+
echo "slim: payload $(mb "$total_before")MB -> $(mb "$total_after")MB across $found binary(ies)"

.github/workflows/release.yml

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,11 @@ jobs:
132132
ARTIFACT=$(find target/x86_64-linux-musl -type f -name mcpp | head -1)
133133
test -n "$ARTIFACT"
134134
file "$ARTIFACT" | grep -q 'statically linked'
135-
# Strip — debug info on a static ELF balloons it ~7×.
136-
strip "$ARTIFACT"
135+
# NB: stripping here is pointless — `mcpp pack` below rebuilds the
136+
# binary and overwrites it, which is why every release up to and
137+
# including 2026.7.28.2 shipped an UNSTRIPPED bin/mcpp despite the
138+
# strip that used to live on this line. The payload is slimmed after
139+
# packing instead, in the inject step below.
137140
138141
# Pack with the freshly-built mcpp (not the bootstrap) so any
139142
# fixes to the pack code path are exercised in the same release
@@ -156,6 +159,10 @@ jobs:
156159
mkdir -p "$INJECT/$WRAPPER/registry/bin"
157160
cp "$XLINGS_BIN" "$INJECT/$WRAPPER/registry/bin/xlings"
158161
chmod +x "$INJECT/$WRAPPER/registry/bin/xlings"
162+
# Slim AFTER pack (pack rebuilds bin/mcpp) and BEFORE tar. Asserts
163+
# the result, so a strip that silently stops working fails the
164+
# release instead of quietly shipping a 34.8MB tarball again.
165+
bash .github/tools/slim_linux_payload.sh "$INJECT/$WRAPPER"
159166
(cd "$INJECT" && tar -czf "$GITHUB_WORKSPACE/${TARBALL}" "$WRAPPER")
160167
rm -rf "$INJECT"
161168
@@ -330,9 +337,15 @@ jobs:
330337
STAGING=$(mktemp -d)
331338
mkdir -p "$STAGING/$WRAPPER/bin"
332339
cp "$MCPP_AARCH64" "$STAGING/$WRAPPER/bin/mcpp"
333-
# Strip with the cross toolchain's strip if present (binary is aarch64).
340+
# The binary is aarch64, so the host x86_64 `strip` cannot touch it —
341+
# resolve a cross-capable one. Required, not best-effort: the old
342+
# `|| true` here meant a missing tool silently shipped a fat tarball.
343+
# (slim_linux_payload.sh runs after xlings is staged, below.)
334344
STRIP=$(find "$HOME/.mcpp" -name 'aarch64-linux-musl-strip' -type f 2>/dev/null | head -1)
335-
[ -n "$STRIP" ] && "$STRIP" "$STAGING/$WRAPPER/bin/mcpp" 2>/dev/null || true
345+
[ -n "$STRIP" ] || STRIP=$(command -v llvm-strip 2>/dev/null || true)
346+
[ -n "$STRIP" ] || STRIP=$(command -v aarch64-linux-gnu-strip 2>/dev/null || true)
347+
[ -n "$STRIP" ] || { echo "no aarch64-capable strip found"; exit 1; }
348+
echo "aarch64 strip: $STRIP"
336349
cp LICENSE "$STAGING/$WRAPPER/" 2>/dev/null || true
337350
cp README.md "$STAGING/$WRAPPER/" 2>/dev/null || true
338351
cat > "$STAGING/$WRAPPER/mcpp" << 'LAUNCHER'
@@ -356,6 +369,9 @@ jobs:
356369
chmod +x "$STAGING/$WRAPPER/registry/bin/xlings"
357370
fi
358371
fi
372+
# Slim both shipped ELFs with the cross strip resolved above, and
373+
# assert the result (the vendored xlings was 86.9MB unstripped here).
374+
bash .github/tools/slim_linux_payload.sh "$STAGING/$WRAPPER" "$STRIP"
359375
mkdir -p dist
360376
(cd "$STAGING" && tar -czf "$GITHUB_WORKSPACE/dist/${TARBALL_NAME}" "$WRAPPER")
361377
cp "dist/${TARBALL_NAME}" "dist/mcpp-linux-aarch64.tar.gz"

mcpp.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mcpp"
3-
version = "2026.7.28.2"
3+
version = "2026.7.29.1"
44
description = "Modern C++ build & package management tool"
55
license = "Apache-2.0"
66
authors = ["mcpp-community"]

src/toolchain/fingerprint.cppm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import mcpp.toolchain.detect;
1818

1919
export namespace mcpp::toolchain {
2020

21-
inline constexpr std::string_view MCPP_VERSION = "2026.7.28.2";
21+
inline constexpr std::string_view MCPP_VERSION = "2026.7.29.1";
2222

2323
struct FingerprintInputs {
2424
Toolchain toolchain;

0 commit comments

Comments
 (0)