Skip to content

Commit 53f0f6f

Browse files
authored
Merge pull request #1525 from dimagi/dmr/cross-repo-ci
Add cross-repo CI: trigger downstream tests on PR success
2 parents 25fc5ef + 9e7494b commit 53f0f6f

2 files changed

Lines changed: 140 additions & 0 deletions

File tree

.github/workflows/build.yml

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,86 @@ jobs:
3636
gradle-version: ${{ env.JOB_GRADLE_VERSION }}
3737
- name: Build with Gradle
3838
run: gradle build
39+
40+
trigger-downstream:
41+
needs: build
42+
if: github.event_name == 'pull_request'
43+
runs-on: ubuntu-latest
44+
strategy:
45+
fail-fast: false
46+
matrix:
47+
include:
48+
- repo: dimagi/commcare-android
49+
check_name: "cross-repo / commcare-android"
50+
workflow_file: commcare-android-pr-workflow.yml
51+
- repo: dimagi/formplayer
52+
check_name: "cross-repo / formplayer"
53+
workflow_file: build.yml
54+
steps:
55+
- name: Generate GitHub App token
56+
id: app-token
57+
uses: actions/create-github-app-token@v1
58+
with:
59+
app-id: ${{ secrets.CROSS_REPO_CI_APP_ID }}
60+
private-key: ${{ secrets.CROSS_REPO_CI_PRIVATE_KEY }}
61+
owner: dimagi
62+
repositories: commcare-core,commcare-android,formplayer
63+
64+
- name: Set pending status
65+
uses: actions/github-script@v7
66+
env:
67+
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
68+
CHECK_NAME: ${{ matrix.check_name }}
69+
REPO: ${{ matrix.repo }}
70+
WORKFLOW_FILE: ${{ matrix.workflow_file }}
71+
with:
72+
github-token: ${{ steps.app-token.outputs.token }}
73+
script: |
74+
await github.rest.repos.createCommitStatus({
75+
owner: 'dimagi',
76+
repo: 'commcare-core',
77+
sha: process.env.HEAD_SHA,
78+
state: 'pending',
79+
context: process.env.CHECK_NAME,
80+
description: 'Waiting for downstream tests...',
81+
target_url: `https://github.com/${process.env.REPO}/actions/workflows/${process.env.WORKFLOW_FILE}`
82+
});
83+
84+
- name: Trigger downstream workflow
85+
uses: actions/github-script@v7
86+
env:
87+
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
88+
MERGE_SHA: ${{ github.sha }}
89+
COMMCARE_CORE_PR: ${{ github.event.pull_request.number }}
90+
CHECK_NAME: ${{ matrix.check_name }}
91+
REPO: ${{ matrix.repo }}
92+
WORKFLOW_FILE: ${{ matrix.workflow_file }}
93+
with:
94+
github-token: ${{ steps.app-token.outputs.token }}
95+
script: |
96+
try {
97+
const [owner, repo] = process.env.REPO.split('/');
98+
await github.rest.actions.createWorkflowDispatch({
99+
owner,
100+
repo,
101+
workflow_id: process.env.WORKFLOW_FILE,
102+
ref: 'master',
103+
inputs: {
104+
commcare_core_sha: process.env.MERGE_SHA,
105+
commcare_core_pr: process.env.COMMCARE_CORE_PR,
106+
commcare_core_check_name: process.env.CHECK_NAME,
107+
commcare_core_status_sha: process.env.HEAD_SHA
108+
}
109+
});
110+
} catch (error) {
111+
// If dispatch fails, set error status so it doesn't stay pending forever
112+
await github.rest.repos.createCommitStatus({
113+
owner: 'dimagi',
114+
repo: 'commcare-core',
115+
sha: process.env.HEAD_SHA,
116+
state: 'error',
117+
context: process.env.CHECK_NAME,
118+
description: `Failed to trigger: ${error.message}`.substring(0, 140)
119+
});
120+
throw error;
121+
}

docs/cross-repo-ci.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Cross-Repo CI
2+
3+
When a commcare-core pull request passes its own tests, the test suites of
4+
[commcare-android](https://github.com/dimagi/commcare-android) and
5+
[formplayer](https://github.com/dimagi/formplayer) are automatically run against
6+
the PR's version of commcare-core. Results appear as status checks on the PR.
7+
8+
## How it works
9+
10+
The `trigger-downstream` job in `.github/workflows/build.yml` runs after the
11+
`build` job succeeds on a pull request. It:
12+
13+
1. Sets **pending** commit statuses on the PR (`cross-repo / commcare-android`
14+
and `cross-repo / formplayer`)
15+
2. Fires `workflow_dispatch` to each downstream repo, passing:
16+
- `commcare_core_sha` — the PR's **merge commit** SHA (the same code the
17+
`build` job tested)
18+
- `commcare_core_status_sha` — the PR's **head commit** SHA (for reporting
19+
status back to the correct commit on the PR)
20+
- `commcare_core_pr` — the PR number
21+
- `commcare_core_check_name` — the status check name
22+
3. Exits immediately (fire-and-forget)
23+
24+
Each downstream repo's CI workflow accepts these inputs via `workflow_dispatch`.
25+
When present, it checks out commcare-core at `commcare_core_sha` (the merge
26+
commit) instead of its default, runs tests, and reports the result back as a
27+
commit status on the commcare-core PR using `commcare_core_status_sha` (the
28+
head commit).
29+
30+
The merge commit is used for checkout so that downstream repos test the same
31+
code that commcare-core's own build tested — i.e., the PR's changes merged
32+
into the base branch. The head SHA is used for status reporting because GitHub
33+
associates PR status checks with the head commit, not the merge commit.
34+
35+
## Authentication
36+
37+
A dedicated GitHub App (**CommCare Cross-Repo CI**) provides cross-repo access.
38+
It has two permissions: Actions (write) and Commit statuses (write), and is
39+
installed on `commcare-core`, `commcare-android`, and `formplayer`.
40+
41+
Each repo stores the app credentials as repo-level secrets:
42+
- `CROSS_REPO_CI_APP_ID`
43+
- `CROSS_REPO_CI_PRIVATE_KEY`
44+
45+
## Re-running failed checks
46+
47+
Click the status check link on the commcare-core PR to navigate to the downstream
48+
workflow run, then use GitHub's "Re-run failed jobs". The re-run preserves the
49+
original inputs and updates the status on the commcare-core PR when it completes.
50+
51+
## Downstream workflow files
52+
53+
- commcare-android: `.github/workflows/commcare-android-pr-workflow.yml`
54+
- formplayer: `.github/workflows/build.yml`
55+
56+
Both workflows behave normally for their own PRs and pushes. The cross-repo CI
57+
path only activates when `workflow_dispatch` inputs are provided.

0 commit comments

Comments
 (0)