diff --git a/.github/buildcheck/tests/test_webuilicenses.py b/.github/buildcheck/tests/test_webuilicenses.py index 281c1e2b..1a4ad79b 100644 --- a/.github/buildcheck/tests/test_webuilicenses.py +++ b/.github/buildcheck/tests/test_webuilicenses.py @@ -96,6 +96,14 @@ def test_check_reports_a_changed_lockfile(self): self.assertEqual(len(problems), 1) self.assertIn("webui-licenses.py generate", problems[0]) + def test_the_lockfile_digest_does_not_depend_on_the_line_ends(self): + lock = self.ui / "package-lock.json" + lf = (json.dumps(json.loads(lock.read_text(encoding="utf-8")), indent=2) + "\n").encode("utf-8") + lock.write_bytes(lf) + digest = webuilicenses.lockfile_digest(self.ui) + lock.write_bytes(lf.replace(b"\n", b"\r\n")) + self.assertEqual(webuilicenses.lockfile_digest(self.ui), digest) + def test_check_reports_a_new_import_of_a_package_the_lockfile_already_had(self): self.generated() (self.ui / "src/lib/more.ts").write_text("import { t } from 'testonly';\n") diff --git a/.github/buildcheck/webuilicenses.py b/.github/buildcheck/webuilicenses.py index f2fd78f5..48364fc8 100644 --- a/.github/buildcheck/webuilicenses.py +++ b/.github/buildcheck/webuilicenses.py @@ -52,7 +52,13 @@ def lockfile_digest(ui): - return hashlib.sha256((Path(ui) / "package-lock.json").read_bytes()).hexdigest() + """SHA-256 of the lockfile as git stores it, with LF line ends. + + A Windows checkout (`core.autocrlf`) has CRLF; hashing those bytes recorded a digest that the + Linux checkout of the same commit in CI never matched, and the release run of v6.0.0 + (40238595195) failed on it. + """ + return hashlib.sha256((Path(ui) / "package-lock.json").read_bytes().replace(b"\r\n", b"\n")).hexdigest() def imports(ui): diff --git a/.github/webui-licenses.py b/.github/webui-licenses.py index 65c6a261..3057dcc5 100644 --- a/.github/webui-licenses.py +++ b/.github/webui-licenses.py @@ -5,7 +5,7 @@ """Generate or check the license texts of the npm packages in the embedded WebUI. webui-licenses.py generate /tools/ui llama/legal/natives/webui-npm - webui-licenses.py check /tools/ui llama/legal/natives/webui-npm [--warn-only] + webui-licenses.py check /tools/ui llama/legal/natives/webui-npm """ import argparse @@ -19,7 +19,6 @@ def main(): parser.add_argument("command", choices=("generate", "check")) parser.add_argument("ui", help="llama.cpp's tools/ui directory at the pinned commit") parser.add_argument("out", help="llama/legal/natives/webui-npm") - parser.add_argument("--warn-only", action="store_true", help="report a stale list as a warning") args = parser.parse_args() if args.command == "generate": packages = generate(args.ui, args.out) @@ -27,10 +26,10 @@ def main(): return 0 problems = check(args.ui, args.out) for problem in problems: - print(f"::{'warning' if args.warn_only else 'error'}::{problem}") + print(f"::error::{problem}") if not problems: print("WebUI license texts match tools/ui/package-lock.json") - return 1 if problems and not args.warn_only else 0 + return 1 if problems else 0 if __name__ == "__main__": diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 915789fc..0e7d8112 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -615,11 +615,12 @@ jobs: cache-dependency-path: llamacpp-ui/tools/ui/package-lock.json # The bundle strips every legal comment, so the texts of its npm packages travel in # llama/legal/natives/webui-npm/ (every natives jar carries them). They were generated from one - # package-lock.json; a llama.cpp bump that changes it must regenerate them before a release. + # package-lock.json; a llama.cpp bump that changes it regenerates them in the same pull request. + # Fails on every run: it only warned outside a release tag until the release run of v6.0.0 + # (40238595195) failed on it hours after the cause. webui-licenses.yml runs the same check in + # every pull request and push, where Publish is usually cancelled at the start gate. - name: Check the WebUI license texts against this package-lock.json - run: > - python3 .github/webui-licenses.py check llamacpp-ui/tools/ui llama/legal/natives/webui-npm - ${{ !startsWith(github.ref, 'refs/tags/v') && '--warn-only' || '' }} + run: python3 .github/webui-licenses.py check llamacpp-ui/tools/ui llama/legal/natives/webui-npm - name: Build WebUI (Svelte/Vite) working-directory: llamacpp-ui/tools/ui env: diff --git a/.github/workflows/webui-licenses.yml b/.github/workflows/webui-licenses.yml new file mode 100644 index 00000000..c0099493 --- /dev/null +++ b/.github/workflows/webui-licenses.yml @@ -0,0 +1,51 @@ +# SPDX-FileCopyrightText: 2026 Bernard Ladenthin +# +# SPDX-License-Identifier: MIT OR Apache-2.0 + +name: WebUI license texts +on: + push: + pull_request: + workflow_dispatch: + +# The license texts of the WebUI's npm packages (llama/legal/natives/webui-npm/, in every natives +# jar) must have been generated from the package-lock.json of the pinned llama.cpp. publish.yml's +# build-webui checks the same, but the Publish runs of pull requests and pushes are usually +# cancelled at the start gate, so a stale list surfaced only in the release run of v6.0.0 +# (40238595195) -- hours after the cause, the llama.cpp bump that changed the lockfile. This job +# needs no npm and no build: a sparse checkout and the stdlib-only check, under a minute. +permissions: + contents: read + +jobs: + webui-licenses: + name: WebUI license texts match the pinned package-lock.json + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + - name: Resolve the pinned llama.cpp commit from CMakeLists.txt + id: tag + run: | + # The rule of publish.yml's build-webui: JLLAMA_GIT_TAG is the number, JLLAMA_GIT_COMMIT + # the commit it names; the checkout takes the commit when it is set. + TAG=$(grep -oE '^set\(JLLAMA_GIT_TAG[[:space:]]+b[0-9]+\)' llama/CMakeLists.txt | grep -oE 'b[0-9]+' | head -1) + if [ -z "$TAG" ]; then + echo "could not resolve llama.cpp JLLAMA_GIT_TAG (b) from CMakeLists.txt" >&2 + exit 1 + fi + COMMIT=$(grep -oE '^set\(JLLAMA_GIT_COMMIT[[:space:]]+"[0-9a-f]{40}"\)' llama/CMakeLists.txt | grep -oE '[0-9a-f]{40}' | head -1 || true) + echo "ref=${COMMIT:-$TAG}" >> "$GITHUB_OUTPUT" + echo "Pinned llama.cpp: $TAG${COMMIT:+ (commit $COMMIT)}" + - name: Checkout llama.cpp tools/ui at the pinned commit + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + repository: ggml-org/llama.cpp + ref: ${{ steps.tag.outputs.ref }} + path: llamacpp-ui + sparse-checkout: tools/ui + sparse-checkout-cone-mode: true + - name: Check the WebUI license texts against this package-lock.json + run: python3 .github/webui-licenses.py check llamacpp-ui/tools/ui llama/legal/natives/webui-npm diff --git a/CLAUDE.md b/CLAUDE.md index 68cc7436..986df336 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -365,7 +365,7 @@ rejects anything else loudly. | `patches.py` | `check-patches.py` (`code-style`) | every hunk of `llama/patches/*.patch` declares exactly the lines it carries -- the half `git apply --check` does not see (see "Local llama.cpp source patches"); and `docs/patches.md` is what `check-patches.py --render` generates from the patch headers (a stale page fails) | | `nativedeps.py` | `verify-native-deps.py` (`package`, `package-android-aar`) | dependency allowlists, 16 KB Android alignment | | `hipoffload.py` | `verify-hip-offload-compressed.py` (the two ROCm jobs) | no uncompressed GPU code bundle | -| `webuilicenses.py` | `webui-licenses.py` (`build-webui`) | the WebUI's npm license texts cover the pinned lockfile and its imports | +| `webuilicenses.py` | `webui-licenses.py` (`build-webui`, and `webui-licenses.yml` on every pull request and push) | the WebUI's npm license texts cover the pinned lockfile (digest over LF line ends) and its imports; it fails every run -- until the release run of v6.0.0 it only warned outside a release tag, and a stale list surfaced there first | | Action (bernardladenthin/actions) | Job here | What it checks or does | |---|---|---| diff --git a/docs/upgrade/llama-cpp-version-bump.md b/docs/upgrade/llama-cpp-version-bump.md index 5b5e5ed5..8401e9fa 100644 --- a/docs/upgrade/llama-cpp-version-bump.md +++ b/docs/upgrade/llama-cpp-version-bump.md @@ -149,8 +149,10 @@ for each: ## License texts on a bump `llama/legal/natives/` holds the license texts of everything compiled into the natives, taken from -the pinned commit. On a bump: regenerate the WebUI's npm texts (`build-webui` warns on a changed -`tools/ui/package-lock.json` and fails on a release tag), +the pinned commit. On a bump: regenerate the WebUI's npm texts whenever +`tools/ui/package-lock.json` changed (the `WebUI license texts` workflow fails the pull request +otherwise, and so does `build-webui`; the digest is taken over LF line ends, so a Windows checkout +records the same one as CI), ```bash python3 .github/webui-licenses.py generate /tools/ui llama/legal/natives/webui-npm diff --git a/llama/legal/natives/webui-npm/LOCKFILE.sha256 b/llama/legal/natives/webui-npm/LOCKFILE.sha256 index d52b9d1c..5e892a12 100644 --- a/llama/legal/natives/webui-npm/LOCKFILE.sha256 +++ b/llama/legal/natives/webui-npm/LOCKFILE.sha256 @@ -1 +1 @@ -ca12fa34cf60965e260b4f00d6dfada6de1e6d683a18ef097f7a24c21f1aa2d0 tools/ui/package-lock.json +d4f8f7d8b556120c93dd6225e6c7fc3f4a130a6b29b37b5289ec8a585c236a2c tools/ui/package-lock.json