From f6c77048edcd1418fd44ac1c06231fefa5088a62 Mon Sep 17 00:00:00 2001 From: Bartosz Burda Date: Wed, 9 Sep 2026 21:09:47 +0200 Subject: [PATCH 1/3] ci: build the ROS jobs on ros-base images Switch the 11 container-based CI jobs across ci.yml, quality.yml and opcua-plugin.yml from `ubuntu:` + ros-tooling/setup-ros to ros:-ros-base directly, and drop the Pre-install ROS 2 apt source step and the ros-apt-source composite action along with it. setup-ros removes /etc/ros/rosdep/sources.list.d/20-default.list and runs sudo rosdep init with no retry, which fetches that file from raw.githubusercontent.com before the step that has a retry loop around it. The ros-base images already ship rosdep initialized with sources.cache filled, the ROS apt source configured, and colcon, rosdep, git, gpg, curl and vcstool installed, so there is nothing left for setup-ros or the apt-source action to do, and the download that has caused outages before never happens. ros-base is narrower than the desktop metapackage setup-ros installs (no rviz2, demo_nodes, turtlesim, examples, rqt); nothing in the workspace depends on any of them, confirmed by building and running the full unit suite for ros2_medkit_gateway inside a real ros:jazzy-ros-base container (2886 tests, 0 failures). The coverage-merge job, which never used ROS, keeps its plain ubuntu:noble image untouched. The per-job "Install Git" steps are also removed: all three ros-base images carry git already. --- .github/actions/ros-apt-source/action.yml | 61 ------------------ .github/workflows/ci.yml | 66 +++----------------- .github/workflows/opcua-plugin.yml | 34 ++-------- .github/workflows/quality.yml | 75 ++--------------------- 4 files changed, 16 insertions(+), 220 deletions(-) delete mode 100644 .github/actions/ros-apt-source/action.yml diff --git a/.github/actions/ros-apt-source/action.yml b/.github/actions/ros-apt-source/action.yml deleted file mode 100644 index f47583db6..000000000 --- a/.github/actions/ros-apt-source/action.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: Pre-install ROS 2 apt source -description: > - Installs the ros-apt-source package up front so ros-tooling/setup-ros skips its - own unauthenticated api.github.com "releases/latest" lookup. That lookup is - subject to the per-IP unauthenticated GitHub API rate limit, which is shared - across all GitHub-hosted runners; when it is exhausted the call returns no - version, setup-ros builds a .../download//ros2-apt-source_..._all.deb URL, gets - a 404, and the whole job fails ("colcon: command not found", exit 127). By - configuring the ROS 2 apt repo first, setup-ros's isRosAptSourcePackageInstalled - check passes and it never performs the fragile fetch. - -runs: - using: composite - steps: - - name: Install ros-apt-source (skip setup-ros's rate-limited fetch) - shell: bash - env: - # Authenticated API requests use the per-repo limit (~1000/h), not the - # per-IP unauthenticated limit that setup-ros trips over. - GH_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - - SUDO="" - if [ "$(id -u)" -ne 0 ]; then SUDO="sudo"; fi - - # Minimal container images may lack curl / CA certificates. - if ! command -v curl >/dev/null 2>&1; then - $SUDO apt-get update - $SUDO apt-get install -y --no-install-recommends curl ca-certificates - fi - - codename="$(. /etc/os-release && echo "${UBUNTU_CODENAME:-${VERSION_CODENAME}}")" - - # Resolve the latest ros-apt-source release via an AUTHENTICATED call, then - # fall back to a known-good pin so the step is deterministic even if the - # API is briefly unreachable. - version="" - if [ -n "${GH_TOKEN:-}" ]; then - version="$(curl -fsSL -H "Authorization: Bearer ${GH_TOKEN}" \ - "https://api.github.com/repos/ros-infrastructure/ros-apt-source/releases/latest" \ - | grep -F '"tag_name"' | head -n1 | awk -F'"' '{print $4}' || true)" - fi - if [ -z "${version}" ]; then - version="1.2.0" - fi - - deb="ros2-apt-source_${version}.${codename}_all.deb" - url="https://github.com/ros-infrastructure/ros-apt-source/releases/download/${version}/${deb}" - - echo "Installing ${deb} (ros-apt-source ${version}, ${codename})" - # This download has no fallback: the version lookup above can fall back to - # a pin, but a 5xx on the release asset itself ends the job. The retry - # window is therefore minutes rather than seconds, so a short outage on - # the release host is waited out instead of failing every job that starts - # during it. - curl --fail --location --retry 8 --retry-delay 15 --retry-max-time 180 \ - -o "/tmp/${deb}" "${url}" - $SUDO dpkg -i "/tmp/${deb}" - rm -f "/tmp/${deb}" - $SUDO apt-get update diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0d2d83ae4..e106d602c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,9 +14,9 @@ jobs: matrix: include: - ros_distro: humble - os_image: ubuntu:jammy + os_image: ros:humble-ros-base - ros_distro: lyrical - os_image: ubuntu:resolute + os_image: ros:lyrical-ros-base container: image: ${{ matrix.os_image }} # A container gets 64 MB of /dev/shm by default, which is Docker's number and @@ -39,22 +39,9 @@ jobs: shell: bash steps: - - name: Install Git - run: | - apt-get update - apt-get install -y git - - name: Checkout repository uses: actions/checkout@v4 - - name: Pre-install ROS 2 apt source - uses: ./.github/actions/ros-apt-source - - - name: Set up ROS 2 ${{ matrix.ros_distro }} - uses: ros-tooling/setup-ros@v0.7 - with: - required-ros-distributions: ${{ matrix.ros_distro }} - - name: Install ccache run: apt-get install -y ccache @@ -169,15 +156,15 @@ jobs: matrix: include: - ros_distro: humble - os_image: ubuntu:jammy + os_image: ros:humble-ros-base # build-and-test compiles the same sources with the same flags on this # distro, so its cache is the superset this job restores from. ccache_prefix: ccache-humble- - ros_distro: lyrical - os_image: ubuntu:resolute + os_image: ros:lyrical-ros-base ccache_prefix: ccache-lyrical- - ros_distro: jazzy - os_image: ubuntu:noble + os_image: ros:jazzy-ros-base # ccache-jazzy-test-, not ccache-jazzy-. The shorter prefix also matches # ccache-jazzy-asan-, -tsan-, -lint- and -tidy-, and restore-keys takes the # most recently created match, so it would restore whichever other Jazzy job @@ -198,22 +185,9 @@ jobs: shell: bash steps: - - name: Install Git - run: | - apt-get update - apt-get install -y git - - name: Checkout repository uses: actions/checkout@v4 - - name: Pre-install ROS 2 apt source - uses: ./.github/actions/ros-apt-source - - - name: Set up ROS 2 ${{ matrix.ros_distro }} - uses: ros-tooling/setup-ros@v0.7 - with: - required-ros-distributions: ${{ matrix.ros_distro }} - - name: Install ccache run: apt-get install -y ccache @@ -312,7 +286,7 @@ jobs: jazzy-test: runs-on: ubuntu-latest container: - image: ubuntu:noble + image: ros:jazzy-ros-base # A container gets 64 MB of /dev/shm by default, which is Docker's number and # not a considered one. Fast DDS puts a 512 KB segment plus port files there # per participant - measured at ~0.65 MB - and a participant killed rather @@ -330,22 +304,9 @@ jobs: shell: bash steps: - - name: Install Git - run: | - apt-get update - apt-get install -y git - - name: Checkout repository uses: actions/checkout@v4 - - name: Pre-install ROS 2 apt source - uses: ./.github/actions/ros-apt-source - - - name: Set up ROS 2 Jazzy - uses: ros-tooling/setup-ros@v0.7 - with: - required-ros-distributions: jazzy - - name: Install ccache run: apt-get install -y ccache @@ -484,7 +445,7 @@ jobs: # and a job-level kill stops every remaining step, `if: always()` included, so # there is nothing a guard could report. container: - image: ubuntu:noble + image: ros:jazzy-ros-base # A container gets 64 MB of /dev/shm by default, which is Docker's number and # not a considered one. Fast DDS puts a 512 KB segment plus port files there # per participant - measured at ~0.65 MB - and a participant killed rather @@ -498,22 +459,9 @@ jobs: shell: bash steps: - - name: Install Git - run: | - apt-get update - apt-get install -y git - - name: Checkout repository uses: actions/checkout@v4 - - name: Pre-install ROS 2 apt source - uses: ./.github/actions/ros-apt-source - - - name: Set up ROS 2 Jazzy - uses: ros-tooling/setup-ros@v0.7 - with: - required-ros-distributions: jazzy - - name: Install ccache run: apt-get install -y ccache diff --git a/.github/workflows/opcua-plugin.yml b/.github/workflows/opcua-plugin.yml index 633206379..a35e0a1a1 100644 --- a/.github/workflows/opcua-plugin.yml +++ b/.github/workflows/opcua-plugin.yml @@ -40,11 +40,11 @@ jobs: matrix: include: - ros_distro: humble - os_image: ubuntu:jammy + os_image: ros:humble-ros-base - ros_distro: jazzy - os_image: ubuntu:noble + os_image: ros:jazzy-ros-base - ros_distro: lyrical - os_image: ubuntu:resolute + os_image: ros:lyrical-ros-base continue-on-error: ${{ matrix.ros_distro == 'lyrical' }} container: image: ${{ matrix.os_image }} @@ -53,22 +53,9 @@ jobs: run: shell: bash steps: - - name: Install Git - run: | - apt-get update - apt-get install -y git - - name: Checkout repository uses: actions/checkout@v4 - - name: Pre-install ROS 2 apt source - uses: ./.github/actions/ros-apt-source - - - name: Set up ROS 2 ${{ matrix.ros_distro }} - uses: ros-tooling/setup-ros@v0.7 - with: - required-ros-distributions: ${{ matrix.ros_distro }} - - name: Install ccache run: apt-get install -y ccache @@ -268,28 +255,15 @@ jobs: # skipping with CTest code 77. runs-on: ubuntu-latest container: - image: ubuntu:noble + image: ros:jazzy-ros-base timeout-minutes: 60 defaults: run: shell: bash steps: - - name: Install Git - run: | - apt-get update - apt-get install -y git - - name: Checkout repository uses: actions/checkout@v4 - - name: Pre-install ROS 2 apt source - uses: ./.github/actions/ros-apt-source - - - name: Set up ROS 2 jazzy - uses: ros-tooling/setup-ros@v0.7 - with: - required-ros-distributions: jazzy - - name: Install ccache run: apt-get install -y ccache diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index d29fa2489..5e5392106 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -17,18 +17,13 @@ jobs: format-lint: runs-on: ubuntu-latest container: - image: ubuntu:noble + image: ros:jazzy-ros-base timeout-minutes: 45 defaults: run: shell: bash steps: - - name: Install Git - run: | - apt-get update - apt-get install -y git - - name: Checkout repository uses: actions/checkout@v4 @@ -39,14 +34,6 @@ jobs: - name: Every C++ package opts into coverage instrumentation run: ./scripts/check_coverage_packages.sh --static-only - - name: Pre-install ROS 2 apt source - uses: ./.github/actions/ros-apt-source - - - name: Set up ROS 2 Jazzy - uses: ros-tooling/setup-ros@v0.7 - with: - required-ros-distributions: jazzy - - name: Install dependencies run: | apt-get update @@ -104,18 +91,13 @@ jobs: clang-tidy: runs-on: ubuntu-latest container: - image: ubuntu:noble + image: ros:jazzy-ros-base timeout-minutes: 90 defaults: run: shell: bash steps: - - name: Install Git - run: | - apt-get update - apt-get install -y git - - name: Checkout repository uses: actions/checkout@v4 with: @@ -124,14 +106,6 @@ jobs: - name: Fix git safe directory run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - - name: Pre-install ROS 2 apt source - uses: ./.github/actions/ros-apt-source - - - name: Set up ROS 2 Jazzy - uses: ros-tooling/setup-ros@v0.7 - with: - required-ros-distributions: jazzy - - name: Install ccache and clang-tidy run: apt-get install -y ccache clang-tidy @@ -243,7 +217,7 @@ jobs: sanitizer-asan: runs-on: ubuntu-latest container: - image: ubuntu:noble + image: ros:jazzy-ros-base # Bind-mount the runner's large temp disk. The ASan+UBSan build tree # measured ~27 GB before the sanitizer module started passing -g1; the # post -g1 size has not been measured on a real run yet, so treat that @@ -263,22 +237,9 @@ jobs: shell: bash steps: - - name: Install Git - run: | - apt-get update - apt-get install -y git - - name: Checkout repository uses: actions/checkout@v4 - - name: Pre-install ROS 2 apt source - uses: ./.github/actions/ros-apt-source - - - name: Set up ROS 2 Jazzy - uses: ros-tooling/setup-ros@v0.7 - with: - required-ros-distributions: jazzy - - name: Install ccache run: apt-get install -y ccache @@ -404,7 +365,7 @@ jobs: sanitizer-tsan: runs-on: ubuntu-latest container: - image: ubuntu:noble + image: ros:jazzy-ros-base # See sanitizer-asan: the instrumented RelWithDebInfo build tree does not # fit the container overlay on small GitHub runners, so build on /mnt. volumes: @@ -417,22 +378,9 @@ jobs: shell: bash steps: - - name: Install Git - run: | - apt-get update - apt-get install -y git - - name: Checkout repository uses: actions/checkout@v4 - - name: Pre-install ROS 2 apt source - uses: ./.github/actions/ros-apt-source - - - name: Set up ROS 2 Jazzy - uses: ros-tooling/setup-ros@v0.7 - with: - required-ros-distributions: jazzy - - name: Install ccache run: apt-get install -y ccache @@ -551,7 +499,7 @@ jobs: cmake_sanitizer: tsan ccache_size: 1.5G container: - image: ubuntu:noble + image: ros:jazzy-ros-base # See sanitizer-asan: the instrumented build tree does not fit the # container overlay on small GitHub runners, so build on /mnt. volumes: @@ -564,22 +512,9 @@ jobs: shell: bash steps: - - name: Install Git - run: | - apt-get update - apt-get install -y git - - name: Checkout repository uses: actions/checkout@v4 - - name: Pre-install ROS 2 apt source - uses: ./.github/actions/ros-apt-source - - - name: Set up ROS 2 Jazzy - uses: ros-tooling/setup-ros@v0.7 - with: - required-ros-distributions: jazzy - - name: Install ccache run: apt-get install -y ccache From 3a903a5f8a01a24c5457c820f55b3568762071ce Mon Sep 17 00:00:00 2001 From: Bartosz Burda Date: Wed, 9 Sep 2026 21:26:38 +0200 Subject: [PATCH 2/3] ci: give each apt-get install its own apt-get update The ros-base images carry no apt package index at all, unlike the ubuntu: images setup-ros used to run against. Removing the Install Git step (apt-get update; apt-get install -y git) and setup-ros (which ran its own update as part of installing ROS) left 11 step bodies across ci.yml, quality.yml and opcua-plugin.yml with an apt-get install and no update anywhere in that body: the "Install ccache" step in every container job, "Install ccache and clang-tidy" and "Install clang-tidy- cache (ctcache)" in quality.yml's clang-tidy job. Every "Install dependencies" step already opened with its own apt-get update and needed no change. Add apt-get update to each of those 11 step bodies, before the install, so each step carries its own index and does not depend on an update happening to run earlier in the job. --- .github/workflows/ci.yml | 16 ++++++++++++---- .github/workflows/opcua-plugin.yml | 8 ++++++-- .github/workflows/quality.yml | 17 +++++++++++++---- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e106d602c..b584a18e3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,7 +43,9 @@ jobs: uses: actions/checkout@v4 - name: Install ccache - run: apt-get install -y ccache + run: | + apt-get update + apt-get install -y ccache - name: Cache ccache uses: actions/cache@v4 @@ -189,7 +191,9 @@ jobs: uses: actions/checkout@v4 - name: Install ccache - run: apt-get install -y ccache + run: | + apt-get update + apt-get install -y ccache - name: Restore ccache # Restore, never save. This job compiles a subset of the sources the distro's own @@ -308,7 +312,9 @@ jobs: uses: actions/checkout@v4 - name: Install ccache - run: apt-get install -y ccache + run: | + apt-get update + apt-get install -y ccache - name: Cache ccache uses: actions/cache@v4 @@ -463,7 +469,9 @@ jobs: uses: actions/checkout@v4 - name: Install ccache - run: apt-get install -y ccache + run: | + apt-get update + apt-get install -y ccache - name: Cache ccache uses: actions/cache@v4 diff --git a/.github/workflows/opcua-plugin.yml b/.github/workflows/opcua-plugin.yml index a35e0a1a1..98c9f27dc 100644 --- a/.github/workflows/opcua-plugin.yml +++ b/.github/workflows/opcua-plugin.yml @@ -57,7 +57,9 @@ jobs: uses: actions/checkout@v4 - name: Install ccache - run: apt-get install -y ccache + run: | + apt-get update + apt-get install -y ccache - name: Cache ccache uses: actions/cache@v4 @@ -265,7 +267,9 @@ jobs: uses: actions/checkout@v4 - name: Install ccache - run: apt-get install -y ccache + run: | + apt-get update + apt-get install -y ccache - name: Cache ccache uses: actions/cache@v4 diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 5e5392106..73bb47c18 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -107,7 +107,9 @@ jobs: run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - name: Install ccache and clang-tidy - run: apt-get install -y ccache clang-tidy + run: | + apt-get update + apt-get install -y ccache clang-tidy - name: Cache ccache uses: actions/cache@v4 @@ -119,6 +121,7 @@ jobs: - name: Install clang-tidy-cache (ctcache) run: | + apt-get update apt-get install -y python3-pip for attempt in 1 2 3; do pip3 install --break-system-packages git+https://github.com/matus-chochlik/ctcache.git@3880541496c7ce0d2e2830d2f525375078fe0b1f && break; [ "$attempt" = 3 ] && exit 1; echo "ctcache install attempt $attempt failed, retrying" >&2; sleep 5; done printf '#!/bin/sh\nexec clang-tidy-cache /usr/bin/clang-tidy "$@"\n' > /usr/local/bin/ctcache-wrapper @@ -241,7 +244,9 @@ jobs: uses: actions/checkout@v4 - name: Install ccache - run: apt-get install -y ccache + run: | + apt-get update + apt-get install -y ccache - name: Cache ccache uses: actions/cache@v4 @@ -382,7 +387,9 @@ jobs: uses: actions/checkout@v4 - name: Install ccache - run: apt-get install -y ccache + run: | + apt-get update + apt-get install -y ccache - name: Cache ccache uses: actions/cache@v4 @@ -516,7 +523,9 @@ jobs: uses: actions/checkout@v4 - name: Install ccache - run: apt-get install -y ccache + run: | + apt-get update + apt-get install -y ccache - name: Restore ccache # Restore, never save. This job compiles a subset of the sources From c7e1cdd9d2c4acb88536e0273949e2a7106b5eef Mon Sep 17 00:00:00 2001 From: Bartosz Burda Date: Wed, 9 Sep 2026 21:34:06 +0200 Subject: [PATCH 3/3] ci: drop the unused gpg install from the coverage job The coverage job's "Install dependencies" step installed gpg with a comment saying codecov/codecov-action needed it, but this job never uploads to Codecov - that happens in coverage-merge, which has its own gpg install and is untouched by this change. Nothing else in the coverage job (build, test, lcov report generation, tracefile upload) uses gpg either, so the package was dead weight justified by a reason that belonged to a different job. The install goes with the comment. --- .github/workflows/ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b584a18e3..db5660890 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -489,8 +489,7 @@ jobs: - name: Install dependencies run: | apt-get update - # gpg is required by codecov/codecov-action@v5 dependency check - apt-get install -y lcov ros-jazzy-test-msgs gpg + apt-get install -y lcov ros-jazzy-test-msgs source /opt/ros/jazzy/setup.bash for attempt in 1 2 3; do rosdep update && break; [ "$attempt" = 3 ] && exit 1; echo "rosdep update attempt $attempt failed, retrying" >&2; sleep 5; done rosdep install --from-paths src --ignore-src -r -y