Central, versioned GitHub Actions reusable workflows.
Tags follow semver. Pin consumers to the moving major tag:
uses: Duatic/ci-workflows/.github/workflows/ci_orchestrator.yml@v1v1 always points at the latest compatible v1.x.y release. Immutable v1.0.0-style tags exist
for rollback/bisection. Breaking input changes bump the major (@v2).
Releases are cut via the Release workflow (workflow_dispatch, .github/workflows/release.yml)
from the Actions tab: pick
patch/minor/major, and it computes the next version from the latest tag, creates the immutable
vX.Y.Z tag, force-moves the corresponding major tag (vX) to the same commit, and publishes a
GitHub Release with auto-generated notes.
Repos only ever call ci_orchestrator.yml - it owns the distro matrix, the ROS_REPO channel
per distro, gating, concurrency, and the draft-PR policy, and internally drives the two leaf
workflows (reusable_ici.yml, pre-commit.yml) that most repos never reference directly.
| Input | Required | Default | Description |
|---|---|---|---|
ros_distro |
no | all |
all runs the full gated matrix (jazzy, then kilted/lyrical/rolling once jazzy succeeds); a single distro name (e.g. kilted) builds only that one. |
runner |
no | ubuntu-latest |
Runner label(s) for the build jobs, e.g. self-hosted. |
badge_gist_id |
no | '' |
Opt-in: gist ID to publish per-distro pass/fail status badges to. Empty disables badge publishing entirely. |
| Secret | Required | Description |
|---|---|---|
CI_PAT |
no | PAT used to clone private upstream dependencies from a repos.list file at the repo root. Pass via secrets: inherit; omit entirely for public-only repos. |
GIST_TOKEN |
no | PAT with gist scope. Required only if badge_gist_id is set. Pass via secrets: inherit. |
What's not an input, because it's derived or auto-detected rather than repo-specific config:
upstream_workspace- auto-detected:reusable_ici.ymlusesrepos.listif it exists at the repo root, otherwise nothing.- Extra
aptdependencies (e.g.libboost-regex-dev) - drop anAptfileat the consumer repo root (one package per line,#comments allowed); picked up automatically. Most repos don't need this file at all. - Extra
pipdependencies not resolvable via rosdep (e.g.open3d) - drop arequirements.txtfile at the consumer repo root;pip installed (full dependency tree,--break-system-packages) after target dependencies are installed, before build/test. Most repos don't need this file at all. - Draft PRs - the full matrix is too expensive to run on drafts. On a draft PR the orchestrator runs a single job that fails immediately with a message to mark the PR ready for review, instead of building anything. Marking the PR "Ready for review" runs the real matrix.
This is the entire CI file a typical repo needs. The only things meant to be edited per repo are the cron/timezone and, whether it has a secret to inherit.
name: CI
on:
workflow_dispatch:
inputs:
ros_distro:
description: 'Distro to build'
type: choice
options: [all, jazzy, kilted, lyrical, rolling]
default: all
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
push:
branches: [main]
schedule:
- cron: '0 1 * * *'
timezone: Europe/Zurich
jobs:
ci:
uses: Duatic/ci-workflows/.github/workflows/ci_orchestrator.yml@v1
with:
ros_distro: ${{ inputs.ros_distro }}
secrets: inheritSame file, plus runner: self-hosted. The repos.list file at the repo root and the secret (repo or org level) are all that's needed for private upstream deps - no extra workflow input.
jobs:
ci:
uses: Duatic/ci-workflows/.github/workflows/ci_orchestrator.yml@v1
with:
ros_distro: ${{ inputs.ros_distro }}
runner: self-hosted
secrets: inheritGitHub's native workflow badge.svg reports at the workflow-file level, so it can't show
one badge per distro, and it won't render for anonymous viewers of a private repo's README.
To get per-distro badges that work regardless of repo visibility, the orchestrator can publish
each distro's pass/fail to a gist, which shields.io's endpoint
renderer then turns into a badge:
- Create a public gist (any placeholder file) and a PAT with only the
gistscope. - Add the PAT as a repo (or org) secret named
GIST_TOKEN. - Pass
badge_gist_idandsecrets: inheritin yourci.yml:jobs: ci: uses: Duatic/ci-workflows/.github/workflows/ci_orchestrator.yml@v1 with: ros_distro: ${{ inputs.ros_distro }} badge_gist_id: <your-gist-id> secrets: inherit
- Point README badges at the gist file, namespaced
<repo-name>-<distro>.json:[](https://github.com/<org>/<repo-name>/actions/workflows/ci.yml)
Badges are only written on pushes to main. Each distro leg renders its own badge JSON and uploads it as an artifact; a badges job in the orchestrator downloads every leg's artifact and does a single multi-file gist PATCH per run.
Use a separate gist per repo, even though the filename is namespaced (<repo-name>-<distro>.json)
and would technically allow sharing one gist across repos. With ~20 repos on the same nightly
cron, concurrent writers hitting the same gist trip GitHub's secondary (abuse) rate limit even
after coalescing each repo down to one write. The abuse limit is sensitive to concurrent writes against
one resource, not just total call volume.
reusable_ici.yml- the upstreamros-industrialindustrial_ci template, builds one distro/channel combination. Auto-detectsrepos.list,Aptfile, andrequirements.txt.pre-commit.yml- runspre-commitacross all files.
- Repos using a private-dependency PAT must have a secret available (repo or org level) and pass
secrets: inherit. - Repos opting into gist-backed badges must have a
GIST_TOKENsecret available (repo or org level) and passsecrets: inherit.