Skip to content

Commit 08cd477

Browse files
Merge pull request #582 from bernardladenthin/fix/post-release-java8-and-logs
fix(post-release): macOS smoke on Java 8, job logs on the runner's gh
2 parents 3457f40 + 7f65b3c commit 08cd477

5 files changed

Lines changed: 103 additions & 17 deletions

File tree

‎.github/buildcheck/postrelease.py‎

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -347,15 +347,35 @@ def run_logs(repository, run_id):
347347
break
348348
page += 1
349349
logs = {}
350+
flags = escape_flag()
350351
for job in jobs:
351352
if job["conclusion"] == "skipped":
352353
continue
353-
out = subprocess.run(["gh", "api", f"repos/{repository}/actions/jobs/{job['id']}/logs"],
354-
capture_output=True, check=True).stdout
355-
logs[job["name"]] = out.decode("utf-8", "replace")
354+
logs[job["name"]] = job_log(repository, job["id"], flags).decode("utf-8", "replace")
356355
return logs
357356

358357

358+
def escape_flag():
359+
"""`--allow-escape-sequences` where this gh has it: newer gh refuses a response with terminal
360+
escapes (every job log has colour codes), older gh does not know the flag."""
361+
help_text = subprocess.run(["gh", "api", "--help"], capture_output=True, text=True).stdout
362+
return ["--allow-escape-sequences"] if "--allow-escape-sequences" in help_text else []
363+
364+
365+
def job_log(repository, job_id, flags=(), attempts=3, pause=5):
366+
"""One job's log through `gh api`, retried; the error names what gh said, which check=True hid."""
367+
import time
368+
for attempt in range(1, attempts + 1):
369+
proc = subprocess.run(["gh", "api", *flags, f"repos/{repository}/actions/jobs/{job_id}/logs"],
370+
capture_output=True)
371+
if proc.returncode == 0:
372+
return proc.stdout
373+
if attempt < attempts:
374+
time.sleep(pause * attempt)
375+
raise RuntimeError(f"the log of job {job_id} could not be read ({attempts} attempts): "
376+
f"{proc.stderr.decode('utf-8', 'replace').strip() or f'gh exited {proc.returncode}'}")
377+
378+
359379
# --- the command --------------------------------------------------------------------------------
360380

361381
def find_run(repository, tag):

‎.github/buildcheck/tests/test_postrelease.py‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,5 +284,49 @@ def test_off_everywhere_passes_and_a_job_with_the_cache_on_fails(self):
284284
["no job log shows the cache off -- the evidence is missing, not the cache"])
285285

286286

287+
class JobLogTest(unittest.TestCase):
288+
def run_with(self, results):
289+
calls = []
290+
291+
def fake(cmd, capture_output):
292+
calls.append(cmd)
293+
code, out, err = results[len(calls) - 1]
294+
return subprocess.CompletedProcess(cmd, code, out, err)
295+
original = postrelease.subprocess.run
296+
postrelease.subprocess.run = fake
297+
try:
298+
return postrelease.job_log("o/r", 7, attempts=3, pause=0), calls
299+
finally:
300+
postrelease.subprocess.run = original
301+
302+
def test_retries_a_failed_read(self):
303+
log, calls = self.run_with([(1, b"", b"HTTP 502"), (0, b"the log", b"")])
304+
self.assertEqual(log, b"the log")
305+
self.assertEqual(len(calls), 2)
306+
self.assertEqual(calls[0], ["gh", "api", "repos/o/r/actions/jobs/7/logs"])
307+
308+
def test_passes_the_escape_flag(self):
309+
calls = []
310+
311+
def fake(cmd, capture_output, text=False):
312+
calls.append(cmd)
313+
if cmd[-1] == "--help":
314+
return subprocess.CompletedProcess(cmd, 0, " --allow-escape-sequences Allow ...\n", "")
315+
return subprocess.CompletedProcess(cmd, 0, b"log", b"")
316+
original = postrelease.subprocess.run
317+
postrelease.subprocess.run = fake
318+
try:
319+
flags = postrelease.escape_flag()
320+
postrelease.job_log("o/r", 7, flags, pause=0)
321+
finally:
322+
postrelease.subprocess.run = original
323+
self.assertEqual(flags, ["--allow-escape-sequences"])
324+
self.assertEqual(calls[-1], ["gh", "api", "--allow-escape-sequences", "repos/o/r/actions/jobs/7/logs"])
325+
326+
def test_names_what_gh_said(self):
327+
with self.assertRaisesRegex(RuntimeError, r"job 7 could not be read \(3 attempts\): HTTP 403: Forbidden$"):
328+
self.run_with([(1, b"", b"HTTP 403: Forbidden\n")] * 3)
329+
330+
287331
if __name__ == "__main__":
288332
unittest.main()

