RHEL8 CI: prebuilt Docker image with toolchain, vcpkg cache, and warm buildcache

This commit is contained in:
Magne Sjaastad
2026-05-23 16:09:17 +02:00
committed by GitHub
parent 17b024e876
commit 0e259e49e8
6 changed files with 723 additions and 130 deletions
+36
View File
@@ -0,0 +1,36 @@
# Keep the Docker build context small and avoid shadowing the in-image build
# tree with stale host outputs. The repo's own .gitignore does not apply to
# Docker contexts -- this file is the canonical list of paths excluded from
# `COPY . /src` (used by .github/docker/Dockerfile.rhel8).
# Local CMake build outputs (host-side; can be tens of GB).
build/
build_*/
cmakebuild/
cmake-build-*/
out/
# vcpkg working directories (only present if vcpkg was run on the host; the
# in-image build re-creates them from the manifest and the baked binary
# cache).
ThirdParty/vcpkg/buildtrees/
ThirdParty/vcpkg/downloads/
ThirdParty/vcpkg/packages/
ThirdParty/vcpkg/installed/
# Build/install logs at the repo root.
*.log
# IDE and editor state.
.vs/
.vscode/
.idea/
*.swp
# Python caches (rips, GrpcInterface tooling, etc.).
__pycache__/
*.pyc
# OS metadata.
.DS_Store
Thumbs.db
+209
View File
@@ -0,0 +1,209 @@
# syntax=docker/dockerfile:1
# =============================================================================
# RHEL8 (Rocky Linux 8) CI image for ResInsight.
#
# Bakes in:
# * The toolchain (gcc-toolset-14, libstdc++exp, Qt)
# * A prebuilt vcpkg binary cache (populated from the manifest)
# * A warm ResInsight buildcache populated by a full ResInsight-tests compile
# at the fixed path /src -> /src/cmakebuild.
#
# The CI workflow runs inside this image, checks out into /src (path must
# match the warmup so buildcache hashes line up), and only recompiles
# translation units that actually changed.
#
# Rebuilt nightly by .github/workflows/build-rhel8-image.yml.
# =============================================================================
# -----------------------------------------------------------------------------
# Stage 1: base toolchain
# -----------------------------------------------------------------------------
FROM rockylinux:8 AS base
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# Base development tools and libraries.
RUN dnf install -y epel-release \
&& dnf config-manager --set-enabled powertools \
&& dnf install -y \
gcc gcc-c++ make cmake ninja-build git curl zip unzip tar \
pkgconfig perl which xz flex bison \
python39 python39-devel python39-pip \
mesa-libGL-devel mesa-libGLU-devel mesa-libEGL-devel \
libxkbcommon-devel libxkbcommon-x11-devel \
xcb-util-keysyms-devel xcb-util-image-devel xcb-util-wm-devel \
xcb-util-renderutil-devel \
fontconfig-devel freetype-devel \
libX11-devel libXext-devel libXrender-devel \
&& dnf install -y \
gcc-toolset-14 gcc-toolset-14-gcc-c++ \
gcc-toolset-14-libatomic-devel gcc-toolset-14-libstdc++-devel \
&& dnf clean all
# Build libstdc++exp from GCC source to match gcc-toolset-14.
RUN source /opt/rh/gcc-toolset-14/enable \
&& GCC_FULL_VERSION=$(gcc -dumpfullversion) \
&& GCC_MAJOR_MINOR=$(echo "$GCC_FULL_VERSION" | cut -d. -f1,2) \
&& GCC_VERSION="${GCC_MAJOR_MINOR}.0" \
&& cd /tmp \
&& curl -LO "https://ftp.gnu.org/gnu/gcc/gcc-${GCC_VERSION}/gcc-${GCC_VERSION}.tar.xz" \
&& tar xf "gcc-${GCC_VERSION}.tar.xz" \
&& mkdir -p "gcc-${GCC_VERSION}/build-libstdcxx" \
&& cd "gcc-${GCC_VERSION}/build-libstdcxx" \
&& ../libstdc++-v3/configure \
--prefix=/opt/libstdcxx-exp \
--disable-multilib \
--disable-libstdcxx-pch \
--with-gxx-include-dir="/opt/rh/gcc-toolset-14/root/usr/include/c++/${GCC_VERSION}" \
&& { make -k -j"$(nproc)" || true; } \
&& test -f src/experimental/.libs/libstdc++exp.a \
&& mkdir -p /opt/libstdcxx-exp/lib \
&& cp src/experimental/.libs/libstdc++exp.a /opt/libstdcxx-exp/lib/ \
&& rm -rf /tmp/gcc-*
# Python and Qt.
ARG QT_VERSION=6.6.3
RUN alternatives --set python3 /usr/bin/python3.9 \
&& python3 -m pip install --upgrade pip \
&& python3 -m pip install grpcio-tools aqtinstall \
&& python3 -m aqt install-qt linux desktop "${QT_VERSION}" gcc_64 \
-O /opt/Qt --modules qtnetworkauth
ENV Qt6_DIR=/opt/Qt/${QT_VERSION}/gcc_64 \
CMAKE_PREFIX_PATH=/opt/Qt/${QT_VERSION}/gcc_64 \
PATH=/opt/Qt/${QT_VERSION}/gcc_64/bin:${PATH}
# buildcache (pinned, built from source). When this binary is on PATH at CMake
# configure time, the project's top-level CMakeLists.txt auto-detects it via
# find_program() and wires CMAKE_CXX_COMPILER_LAUNCHER. No further CMake
# plumbing is needed.
#
# Built from source rather than downloaded because the upstream
# `buildcache-linux.tar.gz` release binary is linked against glibc 2.29+, and
# Rocky Linux 8 ships glibc 2.28 -- the binary loads but crashes the first
# time the dynamic linker touches libm. Compiling here links against the
# image's own glibc and is portable (~1-2 minutes).
# Source from GitLab -- the canonical repo. The GitHub mirror is stale (last
# tag v0.28.4, 2024-03), while GitLab has progressed to v0.33.0 with the GCC
# 14 transitive-include fixes that v0.28.x lacks.
ARG BUILDCACHE_VERSION=v0.33.0
RUN source /opt/rh/gcc-toolset-14/enable \
&& git clone --depth 1 --branch "${BUILDCACHE_VERSION}" \
https://gitlab.com/bits-n-bites/buildcache.git /tmp/buildcache-src \
&& cmake -S /tmp/buildcache-src/src -B /tmp/buildcache-build -G Ninja \
-DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local \
&& cmake --build /tmp/buildcache-build --target install \
&& rm -rf /tmp/buildcache-src /tmp/buildcache-build \
&& buildcache --version
# -----------------------------------------------------------------------------
# Stage 2: populate the vcpkg binary cache
# -----------------------------------------------------------------------------
FROM base AS vcpkg-builder
ENV VCPKG_DEFAULT_BINARY_CACHE=/opt/vcpkg-cache
COPY . /src
# Running the ResInsight CMake configure triggers a vcpkg manifest install,
# which compiles every dependency into VCPKG_DEFAULT_BINARY_CACHE. The same
# toolchain is used here and in the final image, so the vcpkg ABI hashes match
# and the cache is reused by CI builds. Later configure steps may fail inside
# the build container; that is tolerated as the dependencies are already built
# by then.
#
# After configure, verify the cache contains a plausible number of entries.
# vcpkg's `files` binary provider stores one .zip per package under
# <cache>/<2-hex>/<full-hash>.zip. ResInsight depends on ~30+ packages, so a
# threshold of 20 catches the failure mode where the configure dies early
# (e.g. after building only a handful of leaf deps) while staying robust to
# small manifest churn.
RUN git config --global --add safe.directory '*' \
&& cp /src/vcpkg-configuration-rhel8.json /src/vcpkg-configuration.json \
&& mkdir -p "${VCPKG_DEFAULT_BINARY_CACHE}" \
&& source /opt/rh/gcc-toolset-14/enable \
&& ( cd /src/ThirdParty/vcpkg && ./bootstrap-vcpkg.sh ) \
&& export CC=/opt/rh/gcc-toolset-14/root/usr/bin/gcc \
&& export CXX=/opt/rh/gcc-toolset-14/root/usr/bin/g++ \
&& { cmake -S /src -B /tmp/cfg -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DRESINSIGHT_INCLUDE_APPLICATION_UNIT_TESTS=true \
-DRESINSIGHT_ENABLE_UNITY_BUILD=true \
-DRESINSIGHT_ENABLE_HDF5=false \
-DRESINSIGHT_ENABLE_GRPC=false \
-DRESINSIGHT_GCC_STDCPP_EXP_PATH=/opt/libstdcxx-exp/lib \
-DCMAKE_TOOLCHAIN_FILE=ThirdParty/vcpkg/scripts/buildsystems/vcpkg.cmake \
|| true; } \
&& vcpkg_zip_count=$(find "${VCPKG_DEFAULT_BINARY_CACHE}" -type f -name '*.zip' | wc -l) \
&& echo "vcpkg binary cache entries: ${vcpkg_zip_count}" \
&& test "${vcpkg_zip_count}" -ge 20 \
&& rm -rf /tmp/cfg /src
# -----------------------------------------------------------------------------
# Stage 3: warm the ResInsight buildcache by performing a full compile at
# /src -> /src/cmakebuild. CI runs at the same paths to get cache
# hits (buildcache hashes the absolute source path).
# -----------------------------------------------------------------------------
FROM base AS build-warmup
# Cap at 4 GB. The baked cache from this stage is what CI reads, and CI also
# writes back to the same /opt/buildcache, so the cap must be larger than
# (baked entries + per-run accumulation) or LRU eviction starts dropping baked
# entries between nightly rebuilds. 4 GB comfortably exceeds the observed
# warm cache size for a full ResInsight-tests compile.
ENV BUILDCACHE_ACCURACY=SLOPPY \
BUILDCACHE_MAX_CACHE_SIZE=4294967296
COPY --from=vcpkg-builder /opt/vcpkg-cache /opt/vcpkg-cache
COPY . /src
# The buildcache directory is mounted as a BuildKit cache mount so it persists
# across nightly image rebuilds (each rebuild only recompiles drift since the
# previous run). After the compile finishes the populated cache is copied into
# /opt/buildcache, a real path that is preserved in the image layer.
#
# Unity build is OFF here AND in the consumer workflow: with unity ON, a single
# .cpp change invalidates the whole unity blob's cache entry, defeating the
# point. The flag must match between this stage and CI or hit rate collapses
# (the command line is part of the hash).
RUN --mount=type=cache,id=ri-rhel8-buildcache,target=/var/cache/buildcache,sharing=locked \
git config --global --add safe.directory '*' \
&& cp /src/vcpkg-configuration-rhel8.json /src/vcpkg-configuration.json \
&& source /opt/rh/gcc-toolset-14/enable \
&& ( cd /src/ThirdParty/vcpkg && ./bootstrap-vcpkg.sh ) \
&& export CC=/opt/rh/gcc-toolset-14/root/usr/bin/gcc \
&& export CXX=/opt/rh/gcc-toolset-14/root/usr/bin/g++ \
&& export VCPKG_FEATURE_FLAGS=binarycaching \
&& export VCPKG_BINARY_SOURCES="clear;files,/opt/vcpkg-cache,read" \
&& export BUILDCACHE_DIR=/var/cache/buildcache \
&& cmake -S /src -B /src/cmakebuild -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DRESINSIGHT_INCLUDE_APPLICATION_UNIT_TESTS=true \
-DRESINSIGHT_ENABLE_UNITY_BUILD=false \
-DRESINSIGHT_ENABLE_HDF5=false \
-DRESINSIGHT_ENABLE_GRPC=false \
-DRESINSIGHT_GCC_STDCPP_EXP_PATH=/opt/libstdcxx-exp/lib \
-DCMAKE_TOOLCHAIN_FILE=ThirdParty/vcpkg/scripts/buildsystems/vcpkg.cmake \
&& cmake --build /src/cmakebuild --target ResInsight-tests -- -j"$(nproc)" \
&& buildcache -s \
&& mkdir -p /opt/buildcache \
&& cp -a /var/cache/buildcache/. /opt/buildcache/ \
&& test -n "$(ls -A /opt/buildcache)" \
&& rm -rf /src
# -----------------------------------------------------------------------------
# Stage 4: final image
# -----------------------------------------------------------------------------
FROM base
LABEL org.opencontainers.image.source="https://github.com/OPM/ResInsight"
LABEL org.opencontainers.image.description="RHEL8 CI image for ResInsight with prebuilt toolchain, vcpkg binary cache, and warm ResInsight buildcache"
COPY --from=vcpkg-builder /opt/vcpkg-cache /opt/vcpkg-cache
COPY --from=build-warmup /opt/buildcache /opt/buildcache
ENV VCPKG_DEFAULT_BINARY_CACHE=/opt/vcpkg-cache \
BUILDCACHE_DIR=/opt/buildcache \
BUILDCACHE_ACCURACY=SLOPPY \
BUILDCACHE_MAX_CACHE_SIZE=4294967296
+172
View File
@@ -0,0 +1,172 @@
# RHEL8 CI Docker Image
This directory contains the Dockerfile for the prebuilt CI image used by the
RHEL8 unit test workflow. The image bakes in everything that does not change
on a per-PR basis so CI runs only have to recompile the translation units that
actually changed.
## What's in the image
Built from `rockylinux:8` in four stages (see `Dockerfile.rhel8`):
- **Toolchain** — `gcc-toolset-14`, a from-source `libstdc++exp` matched to the
toolset's GCC, Qt 6.6.3 (via `aqtinstall`), Python 3.9, Ninja, CMake.
- **buildcache** — built from source against the image's own glibc (the
upstream `buildcache-linux.tar.gz` binary is linked against glibc 2.29+ and
crashes on Rocky 8's glibc 2.28). Sourced from the canonical GitLab repo
`bits-n-bites/buildcache`. On PATH, so `CMakeLists.txt` auto-wires it as
`CMAKE_CXX_COMPILER_LAUNCHER`.
- **vcpkg binary cache** at `/opt/vcpkg-cache` — populated by running a CMake
configure against the ResInsight manifest, then frozen. Consumed read-only
in CI.
- **Warm ResInsight buildcache** at `/opt/buildcache` — populated by a full
`ResInsight-tests` compile at `/src -> /src/cmakebuild`. The CI workflow
clones into the same `/src` path so buildcache hashes line up; mismatched
source paths or build flags collapse the hit rate.
## Where the images live
The image is published to GitHub Container Registry under the repository
namespace, computed by the workflows as
`ghcr.io/${GITHUB_REPOSITORY,,}/ci-rhel8`. In practice:
| Context | Image path |
| -------------------------------------- | --------------------------------------------------- |
| Upstream (`OPM/ResInsight`) | `ghcr.io/opm/resinsight/ci-rhel8` |
| Fork (e.g. `magnesj/ResInsight`) | `ghcr.io/magnesj/resinsight/ci-rhel8` |
Each push tags both `:latest` and a date tag `:YYYY-MM-DD`.
### Viewing the images
- **Web UI** — package page on GitHub:
`https://github.com/<owner>/ResInsight/pkgs/container/resinsight%2Fci-rhel8`
- **CLI** — list versions and tags via the GitHub API:
```
gh api -H 'Accept: application/vnd.github+json' \
/users/<owner>/packages/container/resinsight%2Fci-rhel8/versions \
--jq '.[] | {tags: .metadata.container.tags, updated_at: .updated_at}'
```
(Use `/orgs/<org>/packages/...` instead of `/users/<owner>/...` for org-owned
packages like the upstream `OPM` namespace.)
## Building the image
### Via GitHub Actions (the supported path)
The image is built and pushed by `.github/workflows/build-rhel8-image.yml`:
- **Nightly** at 00:00 UTC (the unit test workflow runs at 02:00 UTC).
- **On push** to any of: `Dockerfile.rhel8`, `build-rhel8-image.yml`,
`vcpkg.json`, `vcpkg-configuration-rhel8.json`, `.gitmodules`.
- **Manually** via `workflow_dispatch`:
```
gh workflow run build-rhel8-image.yml --repo <owner>/ResInsight --ref <branch>
```
The workflow short-circuits if an image tagged with today's UTC date is
already in GHCR. To force a same-day rebuild (e.g. after iterating on the
Dockerfile within a single day), pass `force_rebuild=true`:
```
gh workflow run build-rhel8-image.yml --repo <owner>/ResInsight --ref <branch> \
-f force_rebuild=true
```
Cold first build runs the full `ResInsight-tests` compile in the warmup stage
(~1.52 h). Incremental nightly rebuilds reuse the BuildKit `gha` cache and
finish much faster.
### Building locally
Useful for validating Dockerfile changes before pushing to CI. From the repo
root:
```
docker buildx build \
--progress=plain \
--file .github/docker/Dockerfile.rhel8 \
--tag resinsight-ci-rhel8:local \
.
```
Notes:
- Requires Docker with BuildKit (default in Docker Desktop / Docker 23+).
The Dockerfile uses `# syntax=docker/dockerfile:1` and a BuildKit cache
mount for the warmup buildcache.
- The build context is ~860 MB even with `.dockerignore` excluding host build
outputs — most of that is `ThirdParty/vcpkg` and other submodules.
- Cold build is ~1.52 h end-to-end on a typical workstation; the resulting
image is ~5.3 GB.
- Tee the output to a log file so a crash of the launching shell does not
lose progress (the build itself keeps running in the Docker daemon):
```
docker buildx build ... 2>&1 | tee ri-rhel8-build.log
```
### Pushing a local image to GHCR
Useful when iterating on Dockerfile changes on a fork before merging: push to
your own GHCR namespace and trigger `rhel8-unit-tests.yml` on the fork to
verify end-to-end.
1. Ensure your `gh` token has the `write:packages` scope:
```
gh auth refresh -h github.com -s write:packages
```
2. Tag the local image for your fork's GHCR namespace and the desired tags:
```
docker tag resinsight-ci-rhel8:local ghcr.io/<owner>/resinsight/ci-rhel8:latest
docker tag resinsight-ci-rhel8:local ghcr.io/<owner>/resinsight/ci-rhel8:$(date -u +%Y-%m-%d)
```
3. Log Docker in to GHCR using the `gh` token and push:
```
gh auth token | docker login ghcr.io -u <owner> --password-stdin
docker push ghcr.io/<owner>/resinsight/ci-rhel8:latest
docker push ghcr.io/<owner>/resinsight/ci-rhel8:$(date -u +%Y-%m-%d)
```
## How the image is consumed
`.github/workflows/rhel8-unit-tests.yml` runs the unit tests inside this
image. It resolves the image name from `${GITHUB_REPOSITORY,,}` so the same
workflow definition works on the upstream repo and on forks without
modification.
The workflow clones the source manually into `/src` (not into
`GITHUB_WORKSPACE`) because buildcache hashes the absolute source path — the
clone path has to match the warmup compile's `/src` for the cache to hit.
Unity build is disabled in both the warmup and the CI configure for the same
reason: with unity on, a single `.cpp` change invalidates the entire unity
blob's cache entry.
## Retention
`.github/workflows/cleanup-rhel8-image.yml` runs daily at 03:00 UTC and
deletes old dated tags from GHCR:
- The `:latest` tag is always kept (the consumer workflow pulls it).
- The 3 most recent versions are kept regardless of age (safety floor so a
string of broken nightly builds can't strand CI without a working image).
- Of the rest, anything older than 5 days is deleted.
The retention window and dry-run mode are exposed as `workflow_dispatch`
inputs for ad-hoc invocation:
```
gh workflow run cleanup-rhel8-image.yml --repo <owner>/ResInsight \
-f dry_run=true -f retention_days=7
```
Owner type (User vs Organization) is auto-detected so the workflow runs
unchanged on the upstream repo and on forks.
+106
View File
@@ -0,0 +1,106 @@
name: Build RHEL8 CI Image
# Builds the Docker image used by the RHEL8 unit test workflow. The image bakes
# in the toolchain (gcc-toolset-14, libstdc++exp, Qt) and a prebuilt vcpkg
# binary cache so CI runs do not have to reprovision them every time.
on:
schedule:
# Nightly at 00:00 UTC, before the RHEL8 unit tests run at 02:00 UTC.
- cron: "0 0 * * *"
push:
paths:
- ".github/docker/Dockerfile.rhel8"
- ".github/workflows/build-rhel8-image.yml"
- "vcpkg.json"
- "vcpkg-configuration-rhel8.json"
- ".gitmodules"
workflow_dispatch:
inputs:
force_rebuild:
description: "Rebuild and overwrite today's image even if it already exists in GHCR"
type: boolean
default: false
concurrency:
group: build-rhel8-image
cancel-in-progress: false
jobs:
build-image:
runs-on: ubuntu-latest
# Cold first build runs the full ResInsight compile in the build-warmup
# stage (~1.5-2 h). Incremental nightly rebuilds reuse the BuildKit cache
# mount and finish much faster.
timeout-minutes: 180
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: recursive
- name: Compute image name and tag
id: meta
run: |
image="ghcr.io/${GITHUB_REPOSITORY,,}/ci-rhel8"
date="$(date -u +%Y-%m-%d)"
repo_name_lc="${GITHUB_REPOSITORY##*/}"
repo_name_lc="${repo_name_lc,,}"
web_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/pkgs/container/${repo_name_lc}%2Fci-rhel8"
{
echo "image=$image"
echo "date=$date"
echo "web_url=$web_url"
} >> "$GITHUB_OUTPUT"
echo "Image refs : $image:latest"
echo " $image:$date"
echo "GHCR page : $web_url"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# The image build is expensive (cold ~1.5-2 h). If an image tagged with
# today's UTC date is already in GHCR, treat the build as already done
# for this day and skip. To force a same-day rebuild, dispatch the
# workflow with force_rebuild=true (or delete the dated tag from GHCR
# and rerun).
- name: Check if today's image already exists in GHCR
id: check
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" && "${{ inputs.force_rebuild }}" == "true" ]]; then
echo "force_rebuild=true -- skipping existence check."
echo "exists=false" >> "$GITHUB_OUTPUT"
exit 0
fi
target="${{ steps.meta.outputs.image }}:${{ steps.meta.outputs.date }}"
if docker buildx imagetools inspect "$target" >/dev/null 2>&1; then
echo "Image $target already exists -- skipping build."
echo "See: ${{ steps.meta.outputs.web_url }}"
echo "exists=true" >> "$GITHUB_OUTPUT"
else
echo "Image $target not found -- proceeding with build."
echo "exists=false" >> "$GITHUB_OUTPUT"
fi
- name: Build and push image
if: steps.check.outputs.exists != 'true'
uses: docker/build-push-action@v6
with:
context: .
file: .github/docker/Dockerfile.rhel8
push: true
tags: |
${{ steps.meta.outputs.image }}:latest
${{ steps.meta.outputs.image }}:${{ steps.meta.outputs.date }}
cache-from: type=gha
cache-to: type=gha,mode=max
+130
View File
@@ -0,0 +1,130 @@
name: Cleanup RHEL8 CI Image Versions
# Daily cleanup of old dated tags pushed by build-rhel8-image.yml.
#
# Retention rules:
# * Versions tagged "latest" are always kept (the consumer workflow pulls
# `:latest`, so deleting it would break CI).
# * The most recent MIN_KEEP versions by updated_at are kept regardless of
# age. This is a safety floor: if the last few nightly builds break, we
# can still roll back to a working older image.
# * Of what remains, anything older than RETENTION_DAYS is deleted.
#
# Owner type (User vs Organization) is auto-detected so the same workflow
# runs unchanged on the upstream (OPM/ResInsight, org) and on forks
# (e.g. magnesj/ResInsight, user).
on:
schedule:
# 03:00 UTC -- after the nightly image build (00:00) and the unit-test
# consumer (02:00) have both finished, so we never race them.
- cron: "0 3 * * *"
workflow_dispatch:
inputs:
dry_run:
description: "List versions that would be deleted without deleting"
type: boolean
default: false
retention_days:
description: "Delete versions older than this many days"
type: string
default: "5"
concurrency:
group: cleanup-rhel8-image
cancel-in-progress: false
jobs:
cleanup:
runs-on: ubuntu-latest
permissions:
packages: write
steps:
- name: Delete old image versions
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DRY_RUN: ${{ inputs.dry_run == true && 'true' || 'false' }}
RETENTION_DAYS: ${{ inputs.retention_days || '5' }}
MIN_KEEP: "3"
run: |
set -euo pipefail
owner="${GITHUB_REPOSITORY_OWNER}"
repo_name_lc="${GITHUB_REPOSITORY##*/}"
repo_name_lc="${repo_name_lc,,}"
pkg="${repo_name_lc}/ci-rhel8"
pkg_enc="${pkg/\//%2F}"
# Owner type drives both the list endpoint and the delete endpoint.
# User-owned packages delete via /user/... (authenticated user), org
# packages via /orgs/<org>/...
owner_type=$(gh api "/users/$owner" --jq '.type')
case "$owner_type" in
Organization)
list_path="/orgs/$owner/packages/container/$pkg_enc/versions"
del_prefix="/orgs/$owner/packages/container/$pkg_enc/versions"
;;
User)
list_path="/users/$owner/packages/container/$pkg_enc/versions"
del_prefix="/user/packages/container/$pkg_enc/versions"
;;
*)
echo "Unknown owner type: $owner_type" >&2
exit 1
;;
esac
echo "Owner : $owner ($owner_type)"
echo "Package : $pkg"
echo "Retention days : $RETENTION_DAYS"
echo "Safety floor : keep $MIN_KEEP most recent versions"
echo "Dry run : $DRY_RUN"
# Tolerate 404: the package legitimately does not exist on a fork
# that has never published an image yet.
if ! versions=$(gh api --paginate "$list_path" 2>/tmp/api.err); then
if grep -qiE 'not found|HTTP 404' /tmp/api.err; then
echo "Package $pkg not found on $owner -- nothing to clean up."
exit 0
fi
cat /tmp/api.err >&2
exit 1
fi
cutoff_epoch=$(date -u -d "$RETENTION_DAYS days ago" +%s)
echo "Cutoff (UTC) : $(date -u -d @"$cutoff_epoch" -Iseconds)"
# jq pipeline:
# 1. drop versions whose tags include "latest"
# 2. sort newest-first
# 3. skip the first MIN_KEEP (safety floor)
# 4. keep only those older than the cutoff
to_delete=$(jq -c \
--argjson keep "$MIN_KEEP" \
--argjson cutoff "$cutoff_epoch" '
[ .[] | select((.metadata.container.tags // []) | index("latest") | not) ]
| sort_by(.updated_at) | reverse
| .[$keep:]
| [ .[] | select((.updated_at | fromdateiso8601) < $cutoff) ]
' <<<"$versions")
count=$(jq 'length' <<<"$to_delete")
echo "Versions to delete: $count"
jq -r '.[] | " - id=\(.id) updated=\(.updated_at) tags=\((.metadata.container.tags // []) | join(","))"' <<<"$to_delete"
if [[ "$count" -eq 0 ]]; then
echo "Nothing to do."
exit 0
fi
if [[ "$DRY_RUN" == "true" ]]; then
echo "Dry run -- not deleting."
exit 0
fi
jq -r '.[].id' <<<"$to_delete" | while read -r id; do
echo "DELETE $del_prefix/$id"
gh api -X DELETE "$del_prefix/$id"
done
echo "Cleanup complete."
+70 -130
View File
@@ -5,145 +5,75 @@ on:
schedule:
# Nightly at 2am UTC
- cron: "0 2 * * *"
jobs:
rhel8-build-and-test:
# Resolves the prebuilt CI image name so it works in forks and upstream alike
# (GHCR requires a lowercase repository path).
resolve-image:
runs-on: ubuntu-latest
outputs:
image: ${{ steps.meta.outputs.image }}
steps:
- name: Compute image name
id: meta
run: echo "image=ghcr.io/${GITHUB_REPOSITORY,,}/ci-rhel8:latest" >> "$GITHUB_OUTPUT"
rhel8-build-and-test:
needs: resolve-image
runs-on: ubuntu-latest
permissions:
contents: read
packages: read
container:
image: rockylinux:8
# Prebuilt image with toolchain, baked vcpkg binary cache, and a warm
# ResInsight buildcache at /opt/buildcache.
# See .github/workflows/build-rhel8-image.yml
image: ${{ needs.resolve-image.outputs.image }}
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Source must live at /src to match the path the image's warmup compiled
# at; buildcache hashes the absolute source path so any drift collapses
# the hit rate. The clone step overrides this default since /src does not
# exist yet at that point.
defaults:
run:
working-directory: /src
steps:
- name: Install EPEL and development tools
run: |
dnf install -y epel-release
dnf config-manager --set-enabled powertools
dnf install -y \
gcc \
gcc-c++ \
make \
cmake \
ninja-build \
git \
curl \
zip \
unzip \
tar \
pkgconfig \
perl \
which \
python39 \
python39-devel \
python39-pip \
mesa-libGL-devel \
mesa-libGLU-devel \
mesa-libEGL-devel \
libxkbcommon-devel \
libxkbcommon-x11-devel \
xcb-util-keysyms-devel \
xcb-util-image-devel \
xcb-util-wm-devel \
xcb-util-renderutil-devel \
fontconfig-devel \
freetype-devel \
libX11-devel \
libXext-devel \
libXrender-devel \
flex \
bison \
xz
- name: Install GCC Toolset 14
run: |
dnf install -y gcc-toolset-14 gcc-toolset-14-gcc-c++ gcc-toolset-14-libatomic-devel gcc-toolset-14-libstdc++-devel
- name: Build libstdc++exp
run: |
source /opt/rh/gcc-toolset-14/enable
# Get full GCC version (e.g., 14.2.1)
GCC_FULL_VERSION=$(gcc -dumpfullversion)
# Get major.minor version for download (e.g., 14.2.0 - releases use .0 patch)
GCC_MAJOR_MINOR=$(echo $GCC_FULL_VERSION | cut -d. -f1,2)
GCC_VERSION="${GCC_MAJOR_MINOR}.0"
echo "GCC full version: $GCC_FULL_VERSION"
echo "GCC download version: $GCC_VERSION"
# Download GCC source
cd /tmp
curl -LO https://ftp.gnu.org/gnu/gcc/gcc-${GCC_VERSION}/gcc-${GCC_VERSION}.tar.xz
tar xf gcc-${GCC_VERSION}.tar.xz
# Build libstdc++exp
mkdir -p gcc-${GCC_VERSION}/build-libstdcxx
cd gcc-${GCC_VERSION}/build-libstdcxx
../libstdc++-v3/configure \
--prefix=/opt/libstdcxx-exp \
--disable-multilib \
--disable-libstdcxx-pch \
--with-gxx-include-dir=/opt/rh/gcc-toolset-14/root/usr/include/c++/${GCC_VERSION}
# Build the experimental library (use -k to continue past unrelated failures like tzdb.cc)
make -k -j$(nproc) || true
# Verify and install the library
if [ ! -f src/experimental/.libs/libstdc++exp.a ]; then
echo "ERROR: libstdc++exp.a was not built"
exit 1
fi
mkdir -p /opt/libstdcxx-exp/lib
cp src/experimental/.libs/libstdc++exp.a /opt/libstdcxx-exp/lib/
echo "libstdc++exp built successfully"
ls -la /opt/libstdcxx-exp/lib/
- name: Configure git safe directory
- name: Clone source into /src
working-directory: /
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# actions/checkout writes inside GITHUB_WORKSPACE (e.g.
# /__w/ResInsight/ResInsight) which would not match the image's baked
# cache path. Clone manually into /src instead. GITHUB_SHA is fetched
# directly (GitHub allows any-SHA fetches on the workflow's own repo).
#
# The insteadOf rewrite is unset after the clone so the token is not
# left in /etc/gitconfig where a later `git config --list` for
# diagnostics could echo it into the log.
run: |
git config --global --add safe.directory '*'
git config --global url."https://x-access-token:${GH_TOKEN}@github.com/".insteadOf "https://github.com/"
git clone --no-checkout "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git" /src
cd /src
git fetch --no-tags --depth 1 origin "${GITHUB_SHA}"
git checkout FETCH_HEAD
git submodule update --init --recursive --depth 1
git config --global --unset url."https://x-access-token:${GH_TOKEN}@github.com/".insteadOf
- name: Checkout
uses: actions/checkout@v6
with:
submodules: recursive
- name: Setup Python
run: |
alternatives --set python3 /usr/bin/python3.9
python3 --version
python3 -m pip install --upgrade pip
python3 -m pip install grpcio-tools
- name: Get Python executable path
id: python-path
run: |
echo "PYTHON_EXECUTABLE=$(python3 -c 'import sys; import pathlib; print(pathlib.PurePath(sys.executable).as_posix())')" >> $GITHUB_OUTPUT
- name: Install Qt 6 using aqtinstall
run: |
python3 -m pip install aqtinstall
python3 -m aqt install-qt linux desktop 6.6.3 gcc_64 -O /opt/Qt --modules qtnetworkauth
echo "Qt6_DIR=/opt/Qt/6.6.3/gcc_64" >> $GITHUB_ENV
echo "CMAKE_PREFIX_PATH=/opt/Qt/6.6.3/gcc_64" >> $GITHUB_ENV
echo "/opt/Qt/6.6.3/gcc_64/bin" >> $GITHUB_PATH
# /src was just freshly cloned, so the ThirdParty/vcpkg/vcpkg binary
# produced by bootstrap-vcpkg.sh is not present. The image's baked
# /opt/vcpkg-cache supplies prebuilt dependencies; the binary itself
# still has to exist locally for `vcpkg install` to run.
- name: Bootstrap vcpkg
run: |
source /opt/rh/gcc-toolset-14/enable
cd ThirdParty/vcpkg
./bootstrap-vcpkg.sh
- name: Get vcpkg submodule SHA
id: vcpkg-sha
shell: bash
run: echo "sha=$(git rev-parse HEAD:ThirdParty/vcpkg)" >> $GITHUB_OUTPUT
- name: Restore vcpkg cache
id: vcpkg-cache
uses: CeetronSolutions/vcpkg-cache@copilot/optimize-cache-storage-structure
with:
cache-key: ${{ runner.os }}-g++-${{ steps.vcpkg-sha.outputs.sha }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json') }}
prefix: vcpkg-g++/
- name: Use RHEL8 vcpkg configuration
run: cp vcpkg-configuration-rhel8.json vcpkg-configuration.json
@@ -152,13 +82,19 @@ jobs:
CC: /opt/rh/gcc-toolset-14/root/usr/bin/gcc
CXX: /opt/rh/gcc-toolset-14/root/usr/bin/g++
VCPKG_FEATURE_FLAGS: "binarycaching"
VCPKG_BINARY_SOURCES: "clear;files,${{ steps.vcpkg-cache.outputs.path }},readwrite"
# Read vcpkg dependencies from the binary cache baked into the image.
# buildcache is auto-discovered on PATH by CMakeLists.txt's
# find_program() and wired as CMAKE_CXX_COMPILER_LAUNCHER -- no
# explicit setup step is needed. BUILDCACHE_DIR etc. are baked in
# the image ENV. Unity build must be OFF to match the warmup build
# (mismatched flag would collapse the cache hit rate).
VCPKG_BINARY_SOURCES: "clear;files,/opt/vcpkg-cache,read"
run: |
source /opt/rh/gcc-toolset-14/enable
cmake -S . -B cmakebuild -G Ninja \
cmake -S /src -B /src/cmakebuild -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DRESINSIGHT_INCLUDE_APPLICATION_UNIT_TESTS=true \
-DRESINSIGHT_ENABLE_UNITY_BUILD=true \
-DRESINSIGHT_ENABLE_UNITY_BUILD=false \
-DRESINSIGHT_ENABLE_HDF5=false \
-DRESINSIGHT_ENABLE_GRPC=false \
-DRESINSIGHT_GCC_STDCPP_EXP_PATH=/opt/libstdcxx-exp/lib \
@@ -167,9 +103,13 @@ jobs:
- name: Build
run: |
source /opt/rh/gcc-toolset-14/enable
cmake --build cmakebuild --target ResInsight-tests -- -j$(nproc)
cmake --build /src/cmakebuild --target ResInsight-tests -- -j$(nproc)
- name: Buildcache stats
if: always()
run: buildcache -s
- name: Run Unit Tests
run: |
source /opt/rh/gcc-toolset-14/enable
./cmakebuild/ResInsight-tests
/src/cmakebuild/ResInsight-tests