Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
83 changes: 83 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,86 @@ jobs:
gradle-version: ${{ env.JOB_GRADLE_VERSION }}
- name: Build with Gradle
run: gradle build

trigger-downstream:
needs: build
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- repo: dimagi/commcare-android
check_name: "cross-repo / commcare-android"
workflow_file: commcare-android-pr-workflow.yml
- repo: dimagi/formplayer
check_name: "cross-repo / formplayer"
workflow_file: build.yml
steps:
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.CROSS_REPO_CI_APP_ID }}
private-key: ${{ secrets.CROSS_REPO_CI_PRIVATE_KEY }}
owner: dimagi
repositories: commcare-core,commcare-android,formplayer

- name: Set pending status
uses: actions/github-script@v7
env:
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
CHECK_NAME: ${{ matrix.check_name }}
REPO: ${{ matrix.repo }}
WORKFLOW_FILE: ${{ matrix.workflow_file }}
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
await github.rest.repos.createCommitStatus({
owner: 'dimagi',
repo: 'commcare-core',
sha: process.env.HEAD_SHA,
state: 'pending',
context: process.env.CHECK_NAME,
description: 'Waiting for downstream tests...',
target_url: `https://github.com/${process.env.REPO}/actions/workflows/${process.env.WORKFLOW_FILE}`
});

- name: Trigger downstream workflow
uses: actions/github-script@v7
env:
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
MERGE_SHA: ${{ github.sha }}
COMMCARE_CORE_PR: ${{ github.event.pull_request.number }}
CHECK_NAME: ${{ matrix.check_name }}
REPO: ${{ matrix.repo }}
WORKFLOW_FILE: ${{ matrix.workflow_file }}
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
try {
const [owner, repo] = process.env.REPO.split('/');
await github.rest.actions.createWorkflowDispatch({
owner,
repo,
workflow_id: process.env.WORKFLOW_FILE,
ref: 'master',
inputs: {
commcare_core_sha: process.env.MERGE_SHA,
commcare_core_pr: process.env.COMMCARE_CORE_PR,
commcare_core_check_name: process.env.CHECK_NAME,
commcare_core_status_sha: process.env.HEAD_SHA
}
});
} catch (error) {
// If dispatch fails, set error status so it doesn't stay pending forever
await github.rest.repos.createCommitStatus({
owner: 'dimagi',
repo: 'commcare-core',
sha: process.env.HEAD_SHA,
state: 'error',
context: process.env.CHECK_NAME,
description: `Failed to trigger: ${error.message}`.substring(0, 140)
});
throw error;
}
57 changes: 57 additions & 0 deletions docs/cross-repo-ci.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Cross-Repo CI

When a commcare-core pull request passes its own tests, the test suites of
[commcare-android](https://github.com/dimagi/commcare-android) and
[formplayer](https://github.com/dimagi/formplayer) are automatically run against
the PR's version of commcare-core. Results appear as status checks on the PR.

## How it works

The `trigger-downstream` job in `.github/workflows/build.yml` runs after the
`build` job succeeds on a pull request. It:

1. Sets **pending** commit statuses on the PR (`cross-repo / commcare-android`
and `cross-repo / formplayer`)
2. Fires `workflow_dispatch` to each downstream repo, passing:
- `commcare_core_sha` — the PR's **merge commit** SHA (the same code the
`build` job tested)
- `commcare_core_status_sha` — the PR's **head commit** SHA (for reporting
status back to the correct commit on the PR)
- `commcare_core_pr` — the PR number
- `commcare_core_check_name` — the status check name
3. Exits immediately (fire-and-forget)

Each downstream repo's CI workflow accepts these inputs via `workflow_dispatch`.
When present, it checks out commcare-core at `commcare_core_sha` (the merge
commit) instead of its default, runs tests, and reports the result back as a
commit status on the commcare-core PR using `commcare_core_status_sha` (the
head commit).

The merge commit is used for checkout so that downstream repos test the same
code that commcare-core's own build tested — i.e., the PR's changes merged
into the base branch. The head SHA is used for status reporting because GitHub
associates PR status checks with the head commit, not the merge commit.

## Authentication

A dedicated GitHub App (**CommCare Cross-Repo CI**) provides cross-repo access.
It has two permissions: Actions (write) and Commit statuses (write), and is
installed on `commcare-core`, `commcare-android`, and `formplayer`.

Each repo stores the app credentials as repo-level secrets:
- `CROSS_REPO_CI_APP_ID`
- `CROSS_REPO_CI_PRIVATE_KEY`

## Re-running failed checks

Click the status check link on the commcare-core PR to navigate to the downstream
workflow run, then use GitHub's "Re-run failed jobs". The re-run preserves the
original inputs and updates the status on the commcare-core PR when it completes.

## Downstream workflow files

- commcare-android: `.github/workflows/commcare-android-pr-workflow.yml`
- formplayer: `.github/workflows/build.yml`

Both workflows behave normally for their own PRs and pushes. The cross-repo CI
path only activates when `workflow_dispatch` inputs are provided.
Loading