Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions .github/workflows/setup_multi_arch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -177,14 +177,15 @@ jobs:
PREBUILT_STAGES: ${{ inputs.prebuilt_stages }}
BASELINE_RUN_ID: ${{ inputs.baseline_run_id }}
STAGE_REUSE_MODE: ${{ inputs.stage_reuse_mode }}
# The checkout commit is used for commit-compatibility checking.
STAGE_REUSE_CURRENT_SHA: ${{ steps.checkout.outputs.commit }}
STAGE_REUSE_MAX_AGE_HOURS: ${{ inputs.stage_reuse_max_age_hours }}
STAGE_REUSE_COMMIT_HISTORY: ${{ inputs.stage_reuse_commit_history }}
# For cross-repo artifact reuse, use repository input (defaults to GITHUB_REPOSITORY).
# External repos set repository to "ROCm/TheRock" to reuse TheRock's prebuilt artifacts.
THEROCK_REPOSITORY: ${{ inputs.repository }}
# Skip path filtering for external repos (git sha won't exist in TheRock)
SKIP_PATH_FILTERS: ${{ inputs.external_repo != '' && 'true' || '' }}
# For baseline run selection, use repository input (defaults to GITHUB_REPOSITORY).
# External repos set repository to "ROCm/TheRock" to fetch TheRock baseline runs.
THEROCK_REPOSITORY: ${{ inputs.repository || github.repository }}

Copy link
Copy Markdown
Contributor

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.

# External repo JSON (e.g., {"repository":"ROCm/rocm-libraries","ref":"..."})
EXTERNAL_REPO: ${{ inputs.external_repo }}
CI_CONFIG_PATH: ci-config
# External repo family overrides allow callers to add/modify GPU family configs
# (e.g., adding test runners for architectures not enabled in TheRock's default config)
Expand Down
14 changes: 11 additions & 3 deletions build_tools/github_actions/configure_external_repo_ci.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand All @@ -30,7 +37,6 @@
import sys
import time
from dataclasses import dataclass, fields
from pathlib import Path
from typing import (
Callable,
Iterable,
Expand Down Expand Up @@ -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*",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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 run_all_tests=False and no matched projects.

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",
Expand Down
68 changes: 52 additions & 16 deletions build_tools/github_actions/configure_multi_arch_ci.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:")
Expand Down Expand Up @@ -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", ""),
)


Expand Down Expand Up @@ -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])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this also populate submodule_paths?

The docstring says the external repository name maps to a TheRock submodule, but returning only changed_files means has_submodule_changes evaluates to None.

That changes downstream test behavior: _determine_test_type() does not apply the submodule-change test policy, and families with submodule_bump_tests_only have their test runners disabled because they require has_submodule_changes is True.

An external rocm-libraries or rocm-systems change appears semantically equivalent to changing that submodule in TheRock, so should this return:

GitContext(
    changed_files=[external_repo_name],
    submodule_paths=[external_repo_name],
)

Could we also add a test asserting that GitContext.from_external_repo(...).has_submodule_changes is True?


@property
def has_submodule_changes(self) -> bool | None:
"""Check if any submodules were modified in the changed files.
Expand Down Expand Up @@ -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")
Expand All @@ -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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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 *.md documents or code files that are known to not affect CI (e.g. github actions workflow files unrelated to TheRock CI, .gitignore files, CMake files for experimental projects not yet integrated into the build system, etc.), the CI should still be able to short-circuit and skip. We shouldn't need to run expensive logic that decides "no builds affected, copy prebuilt files" and then "no tests affected, skip tests" IMO.

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,

@geomin12 geomin12 Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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!)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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.
Expand Down Expand Up @@ -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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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,
Expand Down Expand Up @@ -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 = ""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JSONDecodeError seems serious here. Might want to raise an exception rather than fallback to empty string, or at least log an error.

  • If the CI system is misconfigured, we should get an obvious error that we can fix
  • If github sometimes produces some output we don't expect, it is likely to also produce other output we don't expect elsewhere, so just continuing with fallback behavior seems risky


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.
Expand Down
6 changes: 5 additions & 1 deletion build_tools/github_actions/stage_reuse_decision.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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?
The current fallback clears effective_commit_sha, allowing baseline selection without verifying compatibility with the pinned TheRock commit. It would be safer to select no baseline and rebuild in that case.

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
Expand Down
Loading