This is an automated email from the ASF dual-hosted git repository.

Yicong-Huang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/texera.git


The following commit(s) were added to refs/heads/main by this push:
     new e0547e6d0b feat(ci): make Build stacks optional based on PR labels 
(#4622)
e0547e6d0b is described below

commit e0547e6d0b206fcec305deca37087fd32053e9ac
Author: Yicong Huang <[email protected]>
AuthorDate: Fri May 1 16:43:41 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]>
---
 .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(", ")}`);
             }

Reply via email to