Skip to content

feat(ci): Add consolidated external repo CI configuration script - #6660

Merged
geomin12 merged 5 commits into
mainfrom
users/geomin12/external-repo-change
Jul 21, 2026
Merged

geomin12 merged 5 commits into
mainfrom
users/geomin12/external-repo-change

Conversation

@geomin12

Copy link
Copy Markdown
Contributor

Consolidates duplicated CI configuration logic from rocm-systems and rocm-libraries into a single Python script. Handles event detection, merge-base computation, changed project matching, and skip/run-all logic.

Instead of the diverged python scripts in superrepos, we will now rely on this config script to handle for multi arch external CI

External repos can call this script instead of maintaining separate copies of get_changed_projects.py and its dependencies.

Working here:

ISSUE ID: #3343

geomin12 and others added 3 commits July 17, 2026 11:34
Consolidates duplicated CI configuration logic from rocm-systems and
rocm-libraries into a single Python script. Handles event detection,
merge-base computation, changed project matching, and skip/run-all logic.

External repos can call this script instead of maintaining separate
copies of get_changed_projects.py and its dependencies.

Usage:
  python configure_external_repo_ci.py \
    --event-name pull_request \
    --base-sha ${{ github.event.pull_request.base.sha }} \
    --head-sha ${{ github.event.pull_request.head.sha }}

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add required --github-repo argument so the script knows which
repository it's running in.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Switch from git diff/merge-base to gh api compare endpoint.
This removes the need for full repository checkout - only the
repos-config.json file needs to be checked out now.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@therock-pr-bot

therock-pr-bot Bot commented Jul 17, 2026 •

Copy link
Copy Markdown

✅ All Checks Passed — Ready for Review

Check Status Details
🌿 Branch Name ✅ Pass —
📝 PR Title/Description ✅ Pass —
⛔ Forbidden Files ✅ Pass —
🧪 Unit Test ✅ Pass —
🔎 pre-commit ✅ Pass —
🚫 Draft PR 🔜 To Be Enabled —
🚩 Feature Flag 🔜 To Be Enabled —
📊 Code Coverage 🔜 To Be Enabled —
🤖 therock-pr-bot ✅ Pass —

🎉 All checks passed! This PR is ready for review.

📖 Need help? See the Policy FAQ for details on every check and how to fix failures.

@therock-pr-bot therock-pr-bot Bot added the Not ready to Review PR has unresolved policy failures — reviews blocked label Jul 17, 2026
@therock-pr-bot

therock-pr-bot Bot commented Jul 17, 2026 •

Copy link
Copy Markdown

🎉 All checks passed! This PR is ready for review.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@tony-davis tony-davis left a comment

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.

Nice, Geo; consolidating the diverged get_changed_projects.py copies into one script is a real maintenance win, and moving to the gh api compare endpoint so you don't need a full checkout is a nice touch. A few things before this is ready.

The one that's already blocking you: the therock-pr-bot Unit Test check is red and it's added the "Not ready to Review" label. This script is very testable (the helpers are pure, and configure() just needs get_modified_paths_api mocked), so a configure_external_repo_ci_test.py covering the event branches, the skippable/CI-trigger logic, and subtree matching would both unblock the bot and lock the behavior. This is the same gap I flagged on #6709's compute_affected_stages.py, so it might be worth a small shared test pattern for both.

Two correctness things I'd like to sort out: the push path builds {head_sha}^ as the compare base, and the REST compare API doesn't understand git ^ ancestry (it wants a SHA or ref), so that call will 404 and, since the retry only catches timeouts, crash the step. And the compare endpoint caps files at 300, so a big monorepo PR would silently truncate the changed list and under-test. I left notes on both.

Nothing here is a showstopper for the approach; it's a solid direction. Details below.

One bigger-picture thought: since this and #6709 are a producer/consumer pair for the same feature, I think it'd be worth landing them together (with tests on both) rather than merging one ahead of the other.

f.write(f"{k}={v}\n")


