diff --git a/.github/workflows/ci_orchestrator.yml b/.github/workflows/ci_orchestrator.yml index 23ac541..38da458 100644 --- a/.github/workflows/ci_orchestrator.yml +++ b/.github/workflows/ci_orchestrator.yml @@ -99,5 +99,5 @@ jobs: pre-commit: name: pre-commit - if: ${{ (inputs.ros_distro == '' || inputs.ros_distro == 'all') && !(github.event_name == 'pull_request' && github.event.pull_request.draft) }} + if: ${{ !(github.event_name == 'pull_request' && github.event.pull_request.draft) }} uses: ./.github/workflows/pre-commit.yml diff --git a/.github/workflows/reusable_ici.yml b/.github/workflows/reusable_ici.yml index 30b5833..46fddc9 100644 --- a/.github/workflows/reusable_ici.yml +++ b/.github/workflows/reusable_ici.yml @@ -46,7 +46,7 @@ jobs: steps: - name: Checkout uses: actions/checkout@v7 - - name: Detect upstream workspace and extra apt dependencies + - name: Detect upstream workspace, extra apt dependencies, and pip requirements id: detect run: | if [ -f repos.list ]; then @@ -54,12 +54,17 @@ jobs: else echo "upstream_workspace=" >> "$GITHUB_OUTPUT" fi - if [ -f .github/ci/apt_dependencies ]; then - packages=$(grep -v '^\s*#' .github/ci/apt_dependencies | tr '\n' ' ') + if [ -f Aptfile ]; then + packages=$(grep -v '^\s*#' Aptfile | tr '\n' ' ') echo "before_install=sudo apt-get install -y -qq ${packages}" >> "$GITHUB_OUTPUT" else echo "before_install=" >> "$GITHUB_OUTPUT" fi + if [ -f requirements.txt ]; then + echo "pip_install=python3 -m pip install --break-system-packages --ignore-installed -r requirements.txt" >> "$GITHUB_OUTPUT" + else + echo "pip_install=" >> "$GITHUB_OUTPUT" + fi - name: Cache ccache uses: actions/cache@v6 with: @@ -74,4 +79,5 @@ jobs: ROS_DISTRO: ${{ inputs.ros_distro }} ROS_REPO: ${{ inputs.ros_repo }} BEFORE_INSTALL_UPSTREAM_DEPENDENCIES: ${{ steps.detect.outputs.before_install }} + AFTER_INSTALL_TARGET_DEPENDENCIES: ${{ steps.detect.outputs.pip_install }} BEFORE_INIT: ${{ secrets.CI_PAT != '' && format('apt-get update -qq && apt-get install -y -qq git && git config --global url."https://{0}@github.com/".insteadOf "https://github.com/"', secrets.CI_PAT) || '' }} diff --git a/.github/workflows/self-check.yml b/.github/workflows/self-check.yml new file mode 100644 index 0000000..51e5d3a --- /dev/null +++ b/.github/workflows/self-check.yml @@ -0,0 +1,12 @@ +name: Self-check +# Runs pre-commit against this repo's own files (workflow YAML, docs), dogfooding the +# pre-commit.yml leaf workflow that every consumer repo gets via ci_orchestrator.yml. + +on: + pull_request: + push: + branches: [main] + +jobs: + pre-commit: + uses: ./.github/workflows/pre-commit.yml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..e27b4af --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,41 @@ +# To use: +# +# pre-commit run -a +# +# Or: +# +# pre-commit install # (runs every time you commit in git) +# +# To update this file: +# +# pre-commit autoupdate +# +# See https://github.com/pre-commit/pre-commit + +repos: + # Standard hooks + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-added-large-files + - id: check-case-conflict + - id: check-merge-conflict + - id: check-symlinks + - id: check-yaml + - id: end-of-file-fixer + - id: mixed-line-ending + - id: trailing-whitespace + - id: check-byte-order-marker # Forbid UTF-8 byte-order markers + + # GitHub Actions workflow linting + - repo: https://github.com/rhysd/actionlint + rev: v1.7.7 + hooks: + - id: actionlint + + # Spellcheck in comments and docs + - repo: https://github.com/codespell-project/codespell + rev: v2.3.0 + hooks: + - id: codespell + args: ["--write-changes"] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..74841b8 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,33 @@ +# Contributing + +We encourage community contributions to this repository. Feel free to open an issue or provide a pull request. + +# Licensing + +Any contribution to this repository will be under the BSD License. + +## Tooling + +Please make sure for any pull request that it passes the `pre-commit` checks (see Tooling section). + +### pre-commit + +The [pre-commit](https://pre-commit.com/) tool is used for running certain checks and formatters every time before a commit is done. +Please use it on any pull requests for this repository. It also runs automatically in CI via `.github/workflows/self-check.yml`. + +__Installation:__ + +`apt install pipx && pipx install pre-commit`. Open a new terminal afterwards. + +__Usage:__ +In the repository folder run: `pre-commit run --all`. This can be automated with `pre-commit install`, so all checks are run every time a commit is done. + +## Testing workflow changes + +Beyond `pre-commit` (which lints the YAML itself via `actionlint`), workflow *behavior* changes still +need to be validated against a real consumer, since this repo has no build/test of its own: + +1. Actually dispatch a consumer workflow against your branch/commit and watch it run, e.g. + `gh run watch --repo Duatic/ --exit-status`. +2. When testing something version-sensitive, point the consumer's `uses:` at the exact commit SHA + first, confirm it works, then move a tag - don't debug against a moving tag. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..552656a --- /dev/null +++ b/LICENSE @@ -0,0 +1,11 @@ +Copyright 2026 Duatic AG + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index 5eb06c9..4917346 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,6 @@ # ci-workflows -Central, versioned GitHub Actions reusable workflows for the [Duatic](https://github.com/Duatic) -organization's ROS 2 repositories. Consolidates what used to be copy-pasted, drift-prone -`reusable_ici.yml` / `build-.yml` / `pre-commit.yml` files across every product and -library repo into one entry point. +Central, versioned GitHub Actions reusable workflows. ## Versioning @@ -24,15 +21,15 @@ GitHub Release with auto-generated notes. ## What consumers call -Repos only ever call **`ci_orchestrator.yml`** — it owns the distro matrix, the `ROS_REPO` channel +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. -### `ci_orchestrator.yml` — consumer-facing entry point +### `ci_orchestrator.yml` - consumer-facing entry point | 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 and skips `pre-commit`. | +| `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`. | | Secret | Required | Description | @@ -40,25 +37,14 @@ workflows (`reusable_ici.yml`, `pre-commit.yml`) that most repos never reference | `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. | What's **not** an input, because it's derived or auto-detected rather than repo-specific config: -- **`ROS_REPO` channel** — `rolling` always builds against `testing`, every other distro against - `main`. Not overridable per repo. -- **`upstream_workspace`** — auto-detected: `reusable_ici.yml` uses `repos.list` if it exists at the - repo root, otherwise nothing. -- **Extra `apt` dependencies** (e.g. `libboost-regex-dev`) — drop a `.github/ci/apt_dependencies` - file in the consumer repo (one package per line, `#` comments allowed); picked up automatically. - Most repos don't need this file at all. -- **Concurrency** — every orchestrator run is keyed on `--` - with `cancel-in-progress: true`, so pushing a new commit to a branch/PR cancels the superseded run. - No consumer configuration needed. -- **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" (which the consumer's `pull_request` trigger - must include as a `ready_for_review` type) runs the real matrix. +- **`upstream_workspace`** - auto-detected: `reusable_ici.yml` uses `repos.list` if it exists at the repo root, otherwise nothing. +- **Extra `apt` dependencies** (e.g. `libboost-regex-dev`) - drop an `Aptfile` at the consumer repo root (one package per line, `#` comments allowed); picked up automatically. Most repos don't need this file at all. +- **Extra `pip` dependencies not resolvable via rosdep** (e.g. `open3d`) - drop a `requirements.txt` file at the consumer repo root; `pip install`ed (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. ## Example: library repo, no private deps -This is the *entire* CI file a typical repo needs. The only things meant to be edited per repo are -the `cron`/`timezone` and, at rollout, whether it has a `CI_PAT` secret to inherit. +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. ```yaml name: CI @@ -88,8 +74,7 @@ jobs: ## Example: product repo with a private-dependency PAT, self-hosted runner -Same file, plus `runner: self-hosted`. The `repos.list` file at the repo root and the `CI_PAT` -secret (repo or org level) are all that's needed for private upstream deps — no extra workflow input. +Same 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. ```yaml jobs: @@ -98,20 +83,13 @@ jobs: with: ros_distro: ${{ inputs.ros_distro }} runner: self-hosted - secrets: inherit # forwards CI_PAT + secrets: inherit ``` ## Leaf workflows (internal, not called directly by product repos) -- **`reusable_ici.yml`** — the upstream `ros-industrial` industrial_ci template, builds one - distro/channel combination. Auto-detects `repos.list` and `.github/ci/apt_dependencies`. -- **`pre-commit.yml`** — runs `pre-commit` across all files. +- **`reusable_ici.yml`** - the upstream `ros-industrial` industrial_ci template, builds one distro/channel combination. Auto-detects `repos.list`, `Aptfile`, and `requirements.txt`. +- **`pre-commit.yml`** - runs `pre-commit` across all files. ## Requirements on consumer repos - -- This repo is public, so no per-repo "Access" toggle is needed to resolve - `uses: Duatic/ci-workflows/...`. (A private version of this repo was tried first; cross-repo - reusable-workflow resolution was unreliable for a newly created private repo even with the - organization-access setting enabled, so it was made public instead — it holds no secrets.) -- Repos using a private-dependency PAT must have a `CI_PAT` secret available (repo or org level) - and pass `secrets: inherit`. +- Repos using a private-dependency PAT must have a secret available (repo or org level) and pass `secrets: inherit`.