Reusable GitHub Actions workflows shared by the OSH Gradle repositories.
The pipeline here is a parameterized version of the GitFlow CI workflow that
grew up in osh-no-code
(.github/workflows/ci-pipeline.yml). Consuming repos keep a ~20-line caller
that owns its triggers and passes configuration in; the logic lives here once.
On every push to a GitFlow branch and every pull request:
- Resolve the release channel — decides
SNAPSHOT/RELEASEfrom the branch name. Pull requests are neither, so a PR never publishes. - Enumerate Gradle projects — builds the authoritative list of projects Gradle actually builds, with their directories.
- Detect changed modules — diffs against the PR base or the push's
beforecommit, attributing each changed file to its owning module. - Enforce version bumps — any module with direct file changes must have
bumped
version = "…"in its build file. All failing modules are reported in one run. - Build & test —
./gradlew clean buildacross the whole repo. - Publish — targeted
:<project>:publishfor changed modules, on snapshot/release branches only.
Add a caller workflow to the consuming repo. Copies for two repo shapes are in
examples/:
jobs:
ci:
uses: Botts-Innovative-Research/osh-ci-workflows/.github/workflows/gradle-gitflow-ci.yml@v0.1.0
with:
module-discovery: settings-include-modules
nexus-repo-url: ${{ vars.NEXUS_REPO_URL }}
secrets: inheritThree things the caller must own, because a reusable workflow cannot:
- Triggers.
on:is not parameterizable. Branch lists andpaths-ignorestay in the caller. permissions:. The called workflow'sGITHUB_TOKENcan never hold more permission than the caller granted, socontents: readmust be declared in both places.concurrency:. Keyed on the caller's workflow and ref.
A job that uses uses: cannot also have steps:. If a repo needs extra work
around the pipeline, add it as a separate job.
The version gate and targeted publish both need to know which directories are real Gradle projects. The repos disagree on how to express that, so this is an input:
| Mode | Fits | How |
|---|---|---|
settings-include-modules (default) |
osh-no-code and repos with the same settings.gradle shape |
Parses the includeModules = [ … ] list out of settings.gradle, then maps each name to the directory holding its build.gradle. |
gradle |
osh-core, osh-addons, anything else |
Runs Gradle with an init script and reads back the real project paths and directories. Handles plain include, fileTree scans, and relocated projectDirs. Costs one extra configuration pass. |
gradle mode is the general answer; settings-include-modules is the faster
one and reproduces the original workflow's behavior exactly. Repos in the
first group can move to gradle mode whenever convenient.
| Input | Default | Notes |
|---|---|---|
java-version |
21 |
|
java-distribution |
temurin |
|
runs-on |
ubuntu-latest |
|
module-discovery |
settings-include-modules |
See above. |
root-build-files |
build.gradle settings.gradle settings.gradle.kts gradle.properties gradlew gradlew.bat gradle/* |
Space-separated shell glob patterns. osh-no-code adds readmes.gradle. |
build-task |
clean build |
|
snapshot-branches |
develop |
Space-separated branch globs. |
release-branches |
main release/* hotfix/* |
Space-separated branch globs. Snapshot wins on overlap. |
nexus-repo-url |
'' |
Pass ${{ vars.NEXUS_REPO_URL }}. |
nexus-repo-snapshot |
'' |
|
nexus-repo-release |
'' |
|
osh-core-version |
'' |
The Nexus settings are declared inputs rather than vars.* lookups inside the
callee, so the caller stays in control of where its configuration comes from.
NEXUS_ACTOR and NEXUS_TOKEN, both required. secrets: inherit passes them
through from the caller's org or repo secrets; pass them explicitly if you'd
rather not inherit everything.
Callers pin a tag:
uses: Botts-Innovative-Research/osh-ci-workflows/.github/workflows/gradle-gitflow-ci.yml@v0.1.0Never @main — a single bad commit would break every consuming repo at once.
While this is pre-1.0, tags are exact and immutable: v0.1.0, v0.2.0, and
so on. There is no moving v0 tag, because 0.x carries no compatibility
promise — under semver a breaking change may land in any minor bump, so a
moving tag would silently reshape every caller's pipeline. Callers upgrade by
editing the ref, which keeps the change visible in a diff.
Until 1.0, treat inputs and behavior as unstable. A minor bump may rename or remove an input; the release notes say what changed.
Once the workflow has run in anger across the consuming repos and the inputs
have settled, cut v1.0.0 and switch to a moving v1 major tag: patch and
minor releases move v1, breaking changes cut v2, and repos migrate when
they choose. Pin a full SHA at any point if a repo needs to be pinned harder.
If this repository is private, each consuming repo must be allowed to call it: Settings → Actions → General → Access on this repo. Without it, callers fail to resolve the workflow.
Carried over from the original workflow, both unresolved:
- No backfill. A module that was never published and isn't touched by a run stays unpublished. Fixing this needs a real "does Nexus already have this artifact?" probe.
- Root-change expansion is snapshot-only. When a root-level Gradle file
changes, all modules are published on
developbut not on release branches, where republishing would either be rejected by Nexus or overwrite a released artifact. Blocked on the same probe.
- Action pins are
actions/checkout@v5,actions/setup-java@v5,actions/cache@v4— conservative choices matching what the OSH repos already run. Current majors are v7 / v5 / v6; bumping is a separate change. - Swapping the hand-rolled Gradle cache for
gradle/actions/setup-gradleis a worthwhile follow-up, but it changes caching behavior and should land on its own. - Composite actions for the shared setup prelude (checkout → JDK → cache →
chmod gradlew) are not in this pass.