‎.github/smoke-native-macos.sh‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ trap 'rm -rf "$WORK"' EXIT
6767
unzip -o -q "$JAR" "$DYLIB_ENTRY" -d "$WORK" \
6868
|| fail "the natives jar does not contain $DYLIB_ENTRY — the macOS natives never reached the package job"
6969
DYLIB="$WORK/$DYLIB_ENTRY"
70+
# NativeLoadSmoke is compiled first rather than run as a single source file: the post-release
71+
# consumer runs this script on Java 8, whose launcher has no source-file mode (Java 11+).
72+
SMOKE_CLASSES="$WORK/smoke-classes"
73+
mkdir -p "$SMOKE_CLASSES"
74+
javac -nowarn -cp "$CP" -d "$SMOKE_CLASSES" "$SCRIPT_DIR/smoke/NativeLoadSmoke.java" \
75+
|| fail "NativeLoadSmoke.java does not compile against the set"
7076
echo "extracted: $(cd "$(dirname "$DYLIB")" && pwd)/$(basename "$DYLIB") ($(wc -c < "$DYLIB") bytes)"
7177

7278
# 0) The oldest macOS it loads on. CMAKE_OSX_DEPLOYMENT_TARGET (llama/CMakeLists.txt) pins it;
@@ -98,7 +104,7 @@ codesign --verify --strict --verbose=2 "$DYLIB" \
98104
# and it is the only check that covers load-time failures the signature check cannot see
99105
# (missing dependent library, wrong architecture, unresolved JNI_OnLoad class lookup).
100106
echo "== JVM load + JNI round-trip =="
101-
java -cp "$CP" "$SCRIPT_DIR/smoke/NativeLoadSmoke.java" \
107+
java -cp "$SMOKE_CLASSES:$CP" NativeLoadSmoke \
102108
|| fail "the packaged native library did not load in a JVM"
103109

104110
# 3) HTTPS in both directions out of the packaged jar: the server behind a self-signed certificate
@@ -154,7 +160,7 @@ wait "$SERVER_PID" 2> /dev/null || true
154160
echo "== lib.path: the natives directory, no natives jar on the classpath =="
155161
NATIVES_DIR="$(smoke_natives_directory "$SET_DIR" "$WORK/natives-dir")" || fail "could not unpack the natives jar into a directory"
156162
CP_NO_NATIVES="$(smoke_classpath_without_natives "$SET_DIR")" || exit 1
157-
java -cp "$CP_NO_NATIVES" "-Dnet.ladenthin.llama.lib.path=$NATIVES_DIR" "$SCRIPT_DIR/smoke/NativeLoadSmoke.java" \
163+
java -cp "$SMOKE_CLASSES:$CP_NO_NATIVES" "-Dnet.ladenthin.llama.lib.path=$NATIVES_DIR" NativeLoadSmoke \
158164
> load-dir-out.log 2> load-dir-err.log \
159165
|| { cat load-dir-out.log load-dir-err.log; fail "the packaged native library did not load from the lib.path directory"; }
160166
cat load-dir-out.log

‎CLAUDE.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1099,7 +1099,9 @@ publish jobs) downloads the `llama-smoke-macos-aarch64` smoke set and runs
10991099
**`metal-macos-aarch64` natives jar** of the set and asserts two things: `codesign --verify
11001100
--strict` (re-hashes the code pages against the signature's stored hashes — the direct check for a
11011101
dylib assembled from two builds) and a real JVM load via `.github/smoke/NativeLoadSmoke.java`
1102-
(`java -cp '<set>/*' …`, the JDK single-file source launcher), which forces
1102+
(compiled with `javac` first and run as `java -cp <classes>:'<set>/*' NativeLoadSmoke` -- not through
1103+
the single-file source launcher, which Java 8 lacks and the post-release consumer runs this script
1104+
on), which forces
11031105
`LlamaLoader.initialize() → System.load() → JNI_OnLoad` and then crosses JNI for
11041106
`getLlamaCppBuildInfo()`, checked against the `LlamaCppVersion` pin. A third check comes first:
11051107
the dylib's `minos` (`otool -l`, `LC_BUILD_VERSION`) must not exceed **15.0**, the supported floor.

