feat(pj_base): align VideoFrame proto to Foxglove + zero-copy view de… #372
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |