All checks were successful
joule-moq-remote host agent / joule-moq-remote · does this change touch it (pull_request) Successful in 6s
joule-moq-room room service / joule-moq-room · does this change touch it (pull_request) Successful in 7s
joule-moq-web browser client / joule-moq-web · does this change touch it (pull_request) Successful in 6s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 6s
wai-app and Create / wai-app · does this change touch it (pull_request) Successful in 6s
wai-audit collector and player integration / does this change touch it (pull_request) Successful in 6s
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (pull_request) Successful in 6s
WAI operation catalogue / does this change touch it (pull_request) Successful in 6s
WAI conformance / does this change touch it (pull_request) Successful in 6s
WAI energy receipts / does this change touch it (pull_request) Successful in 5s
WAI examples / WAI examples · does this change touch them (pull_request) Successful in 6s
wai-fleet service / does this change touch it (pull_request) Successful in 6s
WAI JPEG-AI whole decode (pinned inputs, required) / does this change touch it (pull_request) Successful in 7s
wai-ledger service / does this change touch it (pull_request) Successful in 6s
joule-moq-remote host agent / joule-moq-remote, every target (macos) (pull_request) Successful in 1m25s
wai-pack / wai-pack · does this change touch it (pull_request) Successful in 6s
wai-quantum for JavaScript / does this change touch it (pull_request) Successful in 6s
wai-quantum studio and desktop / does this change touch it (pull_request) Successful in 6s
wai-sink-android native sink library / does this change touch it (pull_request) Successful in 6s
wai-sink-swift native sink package / does this change touch it (pull_request) Successful in 6s
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (pull_request) Successful in 1m57s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 2m7s
WAI Studio / does this change touch it (pull_request) Successful in 6s
wai-web reach builds / wai-web reach · does this change touch it (pull_request) Successful in 6s
wai-web wasm32 / does this change touch it (pull_request) Successful in 6s
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (pull_request) Successful in 29s
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (pull_request) Successful in 2m1s
WAI conformance / fast integer kernels, every x86_64 kernel set (AVX-512 silicon) (pull_request) Successful in 2m59s
wai-audit collector and player integration / collector · linux (pull_request) Successful in 1m1s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 26s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 4m19s
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Successful in 16s
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Successful in 1m4s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (pull_request) Successful in 1m15s
wai-app and Create / wai-app · Create, browser end-to-end (macos) (pull_request) Successful in 5m15s
joule-moq-room room service / joule-moq-room, every target (pull_request) Successful in 6m19s
wai-fleet service / service · macos (pull_request) Successful in 1m2s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (pull_request) Successful in 2m46s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 1m43s
wai-ledger service / service · macos (pull_request) Successful in 1m24s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 1m2s
WAI operation catalogue / the catalogue and every door · linux (pull_request) Successful in 4m18s
wai-quantum studio and desktop / desktop app · windows (pull_request) Successful in 9m19s
wai-pack / wai-pack · macos (pull_request) Successful in 3m8s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 30s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 7m49s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 3m23s
WAI conformance / faces from keypoints under wasm32-wasip2 (wasmtime) (pull_request) Successful in 1m39s
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Successful in 3m9s
wai-fleet service / service · linux (pull_request) Successful in 2m5s
wai-quantum studio and desktop / desktop app · macos (pull_request) Successful in 3m28s
wai-ledger service / service · linux (pull_request) Successful in 1m43s
wai-pack / wai-pack · linux (pull_request) Successful in 1m55s
wai-quantum for JavaScript / javascript · linux (pull_request) Successful in 3m0s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (pull_request) Successful in 1m10s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Successful in 16m12s
WAI conformance / each feature of the independent verifiers' steps compiles alone (pull_request) Successful in 11m32s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (pull_request) Successful in 4m51s
WAI examples / WAI examples · the archive, the readiness check, the browser and Apple · macos (pull_request) Successful in 13m12s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 15m2s
wai-quantum studio and desktop / studio · browser (pull_request) Successful in 4m3s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 16m34s
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Successful in 2m40s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 17m22s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 1m44s
wai-quantum studio and desktop / desktop app · linux (pull_request) Successful in 5m46s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 34s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 17s
WAI examples / WAI examples · portable · linux (pull_request) Successful in 9m28s
WAI examples / WAI examples · android-app · linux-arm64 (pull_request) Successful in 5m7s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 14m49s
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Successful in 4m34s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (pull_request) Successful in 2m16s
WAI JPEG-AI whole decode (pinned inputs, required) / fetch pinned JPEG-AI inputs → whole decode + pixel hash, required (pull_request) Successful in 11m4s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 13m6s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 5m58s
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (pull_request) Successful in 10m55s
WAI Studio / Studio in Chromium and Firefox · macos (pull_request) Successful in 9m51s
WAI Studio / Studio in WebKit · linux (pull_request) Successful in 10m15s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 9m21s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 30m14s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 1h4m7s
Every change to WAI added an entry at the head of wai/CHANGELOG.md, and every new module appended to the hand-kept lists of .github/workflows/wai-conformance.yml. So any two branches conflicted on those lines and had to merge one after another. This moves both into files a change adds. - Changelog entries: a change adds wai/changes/<yyyy-mm-dd>-<slug>.md holding the entry text. scripts/changelog-assemble.py renders the entries into CHANGELOG.md's Unreleased section for a release, in a stable order. scripts/check-changelog-fragment.py, run by repo-checks on every pull request, fails a pull request that changes wai/ and adds or edits no entry file directly in wai/changes/ (a release passes, and so does a commit with the trailer line "Changelog: none"). A pull request opened before the check reached main passes as it is; one opened after it that adds its entry to the head of Unreleased fails and is told where the entry goes. wai breaking reads breaking changes from entry files too. - The conformance workflow: each per-module verifier step is a file in .github/wai-conformance/verifiers/, each corpus a file in corpora/, and the integer-kernel tests carry a tag line. scripts/wai-conformance-lists.py derives every list the workflow kept by hand (WAI_FEATURES, the union --bin and --test lists, the features-alone lists, the kernel and corpus steps) from those files when the workflow runs. Its `selftest --exact` holds the derived lists to the hand lists of the commit before this one, and its dry-run prints every command each job runs: the dry-runs before and after this change differ only in the gate's path list, which gains the two paths this change adds. - What stays shared, and how to insert there, is written in wai/CONTRIBUTING.md and wai/changes/README.md: wai-rs Cargo.toml features and [[test]] entries, lib.rs, registry rows, wai/.gitattributes and the live corpus table take an insert beside the related entry, never at the end. Co-Authored-By: Claude Opus 5.5 <[email protected]>
168 lines
6.4 KiB
Python
Executable file
168 lines
6.4 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
"""Does this pull request's own diff touch any path its workflow filters on?
|
|
|
|
Forgejo matches a workflow's `paths:` filter against the **push delta**, not the
|
|
pull request's diff. A rebase moves a branch across every commit main has
|
|
gained, so its push delta touches nearly every path and every path-filtered
|
|
workflow fires. Measured on this repo: a jcp-only change pushed to a fresh
|
|
branch ran 2 workflows; the same change force-pushed after a rebase ran 15,
|
|
including JPEG-AI whole decode and wai-pack on three platforms. That is what
|
|
fills the queue, and it delays everyone's real jobs behind irrelevant ones.
|
|
|
|
This reads the path list out of the workflow file itself — one source of truth,
|
|
rather than a third copy of the list to drift from the two `on:` blocks — and
|
|
compares it against `git diff --name-only BASE...HEAD`.
|
|
|
|
pr-touches-paths.py <workflow.yml> <base-sha> <head-sha> [--paths-from DIR]... [--wide GLOB]...
|
|
|
|
With --paths-from, the files of DIR add the globs their headers declare (leading lines
|
|
`# paths: <glob>...`, as the verifier steps of .github/wai-conformance/ do), and each --wide
|
|
glob of the workflow's list is dropped: it is the forge's filter for those files' paths, wide
|
|
because the forge cannot read them, and the gate replaces it with the paths themselves. So a
|
|
step that brings a path adds it in its own file, and the gate still answers for exactly the
|
|
paths named.
|
|
|
|
Prints `true` or `false`. **Fails open**: anything it cannot determine prints
|
|
`true`, because a wrong `false` silently skips a test and a wrong `true` only
|
|
costs runner time. A gate that guesses must guess toward running.
|
|
"""
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
def paths_of(workflow: Path) -> list[str]:
|
|
"""The union of every `paths:` list under `on:` in this workflow.
|
|
|
|
Parsed rather than imported from a library: the runner image has no PyYAML
|
|
guaranteed, and the shape here is a flat `paths:` followed by `- 'glob'`
|
|
lines. A list this parser fails to read yields none, which fails open.
|
|
"""
|
|
globs: list[str] = []
|
|
in_paths = False
|
|
for line in workflow.read_text().splitlines():
|
|
if re.match(r"\s*paths(-ignore)?:\s*$", line):
|
|
in_paths = True
|
|
continue
|
|
if in_paths:
|
|
stripped = line.strip()
|
|
# A comment or a blank line inside the list does not end it. (It used to:
|
|
# every glob after a list's first comment was dropped, and a pull request
|
|
# touching only those paths was answered `false`.)
|
|
if not stripped or stripped.startswith("#"):
|
|
continue
|
|
m = re.match(r"\s*-\s*'([^']+)'\s*(#.*)?$", line) or re.match(r'\s*-\s*"([^"]+)"\s*(#.*)?$', line)
|
|
if m:
|
|
globs.append(m.group(1))
|
|
continue
|
|
if stripped.startswith("- "):
|
|
globs.append(re.sub(r"\s+#.*$", "", stripped[2:]).strip())
|
|
continue
|
|
in_paths = False
|
|
return globs
|
|
|
|
|
|
HEADER = re.compile(r"^# ([a-z][a-z-]*): ?(.*)$")
|
|
|
|
|
|
def declared_paths(directory: Path) -> "list[str] | None":
|
|
"""The globs the files of `directory` declare: each `# paths:` line of a file's leading
|
|
`# key: value` lines. None when the directory holds no file, which fails open."""
|
|
if not directory.is_dir():
|
|
return None
|
|
files = sorted(p for p in directory.iterdir() if p.is_file() and not p.name.startswith("."))
|
|
if not files:
|
|
return None
|
|
globs: list[str] = []
|
|
for f in files:
|
|
for line in f.read_text(encoding="utf-8").splitlines():
|
|
m = HEADER.match(line)
|
|
if not m:
|
|
break
|
|
if m.group(1) == "paths":
|
|
globs.extend(m.group(2).split())
|
|
return globs
|
|
|
|
|
|
def gate_globs(workflow: Path, paths_from: "list[Path]", wide: "list[str]") -> "list[str] | None":
|
|
"""The globs the gate answers for: the workflow's, each --wide one replaced by the paths
|
|
the --paths-from files declare. None when that cannot be read, which fails open."""
|
|
globs = paths_of(workflow)
|
|
if not paths_from and not wide:
|
|
return globs
|
|
if not paths_from or not wide or any(w not in globs for w in wide):
|
|
return None # a wide glob with nothing to stand for, or one the workflow lacks
|
|
extra: list[str] = []
|
|
for d in paths_from:
|
|
found = declared_paths(d)
|
|
if found is None:
|
|
return None
|
|
extra.extend(found)
|
|
return [g for g in globs if g not in wide] + extra
|
|
|
|
|
|
def matches(path: str, glob: str) -> bool:
|
|
"""`glob` in the Actions sense: `**` spans separators, `*` does not."""
|
|
rx = []
|
|
i = 0
|
|
while i < len(glob):
|
|
if glob.startswith("**", i):
|
|
rx.append(".*")
|
|
i += 2
|
|
elif glob[i] == "*":
|
|
rx.append("[^/]*")
|
|
i += 1
|
|
elif glob[i] == "?":
|
|
rx.append("[^/]")
|
|
i += 1
|
|
else:
|
|
rx.append(re.escape(glob[i]))
|
|
i += 1
|
|
return re.fullmatch("".join(rx), path) is not None
|
|
|
|
|
|
def main() -> None:
|
|
args = sys.argv[1:]
|
|
paths_from: list[Path] = []
|
|
wide: list[str] = []
|
|
while len(args) > 3:
|
|
if args[-2] == "--paths-from":
|
|
paths_from.insert(0, Path(args[-1]))
|
|
elif args[-2] == "--wide":
|
|
wide.insert(0, args[-1])
|
|
else:
|
|
print("true") # an argument this script does not read: run it
|
|
return
|
|
args = args[:-2]
|
|
if len(args) != 3:
|
|
print("true") # cannot tell: run it
|
|
return
|
|
workflow, base, head = Path(args[0]), args[1], args[2]
|
|
if not workflow.is_file() or not base or not head:
|
|
print("true")
|
|
return
|
|
globs = gate_globs(workflow, paths_from, wide)
|
|
if not globs:
|
|
# No filter, or a shape this parser does not read: the workflow wanted
|
|
# to run on everything, or we cannot say it did not.
|
|
print("true")
|
|
return
|
|
try:
|
|
out = subprocess.run(
|
|
["git", "diff", "--name-only", f"{base}...{head}"],
|
|
capture_output=True, text=True, check=True,
|
|
).stdout
|
|
except (subprocess.CalledProcessError, OSError):
|
|
print("true")
|
|
return
|
|
changed = [l for l in out.splitlines() if l.strip()]
|
|
if not changed:
|
|
print("true")
|
|
return
|
|
hit = any(matches(p, g) for p in changed for g in globs)
|
|
print("true" if hit else "false")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|