From 16de0c8cda7be499094e49752cb462ead9345d8d Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 10 Nov 2026 11:03:36 +0200 Subject: [PATCH] ci: merge the cheap script gates into one job; cancel superseded bench runs Six tree-wide script gates each queued for a runner of their own; on a busy PR a sub-minute grep showed 40+ minutes of wall time, nearly all waiting. They now run as steps of one job in source-gates.yml, still with no paths filter. python-bench and build-container gain cancel-in-progress. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/build-container.yml | 5 + .../workflows/check-16k-page-alignment.yml | 52 ---------- .../check-android-dependency-alignment.yml | 2 +- .../workflows/check-android-gms-isolation.yml | 47 --------- .github/workflows/check-author-tags.yml | 46 --------- .../workflows/check-control-characters.yml | 39 -------- .github/workflows/check-copyright-headers.yml | 46 --------- .github/workflows/check-since-tags.yml | 52 ---------- .github/workflows/python-bench.yml | 5 + .github/workflows/source-gates.yml | 95 +++++++++++++++++++ CLAUDE.md | 8 +- 11 files changed, 110 insertions(+), 287 deletions(-) delete mode 100644 .github/workflows/check-16k-page-alignment.yml delete mode 100644 .github/workflows/check-android-gms-isolation.yml delete mode 100644 .github/workflows/check-author-tags.yml delete mode 100644 .github/workflows/check-control-characters.yml delete mode 100644 .github/workflows/check-copyright-headers.yml delete mode 100644 .github/workflows/check-since-tags.yml create mode 100644 .github/workflows/source-gates.yml diff --git a/.github/workflows/build-container.yml b/.github/workflows/build-container.yml index f51a124a752..49453c7be71 100644 --- a/.github/workflows/build-container.yml +++ b/.github/workflows/build-container.yml @@ -7,6 +7,11 @@ on: - '.github/workflows/build-container.yml' workflow_dispatch: +concurrency: + # Only the latest revision needs validation; a superseded run is wasted runner time. + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: build-and-push: runs-on: ubuntu-24.04 diff --git a/.github/workflows/check-16k-page-alignment.yml b/.github/workflows/check-16k-page-alignment.yml deleted file mode 100644 index 8de588f51b2..00000000000 --- a/.github/workflows/check-16k-page-alignment.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: Check 16 KB page alignment - -# Google Play requires every 64-bit native library in an upload to be linked -# for 16 KB memory pages: API 35+ uploads since 2025-11-01, and Wear OS uploads -# containing native code from 2026-09-15. -# See https://developer.android.com/guide/practices/page-sizes. -# -# Alignment is decided at link time, so nothing downstream can repair it -- not -# AGP's ZIP alignment, not bundletool, not the developer. A Codename One -# artifact that gets it wrong ships a broken app the developer cannot fix, and -# the breakage is invisible until Play review or a real 16 KB device: the app -# builds and runs on every 4 KB device in the office. -# -# Deliberately its own workflow rather than a step in pr.yml, whose paths filter -# would let a PR that only adds a native artifact skip the check. The scan is a -# ZIP listing plus an ELF header read, so running it on every PR costs nothing. - -permissions: - contents: read - -on: - workflow_dispatch: - pull_request: - branches: - - master - push: - branches: - - master - -concurrency: - # Only the latest revision needs validation. PR numbers keep forks isolated; - # event names keep a manual/scheduled run from cancelling a push or PR run. - group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -jobs: - check-16k-page-alignment: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v6 - - - name: Unit-test the gate's classification boundaries - # Every case in here is one a review caught: something unreadable - # passing as compliant, or a valid artifact rejected by a rule that - # does not apply to it. Run before the scan, so a broken gate is - # reported as a broken gate rather than as a clean tree. - shell: bash - run: python3 scripts/test_check_16k_page_alignment.py - - - name: Scan tracked native artifacts for 16 KB page alignment - shell: bash - run: scripts/check-16k-page-alignment.py --verbose diff --git a/.github/workflows/check-android-dependency-alignment.yml b/.github/workflows/check-android-dependency-alignment.yml index dc574313d4b..2f0c66d371b 100644 --- a/.github/workflows/check-android-dependency-alignment.yml +++ b/.github/workflows/check-android-dependency-alignment.yml @@ -1,6 +1,6 @@ name: Check Android dependency 16 KB alignment -# check-16k-page-alignment.yml reads every TRACKED artifact. Most native code a +# source-gates.yml (the 16 KB step) reads every TRACKED artifact. Most native code a # Codename One Android app ships is not tracked: the builder writes Maven # coordinates into the generated build.gradle (PlatformFeatureCatalog entries, # the play-services defaults) and Gradle downloads them on the build machine. diff --git a/.github/workflows/check-android-gms-isolation.yml b/.github/workflows/check-android-gms-isolation.yml deleted file mode 100644 index 461f35e5b75..00000000000 --- a/.github/workflows/check-android-gms-isolation.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: Check Android Play services isolation - -# Codename One applications run on Android without Google Play services -# (GrapheneOS, CalyxOS, /e/OS, LineageOS without GApps, Huawei). There the Play -# services libraries are still in the APK but the service behind them is not, so -# every call into them has to sit behind GoogleServices.isPlayServicesAvailable. -# This holds the Android port to that: only the guarded proxies listed in -# scripts/android-gms-isolation-allowlist.txt may name a Google Play type. -# -# Pure source scan, seconds, no build. Its own workflow so the paths filter can -# be exactly the inputs it reads. - -permissions: - contents: read - -on: - workflow_dispatch: - pull_request: - branches: - - master - paths: - - 'Ports/Android/src/**' - - 'scripts/check-android-gms-isolation.py' - - 'scripts/android-gms-isolation-allowlist.txt' - - '.github/workflows/check-android-gms-isolation.yml' - push: - branches: - - master - paths: - - 'Ports/Android/src/**' - - 'scripts/check-android-gms-isolation.py' - - 'scripts/android-gms-isolation-allowlist.txt' - - '.github/workflows/check-android-gms-isolation.yml' - -concurrency: - group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -jobs: - check-android-gms-isolation: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v6 - - - name: Google Play types only in guarded proxies - shell: bash - run: scripts/check-android-gms-isolation.py diff --git a/.github/workflows/check-author-tags.yml b/.github/workflows/check-author-tags.yml deleted file mode 100644 index b20c333d189..00000000000 --- a/.github/workflows/check-author-tags.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Check author and version tags - -# @author and @version are banned from Java documentation comments, the same way -# @since is. They say nothing about the API, and a block tag's body runs to the -# next tag or the end of the comment -- so text written after one was swallowed -# into it and never published. See scripts/check-author-tags.py. -# -# Its own workflow rather than a step in pr.yml, whose paths filter excludes -# scripts/**: a change that weakened the gate could otherwise merge without the -# gate ever running. It covers every tracked .java file, ports and tools included. - -permissions: - contents: read - -on: - workflow_dispatch: - pull_request: - branches: - - master - paths: - - '**/*.java' - - 'scripts/check-author-tags.py' - - '.github/workflows/check-author-tags.yml' - push: - branches: - - master - paths: - - '**/*.java' - - 'scripts/check-author-tags.py' - - '.github/workflows/check-author-tags.yml' - -concurrency: - # Only the latest revision needs validation. PR numbers keep forks isolated; - # event names keep a manual/scheduled run from cancelling a push or PR run. - group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -jobs: - check-author-tags: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v6 - - - name: Reject @author and @version in documentation comments - shell: bash - run: scripts/check-author-tags.py diff --git a/.github/workflows/check-control-characters.yml b/.github/workflows/check-control-characters.yml deleted file mode 100644 index cae4d776ea6..00000000000 --- a/.github/workflows/check-control-characters.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Check control characters - -# A raw control byte in a source file is invisible in an editor and turns the -# file binary to grep, which then reports "no match" instead of an error -- a -# search comes back empty and reads as "the symbol is not there". Every -# language here has an escape that compiles to the same value, so the gate is -# absolute: no baseline, no exclusions. -# -# Deliberately its own workflow rather than a step in pr.yml: that workflow's -# paths filter excludes scripts/** and docs/**, so a PR touching only those -# would skip the check -- and this tree has had a corrupted file in each. - -permissions: - contents: read - -on: - workflow_dispatch: - pull_request: - branches: - - master - push: - branches: - - master - -concurrency: - # Only the latest revision needs validation. PR numbers keep forks isolated; - # event names keep a manual/scheduled run from cancelling a push or PR run. - group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -jobs: - check-control-characters: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v6 - - - name: Scan tracked text files for raw control characters - shell: bash - run: scripts/check-control-characters.py diff --git a/.github/workflows/check-copyright-headers.yml b/.github/workflows/check-copyright-headers.yml deleted file mode 100644 index 2bfdbebdffc..00000000000 --- a/.github/workflows/check-copyright-headers.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Check copyright headers - -# New source files must use Codename One's complete GPLv2 + Classpath -# Exception header. Existing older source may retain Oracle's complete form. - -permissions: - contents: read - -on: - workflow_dispatch: - pull_request: - branches: - - master - push: - branches: - - master - -concurrency: - # Only the latest revision needs validation. PR numbers keep forks isolated; - # event names keep a manual/scheduled run from cancelling a push or PR run. - group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -jobs: - check-copyright-headers: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Validate added and modified source headers - shell: bash - env: - EVENT_NAME: ${{ github.event_name }} - PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} - PUSH_BASE_SHA: ${{ github.event.before }} - run: | - if [[ "$EVENT_NAME" == "pull_request" ]]; then - base_sha="$PR_BASE_SHA" - elif [[ -n "$PUSH_BASE_SHA" && "$PUSH_BASE_SHA" != "0000000000000000000000000000000000000000" ]]; then - base_sha="$PUSH_BASE_SHA" - else - base_sha="$(git rev-parse HEAD^)" - fi - scripts/check-copyright-headers.sh --base "$base_sha" --head "$GITHUB_SHA" diff --git a/.github/workflows/check-since-tags.yml b/.github/workflows/check-since-tags.yml deleted file mode 100644 index 955df73697c..00000000000 --- a/.github/workflows/check-since-tags.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: Check since tags - -# Fails the build if Java sources under CodenameOne/src, or the backend runtime -# under vm/backend, carry @since tags or Markdown/Javadoc "Since" sections. -# Generated documentation changes have repeatedly guessed versions, which is worse -# than omitting the metadata. Both are published API references. - -permissions: - contents: read - -on: - workflow_dispatch: - pull_request: - branches: - - master - paths: - - 'CodenameOne/src/**/*.java' - - 'vm/backend/src/**/*.java' - - 'vm/backend/impl/**/*.java' - - 'scripts/check-since-tags.sh' - - '.github/workflows/check-since-tags.yml' - push: - branches: - - master - paths: - - 'CodenameOne/src/**/*.java' - - 'vm/backend/src/**/*.java' - - 'vm/backend/impl/**/*.java' - - 'scripts/check-since-tags.sh' - - '.github/workflows/check-since-tags.yml' - -concurrency: - # Only the latest revision needs validation. PR numbers keep forks isolated; - # event names keep a manual/scheduled run from cancelling a push or PR run. - group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -jobs: - check-since-tags: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v6 - - - name: Reject since markers in API docs - shell: bash - run: scripts/check-since-tags.sh - - - name: Reject since markers in the backend API docs - shell: bash - run: | - scripts/check-since-tags.sh vm/backend/src - scripts/check-since-tags.sh vm/backend/impl diff --git a/.github/workflows/python-bench.yml b/.github/workflows/python-bench.yml index baab8fd2f88..bbcb22576cc 100644 --- a/.github/workflows/python-bench.yml +++ b/.github/workflows/python-bench.yml @@ -31,6 +31,11 @@ on: - 'maven/pom.xml' workflow_dispatch: +concurrency: + # Only the latest revision needs validation; a superseded run is wasted runner time. + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + permissions: contents: read # The pr-ci-container image lives on ghcr; without this the job dies in diff --git a/.github/workflows/source-gates.yml b/.github/workflows/source-gates.yml new file mode 100644 index 00000000000..77eb90aeedb --- /dev/null +++ b/.github/workflows/source-gates.yml @@ -0,0 +1,95 @@ +name: Source gates + +# The cheap tree-wide script gates, as steps of ONE job. Each used to be its own +# workflow, and on a busy PR every one of them queued for a runner of its own: +# a sub-minute grep showed 40+ minutes of wall time, nearly all of it waiting. +# +# Deliberately not a step in pr.yml, and deliberately with no paths filter: +# pr.yml's filter excludes scripts/** and docs/**, so a PR touching only those +# (or weakening one of these scripts) would skip the gates. Every step runs on +# every PR; together they cost about a minute. +# +# Steps use `if: !cancelled()` so one failing gate does not hide the others. + +permissions: + contents: read + +on: + workflow_dispatch: + pull_request: + branches: + - master + push: + branches: + - master + +concurrency: + # Only the latest revision needs validation. PR numbers keep forks isolated; + # event names keep a manual/scheduled run from cancelling a push or PR run. + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + source-gates: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v6 + with: + # The copyright-header gate diffs against the PR base. + fetch-depth: 0 + + # A raw control byte makes a file binary to grep, which then reports "no + # match" instead of an error. Absolute: no baseline, no exclusions. + - name: Scan tracked text files for raw control characters + if: ${{ !cancelled() }} + shell: bash + run: scripts/check-control-characters.py + + # @author and @version swallow the text after them into the tag body, + # which the doclet then drops. See scripts/check-author-tags.py. + - name: Reject @author and @version in documentation comments + if: ${{ !cancelled() }} + shell: bash + run: scripts/check-author-tags.py + + - name: Reject since markers in API docs + if: ${{ !cancelled() }} + shell: bash + run: | + scripts/check-since-tags.sh + scripts/check-since-tags.sh vm/backend/src + scripts/check-since-tags.sh vm/backend/impl + + - name: Google Play types only in guarded proxies + if: ${{ !cancelled() }} + shell: bash + run: scripts/check-android-gms-isolation.py + + # Unit-test the gate's classification boundaries before the scan, so a + # broken gate is reported as a broken gate rather than as a clean tree. + - name: Unit-test the 16 KB page alignment gate + if: ${{ !cancelled() }} + shell: bash + run: python3 scripts/test_check_16k_page_alignment.py + + - name: Scan tracked native artifacts for 16 KB page alignment + if: ${{ !cancelled() }} + shell: bash + run: scripts/check-16k-page-alignment.py --verbose + + - name: Validate added and modified source headers + if: ${{ !cancelled() }} + shell: bash + env: + EVENT_NAME: ${{ github.event_name }} + PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} + PUSH_BASE_SHA: ${{ github.event.before }} + run: | + if [[ "$EVENT_NAME" == "pull_request" ]]; then + base_sha="$PR_BASE_SHA" + elif [[ -n "$PUSH_BASE_SHA" && "$PUSH_BASE_SHA" != "0000000000000000000000000000000000000000" ]]; then + base_sha="$PUSH_BASE_SHA" + else + base_sha="$(git rev-parse HEAD^)" + fi + scripts/check-copyright-headers.sh --base "$base_sha" --head "$GITHUB_SHA" diff --git a/CLAUDE.md b/CLAUDE.md index b207823d982..b57c5bf0da7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -513,7 +513,7 @@ guessed at versions, which is worse than none -- `scripts/check-since-tags.sh`. the next tag or the end of the comment: text written after one was swallowed into it, and since the doclet drops `@author`, 90 published comments had silently lost a "See also" or "Deprecated" section. `scripts/check-author-tags.py` rejects them -in every tracked `.java` file (its own workflow, `check-author-tags.yml`), in `///` +in every tracked `.java` file (`source-gates.yml`), in `///` and `/** */` comments. A plain `/* */` comment is left alone: vendored license headers (MiG Layout's BSD notice) carry an `@author` that is part of the notice. @@ -566,7 +566,7 @@ escape (`'\0'`, `"\u001f"`, `\x1f`). TAB, LF and CR are the only control characters allowed. It covers every tracked text file -- source, scripts, config and docs -- because this tree had corrupted files in several of them. -It runs as its own workflow (`.github/workflows/check-control-characters.yml`) +It runs in its own workflow (`.github/workflows/source-gates.yml`, one job with the other cheap tree-wide script gates) rather than inside `pr.yml`, whose `paths` filter excludes `scripts/**` and `docs/**` and would let a PR touching only those skip the check. @@ -614,8 +614,8 @@ not honour -- check `.note.android.ident`, which records the API level the slice was really compiled against. `scripts/check-16k-page-alignment.py` gates this over every tracked archive and -loose `.so` in the tree, in its own workflow -(`.github/workflows/check-16k-page-alignment.yml`) rather than inside `pr.yml`, +loose `.so` in the tree, as a step of +`.github/workflows/source-gates.yml` rather than inside `pr.yml`, whose `paths` filter would let a PR that only adds a native artifact skip it. Containers and libraries are found by magic bytes, not by extension, so a `.cn1lib` or any future container is covered.