Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
5b386c6
test: the target rows' equivalence snapshot, written by the code befo…
Sunrisepeak Nov 8, 2026
6491b8c
target rows and providers are data: data/builtin.toml, compiled by bu…
Sunrisepeak Nov 8, 2026
762419c
test: the unit tests are mtest's
Sunrisepeak Nov 8, 2026
8355099
target: the rows a process knows and the selection of SPEC-016 §4
Sunrisepeak Nov 8, 2026
55584d0
docs: the target-model design, with the test and build infrastructure…
Sunrisepeak Nov 8, 2026
f47ed71
docs: T5 and T6 confirmed
Sunrisepeak Nov 8, 2026
a2601da
target: prepare selects the identity and its provider
Sunrisepeak Nov 8, 2026
058f21d
target: the identity follows the C library; the selection says why a …
Sunrisepeak Nov 8, 2026
43ac273
target: a platform defines rows and providers (mcpp#911 G1, G3, G6)
Sunrisepeak Nov 8, 2026
2c81a77
target: a platform's compiler driver links any row; an artifact for a…
Sunrisepeak Nov 8, 2026
fab7b9f
target: the row's defaults are replaced by name; why target, why flag…
Sunrisepeak Nov 8, 2026
dd9ceb7
pack: generated manifests quote target names; the platform's artifact…
Sunrisepeak Nov 8, 2026
e6ffe8a
target: clang pairs with the MinGW-w64 payload (SPEC-016 §6)
Sunrisepeak Nov 8, 2026
d6d351a
a required capability takes its package's default provider; tests use…
Sunrisepeak Nov 8, 2026
da989b1
a patch names files from the package's root; mcpp patch edit and save…
Sunrisepeak Nov 8, 2026
17c929d
an artifact compiles the shared sources it links with its own flags (…
Sunrisepeak Nov 8, 2026
4a3cbe4
pack: a row a platform defines is packed and carries as a built-in ro…
Sunrisepeak Nov 8, 2026
a3a24db
run: an artifact for another system is built, not run, until a runner…
Sunrisepeak Nov 8, 2026
af4f204
references: a git reference follows the default branch; git+ on the c…
Sunrisepeak Nov 9, 2026
cc06741
mcpp new --template platform; mcpp check platform (SPEC-016 §2, §3)
Sunrisepeak Nov 9, 2026
bdafae6
2026.11.9.1: version and CHANGELOG
Sunrisepeak Nov 9, 2026
8db3d12
test: a test keeps what it reaches (design 2026-11-09 §17.2)
Sunrisepeak Nov 9, 2026
45bfae9
test: a protocol program runs as a process per file of its cases; mcp…
Sunrisepeak Nov 9, 2026
1b60252
target: a row's defaults are not replaced in place (mcpp#911 G2, revi…
Sunrisepeak Nov 9, 2026
1fa0939
SPEC-016; specs and docs of the round; CI findings fixed
Sunrisepeak Nov 9, 2026
21537b6
mcpp why platform and reading-time checks replace mcpp check; a patch…
Sunrisepeak Nov 9, 2026
cd18669
test: an archive's tests run by mcpp test's own runner; the bootstrap…
Sunrisepeak Nov 9, 2026
a31c762
fix: a cross target's toolchain section is not the build program's; a…
Sunrisepeak Nov 9, 2026
472829a
graph: the package being built stands for itself in its tests' graph
Sunrisepeak Nov 9, 2026
c382ae0
test: a pattern names a part of a program as the run names it; the Wi…
Sunrisepeak Nov 9, 2026
2fc69da
manifest: a provider's rows as a sorted vector (two loop levels; mcpp…
Sunrisepeak Nov 9, 2026
82e4201
docs(design): the round's later decisions in §19
Sunrisepeak Nov 9, 2026
8040480
ci: a release asset whose web address fails is fetched by the API; th…
Sunrisepeak Nov 9, 2026
c82c95c
pack: several programs, none named after the package: the first by na…
Sunrisepeak Nov 9, 2026
6e73227
ci: the e2e mtest engine is the xlings package xim:mtest, installed w…
Sunrisepeak Nov 9, 2026
77de8a4
ci: the pinned mcpp is installed with an index refresh per failed att…
Sunrisepeak Nov 9, 2026
a09b891
ci: xlings by GLOBAL, then by CN, then the release itself, for the bo…
Sunrisepeak Nov 9, 2026
bdacf39
test: e2e 298 overrides only a row its convention serves on this host
Sunrisepeak Nov 9, 2026
0a073b8
ci: a script the pr set leaves out is reported unaffected, and R5 acc…
Sunrisepeak Nov 9, 2026
3a0e411
refactor: the key graph and the locked targets leave their phase func…
Sunrisepeak Nov 9, 2026
7d3d48b
fix: a graph's C library leads a Mach-O image's link
Sunrisepeak Nov 9, 2026
55d5ad5
test: e2e 298 reads the driver's family and declares its skip when no…
Sunrisepeak Nov 9, 2026
e976f94
fix: a paired provider this host lacks is host-cannot-serve; the matr…
Sunrisepeak Nov 9, 2026
37b2ac1
chore: mcpp's tests and its skeleton take mtest 0.5.2
Sunrisepeak Nov 9, 2026
aa64db1
refactor: the convention's decision leaves phase5_toolchain_after_graph
Sunrisepeak Nov 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
413 changes: 413 additions & 0 deletions .agents/docs/2026-11-09-target-model-design.md

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion .agents/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ superseded_by: 2026-09-07-....md # when status is superseded
---
```

355 records.
356 records.

## By subject

Expand Down Expand Up @@ -138,6 +138,10 @@ Records that declare one. Everything else is listed by date below.
- [#674 设计方案评审:`-include unistd.h` 在 Windows + `presents = "posix"` 上的可行性](2026-09-20-issue-674-design-review.md) — active
- [`__cxa_thread_atexit` 在 openkal-Windows 上:两层都已定位并修复](2026-09-20-cxa-thread-atexit-finding.md) — landed

### target-model

- [目标与提供者:统一的目标模型(结合 mcpp#911)](2026-11-09-target-model-design.md) — active

### targets

- [A verified Web run that asked the host for node](2026-09-12-a-verified-web-run-that-asked-the-host-for-node.md) — landed
Expand Down Expand Up @@ -179,6 +183,7 @@ Records that declare one. Everything else is listed by date below.

### 2026-11

- [目标与提供者:统一的目标模型(结合 mcpp#911)](2026-11-09-target-model-design.md) — active
- [生态顶层方案:#885 收尾之后的工作流、原则与依赖](2026-11-08-ecosystem-top-level-design.md) — active
- [#885 收尾补充方案:目标角色、平台事实、系统接口与版本书写](2026-11-08-ecosystem-885-supplement-design.md) — active
- [生态 #885 收尾设计:核心、插件、openkal、语言服务与编辑器的一轮闭合](2026-11-07-ecosystem-885-closeout-design.md) — active
Expand Down
Empty file removed .edited
Empty file.
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
# The cross-host admission compares the exact source bytes compiled by each host.
tests/fixtures/openkal-hosted-threads/src/main.cpp.in text eol=lf
# The rows' snapshot is compared line by line with what the engine answers, in its own bytes.
tests/fixtures/target-rows.snapshot text eol=lf
2 changes: 2 additions & 0 deletions .github/actions/bootstrap-mcpp/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ runs:
env:
XLINGS_NON_INTERACTIVE: '1'
XLINGS_VERSION: ${{ inputs.xlings-version }}
GITHUB_TOKEN: ${{ github.token }}
run: |
REPO_DIR="$(pwd)"
# Always install the pinned version — the cache may hold an older
Expand Down Expand Up @@ -231,6 +232,7 @@ runs:
env:
XLINGS_NON_INTERACTIVE: '1'
XLINGS_VERSION: ${{ inputs.xlings-version }}
GITHUB_TOKEN: ${{ github.token }}
run: |
REPO_DIR="$(pwd)"
# FAST PATH: see the unix leg for the reasoning. The cost on Windows is
Expand Down
49 changes: 33 additions & 16 deletions .github/actions/e2e-mtest/action.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: e2e-mtest
description: >
mtest for the e2e suite (design 2026-11-08 W8; tests/e2e/mtest.toml): the
release this action pins, its sha256 checked, and the stores main's last
release this action pins, installed by xlings, and the stores main's last
successful CI run kept for this host, taken into tests/e2e/.mtest so that
`--shard` divides the suite by measured time and a run orders its tests
longest first. Exports E2E_MTEST for run_all.sh. A run with no store of main
Expand All @@ -12,13 +12,13 @@ inputs:
description: the host's name in the stores' artifact names (linux, macos, windows)
required: true
version:
description: the mtest release
description: the mtest release, an xlings package (xim:mtest), whose sha256 the index states
required: false
default: '0.4.0'
default: '0.5.2'
sha256:
description: sha256 of mtest-<version>-openkal-bundle-all.tar.gz
description: sha256 of mtest-<version>-openkal-bundle-all.tar.gz, for the release taken when xlings cannot install it
required: false
default: 'b62e4ac7cd76b20dbdc48a5d041d377444e8815ee7a4ff1e7686145d13acc925'
default: 'be443f8c546b3f4628dcd3f1eb6f45b4d698a7f805232f966349faf8250863c6'
token:
description: a token that reads this repository's actions
required: false
Expand All @@ -27,24 +27,41 @@ inputs:
runs:
using: composite
steps:
# THE ENGINE IS AN XLINGS PACKAGE (xim:mtest), installed as the job installs
# mcpp, verified by the index's sha256, and kept in the job's xlings cache:
# by the configured mirror (GLOBAL), then by CN (GitCode first), each with a
# refreshed index; else the release bundle itself (fetch_release.sh: the
# web address, then the API's), checked against the pinned sha256.
- name: mtest
shell: bash
env:
MTEST_VERSION: ${{ inputs.version }}
MTEST_SHA256: ${{ inputs.sha256 }}
GITHUB_TOKEN: ${{ inputs.token }}
run: |
set -euo pipefail
archive="mtest-$MTEST_VERSION-openkal-bundle-all.tar.gz"
temp="$RUNNER_TEMP"; python=python3
if [ "$RUNNER_OS" = Windows ]; then temp=$(cygpath -u "$RUNNER_TEMP"); python=python; fi
curl -fsSL --retry 5 --retry-all-errors --retry-delay 5 -o "$temp/$archive" \
"https://github.com/mcpp-community/mtest/releases/download/v$MTEST_VERSION/$archive"
actual=$("$python" -c "import hashlib,sys; print(hashlib.sha256(open(sys.argv[1],'rb').read()).hexdigest())" "$temp/$archive")
[ "$actual" = "$MTEST_SHA256" ] || { echo "::error::$archive: sha256 $actual, the action pins $MTEST_SHA256"; exit 1; }
mkdir -p "$temp/mtest" && tar xzf "$temp/$archive" -C "$temp/mtest"
launcher="$temp/mtest/mtest-$MTEST_VERSION-openkal-bundle-all/bin/mtest"
[ "$RUNNER_OS" = Windows ] && launcher="$launcher.exe"
"$launcher" --version
launcher=""
for mirror in GLOBAL CN; do
xlings config --mirror "$mirror" > /dev/null 2>&1 || true
xlings update > /dev/null 2>&1 || true
if xlings install "mtest@$MTEST_VERSION" -y; then launcher=$(command -v mtest); break; fi
done
xlings config --mirror GLOBAL > /dev/null 2>&1 || true
if [ -z "$launcher" ]; then
echo "::warning::xlings could not install mtest $MTEST_VERSION; taking the release itself"
archive="mtest-$MTEST_VERSION-openkal-bundle-all.tar.gz"
temp="$RUNNER_TEMP"; python=python3
if [ "$RUNNER_OS" = Windows ]; then temp=$(cygpath -u "$RUNNER_TEMP"); python=python; fi
bash "$GITHUB_WORKSPACE/.github/tools/fetch_release.sh" \
"https://github.com/mcpp-community/mtest/releases/download/v$MTEST_VERSION/$archive" "$temp/$archive"
actual=$("$python" -c "import hashlib,sys; print(hashlib.sha256(open(sys.argv[1],'rb').read()).hexdigest())" "$temp/$archive")
[ "$actual" = "$MTEST_SHA256" ] || { echo "::error::$archive: sha256 $actual, the action pins $MTEST_SHA256"; exit 1; }
mkdir -p "$temp/mtest" && tar xzf "$temp/$archive" -C "$temp/mtest"
launcher="$temp/mtest/mtest-$MTEST_VERSION-openkal-bundle-all/bin/mtest"
[ "$RUNNER_OS" = Windows ] && launcher="$launcher.exe"
fi
"$launcher" --version | grep -qF "$MTEST_VERSION" \
|| { echo "::error::the mtest at $launcher is not $MTEST_VERSION: $("$launcher" --version)"; exit 1; }
echo "E2E_MTEST=$launcher" >> "$GITHUB_ENV"
# The pull request's change, for the `pr` set: the base's tip and HEAD's
# parents, so that `git diff <base>...HEAD` has its merge base. When they
Expand Down
28 changes: 18 additions & 10 deletions .github/actions/setup-macos-llvm/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,25 @@ runs:
env:
XLINGS_NON_INTERACTIVE: '1'
XLINGS_VERSION: ${{ inputs.xlings-version }}
GITHUB_TOKEN: ${{ github.token }}
run: |
WORK=$(mktemp -d)
tarball="xlings-${XLINGS_VERSION}-macosx-arm64.tar.gz"
# Retried and verified — .github/tools/fetch_release.sh. A bare curl
# here is the `curl: (52) Empty reply from server` flake.
bash "$GITHUB_WORKSPACE/.github/tools/fetch_release.sh" \
"https://github.com/d2learn/xlings/releases/download/v${XLINGS_VERSION}/${tarball}" \
"${WORK}/${tarball}"
tar -xzf "${WORK}/${tarball}" -C "${WORK}"
XLINGS_DIR="${WORK}/xlings-${XLINGS_VERSION}-macosx-arm64"
"$XLINGS_DIR/subos/default/bin/xlings" self install
# THE CACHE ALREADY HOLDS THE PINNED XLINGS (its key names the version):
# nothing is downloaded, as bootstrap-mcpp's fast path does. This step
# fetched the release on every run, a cache hit or not, and a 504 of
# that fetch failed the job (2026-11-09).
XL="$HOME/.xlings/subos/default/bin/xlings"
if ! { [ -x "$XL" ] && "$XL" --version 2>/dev/null | head -1 | grep -qF "$XLINGS_VERSION"; }; then
WORK=$(mktemp -d)
tarball="xlings-${XLINGS_VERSION}-macosx-arm64.tar.gz"
# Retried and verified, and by the API's address when the web's
# fails — .github/tools/fetch_release.sh.
bash "$GITHUB_WORKSPACE/.github/tools/fetch_release.sh" \
"https://github.com/d2learn/xlings/releases/download/v${XLINGS_VERSION}/${tarball}" \
"${WORK}/${tarball}"
tar -xzf "${WORK}/${tarball}" -C "${WORK}"
XLINGS_DIR="${WORK}/xlings-${XLINGS_VERSION}-macosx-arm64"
"$XLINGS_DIR/subos/default/bin/xlings" self install
fi
export PATH="$HOME/.xlings/subos/default/bin:$PATH"
xlings --version
echo "PATH=$HOME/.xlings/subos/default/bin:$PATH" >> "$GITHUB_ENV"
Expand Down
19 changes: 15 additions & 4 deletions .github/tools/check_e2e_coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
under tests/e2e:
ran some report says pass, fail or timeout;
unaffected no report ran it, and a run narrowed to a set (`E2E_SET`, the
`pr` set of a pull request) says the change does not affect it:
the full runs of main are its coverage, and this run's claim is
about the tests the change reaches;
job no report ran it, but a workflow names it outside a comment, as a
whole token: by file name, by name without `.sh`, or by its number
as an `E2E_ONLY` pattern such as `239_*.sh`. A dedicated job runs it
Expand All @@ -34,7 +38,8 @@
calls a target row `verified` when CI builds and runs its artefact, and the
`*-openkal` rows claim that for every system. tests/e2e/host-requirements.tsv
lists such a test with the hosts its result must come from; a listed test that
did not pass on one of them, or that does not exist, fails the check.
did not pass on one of them, or that does not exist, fails the check; one a
set left out on that host as unaffected meets it for that run.
With --timings-out DIR it writes `<host>.tsv`, the measured duration of every
test that ran, merged across that host's shards, in the format run_all.sh reads
Expand Down Expand Up @@ -133,6 +138,7 @@ def main() -> int:
ran: dict[str, set[str]] = defaultdict(set)
passed: dict[str, set[str]] = defaultdict(set)
skipped: dict[str, set[str]] = defaultdict(set)
unaffected: dict[str, set[str]] = defaultdict(set)
shard_ms: dict[tuple[str, int], int] = defaultdict(int)
timings: dict[str, dict[str, int]] = defaultdict(dict)
reports = 0
Expand All @@ -146,6 +152,8 @@ def main() -> int:
timings[host][test] = ms
elif status == "skip":
skipped[test].add(f"{host}: {detail}")
elif status == "unaffected":
unaffected[test].add(host)
if reports == 0:
print(f"no report under {args.reports}: nothing can be said about coverage")
return 1
Expand All @@ -157,6 +165,8 @@ def main() -> int:
counts["ran"] += 1
if test in exceptions:
stale.append(f"{test} is excused but ran on {', '.join(sorted(ran[test]))}")
elif test in unaffected:
counts["unaffected"] += 1
elif named_by_a_workflow(test, workflows):
counts["job"] += 1
elif test in exceptions:
Expand All @@ -171,13 +181,14 @@ def main() -> int:
if test not in tests:
stale.append(f"{test} is required on hosts but does not exist")
continue
for host in sorted(hosts - passed[test]):
for host in sorted(hosts - passed[test] - unaffected[test]):
why = [d for d in skipped[test] if d.startswith(f"{host}: ")]
unmet.append(f"{test} did not pass on {host}"
f"{' (' + '; '.join(why) + ')' if why else (' (it ran and did not pass)' if host in ran[test] else ' (no report of it there)')}")

print(f"{len(tests)} tests: {counts['ran']} ran on a shard, {counts['job']} run by a "
f"dedicated job, {counts['excused']} excused, {len(uncovered)} uncovered")
print(f"{len(tests)} tests: {counts['ran']} ran on a shard, {counts['unaffected']} unaffected by "
f"the change, {counts['job']} run by a dedicated job, {counts['excused']} excused, "
f"{len(uncovered)} uncovered")
for (host, shard), ms in sorted(shard_ms.items()):
print(f" {host} shard {shard}: {ms / 60000:.1f} min of tests")
for line in uncovered:
Expand Down
14 changes: 8 additions & 6 deletions .github/tools/check_target_tiers.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#!/usr/bin/env python3
"""Every documented tier agrees with the target table.
WHY THIS EXISTS. `kKnownTargets` in modules/toolchain-model/src/triple.cppm is
the single source for a row's tier, and four documents restate it: both
WHY THIS EXISTS. The built-in rows, modules/toolchain-model/data/builtin.toml
(SPEC-016 §2.2), are the single source for a row's tier, and four documents restate it: both
READMEs, both copies of docs/21. When `wasm32-emscripten` became `verified` and
the Android rows gained tiers, docs/21 was updated and the READMEs were not --
so the front page told a reader that three targets were `planned` while the
Expand All @@ -16,17 +16,19 @@
"""
import re
import sys
import tomllib
from pathlib import Path

ROOT = Path(__file__).resolve().parents[2]
TABLE = ROOT / "modules/toolchain-model/src/triple.cppm"
TABLE = ROOT / "modules/toolchain-model/data/builtin.toml"
TIERS = ("verified", "preview", "planned")

# The engine's answer.
rows = {}
for m in re.finditer(r'^\s*\{\s*"([a-z0-9_.+-]+)",\s*"(verified|preview|planned)"',
TABLE.read_text(), re.M):
rows[m.group(1)] = m.group(2)
with TABLE.open("rb") as f:
for row in tomllib.load(f).get("platform", {}).get("define", []):
if row.get("tier") in TIERS:
rows[row["triple"]] = row["tier"]
if len(rows) < 20:
sys.exit(f"ERROR: only {len(rows)} rows parsed from {TABLE.name}; "
"the pattern no longer matches the table")
Expand Down
11 changes: 6 additions & 5 deletions .github/tools/check_windows_home_legacy.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,23 @@
assert original.count(marker) == 1, 'identify the test-only code-page setting explicitly'


def run_case(pattern, passed_case):
result = subprocess.run([os.environ['MCPP'], '--offline', 'test', pattern], cwd=root,
def run_case(passed_case):
# The case by its identifier (mtest's): it runs in the process of its file.
result = subprocess.run([os.environ['MCPP'], '--offline', 'test', '--case', passed_case], cwd=root,
capture_output=True, text=True, encoding='utf-8', errors='replace',
timeout=600)
sys.stdout.write(result.stdout)
sys.stderr.write(result.stderr)
assert result.returncode == 0, result.returncode
output = re.sub(r'\x1b\[[0-9;]*m', '', result.stdout + result.stderr)
assert '[ OK ] ' + passed_case in output, \
assert re.search(r'^\s*ok\s+' + re.escape(passed_case) + r'\s*$', output, re.M), \
'the legacy ACP case did not pass; a UTF-8-only skip is not proof'


try:
manifest.write_bytes(original.replace(marker, b'windows_code_page = "legacy"', 1))
run_case('home', 'HomeSelection.UnicodeLocatorRoundTripDoesNotNarrowTheSelectedPath')
run_case('bmi_cache', 'BMICacheMaintenance.RunnerCachePathDoesNotUseTheWindowsAnsiCodePage')
run_case('HomeSelection/UnicodeLocatorRoundTripDoesNotNarrowTheSelectedPath')
run_case('BMICacheMaintenance/RunnerCachePathDoesNotUseTheWindowsAnsiCodePage')
finally:
manifest.write_bytes(original)
print('PASS: home and runner-cache paths ran under legacy Windows ACP; manifest restored')
29 changes: 28 additions & 1 deletion .github/tools/fetch_release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,33 @@ verify() {
esac
}

# THE SAME FILE BY GITHUB'S API. The web address of a release asset
# (`github.com/<owner>/<repo>/releases/download/<tag>/<name>`) answered 504 for
# minutes at a time while the API's address of the same asset answered 200
# (measured 2026-11-09 on xlings-2026.10.26.1-macosx-arm64.tar.gz, three times
# each). A failed web attempt is followed by the API's, with the job's token
# where there is one (the API's anonymous limit is 60 requests an hour).
api_fetch() {
local rest="${url#https://github.com/}"
[ "$rest" != "$url" ] || return 1
local owner repo releases download tag name
IFS=/ read -r owner repo releases download tag name <<<"$rest"
[ "$releases/$download" = "releases/download" ] && [ -n "$name" ] || return 1
local auth=()
local token="${GITHUB_TOKEN:-${GH_TOKEN:-}}"
[ -z "$token" ] || auth=(-H "Authorization: Bearer $token")
local id
id=$(curl -fsSL --retry 3 --retry-all-errors --connect-timeout 20 --max-time 60 "${auth[@]}" \
"https://api.github.com/repos/$owner/$repo/releases/tags/$tag" \
| python3 -c 'import json,sys; n=sys.argv[1]; print(next((str(a["id"]) for a in json.load(sys.stdin)["assets"] if a["name"] == n), ""))' "$name") \
|| return 1
[ -n "$id" ] || return 1
echo "fetch_release: the web address failed; asking the API for asset $id" >&2
curl -fsSL --retry 3 --retry-delay 2 --retry-all-errors --connect-timeout 20 --max-time 600 \
"${auth[@]}" -H "Accept: application/octet-stream" \
-o "$dest" "https://api.github.com/repos/$owner/$repo/releases/assets/$id"
}

for i in $(seq 1 "$attempts"); do
rm -f "$dest"
# --retry-all-errors is what covers exit 52; the rest bound how long a single
Expand All @@ -53,7 +80,7 @@ for i in $(seq 1 "$attempts"); do
if curl -fsSL \
--retry 3 --retry-delay 2 --retry-all-errors \
--connect-timeout 20 --max-time 600 \
-o "$dest" "$url"; then
-o "$dest" "$url" || { rm -f "$dest"; api_fetch; }; then
if verify; then
[ "$i" -eq 1 ] || echo "fetch_release: succeeded on attempt $i" >&2
exit 0
Expand Down
Loading
Loading