-
Notifications
You must be signed in to change notification settings - Fork 295
feat(ci): add dynamic stage determination for external repos #7130
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
edf376c
34ccc5a
1217e49
a398618
9d474a2
071fb1f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,7 +5,14 @@ | |
| """Configure CI for external repos (rocm-systems, rocm-libraries). | ||
|
|
||
| This script determines which projects changed and whether to run/skip tests. | ||
| It consolidates logic previously duplicated across external repo workflows. | ||
|
|
||
| Stage Reuse: | ||
| TheRock's stage_reuse_decision.py handles stage impact analysis and | ||
| baseline run selection using commit compatibility. The external repo | ||
| workflow should: | ||
| 1. Resolve therock_ref using resolve_therock_ref.py (merge-base pinning) | ||
| 2. Pass that ref to setup_multi_arch.yml | ||
| 3. TheRock's stage_reuse_decision.py finds commit-compatible baseline runs | ||
|
|
||
| Usage: | ||
| python configure_external_repo_ci.py \ | ||
|
|
@@ -30,7 +37,6 @@ | |
| import sys | ||
| import time | ||
| from dataclasses import dataclass, fields | ||
| from pathlib import Path | ||
| from typing import ( | ||
| Callable, | ||
| Iterable, | ||
|
|
@@ -58,8 +64,10 @@ | |
| ] | ||
|
|
||
| # Patterns that trigger a full test run when changed (CI infrastructure) | ||
| # NOTE: .github/workflows/therock* is intentionally excluded since workflow | ||
| # changes should still use stage reuse to determine which stages to rebuild. | ||
| # The workflow itself doesn't affect TheRock build stages. | ||
| FULL_TEST_TRIGGER_PATTERNS = [ | ||
| ".github/workflows/therock*", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understand the intent to let workflow-only changes reuse unaffected build-stage artifacts. Could you clarify how removing this pattern affects test coverage downstream? FULL_TEST_TRIGGER_PATTERNS controls run_all_tests, so a change only under .github/workflows/therock* now appears to fall through with Stage reuse and test selection seem independent: we could reuse unchanged build artifacts while still running the full test suite to validate the workflow being modified. Is that still guaranteed elsewhere? |
||
| ".github/scripts/therock*", | ||
| ".github/scripts/get_changed_projects.py", | ||
| ".github/scripts/ci_utils.py", | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -161,6 +161,10 @@ class CIInputs: | |
| # Repository to query for baseline runs (for cross-repo artifact reuse) | ||
| baseline_repository: str = "" | ||
|
|
||
| # External repo JSON (e.g., '{"repository":"ROCm/rocm-libraries","ref":"..."}') | ||
| # Non-empty when an external repo calls TheRock workflows | ||
| external_repo: str = "" | ||
|
|
||
| def log(self) -> None: | ||
| """Log parsed inputs for CI diagnostics.""" | ||
| print("CIInputs:") | ||
|
|
@@ -260,6 +264,7 @@ def from_environ() -> "CIInputs": | |
| prebuilt_stages=os.environ.get("PREBUILT_STAGES", ""), | ||
| baseline_run_id=os.environ.get("BASELINE_RUN_ID", ""), | ||
| baseline_repository=os.environ.get("THEROCK_REPOSITORY", ""), | ||
| external_repo=os.environ.get("EXTERNAL_REPO", ""), | ||
| ) | ||
|
|
||
|
|
||
|
|
@@ -309,6 +314,17 @@ def empty() -> "GitContext": | |
| """ | ||
| return GitContext() | ||
|
|
||
| @staticmethod | ||
| def from_external_repo(external_repo_name: str) -> "GitContext": | ||
| """Create context for external repo builds (e.g., rocm-libraries). | ||
|
|
||
| For external repos, we treat the repo name as the changed file so that | ||
| stage reuse analysis can determine which TheRock stages are affected. | ||
| The external repo name maps to a submodule in TheRock. | ||
| """ | ||
| print(f"External repo detected: {external_repo_name}") | ||
| return GitContext(changed_files=[external_repo_name]) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this also populate The docstring says the external repository name maps to a TheRock submodule, but returning only That changes downstream test behavior: An external GitContext(
changed_files=[external_repo_name],
submodule_paths=[external_repo_name],
)Could we also add a test asserting that |
||
|
|
||
| @property | ||
| def has_submodule_changes(self) -> bool | None: | ||
| """Check if any submodules were modified in the changed files. | ||
|
|
@@ -571,6 +587,9 @@ def should_skip_ci( | |
| - 'ci:skip' PR label | ||
| - Only skippable files changed (docs, .md, etc.) | ||
| - No files changed | ||
|
|
||
| For external repo builds, path filtering is skipped since the external repo | ||
| name is used for stage reuse analysis, not for CI skip decisions. | ||
| """ | ||
| if "ci:skip" in ci_inputs.pr_labels: | ||
| print(" Skipping: 'ci:skip' PR label") | ||
|
|
@@ -594,6 +613,12 @@ def should_skip_ci( | |
| if "ci:asan" in ci_inputs.pr_labels and ci_inputs.build_variant == "asan": | ||
| print(" Running: 'ci:asan' PR label triggers ASAN CI") | ||
|
|
||
| # External repo builds skip path filtering - they always run CI and use | ||
| # stage reuse to determine which stages to rebuild. | ||
| if ci_inputs.external_repo: | ||
| print(" External repo build: skipping path filter checks, using stage reuse") | ||
| return False | ||
|
Comment on lines
+621
to
+627
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't seem flexible enough, at least on the surface. If a PR to rocm-libraries or rocm-systems only modifies non-code files like Can we reuse the skip path filters that currently exist in those repositories like https://github.com/ROCm/rocm-libraries/blob/7df08c3d5d7ca4087a21810122f19d65fc102975/.github/scripts/therock_configure_ci.py#L28-L70 ? We could start with this as you have it, but we get frequent requests for ways to skip CI where it isn't applicable, so I don't want to regress there. For example,
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes! do not worry, I am aware of this. I'm trying to make incremental PRs all in parallel to handle scoped items so it isn't massive! this is at least letting CI know that rules that apply to TheRock do not apply to external repo (such as CMakeLists.txt edit, don't run everything!)
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could add a TODO here or link to an issue with the plan so we don't lose track and if someone later finds the code they know what the ideal/planned state is. |
||
|
|
||
| # If we have a list of changed files (push/pull_request events), check if | ||
| # CI should run for that set of changed files. For example: if only .md | ||
| # files are changed, skip CI. | ||
|
|
@@ -892,14 +917,17 @@ def decide_jobs( | |
| baseline_repository = ci_inputs.baseline_repository | ||
| baseline_run_id = ci_inputs.baseline_run_id | ||
|
|
||
| # Apply automatic stage reuse when running in the same repo as baseline. | ||
| current_repo = os.environ.get("GITHUB_REPOSITORY", "") | ||
| if not baseline_repository or baseline_repository == current_repo: | ||
| # reuse-stage mode returns non-empty applied_reuse_stages. | ||
| for stage in auto_stage_reuse.applied_reuse_stages: | ||
| stage_decisions.setdefault(stage, JobAction.PREBUILT) | ||
| if auto_stage_reuse.applied_reuse_stages and auto_stage_reuse.baseline_run_id: | ||
| baseline_run_id = auto_stage_reuse.baseline_run_id | ||
| # Apply automatic stage reuse. For external repos (rocm-libraries, rocm-systems), | ||
| # we reuse stages from TheRock baselines. For same-repo runs, baseline_repository | ||
| # is empty or matches the current repo. | ||
| # reuse-stage mode returns non-empty applied_reuse_stages. | ||
| for stage in auto_stage_reuse.applied_reuse_stages: | ||
| stage_decisions.setdefault(stage, JobAction.PREBUILT) | ||
| if auto_stage_reuse.applied_reuse_stages and auto_stage_reuse.baseline_run_id: | ||
| baseline_run_id = auto_stage_reuse.baseline_run_id | ||
| # For external repos, use their baseline_repository (ROCm/TheRock) | ||
| if ci_inputs.baseline_repository: | ||
| baseline_repository = ci_inputs.baseline_repository | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is already done in line 917 |
||
|
|
||
| build_rocm = BuildRocmDecision( | ||
| action=JobAction.RUN, | ||
|
|
@@ -1393,15 +1421,23 @@ def configure(ci_inputs: CIInputs, git_context: GitContext) -> CIOutputs: | |
| def main(): | ||
| ci_inputs = CIInputs.from_environ() | ||
|
|
||
| # Skip path filtering for external repos (e.g., rocm-libraries calling TheRock workflows) | ||
| # The "run everything" is initial state for superrepo multi-arch CI migration. | ||
| # We will eventually support path filtering and component selection. | ||
| # TODO: Provide custom decision logic to run specific components and paths | ||
| skip_path_filters = os.environ.get("SKIP_PATH_FILTERS", "").lower() == "true" | ||
| # Check if this is an external repo build (e.g., rocm-libraries calling TheRock workflows) | ||
| if ci_inputs.external_repo: | ||
| # External repo: use repo name for stage reuse analysis. | ||
| # external_repo is JSON like {"repository":"ROCm/rocm-libraries","ref":"..."} | ||
| try: | ||
| external_repo = json.loads(ci_inputs.external_repo) | ||
| repo_full_name = external_repo.get("repository", "") | ||
| external_repo_name = ( | ||
| repo_full_name.split("/")[-1] if "/" in repo_full_name else "" | ||
| ) | ||
| except (json.JSONDecodeError, TypeError): | ||
| external_repo_name = "" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| if skip_path_filters: | ||
| # External repo: skip path filtering, run everything | ||
| git_context = GitContext.empty() | ||
| if external_repo_name: | ||
| git_context = GitContext.from_external_repo(external_repo_name) | ||
| else: | ||
| git_context = GitContext.empty() | ||
| elif (ci_inputs.is_pull_request or ci_inputs.is_push) and ci_inputs.base_ref: | ||
| # 'pull_request' and 'push' events can use the list of changed files | ||
| # compared to the "prior commit" to affect job selections/options. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -474,7 +474,11 @@ def _default_baseline_selector(*, platform: str) -> BaselineSelector: | |
| extra "passing build" check is needed here. | ||
| """ | ||
|
|
||
| github_repository = os.environ.get("GITHUB_REPOSITORY", "ROCm/TheRock") | ||
| # THEROCK_REPOSITORY is set by setup_multi_arch.yml to the repository input | ||
| # (ROCm/TheRock for external repos, or github.repository for normal runs). | ||
| github_repository = os.environ.get( | ||
| "THEROCK_REPOSITORY", os.environ.get("GITHUB_REPOSITORY", "ROCm/TheRock") | ||
| ) | ||
|
Comment on lines
+477
to
+481
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This correctly redirects external-repo baseline queries to TheRock. However, with this new external-repo path, could we fail closed if the branch-history query below fails or returns no commits while STAGE_REUSE_CURRENT_SHA is set? |
||
| branch = os.environ.get("STAGE_REUSE_BASELINE_BRANCH", "main") | ||
| workflow_name = os.environ.get("STAGE_REUSE_BASELINE_WORKFLOW", "multi_arch_ci.yml") | ||
| current_commit_sha = os.environ.get("STAGE_REUSE_CURRENT_SHA") or None | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PR description mentions adding GITHUB_TOKEN for the baseline API calls, but I don't see it passed into this step's environment.
Should this include:
GITHUB_TOKEN: ${{ github.token }}The stage-reuse path now queries branch history and baseline workflow runs, so explicitly passing the workflow token would ensure those requests are authenticated.