This is an automated email from the ASF dual-hosted git repository.
Yicong-Huang pushed a commit to branch release/v1.1.0-incubating
in repository https://gitbox.apache.org/repos/asf/texera.git
The following commit(s) were added to refs/heads/release/v1.1.0-incubating by
this push:
new c0bba57887 feat(ci): make Build stacks optional based on PR labels
(#4622)
c0bba57887 is described below
commit c0bba57887252563b60af02994f95153e963d598
Author: Yicong Huang <[email protected]>
AuthorDate: Fri May 1 17:08:22 2026 -0700
feat(ci): make Build stacks optional based on PR labels (#4622)
### What changes were proposed in this PR?
Gate the Build workflow's main stacks on PR labels.
`required-checks.yml`'s `precheck` job now waits for the Pull Request
Labeler workflow to finish (polls the `labeler` check on the PR head
SHA, up to 5 min) and reads the resulting labels to decide which stacks
run:
| PR labels | frontend | scala | python | agent-service |
|---|---|---|---|---|
| only `docs` and/or `dev` | skip | skip | skip | skip |
| no `frontend` label | skip | run | run | run |
| includes `frontend` (or any non-skip label) | run | run | run | run |
| `push` / `workflow_dispatch` (no PR) | run | run | run | run |
`.github/labeler.yml`: rename the existing `build` label to `dev` so the
name matches the role precheck reads. The labeler applies it for
`bin/**` changes (the previous `deployment/**` glob is dropped because
that directory no longer exists).
The backport matrix inherits the same `run_*` decisions: each
`release/*` target only re-validates the stacks selected by the table
above. A docs-only PR with a `release/*` label still spawns a backport
run, but every stack inside it is skipped.
### Any related issues, documentation, discussions?
Closes #4621. Picks up the idea from the closed prior attempt #3642.
`.asf.yaml` ruleset's required check names are static; skipped stacks
now report `skipped` rather than `success`. The `Required Checks`
aggregator added in #4624 already treats `skipped` as a pass, so branch
protection stays green.
### How was this PR tested?
Self-test on this PR: it touches `.github/workflows/**` and
`.github/labeler.yml`, so labeler should add `ci` (and not `frontend`).
Expected precheck output: `run_frontend=false`, others `true`.
Adding/removing the `frontend` label should flip the frontend stack
on/off; replacing all labels with `docs` only (or `dev` only) should
skip every stack.
### Was this PR authored or co-authored using generative AI tooling?
Generated-by: Claude Opus 4.7
Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
(backported from commit e0547e6d0b206fcec305deca37087fd32053e9ac)
---
.github/labeler.yml | 3 +-
.github/workflows/required-checks.yml | 78 +++++++++++++++++++++++++++++------
2 files changed, 67 insertions(+), 14 deletions(-)
diff --git a/.github/labeler.yml b/.github/labeler.yml
index 72ff0ffd4a..0e12f50975 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -53,11 +53,10 @@ ci:
- any-glob-to-any-file:
- '.github/workflows/**'
-build:
+dev:
- changed-files:
- any-glob-to-any-file:
- 'bin/**'
- - 'deployment/**'
dependencies:
- changed-files:
diff --git a/.github/workflows/required-checks.yml
b/.github/workflows/required-checks.yml
index e189e79c38..2f858cd951 100644
--- a/.github/workflows/required-checks.yml
+++ b/.github/workflows/required-checks.yml
@@ -43,9 +43,14 @@ concurrency:
jobs:
# Precheck decides which downstream jobs run for this event:
+ # - On PR events, wait for the Pull Request Labeler workflow to finish so
+ # the labels it applies (frontend, docs, dev, ...) are available, then
+ # gate run_* outputs on those labels.
# - run_frontend / run_scala / run_python / run_agent_service: gate the
- # main build stacks. All true today; placeholder for future path- or
- # label-based selection.
+ # main build stacks. PRs labelled exclusively with docs and/or dev skip
+ # every stack; otherwise frontend skips when no `frontend` label is
+ # present (the other stacks always run when at least one non-docs/dev
+ # label exists). Push and dispatch events run every stack.
# - backport_targets: JSON array of release/* labels currently on the PR.
# Drives the backport matrix; empty array means no backport runs.
precheck:
@@ -58,28 +63,77 @@ jobs:
run_agent_service: ${{ steps.decide.outputs.run_agent_service }}
backport_targets: ${{ steps.decide.outputs.backport_targets }}
steps:
+ - name: Wait for Pull Request Labeler
+ if: github.event_name == 'pull_request'
+ uses: actions/github-script@v8
+ with:
+ script: |
+ const ref = context.payload.pull_request.head.sha;
+ const maxAttempts = 30;
+ for (let i = 0; i < maxAttempts; i++) {
+ const { data } = await github.rest.checks.listForRef({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ ref,
+ check_name: "labeler",
+ });
+ const check = data.check_runs[0];
+ if (check && check.status === "completed") {
+ core.info(`labeler ${check.conclusion}`);
+ return;
+ }
+ core.info(`labeler not ready (attempt ${i + 1}/${maxAttempts})`);
+ await new Promise((r) => setTimeout(r, 10000));
+ }
+ core.warning("labeler did not complete within 5 minutes;
proceeding with current labels.");
+
- name: Decide which jobs to run
id: decide
uses: actions/github-script@v8
with:
script: |
const eventName = context.eventName;
+ let labels = [];
- // Main build stacks: always run.
- const stacks = ["run_frontend", "run_scala", "run_python",
"run_agent_service"];
- for (const key of stacks) {
- core.setOutput(key, "true");
+ if (eventName === "pull_request") {
+ // Re-fetch labels: the labeler may have just added some.
+ const { data: pr } = await github.rest.pulls.get({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ pull_number: context.payload.pull_request.number,
+ });
+ labels = pr.labels.map((l) => l.name);
+ core.info(`PR labels: ${labels.join(", ") || "(none)"}`);
}
- // Backport targets: all current release/* labels on the PR.
- let targets = [];
- if (eventName === "pull_request") {
- const labels = context.payload.pull_request.labels.map((l) =>
l.name);
- targets = [...new Set(labels.filter((n) =>
/^release\/.+$/.test(n)))].sort();
+ const SKIP_ONLY = new Set(["docs", "dev"]);
+ const onlySkippable =
+ eventName === "pull_request" &&
+ labels.length > 0 &&
+ labels.every((l) => SKIP_ONLY.has(l));
+
+ let runFrontend = true;
+ let runScala = true;
+ let runPython = true;
+ let runAgentService = true;
+
+ if (onlySkippable) {
+ runFrontend = runScala = runPython = runAgentService = false;
+ core.info("Labels are docs/dev only; skipping all build
stacks.");
+ } else if (eventName === "pull_request" &&
!labels.includes("frontend")) {
+ runFrontend = false;
+ core.info("No frontend label; skipping frontend stack.");
}
+ core.setOutput("run_frontend", runFrontend ? "true" : "false");
+ core.setOutput("run_scala", runScala ? "true" : "false");
+ core.setOutput("run_python", runPython ? "true" : "false");
+ core.setOutput("run_agent_service", runAgentService ? "true" :
"false");
+
+ // Backport targets: all current release/* labels on the PR.
+ const targets = [...new Set(labels.filter((n) =>
/^release\/.+$/.test(n)))].sort();
if (targets.length === 0) {
- core.info(`No backport targets on PR.`);
+ core.info("No backport targets on PR.");
} else {
core.info(`Backport targets: ${targets.join(", ")}`);
}