‎TODO.md‎

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -64,17 +64,31 @@ so entries below describe remaining work or an explicit deferral policy.
6464
smoke and the WebUI check pass on the jars from Central (in Docker, JDK 21, not 8). Open:
6565
- [x] The `java/audit-publication` pin is the actions release that carries it: v1.1.0
6666
(`44390bd4`), like every other pin of the workflows.
67-
- [ ] Read the first real run: a `workflow_dispatch` with `v6.0.0`
68-
(`gh workflow run post-release.yml -f tag=v6.0.0`; expected: one android finding) and
69-
the first `workflow_run` after 6.1.0. The two `workflow_run` runs after #573
70-
(40367736958, 40367890447) ended `skipped`, correctly: no `v*` tag is behind those
71-
Publish runs. Unverified on a runner: the Java 8
72-
rows (Temurin 8 on Linux and Windows, Zulu 8 on macOS arm64), `smoke-native-macos.sh`
73-
and `smoke-test-natives.ps1` on Java 8, `gh attestation verify` with the workflow token
74-
(`attestations: read`), and the keyserver fetch from a runner.
75-
- [ ] Repeat the `LocalCheck` (Java 8) and `FfmCheck` acceptances against the 6.0.0 jars from
76-
Central, as far as the run above does not cover them; the earlier acceptances were made
77-
on b11827 artifacts.
67+
- [x] **The `workflow_dispatch` with `v6.0.0`** (2026-10-27). The first one (40373377002) found
68+
two defects of the workflow itself: `smoke-native-macos.sh` launched
69+
`NativeLoadSmoke.java` as a single source file, which Java 8 cannot ("Could not find or
70+
load main class"), and `postrelease.run_logs` died on the first job log with a bare
71+
`CalledProcessError` -- the runner's gh refuses a response with terminal escapes
72+
unless given `--allow-escape-sequences`, which the gh 2.87 used locally does not know.
73+
Both fixed (the smoke compiles the class first; `job_log` passes the flag where `gh api
74+
--help` lists it, retries, and names what gh said). Run 40515663015 from the fix branch:
75+
the 8 audit checks pass on the runner (signatures with the key from the keyserver,
76+
provenance with the workflow token's `attestations: read`), `post-release-check.py` reads all 73
77+
job logs (68 show the cache off, none on) and fails android on exactly the expected
78+
checker-qual finding, and all four Java 8 consumers pass -- Temurin 8 on Linux x86-64,
79+
Linux aarch64 and Windows, Zulu 8 on macOS arm64, including `smoke-native-macos.sh`
80+
(load, HTTPS, lib.path) and `smoke-test-natives.ps1` on Java 8.
81+
- [ ] The first `workflow_run` after 6.1.0. The `workflow_run` runs after #573 and since
82+
(40367736958, 40367890447, ...) ended `skipped`, correctly: no `v*` tag is behind those
83+
Publish runs.
84+
- [x] **`LocalCheck` (Java 8) and `FfmCheck` against the 6.0.0 jars from Central**
85+
(2026-10-27): `llama-platform` + `llama-ffm` 6.0.0 resolved into an empty local
86+
repository (13 jars), Qwen3-0.6B Q4_K_M. `LocalCheck` -- `LlamaModel` chat ("Paris."),
87+
an encode/decode round trip and `getJllamaBuildInfo()` (b11887, `jllama.version=6.0.0`)
88+
-- passes on Temurin 8 in Docker (Linux x86-64, `cpu-linux-x86-64`, GNU 14.2.1, module
89+
`haswell`) and on the Windows host with JDK 21 (`cpu-windows-x86-64`, Clang 20.1.8).
90+
`FfmCheck` -- `Jllama.apiVersion()` 6.0.0, `FfmLlamaModel.chatCompletions` ("Paris.")
91+
and a tokenize/detokenize round trip through FFM -- passes on Temurin 25 in Docker.
7892

7993
## Delivery roadmap — remaining work
8094

0 commit comments

Comments
 (0)