def configure(

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 the core decision logic and there's no test for it (which is what has therock-pr-bot blocking the PR). A small configure_external_repo_ci_test.py would do it: schedule/workflow_dispatch -> run-all; only *.md changed -> skip; a .github/workflows/therock* change -> run-all; and a projects/rocblas/... change -> changed_projects=projects/rocblas, with get_modified_paths_api monkeypatched so no network is needed. The pure helpers (is_skippable, find_matched_subtrees, matches_patterns) are trivial to cover too.

Comment on lines +222 to +225
elif event_name == "push" and head_sha:
# For push, compare HEAD^ to HEAD using the API
logger.info(f"Getting push diff via API: {head_sha}^...{head_sha}")
modified_paths = get_modified_paths_api(github_repo, f"{head_sha}^", head_sha)

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 compare API takes a SHA or ref for the base, not git ^ ancestry, so {head_sha}^...{head_sha} will fail (and because retry only catches TimeoutExpired, the resulting CalledProcessError will crash the step rather than degrade gracefully). For push, the caller has the previous SHA in github.event.before; passing that in as --base-sha and comparing directly is the clean fix. Suggestion assumes that contract (and falls through to run-all if base_sha is missing).

Suggested change
elif event_name == "push" and head_sha:
# For push, compare HEAD^ to HEAD using the API
logger.info(f"Getting push diff via API: {head_sha}^...{head_sha}")
modified_paths = get_modified_paths_api(github_repo, f"{head_sha}^", head_sha)
elif event_name == "push" and base_sha and head_sha:
# For push, the caller passes the "before" SHA as base_sha; the compare
# API does not understand git "^" ancestry syntax.
logger.info(f"Getting push diff via API: {base_sha}...{head_sha}")
modified_paths = get_modified_paths_api(github_repo, base_sha, head_sha)

Comment on lines +123 to +136
result = subprocess.run(
[
"gh",
"api",
f"repos/{github_repo}/compare/{base_sha}...{head_sha}",
"--jq",
".files[].filename",
],
capture_output=True,
text=True,
check=True,
timeout=60,
)
return set(result.stdout.splitlines())

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 compare endpoint returns at most 300 files (it paginates commits, not files), so a large PR in a monorepo would silently give a truncated list and we'd under-build/under-test; exactly the case where you most want full coverage. Worth guarding: if the returned file count hits the cap (or the response's truncation flag is set), fall back to run_all_tests. Even a simple "if len(files) >= 300: treat as run-all" is safer than quietly missing projects.

try:
with open(config_path, "r", encoding="utf-8") as f:
data = json.load(f)
return [RepoEntry(**entry) for entry in data.get("repositories", [])]

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.

repos-config.json is a shared, evolving file. The moment someone adds a new key to a repo entry, RepoEntry(**entry) raises TypeError, which the broad except Exception below swallows into an empty config, and then configure() silently forces a full build for all external CI. I'd filter to known fields so unknown keys are ignored, e.g. build a {f.name for f in dataclasses.fields(RepoEntry)} set and pass only those (needs adding fields to the dataclasses import). Happy to turn this into a two-part suggestion if you want it.

return decorator # type: ignore[return-value]


@retry(max_attempts=3, delay_seconds=2, exceptions=(subprocess.TimeoutExpired,))

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.

Heads-up that the retry only covers TimeoutExpired, so a transient gh api failure (5xx / rate limit) raises CalledProcessError and isn't retried. If you want those retried, add it to the exceptions tuple, though note that'd also retry non-transient 4xx, so you may want to narrow it to specific exit conditions instead. Minor; take it or leave it.

- Add unit tests for all major code paths (configure_external_repo_ci_test.py)
- Fix push event to use base_sha directly (GitHub API doesn't support ^ syntax)
- Handle truncated API response (>300 files) by falling back to run_all_tests
- Filter unknown fields in RepoEntry to prevent TypeError on config changes
- Add CalledProcessError to retry decorator for transient API failures

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@therock-pr-bot therock-pr-bot Bot removed the Not ready to Review PR has unresolved policy failures — reviews blocked label Jul 21, 2026
@geomin12
geomin12 requested a review from tony-davis July 21, 2026 17:03

@tony-davis tony-davis left a comment

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.

Thanks, Geo; this addresses everything cleanly. The new configure_external_repo_ci_test.py covers the event branches and the fallbacks with real assertions, the push path now uses base_sha instead of the ^ trick, the 300-file truncation falls back to run-all, and RepoEntry ignores unknown config keys now. Bot's green and the label's cleared. Nice work.

One non-blocking thought for later: the compare API's 300 cap is a "probably truncated" heuristic, so if you ever see a huge PR that should've been selective quietly go full-build, that's the spot to look. Not a merge blocker.

Since this and #6709 are a producer/consumer pair, I'd still land them together. Approving.

@geomin12
geomin12 merged commit 2dbabe9 into main Jul 21, 2026
145 of 154 checks passed
@geomin12
geomin12 deleted the users/geomin12/external-repo-change branch July 21, 2026 21:15
@github-project-automation github-project-automation Bot moved this from TODO to Done in TheRock Triage Jul 21, 2026
geomin12 added a commit to ROCm/rocm-libraries that referenced this pull request Jul 21, 2026
#9665)

After ROCm/TheRock#6660 landed, we are now using
the external configure ci script to maintain in one central location. We
are incorporating this script in multi-arch CI!

Working here:
https://github.com/ROCm/rocm-libraries/actions/runs/29869577087/job/88766169112

ISSUE ID: ROCm/TheRock#3343

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
geomin12 added a commit to ROCm/rocm-systems that referenced this pull request Jul 21, 2026
#8987)

After ROCm/TheRock#6660 landed, we are now using
the external configure ci script to maintain in one central location. We
are incorporating this script in multi-arch CI!

Working here:
https://github.com/ROCm/rocm-systems/actions/runs/29869549929/job/88766078180

ISSUE ID: ROCm/TheRock#3343

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants