codeconsole commented on code in PR #16673:
URL: https://github.com/apache/grails-core/pull/16673#discussion_r4332495023
##########
RELEASE.md:
##########
@@ -27,6 +27,14 @@ Prior to starting the release process, ensure that any other
dependent library i
Due to a limitation with GitHub, private groups cannot be used as approvers
for an environment. For this reason, prior to performing the release, add
GitHub username to asf.yaml in the environment section for approvers. Only 6
approvers may exist on a given environment.
+A push to a release branch below the default branch builds and publishes
without running the tests (see the CI policy in `AGENTS.md`), so the commit a
release is cut from may never have been tested as a whole on that branch.
Before creating the release, run the `CI` workflow manually on the release
branch with its `run-tests` input ticked and wait for it to pass:
Review Comment:
`gradle.yml` now cancels the run in progress for every event, and a dispatch
shares its concurrency group (`CI-refs/heads/7.0.x`) with pushes to that
branch. So a merge to 7.0.x while this run is going cancels its test jobs, and
the push's own run skips the tests. It's worth a sentence here: if the run ends
up cancelled, run it again once nothing else is being merged to the branch.
##########
.github/workflows/gradle.yml:
##########
@@ -19,14 +19,37 @@ on:
branches:
- '[0-9]+.[0-9]+.x'
pull_request:
+ # ready_for_review is not a default type: a draft runs nothing until it is
ready or labelled "run ci".
+ types: [ opened, synchronize, reopened, ready_for_review ]
workflow_dispatch:
-# Queue jobs - cancel in-progress PR runs when new commits pushed, but allow
branch builds to complete
+ inputs:
+ run-tests:
+ description: "Run the test jobs where the branch rule would skip them
(a release branch below the default); a [skip tests] head commit still skips
them"
+ type: boolean
+ default: false
+# A new commit on the same pull request or branch cancels the build and test
jobs of the run in flight: their
+# results would be for a superseded commit, and the run that replaces it
publishes the newer one. The publish
+# jobs (publishGradle, publish, publishForge, verifyWrapper, docs) run on
always() with explicit result checks
+# instead, so a publish that has started completes: an interrupted upload
would leave the snapshot repository
+# with modules from two commits. Nothing of the cancelled commit is published
when the cancel arrives before
+# publish starts, because a cancelled test job fails the explicit checks. The
replacing run is pending until
Review Comment:
This holds when the tests ran, but not on a build-only run, and a build-only
run is now what every push to 7.x gets.
On a build-only run the test jobs are `skipped`, not `cancelled`, and
`skipped` passes `publish`'s checks. So once `buildGradle` has succeeded, a
cancel doesn't stop the publish chain:
- `publishGradle` and `publish` are `always()` jobs whose needs all
succeeded or were skipped, so they run in full after the cancel. `docs` follows
`publish`.
- `buildForge` is still running, so it's cancelled, and `publishForge` and
`verifyWrapper` are skipped.
In 39304647851, the last build-only push to 7.0.x, `buildGradle` finished 2
minutes in, `publish` started at 5 and took 15 minutes, and `buildForge` ran
until minute 12. So when a second merge lands on 7.0.x more than two minutes
after the first:
- The superseded commit's plugins, core and docs are published anyway, and
the replacing run waits behind them.
- The snapshot repository holds that commit's core with the previous
commit's Forge and CLI until the replacing run's `publishForge`. Before this
change the first run finished, so its Forge was published too.
Two ways to fix it:
- Add `buildForge` and `validateDependencies` to the `needs` of
`publishGradle` and `publish`, with the same explicit `success` checks. A
cancel before the builds finish then publishes nothing of that commit, whether
or not the tests ran. A build-only publish starts about 10 minutes later, and a
commit whose Forge build or dependency validation failed is no longer
published. A tested run publishes at the same time as now, since `publish`
already waits for tests that take longer than either build.
- Or keep the graph and correct the text: on a build-only run, a cancel
after the plugin build still publishes the commit, without its Forge. The same
claim is in `publish`'s comment (line 590) and in the CI policy section of
AGENTS.md (line 258).
##########
CONTRIBUTING.md:
##########
@@ -272,6 +272,25 @@ Grails makes use of [Release
Drafter](https://github.com/release-drafter/release
commit messages are important. They should follow the project's rules. While a
change can be incrementally made under
many commits, pull requests should be squashed into a single, meaningful
commit message.
+### Continuous Integration
+
+Every push and pull request runs the GitHub Actions workflows, and runner time
is shared with every other Apache
+project, so two controls limit what runs:
+
+* A draft pull request runs no workflow unless it carries the `run ci` label;
marking it ready for review starts CI.
+ A pull request labelled `no ci` runs no workflow at all, draft or not. (The
Release Drafter still labels every
+ pull request into a release branch; it runs no build.) Labels are read when
a push arrives, so add `run ci` (or
+ remove `no ci`) and then push, or mark the draft ready, to get a run.
Pushing to a draft still cancels the run in
+ flight for it, as every push to a pull request or a branch does.
Review Comment:
"as every push to a pull request or a branch does": on a branch, only CI and
the Groovy joint build cancel the run in flight. Code Style, Build Health,
CodeQL and RAT still cancel only on pull requests. AGENTS.md says "through the
concurrency groups of the test workflows", and the same qualifier would work
here.
##########
AGENTS.md:
##########
@@ -246,6 +247,17 @@ Rules:
- Do not use a newer third-party SHA, tag, or major version until it is on the
allowlist. If you need a new pin, open a PR against
`apache/infrastructure-actions` (`actions.yml`, not the generated
`approved_patterns.yml`).
- Before adding or bumping a third-party `uses:` line, search
`approved_patterns.yml` for that action and copy an approved SHA.
+### CI policy
+
+GitHub-hosted runner time is shared with every other Apache project, so the
workflows run tests only where the result is worth the runner time:
+
+- A draft pull request runs nothing unless it carries the `run ci` label, and
a pull request labelled `no ci` runs nothing at all: every workflow that reacts
to a pull request skips all of its jobs, except the Release Drafter
(`release-notes.yml`), which keeps reacting to every pull request into a
release branch, draft or not, because its autolabeler must label drafts; it
runs no build. Marking a draft ready for review starts CI (the workflows list
the `ready_for_review` trigger type, which is not a default). Labels and the
draft flag are read when a run starts, so set them before pushing; adding `run
ci` or removing `no ci` takes effect on the next push, or when the draft is
marked ready, and adding `no ci` does not stop a run in flight. A push to a
draft, or to a `no ci` pull request, still cancels the run in flight for that
pull request, as every push to a pull request or a branch does through the
concurrency groups of the test workflows; the run that replaces it does
nothing. A
new workflow with a `pull_request` trigger lists the same `types` as its
siblings and gates its jobs the same way.
+- Every workflow that runs tests calls `.github/workflows/ci-policy.yml` as
its first job, `policy`, and gates its test jobs (and the test steps of its
build jobs) on `needs.policy.outputs.run-tests`. Never test
`github.event.head_commit.message` in a workflow: a `pull_request` payload has
no such field, so that test silently ran the tests on every pull request. The
policy reads the head commit of the push, of the pull request, or of the
dispatched branch, and matches the marker against the start of the message's
first line. The read is retried with a growing pause; when it still fails, the
tests run, as they did before the policy existed, and a warning annotation and
the step summary say so, so an API outage costs runner time rather than a test
result.
+- The tests run by default only on the default branch and the release branches
above it, and there a commit message starting with `[skip tests]` skips them.
Everywhere else, that is on a release branch below the default (7.0.x, 7.1.x
and 7.2.x while 8.0.x is the default) and on a pull request whose base is a
side branch rather than a release branch, the tests run only on request: when
the commit message starts with `[run tests]`, when the pull request carries the
`run ci` label, or when a manual run (`workflow_dispatch`) in
`apache/grails-core` has its `run-tests` input ticked. Otherwise the run
builds, and on a push publishes snapshots, without tests, exactly like a `[skip
tests]` commit; a `[skip tests]` commit also wins over the label and the input.
The label is the way to test a backport pull request without rewriting its head
commit, and the input is how the commit a release is cut from gets tested (see
RELEASE.md); both are read when the run starts. A marker anywhere but at th
e start of the first line is ignored. The rule follows the default branch:
when a new one is promoted, the branches below it stop running tests by default
without an edit. The older release branches are kept quiet on purpose: a
backport lands as one push to the oldest branch and a merge-up to each branch
above it, and its tests belong on the pull request, labelled `run ci`, not on
every push after it. Side branches are unreviewed development work (see the
review process in CONTRIBUTING.md); their tests run when the branch is proposed
to a release branch. In a fork, where the runner time is the fork owner's and
not the ASF's, a manual run runs the tests unless the commit message starts
with `[skip tests]`.
+- The jobs that run no tests (dependency validation, the build-without-tests
steps, code style, build health, and the snapshot publish) still run on every
push to a release branch and on every non-draft pull request; RAT and CodeQL
run on every push to a release branch and on every non-draft pull request into
one, since their `pull_request` triggers list only release-branch bases. Only
the `no ci` label, or a draft without `run ci`, stops them.
Review Comment:
The snapshot publish doesn't run on pull requests: `publishGradle`,
`publish`, `publishForge` and `verifyWrapper` need a `push` or
`workflow_dispatch`, and `docs` needs a `push`. So "and on every non-draft pull
request" is true of everything in the list except it.
##########
.github/workflows/ci-policy.yml:
##########
@@ -0,0 +1,142 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Decides, once per run of a calling workflow, whether that workflow's test
jobs run. Every workflow that
+# runs tests calls this workflow as its first job, `policy`, and gates its
test jobs on the run-tests
+# output, so the rules live here and nowhere else; see the "CI policy" section
of AGENTS.md for the
+# reasons behind them.
+#
+# The tests run when the first line of the commit message starts with [run
tests] and do not run when it
+# starts with [skip tests]; a marker anywhere else is ignored. Without a
marker, an explicit request runs
+# them: the "run ci" label on a pull request, or the run-tests input of a
manual run. Without either, the
+# target branch decides: the default branch and every release branch above it
run the tests; a release
+# branch below the default (7.0.x while 8.0.x is the default) and a branch
that is not a release branch
+# (the base of a feature-to-feature pull request) build and publish without
them. The commit is the head
+# commit of the push, of the pull request, or of the dispatched branch. Two
exceptions: a manual run in a
+# fork, whose runner time is the fork owner's and not the ASF's, runs the
tests unless the message says
+# [skip tests]; and when the commit cannot be read from the API after several
attempts, the tests run,
+# as they did before this policy existed, so an API outage costs runner time
rather than a test result.
+#
+# The "no ci" label and the draft rule (a draft runs nothing without the "run
ci" label) are not handled
+# here: the calling job carries them in its `if`, so such a pull request runs
no job at all, not even this one.
+# When the tests do not run, a notice annotation on the run says so, so an
all-green pull request shows it.
+name: "CI policy"
+on:
+ workflow_call:
+ outputs:
+ run-tests:
+ description: "'true' when the calling workflow runs its test jobs,
'false' when it runs only its build and publish jobs"
+ value: ${{ jobs.decide.outputs.run-tests }}
+permissions:
+ contents: read
+jobs:
+ decide:
+ name: "Decide whether the tests run"
+ runs-on: ubuntu-24.04
+ outputs:
+ run-tests: ${{ steps.decide.outputs.run-tests }}
+ steps:
+ - name: "⚖️ Apply the CI policy"
+ id: decide
+ # The commit message is untrusted input (a pull request may come from
any fork), so it reaches the
+ # script only through the environment, never through expression
interpolation.
+ env:
+ GH_TOKEN: ${{ github.token }}
+ EVENT_NAME: ${{ github.event_name }}
+ DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
+ TARGET_BRANCH: ${{ github.base_ref || github.ref_name }}
+ HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
+ PUSH_MESSAGE: ${{ github.event.head_commit.message }}
+ REPOSITORY_OWNER: ${{ github.repository_owner }}
+ RUN_CI_LABEL: ${{ contains(github.event.pull_request.labels.*.name,
'run ci') }}
+ DISPATCH_RUN_TESTS: ${{ github.event.inputs.run-tests }}
+ run: |
+ set -euo pipefail
+ message_read=true
+ if [[ "$EVENT_NAME" == push ]]; then
+ message="$PUSH_MESSAGE"
+ else
+ # A pull_request or workflow_dispatch payload carries no commit
message, so read the head commit,
+ # retrying with a growing pause: a transient API error must not
decide the run.
+ message_read=false
+ for pause in 2 4 8 16 0; do
+ if message="$(gh api
"repos/${GITHUB_REPOSITORY}/commits/${HEAD_SHA}" --jq '.commit.message')"; then
+ message_read=true
+ break
+ fi
+ if (( pause > 0 )); then
+ echo "Reading commit ${HEAD_SHA} failed; retrying in ${pause}s"
+ sleep "$pause"
+ fi
+ done
+ fi
+ if [[ "$message_read" == true ]]; then
+ subject="${message%%$'\n'*}"
+ subject="${subject#"${subject%%[![:space:]]*}"}"
+ else
+ subject="(the commit message could not be read)"
+ fi
+
+ run_tests=true
+ if [[ "$message_read" != true ]]; then
+ reason="the commit message could not be read from the GitHub API
after 5 attempts, and then the tests run"
+ echo "::warning title=CI policy::Could not read commit ${HEAD_SHA}
from the GitHub API after 5 attempts; the tests run."
+ elif [[ "$subject" == '[skip tests]'* ]]; then
Review Comment:
Before this change a pull request always ran the tests, because
`head_commit.message` is empty on `pull_request`. Now a `[skip tests]` head
commit skips them on a pull request into 8.0.x and above too, and on a fork
pull request the author picks that commit. The skipped test checks count as
passing in the merge box.
The push after the merge doesn't always catch it either. The repository's
squash title setting is `COMMIT_OR_PR_TITLE`, so squashing a single-commit pull
request gives the merge commit the same `[skip tests]` title.
The notice annotation helps, but it only shows on the run page. Could `[skip
tests]` on a `pull_request` event count only when
`github.event.pull_request.author_association` is `OWNER`, `MEMBER` or
`COLLABORATOR`? Committers would keep the marker for docs-only and CI-only pull
requests, and a contributor's pull request into a tested branch would always
run the tests. Not blocking.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]