Skip to content

feat(pj_base): align VideoFrame proto to Foxglove + zero-copy view de… #372

feat(pj_base): align VideoFrame proto to Foxglove + zero-copy view de…

feat(pj_base): align VideoFrame proto to Foxglove + zero-copy view de… #372

Workflow file for this run

name: Linux CI
on:
push:
branches: [main]
pull_request:
branches: [main]
types: [opened, synchronize, reopened, ready_for_review]
workflow_dispatch: {}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
if: github.event.pull_request.draft == false
runs-on: ubuntu-22.04
permissions:
contents: read
packages: write # push/pull the Conan cache OCI artifact on ghcr.io
steps:
- uses: actions/checkout@v4
- uses: conan-io/setup-conan@v1
with:
# We back the cache via ghcr.io/oras instead of actions/cache (which
# has a 10 GB per-repo soft cap and 7-day eviction). ghcr.io public
# repos: unlimited storage and bandwidth; tags survive forever.
cache_packages: false
- name: Setup oras (OCI Registry As Storage)
# Primary install path. continue-on-error so a 5xx on the github.com
# release CDN doesn't kill CI — we have a fallback below, and worst
# case we degrade to "no cache this run" (build still completes).
id: setup_oras
uses: oras-project/setup-oras@v1
continue-on-error: true
- name: Setup oras (manual download fallback)
# Only fires if setup-oras failed. Re-download from the same release
# URL but with longer exponential backoff (5 retries) — covers brief
# 5xx blips that exceed the action's built-in retry budget.
id: setup_oras_fallback
if: steps.setup_oras.outcome == 'failure'
continue-on-error: true
shell: bash
run: |
set -euo pipefail
version="1.3.0"
url="https://github.com/oras-project/oras/releases/download/v${version}/oras_${version}_linux_amd64.tar.gz"
tarball="${RUNNER_TEMP}/oras.tar.gz"
install_dir="${RUNNER_TEMP}/oras-bin"
mkdir -p "${install_dir}"
attempts=0
until curl -fsSL "${url}" -o "${tarball}"; do
attempts=$((attempts + 1))
if [ "${attempts}" -ge 5 ]; then
echo "Download failed after ${attempts} attempts" >&2
exit 1
fi
delay=$((5 * (2 ** attempts)))
echo "Download attempt ${attempts} failed; retrying in ${delay}s..."
sleep "${delay}"
done
tar -xzf "${tarball}" -C "${install_dir}"
echo "${install_dir}" >> "${GITHUB_PATH}"
"${install_dir}/oras" version
- name: Compute Conan cache tag
id: conan_cache
shell: bash
run: |
set -euo pipefail
# Hash every conanfile.{py,txt} under the tree so the key invalidates
# whenever any of them changes. The top-level is now conanfile.py
# (Conan recipe); subtrees like pj_ported_plugins/ still use .txt.
hash=$(find . \( -name 'conanfile.py' -o -name 'conanfile.txt' \) \
-not -path './build/*' -not -path './.git/*' \
-print0 | sort -z | xargs -0 sha256sum | sha256sum | cut -c1-16)
owner="${GITHUB_REPOSITORY_OWNER,,}"
# Build-type suffix leaves room for a future debug-asan variant
# without colliding on the same tag.
tag="ghcr.io/${owner}/plotjuggler-core-conan-cache:linux-gcc-relwithdebinfo-${hash}"
main_tag="ghcr.io/${owner}/plotjuggler-core-conan-cache:linux-gcc-relwithdebinfo-main"
echo "hash=${hash}" >> "${GITHUB_OUTPUT}"
echo "tag=${tag}" >> "${GITHUB_OUTPUT}"
echo "main_tag=${main_tag}" >> "${GITHUB_OUTPUT}"
echo "Cache tag: ${tag}"
echo "Main fallback cache tag: ${main_tag}"
- name: Login to ghcr.io
# Required for both pull (private packages) and push. GITHUB_TOKEN is
# auto-provisioned and scoped to this repo's packages. Skip if neither
# oras install path succeeded — graceful degradation to no-cache build.
if: github.repository_owner == 'PlotJuggler' && (steps.setup_oras.outcome == 'success' || steps.setup_oras_fallback.outcome == 'success')
shell: bash
env:
GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "${GHCR_TOKEN}" | oras login ghcr.io -u "${GITHUB_ACTOR}" --password-stdin
- name: Restore Conan cache from ghcr.io
if: github.repository_owner == 'PlotJuggler' && (steps.setup_oras.outcome == 'success' || steps.setup_oras_fallback.outcome == 'success')
shell: bash
run: |
set +e
exact_tag="${{ steps.conan_cache.outputs.tag }}"
main_tag="${{ steps.conan_cache.outputs.main_tag }}"
pull_dir="${RUNNER_TEMP}/conan-cache-pull"
mkdir -p "${pull_dir}"
tarball="${pull_dir}/conan-cache.tar.zst"
for tag in "${exact_tag}" "${main_tag}"; do
rm -f "${tarball}"
echo "Trying Conan cache: ${tag}"
(
cd "${pull_dir}"
oras pull "${tag}" 2>&1
)
pulled=$?
if [ "${pulled}" -eq 0 ] && [ -f "${tarball}" ]; then
echo "Cache HIT: extracting ${tarball}"
mkdir -p "${HOME}/.conan2"
tar --zstd -xf "${tarball}" -C "${HOME}"
echo "CONAN_CACHE_HIT=1" >> "${GITHUB_ENV}"
echo "CONAN_CACHE_HIT_TAG=${tag}" >> "${GITHUB_ENV}"
if [ "${tag}" = "${exact_tag}" ]; then
echo "CONAN_CACHE_EXACT_HIT=1" >> "${GITHUB_ENV}"
fi
exit 0
fi
echo "Cache MISS for ${tag}"
done
# Always exit 0 — a missing tag is not a CI failure.
exit 0
- name: Install Qt 6.8.3
uses: jurplel/install-qt-action@v4
with:
version: '6.8.3'
modules: 'qtcharts'
cache: 'true'
- name: Install system packages
# abigail-tools provides abidw/abidiff for the ABI drift gate (below).
run: sudo apt-get update && sudo apt-get install -y xvfb ccache ninja-build zstd abigail-tools
- name: Configure ccache
# Compiler-output cache for our own C++ — complementary to the Conan
# cache (which holds prebuilt third-party packages, not our objects).
uses: actions/cache@v4
with:
path: ~/.cache/ccache
key: ccache-linux-relwithdebinfo-${{ steps.conan_cache.outputs.hash }}-${{ github.sha }}
restore-keys: |
ccache-linux-relwithdebinfo-${{ steps.conan_cache.outputs.hash }}-
ccache-linux-relwithdebinfo-
ccache-linux-
- name: Set ccache limits
run: |
ccache --set-config=max_size=500M
ccache --set-config=compression=true
ccache -z
- name: Conan install
run: >
conan install . --output-folder=build --build=missing
-s build_type=RelWithDebInfo -s compiler.cppstd=20
-o "plotjuggler_core/*:with_tests=True"
-o "plotjuggler_core/*:with_parquet_example=False"
- name: Save Conan cache to ghcr.io
# Only push from the canonical repo on real pushes (forks lack write
# access to ghcr.io packages). Skip on exact cache HIT — the tag content
# is already up to date, no point re-uploading the same bytes. Skip if
# neither oras install path succeeded (graceful degradation).
if: github.event_name == 'push' && github.repository == 'PlotJuggler/plotjuggler_core' && env.CONAN_CACHE_EXACT_HIT != '1' && (steps.setup_oras.outcome == 'success' || steps.setup_oras_fallback.outcome == 'success')
shell: bash
run: |
set -euo pipefail
tag="${{ steps.conan_cache.outputs.tag }}"
tarball="${RUNNER_TEMP}/conan-cache.tar.zst"
# Trim build trees before packing so the uploaded artifact only
# contains the package binaries consumers actually need.
conan cache clean "*" --build --source --temp
echo "Packing ~/.conan2 -> ${tarball}"
tar --zstd -cf "${tarball}" -C "${HOME}" .conan2
size_mb=$(du -m "${tarball}" | cut -f1)
echo "Compressed cache size: ${size_mb} MB"
(
cd "${RUNNER_TEMP}"
oras push "${tag}" "conan-cache.tar.zst:application/vnd.oci.image.layer.v1.tar+zstd"
if [ "${GITHUB_REF_NAME}" = "main" ]; then
oras push "${{ steps.conan_cache.outputs.main_tag }}" "conan-cache.tar.zst:application/vnd.oci.image.layer.v1.tar+zstd"
fi
)
- name: Configure
run: >
cmake -S . -B build -G Ninja
-DCMAKE_TOOLCHAIN_FILE=${{ github.workspace }}/build/conan_toolchain.cmake
-DCMAKE_BUILD_TYPE=RelWithDebInfo
-DCMAKE_C_COMPILER_LAUNCHER=ccache
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache
-DPJ_BUILD_PARQUET_IMPORT_EXAMPLE=OFF
-DPJ_ENABLE_ABI_CHECK=ON
- name: Build
run: cmake --build build
- name: ABI drift gate
# Mechanically enforces the Release Versioning policy (CLAUDE.md): a non-MAJOR
# change must not break ABI. Diffs the mock_data_source_plugin canary DSO against
# the checked-in pj_base/abi/baseline.abi via libabigail and FAILS on an
# ABI-INCOMPATIBLE change (bit 8). Backward-compatible additions (bit 4) pass with
# a "baseline may be stale" nudge — refresh via the abi_update_baseline target.
run: cmake --build build --target abi_check
- name: ccache stats
if: always()
run: ccache -s
- name: Test
run: xvfb-run -a ctest --test-dir build --output-on-failure