From 2249054359d85994878cddcd9f1126db5de0f020 Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Sat, 4 Jul 2026 11:46:01 +0000 Subject: [PATCH 01/21] feat: add docs flavor --- .devcontainer/docs-test/devcontainer.json | 4 + .devcontainer/docs/Dockerfile | 34 ++ .devcontainer/docs/apt-requirements.json | 5 + .devcontainer/docs/devcontainer-metadata.json | 16 + .devcontainer/docs/devcontainer.json | 31 ++ .devcontainer/docs/requirements.in | 1 + .devcontainer/docs/requirements.txt | 293 ++++++++++++++++++ .github/RELEASE_TEMPLATE.md | 11 +- .github/dependabot.yml | 1 + .github/workflows/build-push-test.yml | 2 +- .github/workflows/image-cleanup.yml | 2 +- .github/workflows/pr-image-cleanup.yml | 2 +- .github/workflows/release-build.yml | 2 +- .github/workflows/update-dependencies.yml | 4 +- .github/workflows/vulnerability-scan.yml | 2 +- README.md | 14 +- test/docs/integration-tests.bats | 85 +++++ test/docs/workspace/diagram.puml | 4 + test/docs/workspace/graph.dot | 4 + test/docs/workspace/sample.sbdl | 5 + 20 files changed, 506 insertions(+), 16 deletions(-) create mode 100644 .devcontainer/docs-test/devcontainer.json create mode 100644 .devcontainer/docs/Dockerfile create mode 100644 .devcontainer/docs/apt-requirements.json create mode 100644 .devcontainer/docs/devcontainer-metadata.json create mode 100644 .devcontainer/docs/devcontainer.json create mode 100644 .devcontainer/docs/requirements.in create mode 100644 .devcontainer/docs/requirements.txt create mode 100644 test/docs/integration-tests.bats create mode 100644 test/docs/workspace/diagram.puml create mode 100644 test/docs/workspace/graph.dot create mode 100644 test/docs/workspace/sample.sbdl diff --git a/.devcontainer/docs-test/devcontainer.json b/.devcontainer/docs-test/devcontainer.json new file mode 100644 index 000000000..80efc7c32 --- /dev/null +++ b/.devcontainer/docs-test/devcontainer.json @@ -0,0 +1,4 @@ +{ + "image": "ghcr.io/philips-software/amp-devcontainer-docs:${localEnv:IMAGE_VERSION}", + "workspaceFolder": "/workspaces/amp-devcontainer/test/docs/workspace" +} diff --git a/.devcontainer/docs/Dockerfile b/.devcontainer/docs/Dockerfile new file mode 100644 index 000000000..a44fab8f3 --- /dev/null +++ b/.devcontainer/docs/Dockerfile @@ -0,0 +1,34 @@ +# syntax=docker/dockerfile:1 + +ARG BASE_IMAGE=ghcr.io/philips-software/amp-devcontainer-base:edge +FROM ${BASE_IMAGE} + +ARG DEBIAN_FRONTEND=noninteractive + +HEALTHCHECK NONE + +SHELL ["/bin/bash", "-Eeuo", "pipefail", "-c"] + +# Set default environment options +ENV PYTHONPYCACHEPREFIX=/cache/.python + +# Install the base system with all tool dependencies +RUN --mount=type=bind,source=.devcontainer/docs/apt-requirements.json,target=/tmp/apt-requirements.json \ + --mount=type=bind,source=.devcontainer/docs/requirements.txt,target=/tmp/requirements.txt \ + --mount=type=cache,target=/cache,sharing=locked \ + --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + --mount=type=cache,target=/var/log,sharing=locked <&1 | to_semver) + + assert_equal "$INSTALLED_VERSION" "$EXPECTED_VERSION" +} + +# bats test_tags=Version,Pip +@test "pip version should be aligned with the expected version" { + EXPECTED_VERSION=$(get_expected_semver_for python3-pip) + INSTALLED_VERSION=$(pip --version | to_semver) + + assert_equal "$INSTALLED_VERSION" "$EXPECTED_VERSION" +} + +# bats test_tags=Version,Sbdl +@test "sbdl version should be aligned with the expected version" { + EXPECTED_VERSION=$(grep sbdl ${BATS_TEST_DIRNAME}/../../.devcontainer/docs/requirements.in | to_semver) + INSTALLED_VERSION=$(sbdl --version | to_semver) + + assert_equal "$INSTALLED_VERSION" "$EXPECTED_VERSION" +} + +## This section contains tests for the functional correctness of the installed tools. + +# bats test_tags=Functional,Plantuml +@test "plantuml should render a diagram to a png image" { + run plantuml -o build -tpng diagram.puml + + assert_success + assert [ -f build/diagram.png ] +} + +# bats test_tags=Functional,Graphviz +@test "graphviz should render a graph to an svg image" { + run dot -Tsvg graph.dot + + assert_success + assert_output --partial " Bob: Authentication Request +Bob --> Alice: Authentication Response +@enduml diff --git a/test/docs/workspace/graph.dot b/test/docs/workspace/graph.dot new file mode 100644 index 000000000..3f08981a1 --- /dev/null +++ b/test/docs/workspace/graph.dot @@ -0,0 +1,4 @@ +digraph { + a -> b; + b -> c; +} diff --git a/test/docs/workspace/sample.sbdl b/test/docs/workspace/sample.sbdl new file mode 100644 index 000000000..d6c1957df --- /dev/null +++ b/test/docs/workspace/sample.sbdl @@ -0,0 +1,5 @@ +#!sbdl +req-doc-0001 is requirement { + custom:title is "Sample requirement" + description is "A sample requirement used to validate the sbdl toolchain" +} From f9ce339360af63412177c4d5923d4524164142ae Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Sat, 4 Jul 2026 11:54:50 +0000 Subject: [PATCH 02/21] docs: minor re-word of README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 396a1cb8d..fe27eb07e 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ It consolidates common tooling (e.g. certificates and test tooling) so that the #### amp-devcontainer-cpp -The amp-devcontainer-cpp built from this repository contains compilers and tools to facilitate modern C++ development. +The amp-devcontainer-cpp image built from this repository contains compilers and tools to facilitate modern C++ development. The amp-devcontainer-cpp includes support for host- and cross-compilation using gcc and clang compilers. Next to the compilers there is support for package management (using [CPM.cmake](https://github.com/cpm-cmake/CPM.cmake) and [Conan](https://conan.io/)) code-coverage measurement, mutation testing (using [mull](https://github.com/mull-project/mull)), fuzzing (using [libfuzzer](https://www.llvm.org/docs/LibFuzzer.html)) and static analysis and formatting (clang-format, clang-tidy, clangd, include-what-you-use). @@ -83,7 +83,7 @@ It includes [sbdl](https://sbdl.dev/) to capture the essence of system compositi #### amp-devcontainer-embedded-cpp -The amp-devcontainer-embedded-cpp built from this repository contains the same tooling as amp-devcontainer-cpp, extended with support for embedded C++ development. +The amp-devcontainer-embedded-cpp image built from this repository contains the same tooling as amp-devcontainer-cpp, extended with support for embedded C++ development. On top of the host- and cross-compilation support it adds the arm-gcc (arm-none-eabi) toolchain for cross-compilation towards the ARM Cortex architecture, together with debugging support for micro-controllers (using [cortex-debug](https://github.com/Marus/cortex-debug) and gdb-multiarch). The default build system is set up to use CMake, Ninja and CCache. From 1541a88c816804d2a48e37586b095c038409e907 Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Thu, 9 Jul 2026 09:05:14 +0200 Subject: [PATCH 03/21] chore: make Dockerfiles more consistent --- .devcontainer/cpp/Dockerfile | 4 +--- .devcontainer/docs/Dockerfile | 1 - .devcontainer/rust/Dockerfile | 16 +++++++++------- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/.devcontainer/cpp/Dockerfile b/.devcontainer/cpp/Dockerfile index 4ec851193..7ff9210a7 100644 --- a/.devcontainer/cpp/Dockerfile +++ b/.devcontainer/cpp/Dockerfile @@ -111,7 +111,6 @@ ENV CCACHE_DIR=/cache/.ccache \ PATH="$PATH:/usr/lib/llvm-${CLANG_VERSION}/bin:/opt/gcc-arm-none-eabi/bin" \ PYTHONPYCACHEPREFIX=/cache/.python -# Install the base system with all tool dependencies RUN --mount=type=bind,source=.devcontainer/cpp/apt-requirements-base.json,target=/tmp/apt-requirements-base.json \ --mount=type=bind,source=.devcontainer/cpp/apt-requirements-clang.json,target=/tmp/apt-requirements-clang.json \ --mount=type=bind,source=.devcontainer/cpp/requirements.txt,target=/tmp/requirements.txt \ @@ -178,8 +177,7 @@ RUN --mount=type=cache,target=/var/log,sharing=locked \ --slave /usr/bin/mull-reporter mull-reporter /usr/bin/mull-reporter-${CLANG_VERSION} \ --slave /usr/lib/mull-ir-frontend mull-ir-frontend /usr/lib/mull-ir-frontend-${CLANG_VERSION} \ && update-alternatives --install /usr/bin/python python /usr/bin/python3 10 \ - && mkdir /root/.amp \ - && cp /etc/skel/.bashrc /root/.bashrc + && mkdir /root/.amp # Set up package managers CPM and Conan # - Install CPM.cmake to the CMake module path diff --git a/.devcontainer/docs/Dockerfile b/.devcontainer/docs/Dockerfile index a44fab8f3..fef262b7e 100644 --- a/.devcontainer/docs/Dockerfile +++ b/.devcontainer/docs/Dockerfile @@ -12,7 +12,6 @@ SHELL ["/bin/bash", "-Eeuo", "pipefail", "-c"] # Set default environment options ENV PYTHONPYCACHEPREFIX=/cache/.python -# Install the base system with all tool dependencies RUN --mount=type=bind,source=.devcontainer/docs/apt-requirements.json,target=/tmp/apt-requirements.json \ --mount=type=bind,source=.devcontainer/docs/requirements.txt,target=/tmp/requirements.txt \ --mount=type=cache,target=/cache,sharing=locked \ diff --git a/.devcontainer/rust/Dockerfile b/.devcontainer/rust/Dockerfile index 90d30cac6..387dd71de 100644 --- a/.devcontainer/rust/Dockerfile +++ b/.devcontainer/rust/Dockerfile @@ -10,18 +10,20 @@ ARG DEBIAN_FRONTEND=noninteractive HEALTHCHECK NONE -SHELL ["/bin/bash", "-o", "pipefail", "-c"] +SHELL ["/bin/bash", "-Eeuo", "pipefail", "-c"] -# Install the base system with all tool dependencies -# hadolint ignore=DL3008 RUN --mount=type=bind,source=.devcontainer/rust/apt-requirements.json,target=/tmp/apt-requirements.json \ --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt,sharing=locked \ - --mount=type=cache,target=/var/log,sharing=locked \ - apt-get update \ - && jq -r 'to_entries | .[] | .key + "=" + .value' /tmp/apt-requirements.json | xargs apt-get install -y --no-install-recommends \ - && update-alternatives --install /usr/bin/cc cc /usr/bin/gcc-15 10 \ + --mount=type=cache,target=/var/log,sharing=locked <&1 | to_semver) + + assert_equal "$INSTALLED_VERSION" "$EXPECTED_VERSION" +} + # bats test_tags=Version,Sbdl @test "sbdl version should be aligned with the expected version" { EXPECTED_VERSION=$(grep sbdl ${BATS_TEST_DIRNAME}/../../.devcontainer/docs/requirements.in | to_semver) From 70c09a49240476bdc16c69137da15d1527450680 Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Thu, 9 Jul 2026 11:44:07 +0200 Subject: [PATCH 05/21] test: fix graphviz version test --- test/docs/integration-tests.bats | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/docs/integration-tests.bats b/test/docs/integration-tests.bats index 53c120ec9..a20a6cc70 100644 --- a/test/docs/integration-tests.bats +++ b/test/docs/integration-tests.bats @@ -21,10 +21,10 @@ teardown() { # bats test_tags=Version,Graphviz @test "graphviz version should be aligned with the expected version" { - EXPECTED_VERSION=$(get_expected_semver_for graphviz) - INSTALLED_VERSION=$(dot -V 2>&1 | to_semver) + EXPECTED_MAJOR_MINOR_VERSION=$(get_expected_semver_for graphviz | cut -d. -f1,2) + INSTALLED_MAJOR_MINOR_VERSION=$(dot -V 2>&1 | to_semver | cut -d. -f1,2) - assert_equal "$INSTALLED_VERSION" "$EXPECTED_VERSION" + assert_equal "$INSTALLED_MAJOR_MINOR_VERSION" "$EXPECTED_MAJOR_MINOR_VERSION" } # bats test_tags=Version,Pip From 543f843abe6bef749a01c4ee5515af9714ab3604 Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Thu, 9 Jul 2026 14:08:20 +0000 Subject: [PATCH 06/21] chore: fix test --- test/docs/integration-tests.bats | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/docs/integration-tests.bats b/test/docs/integration-tests.bats index a20a6cc70..1327f9386 100644 --- a/test/docs/integration-tests.bats +++ b/test/docs/integration-tests.bats @@ -21,10 +21,10 @@ teardown() { # bats test_tags=Version,Graphviz @test "graphviz version should be aligned with the expected version" { - EXPECTED_MAJOR_MINOR_VERSION=$(get_expected_semver_for graphviz | cut -d. -f1,2) - INSTALLED_MAJOR_MINOR_VERSION=$(dot -V 2>&1 | to_semver | cut -d. -f1,2) + EXPECTED_VERSION=$(get_expected_semver_for graphviz) + INSTALLED_VERSION=$(dot -V 2>&1 | to_semver) - assert_equal "$INSTALLED_MAJOR_MINOR_VERSION" "$EXPECTED_MAJOR_MINOR_VERSION" + assert_equal "$INSTALLED_VERSION" "$EXPECTED_VERSION" } # bats test_tags=Version,Pip @@ -37,10 +37,10 @@ teardown() { # bats test_tags=Version,Plantuml @test "plantuml version should be aligned with the expected version" { - EXPECTED_VERSION=$(get_expected_semver_for plantuml) - INSTALLED_VERSION=$(plantuml -version 2>&1 | to_semver) + EXPECTED_MAJOR_MINOR_VERSION=$(get_expected_semver_for plantuml | cut -d. -f1,2) + INSTALLED_MAJOR_MINOR_VERSION=$(plantuml -version 2>&1 | to_semver | cut -d. -f1,2) - assert_equal "$INSTALLED_VERSION" "$EXPECTED_VERSION" + assert_equal "$INSTALLED_MAJOR_MINOR_VERSION" "$EXPECTED_MAJOR_MINOR_VERSION" } # bats test_tags=Version,Sbdl From a05c911ce25fd909db73e14a93135297ca5d6411 Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Thu, 9 Jul 2026 14:10:47 +0000 Subject: [PATCH 07/21] chore: process review comments --- .github/workflows/update-dependencies.yml | 4 ++-- .github/workflows/vulnerability-scan.yml | 2 +- README.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/update-dependencies.yml b/.github/workflows/update-dependencies.yml index 6655fe0cc..597706eff 100644 --- a/.github/workflows/update-dependencies.yml +++ b/.github/workflows/update-dependencies.yml @@ -57,8 +57,8 @@ jobs: strategy: fail-fast: false matrix: - flavor: ["cpp", "docs", "embedded-cpp", "rust"] - file: ["devcontainer-metadata.json", "devcontainer.json"] + flavor: [cpp, docs, embedded-cpp, rust] + file: [devcontainer-metadata.json, devcontainer.json] permissions: contents: read # peter-evans/create-pull-request inherits app permissions; so we only need contents: read here steps: diff --git a/.github/workflows/vulnerability-scan.yml b/.github/workflows/vulnerability-scan.yml index 14e0cfb9e..0b1a1cb78 100644 --- a/.github/workflows/vulnerability-scan.yml +++ b/.github/workflows/vulnerability-scan.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - flavor: ["cpp", "docs", "embedded-cpp", "rust"] + flavor: [cpp, docs, embedded-cpp, rust] permissions: security-events: write # is needed by github/codeql-action/upload-sarif to upload sarif files steps: diff --git a/README.md b/README.md index fe27eb07e..d30fc3e02 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ The default build system is set up to use CMake, Ninja and CCache. #### amp-devcontainer-docs The amp-devcontainer-docs image built from this repository contains tooling to generate documentation from repository sources. -It includes [sbdl](https://sbdl.dev/) to capture the essence of system composition and behaviour in a simple yet powerful domain-specific language, together with [PlantUML](https://plantuml.com/) and [Graphviz](https://graphviz.org/) for rendering diagrams. +It includes [sbdl](https://sbdl.dev/) to capture the essence of system composition and behavior in a simple yet powerful domain-specific language, together with [PlantUML](https://plantuml.com/) and [Graphviz](https://graphviz.org/) for rendering diagrams. #### amp-devcontainer-embedded-cpp From bfc6434180b3becc606737d959cde2f284a39d53 Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Thu, 9 Jul 2026 14:58:05 +0000 Subject: [PATCH 08/21] chore: updated out of date comment --- .devcontainer/cpp/Dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/.devcontainer/cpp/Dockerfile b/.devcontainer/cpp/Dockerfile index 7ff9210a7..c3cc37067 100644 --- a/.devcontainer/cpp/Dockerfile +++ b/.devcontainer/cpp/Dockerfile @@ -165,7 +165,6 @@ RUN --mount=type=cache,target=/cache,sharing=locked \ && apt-get clean # Update all tool alternatives to the correct version -# and patch root's bashrc to include bash-completion RUN --mount=type=cache,target=/var/log,sharing=locked \ update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-15 10 \ --slave /usr/bin/g++ g++ /usr/bin/g++-15 \ From eeb3eaddf4c225de69a02881e7c5145104af618c Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Thu, 9 Jul 2026 19:31:32 +0000 Subject: [PATCH 09/21] feat: add missing tools to sbom for rust --- .devcontainer/rust/Dockerfile | 41 ++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/.devcontainer/rust/Dockerfile b/.devcontainer/rust/Dockerfile index 387dd71de..4d3540696 100644 --- a/.devcontainer/rust/Dockerfile +++ b/.devcontainer/rust/Dockerfile @@ -4,6 +4,10 @@ ARG BASE_IMAGE=ghcr.io/philips-software/amp-devcontainer-base:edge FROM ${BASE_IMAGE} ARG CARGO_BINSTALL_VERSION=1.17.9 +ARG CARGO_BINUTILS_VERSION=0.3.6 +ARG CARGO_MUTANTS_VERSION=25.3.1 +ARG FLIP_LINK_VERSION=0.1.12 +ARG PROBE_RS_TOOLS_VERSION=0.30.0 ARG RUST_VERSION=1.94.1 ARG DEBIAN_FRONTEND=noninteractive @@ -39,4 +43,39 @@ RUN rustup set profile minimal \ # Install additional rust tools RUN wget -qO - "https://github.com/cargo-bins/cargo-binstall/releases/download/v${CARGO_BINSTALL_VERSION}/cargo-binstall-$(uname -m)-unknown-linux-gnu.tgz" | tar xz -C "/usr/bin" \ - && cargo-binstall -y --locked cargo-binutils@0.3.6 cargo-mutants@25.3.1 flip-link@0.1.12 probe-rs-tools@0.30.0 + && cargo-binstall -y --locked "cargo-binutils@${CARGO_BINUTILS_VERSION}" "cargo-mutants@${CARGO_MUTANTS_VERSION}" "flip-link@${FLIP_LINK_VERSION}" "probe-rs-tools@${PROBE_RS_TOOLS_VERSION}" + +# Annotate tools that Syft cannot detect on its own with a `.note.package` ELF +# section (systemd ELF package metadata convention, see +# https://systemd.io/ELF_PACKAGE_METADATA/) so they are included in the SBOM +# generated for the image. +RUN < "${note}" + + local tagged=0 binary + for binary in "$@"; do + if [ -x "${binary}" ]; then + objcopy --add-section .note.package="${note}" \ + --set-section-flags .note.package=noload,readonly "${binary}" + tagged=1 + fi + done + + rm -f "${note}" + [ "${tagged}" -eq 1 ] || { echo "no binaries found to annotate for ${name}" >&2; exit 1; } + } + + add_sbom_note cargo-binstall "${CARGO_BINSTALL_VERSION}" "pkg:cargo/cargo-binstall@${CARGO_BINSTALL_VERSION}" \ + /usr/bin/cargo-binstall + add_sbom_note cargo-binutils "${CARGO_BINUTILS_VERSION}" "pkg:cargo/cargo-binutils@${CARGO_BINUTILS_VERSION}" \ + "${CARGO_HOME}"/bin/cargo-{cov,nm,objcopy,profdata,readobj,size,strip} + add_sbom_note cargo-mutants "${CARGO_MUTANTS_VERSION}" "pkg:cargo/cargo-mutants@${CARGO_MUTANTS_VERSION}" \ + "${CARGO_HOME}/bin/cargo-mutants" + add_sbom_note flip-link "${FLIP_LINK_VERSION}" "pkg:cargo/flip-link@${FLIP_LINK_VERSION}" \ + "${CARGO_HOME}/bin/flip-link" + add_sbom_note probe-rs-tools "${PROBE_RS_TOOLS_VERSION}" "pkg:cargo/probe-rs-tools@${PROBE_RS_TOOLS_VERSION}" \ + "${CARGO_HOME}"/bin/probe-rs "${CARGO_HOME}"/bin/cargo-embed "${CARGO_HOME}"/bin/cargo-flash +EOF From e60b4524bd4e262e4acfb7126844ee841fc93f18 Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Thu, 9 Jul 2026 20:07:08 +0000 Subject: [PATCH 10/21] feat: add missing tools to sbom for base and cpp flavors --- .devcontainer/base/Dockerfile | 4 +++ .devcontainer/cpp/Dockerfile | 49 ++++++++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/.devcontainer/base/Dockerfile b/.devcontainer/base/Dockerfile index b56a5774d..af4966fc2 100644 --- a/.devcontainer/base/Dockerfile +++ b/.devcontainer/base/Dockerfile @@ -51,6 +51,10 @@ RUN --mount=type=bind,source=.devcontainer/base/apt-requirements.json,target=/tm cp -r /src/bats-support /usr/local/bats-support cp -r /src/bats-assert /usr/local/bats-assert + # Retain the bats-core manifest so the tool is captured in the SBOM + mkdir -p /usr/local/lib/bats-core + cp /src/bats-core/package.json /usr/local/lib/bats-core/package.json + # Generate locale sed -i '/C.UTF-8/s/^# //' /etc/locale.gen locale-gen diff --git a/.devcontainer/cpp/Dockerfile b/.devcontainer/cpp/Dockerfile index c3cc37067..cf1f84bda 100644 --- a/.devcontainer/cpp/Dockerfile +++ b/.devcontainer/cpp/Dockerfile @@ -9,6 +9,8 @@ ARG CCACHE_VERSION=4.13.1 ARG XWIN_VERSION=0.8.0 # Whether or not to build the embedded flavor of this container ARG BUILD_EMBEDDED_FLAVOR=false +# Version of the Arm GNU toolchain used for the embedded flavor +ARG ARM_GNU_TOOLCHAIN_VERSION=15.2.rel1 # Downloader stage for AMD64 architecture FROM scratch AS downloader-amd64 @@ -53,6 +55,7 @@ ARG CCACHE_MINISIGN_PUBKEY ARG CCACHE_VERSION ARG XWIN_VERSION ARG BUILD_EMBEDDED_FLAVOR +ARG ARM_GNU_TOOLCHAIN_VERSION SHELL ["/bin/bash", "-Eeuo", "pipefail", "-c"] @@ -65,7 +68,7 @@ RUN --mount=from=downloader,target=/dl \ apt-get update && apt-get install --no-install-recommends -y minisign if [[ "${BUILD_EMBEDDED_FLAVOR}" == "true" ]]; then - ARM_GNU_TOOLCHAIN_URL="https://developer.arm.com/-/media/Files/downloads/gnu/15.2.rel1/binrel/arm-gnu-toolchain-15.2.rel1-$(uname -m)-arm-none-eabi.tar.xz" + ARM_GNU_TOOLCHAIN_URL="https://developer.arm.com/-/media/Files/downloads/gnu/${ARM_GNU_TOOLCHAIN_VERSION}/binrel/arm-gnu-toolchain-${ARM_GNU_TOOLCHAIN_VERSION}-$(uname -m)-arm-none-eabi.tar.xz" ARM_GNU_TOOLCHAIN_TAR="/tmp/arm-gnu-toolchain.tar.xz" if [[ "$(uname -m)" == "x86_64" ]]; then @@ -95,6 +98,9 @@ ARG CLANG_VERSION=22 ARG CPM_VERSION=0.42.1 ARG INCLUDE_WHAT_YOU_USE_VERSION=0.26 ARG BUILD_EMBEDDED_FLAVOR +ARG CCACHE_VERSION +ARG XWIN_VERSION +ARG ARM_GNU_TOOLCHAIN_VERSION ARG DEBIAN_FRONTEND=noninteractive @@ -181,7 +187,48 @@ RUN --mount=type=cache,target=/var/log,sharing=locked \ # Set up package managers CPM and Conan # - Install CPM.cmake to the CMake module path # - Configure a default profile for Conan and set the CMake generator to Ninja +# +# NOTE: CPM.cmake is intentionally absent from the generated SBOM. It is a single +# CMake module (not an ELF binary), so it cannot carry a `.note.package` ELF +# section and Syft has no cataloger for standalone `.cmake` files. Its version is +# pinned and tracked via the CPM_VERSION build argument above. RUN --mount=type=cache,target=/cache,sharing=locked \ wget --no-hsts -qP /usr/local/lib/python*/dist-packages/cmake/data/share/cmake-*/Modules/ https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_VERSION}/CPM.cmake \ && conan profile detect \ && echo -e "\n[conf]\ntools.cmake.cmaketoolchain:generator=Ninja" >> "$(conan profile path default)" + +# Annotate tools that Syft cannot detect on its own with a `.note.package` ELF +# section (systemd ELF package metadata convention, see +# https://systemd.io/ELF_PACKAGE_METADATA/) so they are included in the SBOM +# generated for the image. +RUN < "${note}" + + local tagged=0 binary + for binary in "$@"; do + if [ -x "${binary}" ]; then + objcopy --add-section .note.package="${note}" \ + --set-section-flags .note.package=noload,readonly "${binary}" + tagged=1 + fi + done + + rm -f "${note}" + [ "${tagged}" -eq 1 ] || { echo "no binaries found to annotate for ${name}" >&2; exit 1; } + } + + add_sbom_note ccache "${CCACHE_VERSION}" "pkg:generic/ccache@${CCACHE_VERSION}" \ + /usr/local/bin/ccache + add_sbom_note xwin "${XWIN_VERSION}" "pkg:cargo/xwin@${XWIN_VERSION}" \ + /usr/local/bin/xwin + add_sbom_note include-what-you-use "${INCLUDE_WHAT_YOU_USE_VERSION}" "pkg:generic/include-what-you-use@${INCLUDE_WHAT_YOU_USE_VERSION}" \ + /usr/local/bin/include-what-you-use + + if [[ "${BUILD_EMBEDDED_FLAVOR}" == "true" ]]; then + add_sbom_note arm-gnu-toolchain "${ARM_GNU_TOOLCHAIN_VERSION}" "pkg:generic/arm-gnu-toolchain@${ARM_GNU_TOOLCHAIN_VERSION}" \ + /opt/gcc-arm-none-eabi/bin/arm-none-eabi-{gcc,g++,ld,objcopy,size} + fi +EOF From c3709436063d80c1bd79ede64faff56a8264887f Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Thu, 9 Jul 2026 20:23:27 +0000 Subject: [PATCH 11/21] feat: add tool inventory --- .devcontainer/cpp/tool-inventory.json | 19 ++++++++ .github/scripts/generate-tool-inventory.sh | 50 ++++++++++++++++++++++ .github/workflows/build-push-test.yml | 5 +++ .github/workflows/wc-build-push-test.yml | 10 +++++ .github/workflows/wc-build-push.yml | 29 +++++++++++++ 5 files changed, 113 insertions(+) create mode 100644 .devcontainer/cpp/tool-inventory.json create mode 100644 .github/scripts/generate-tool-inventory.sh diff --git a/.devcontainer/cpp/tool-inventory.json b/.devcontainer/cpp/tool-inventory.json new file mode 100644 index 000000000..1048c70aa --- /dev/null +++ b/.devcontainer/cpp/tool-inventory.json @@ -0,0 +1,19 @@ +[ + "ccache", + "clang-22", + "clang-format-22", + "clang-tidy-22", + "clang-tools-22", + "clangd-22", + "cmake", + "conan", + "g++-15", + "gcovr", + "gdb-multiarch", + "include-what-you-use", + "lld-22", + "llvm-22", + "mull-22", + "ninja-build", + "xwin" +] diff --git a/.github/scripts/generate-tool-inventory.sh b/.github/scripts/generate-tool-inventory.sh new file mode 100644 index 000000000..39f512ad8 --- /dev/null +++ b/.github/scripts/generate-tool-inventory.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# Derive a curated, per-flavor tool inventory from a Syft SPDX SBOM. +# +# The inventory is a filtered view of the (complete) SBOM: it keeps only the +# packages whose name is listed in the flavor's allowlist and projects them to +# {name, version, purl} pairs. Because the versions come straight from the SBOM, +# the inventory can never disagree with it. +# +# The script fails if an allowlisted tool is absent from the SBOM. This turns a +# version bump that renames a package (e.g. clang-22 -> clang-23) or a tool that +# silently dropped out of the image into a hard, actionable CI error. +# +# Usage: generate-tool-inventory.sh +set -Eeuo pipefail + +SBOM="${1:?path to SPDX SBOM required}" +ALLOWLIST="${2:?path to allowlist JSON required}" +FLAVOR="${3:?flavor name required}" + +inventory="$(jq -n \ + --slurpfile sbom "${SBOM}" \ + --slurpfile allow "${ALLOWLIST}" \ + --arg flavor "${FLAVOR}" ' + ($allow[0] | map(ascii_downcase)) as $names + | [ $sbom[0].packages[] + | select(.name != null) + | . as $pkg + | select($names | index($pkg.name | ascii_downcase)) + | { name: $pkg.name, + version: $pkg.versionInfo, + purl: ([ $pkg.externalRefs[]? | select(.referenceType == "purl") | .referenceLocator ] | first) } ] + | unique_by(.name | ascii_downcase) + | sort_by(.name | ascii_downcase) + | { flavor: $flavor, tools: . }')" + +missing="$(jq -rn \ + --argjson inventory "${inventory}" \ + --slurpfile allow "${ALLOWLIST}" ' + ($inventory.tools | map(.name | ascii_downcase)) as $found + | $allow[0] + | map(select((ascii_downcase) as $name | ($found | index($name)) | not)) + | .[]')" + +if [ -n "${missing}" ]; then + echo "error: the following allowlisted tools were not found in the SBOM:" >&2 + echo "${missing}" | sed 's/^/ - /' >&2 + exit 1 +fi + +echo "${inventory}" diff --git a/.github/workflows/build-push-test.yml b/.github/workflows/build-push-test.yml index 18a73c959..0d1e38983 100644 --- a/.github/workflows/build-push-test.yml +++ b/.github/workflows/build-push-test.yml @@ -43,13 +43,17 @@ jobs: - flavor: cpp dockerfile: .devcontainer/cpp/Dockerfile build-embedded-flavor: "false" + tool-inventory-file: .devcontainer/cpp/tool-inventory.json - flavor: docs dockerfile: .devcontainer/docs/Dockerfile + tool-inventory-file: "" - flavor: embedded-cpp dockerfile: .devcontainer/cpp/Dockerfile build-embedded-flavor: "true" + tool-inventory-file: "" - flavor: rust dockerfile: .devcontainer/rust/Dockerfile + tool-inventory-file: "" uses: ./.github/workflows/wc-build-push-test.yml secrets: TEST_GITHUB_TOKEN: ${{ secrets.TEST_GITHUB_TOKEN }} @@ -69,3 +73,4 @@ jobs: image-name: ${{ github.repository }}-${{ matrix.flavor }} integration-test-file: test/${{ matrix.flavor }}/integration-tests.bats integration-test-podman: true + tool-inventory-file: ${{ matrix.tool-inventory-file }} diff --git a/.github/workflows/wc-build-push-test.yml b/.github/workflows/wc-build-push-test.yml index 7fa642607..e128336f4 100644 --- a/.github/workflows/wc-build-push-test.yml +++ b/.github/workflows/wc-build-push-test.yml @@ -82,6 +82,15 @@ on: required: false type: string default: '["ubuntu-latest"]' + tool-inventory-file: + description: >- + Path to a JSON allowlist of package names used to derive a curated tool + inventory from the generated SBOM. When empty, no inventory is produced. + + Examples: + '.devcontainer//tool-inventory.json' + required: false + type: string outputs: digest: value: ${{ jobs.build-push.outputs.digest }} @@ -133,6 +142,7 @@ jobs: devcontainer-metadata-file: ${{ inputs.devcontainer-metadata-file }} runner-labels: ${{ inputs.runner-labels }} build-test-runner-labels: ${{ inputs.build-test-runner-labels }} + tool-inventory-file: ${{ inputs.tool-inventory-file }} integration-test-docker: name: ๐Ÿงช diff --git a/.github/workflows/wc-build-push.yml b/.github/workflows/wc-build-push.yml index 8d7258107..7ff04935d 100644 --- a/.github/workflows/wc-build-push.yml +++ b/.github/workflows/wc-build-push.yml @@ -38,6 +38,9 @@ on: runner-labels: required: true type: string + tool-inventory-file: + required: false + type: string outputs: digest: value: ${{ jobs.merge-image.outputs.digest }} @@ -248,6 +251,32 @@ jobs: with: image: ${{ needs.sanitize-image-name.outputs.fully-qualified-image-name }}@${{ steps.inspect-manifest.outputs.digest }} dependency-snapshot: true + output-file: ${{ runner.temp }}/sbom.spdx.json + - name: Derive tool inventory from SBOM + if: ${{ inputs.tool-inventory-file != '' }} + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + sparse-checkout: | + .github/scripts/generate-tool-inventory.sh + ${{ inputs.tool-inventory-file }} + sparse-checkout-cone-mode: false + - name: Generate tool inventory + if: ${{ inputs.tool-inventory-file != '' }} + run: | + set -Eeuo pipefail + bash .github/scripts/generate-tool-inventory.sh \ + "${SBOM}" "${ALLOWLIST}" "${FLAVOR}" | tee tool-inventory.json + env: + SBOM: ${{ runner.temp }}/sbom.spdx.json + ALLOWLIST: ${{ inputs.tool-inventory-file }} + FLAVOR: ${{ needs.sanitize-image-name.outputs.image-basename }} + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + if: ${{ inputs.tool-inventory-file != '' }} + with: + name: tool-inventory-${{ needs.sanitize-image-name.outputs.image-basename }} + path: tool-inventory.json + retention-days: 10 - uses: actions/attest@a1948c3f048ba23858d222213b7c278aabede763 # v4.1.1 with: subject-name: ${{ needs.sanitize-image-name.outputs.fully-qualified-image-name }} From c2e28c28296f28b12c4ed59ceac6a7461a4f9fda Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Thu, 9 Jul 2026 20:29:28 +0000 Subject: [PATCH 12/21] refactor: reduce duplication --- .devcontainer/cpp/Dockerfile | 20 ++--------------- .devcontainer/rust/Dockerfile | 20 ++--------------- .devcontainer/scripts/add-sbom-note.sh | 31 ++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 36 deletions(-) create mode 100644 .devcontainer/scripts/add-sbom-note.sh diff --git a/.devcontainer/cpp/Dockerfile b/.devcontainer/cpp/Dockerfile index cf1f84bda..99c4cbe3a 100644 --- a/.devcontainer/cpp/Dockerfile +++ b/.devcontainer/cpp/Dockerfile @@ -201,24 +201,8 @@ RUN --mount=type=cache,target=/cache,sharing=locked \ # section (systemd ELF package metadata convention, see # https://systemd.io/ELF_PACKAGE_METADATA/) so they are included in the SBOM # generated for the image. -RUN < "${note}" - - local tagged=0 binary - for binary in "$@"; do - if [ -x "${binary}" ]; then - objcopy --add-section .note.package="${note}" \ - --set-section-flags .note.package=noload,readonly "${binary}" - tagged=1 - fi - done - - rm -f "${note}" - [ "${tagged}" -eq 1 ] || { echo "no binaries found to annotate for ${name}" >&2; exit 1; } - } +RUN --mount=type=bind,source=.devcontainer/scripts/add-sbom-note.sh,target=/tmp/add-sbom-note.sh < "${note}" - - local tagged=0 binary - for binary in "$@"; do - if [ -x "${binary}" ]; then - objcopy --add-section .note.package="${note}" \ - --set-section-flags .note.package=noload,readonly "${binary}" - tagged=1 - fi - done - - rm -f "${note}" - [ "${tagged}" -eq 1 ] || { echo "no binaries found to annotate for ${name}" >&2; exit 1; } - } +RUN --mount=type=bind,source=.devcontainer/scripts/add-sbom-note.sh,target=/tmp/add-sbom-note.sh < ... +set -Eeuo pipefail + +name="${1:?tool name required}" +version="${2:?tool version required}" +purl="${3:?tool purl required}" +shift 3 + +note="$(mktemp)" +trap 'rm -f "${note}"' EXIT +printf '{"name":"%s","version":"%s","purl":"%s"}' "${name}" "${version}" "${purl}" > "${note}" + +tagged=0 +for binary in "$@"; do + if [ -x "${binary}" ]; then + objcopy --add-section .note.package="${note}" \ + --set-section-flags .note.package=noload,readonly "${binary}" + tagged=1 + fi +done + +[ "${tagged}" -eq 1 ] || { echo "no binaries found to annotate for ${name}" >&2; exit 1; } From 4f81c6834ef84753fc017fb7b3f42c9b1fb50672 Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Thu, 9 Jul 2026 20:32:28 +0000 Subject: [PATCH 13/21] feat: roll-out to other flavors --- .devcontainer/base/tool-inventory.json | 8 ++++++++ .devcontainer/docs/tool-inventory.json | 5 +++++ .../embedded-cpp/tool-inventory.json | 20 +++++++++++++++++++ .devcontainer/rust/tool-inventory.json | 10 ++++++++++ .github/workflows/build-push-test.yml | 7 ++++--- 5 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 .devcontainer/base/tool-inventory.json create mode 100644 .devcontainer/docs/tool-inventory.json create mode 100644 .devcontainer/embedded-cpp/tool-inventory.json create mode 100644 .devcontainer/rust/tool-inventory.json diff --git a/.devcontainer/base/tool-inventory.json b/.devcontainer/base/tool-inventory.json new file mode 100644 index 000000000..3f0f08e44 --- /dev/null +++ b/.devcontainer/base/tool-inventory.json @@ -0,0 +1,8 @@ +[ + "bats", + "bats-assert", + "bats-support", + "git", + "gnupg2", + "wget" +] diff --git a/.devcontainer/docs/tool-inventory.json b/.devcontainer/docs/tool-inventory.json new file mode 100644 index 000000000..e6f98568f --- /dev/null +++ b/.devcontainer/docs/tool-inventory.json @@ -0,0 +1,5 @@ +[ + "graphviz", + "plantuml", + "sbdl" +] diff --git a/.devcontainer/embedded-cpp/tool-inventory.json b/.devcontainer/embedded-cpp/tool-inventory.json new file mode 100644 index 000000000..cc90c4d81 --- /dev/null +++ b/.devcontainer/embedded-cpp/tool-inventory.json @@ -0,0 +1,20 @@ +[ + "arm-gnu-toolchain", + "ccache", + "clang-22", + "clang-format-22", + "clang-tidy-22", + "clang-tools-22", + "clangd-22", + "cmake", + "conan", + "g++-15", + "gcovr", + "gdb-multiarch", + "include-what-you-use", + "lld-22", + "llvm-22", + "mull-22", + "ninja-build", + "xwin" +] diff --git a/.devcontainer/rust/tool-inventory.json b/.devcontainer/rust/tool-inventory.json new file mode 100644 index 000000000..7740357b2 --- /dev/null +++ b/.devcontainer/rust/tool-inventory.json @@ -0,0 +1,10 @@ +[ + "cargo-binstall", + "cargo-binutils", + "cargo-mutants", + "flip-link", + "g++-15", + "probe-rs-tools", + "rust", + "rustup" +] diff --git a/.github/workflows/build-push-test.yml b/.github/workflows/build-push-test.yml index 0d1e38983..c879a1bb5 100644 --- a/.github/workflows/build-push-test.yml +++ b/.github/workflows/build-push-test.yml @@ -33,6 +33,7 @@ jobs: image-name: ${{ github.repository }}-base integration-test-file: test/base/integration-tests.bats integration-test-podman: true + tool-inventory-file: .devcontainer/base/tool-inventory.json build-push-test-flavors: name: ๐Ÿจ ${{ matrix.flavor }} @@ -46,14 +47,14 @@ jobs: tool-inventory-file: .devcontainer/cpp/tool-inventory.json - flavor: docs dockerfile: .devcontainer/docs/Dockerfile - tool-inventory-file: "" + tool-inventory-file: .devcontainer/docs/tool-inventory.json - flavor: embedded-cpp dockerfile: .devcontainer/cpp/Dockerfile build-embedded-flavor: "true" - tool-inventory-file: "" + tool-inventory-file: .devcontainer/embedded-cpp/tool-inventory.json - flavor: rust dockerfile: .devcontainer/rust/Dockerfile - tool-inventory-file: "" + tool-inventory-file: .devcontainer/rust/tool-inventory.json uses: ./.github/workflows/wc-build-push-test.yml secrets: TEST_GITHUB_TOKEN: ${{ secrets.TEST_GITHUB_TOKEN }} From 52d0b6afa38a38e890a0adad722f095a5cb3d690 Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Thu, 9 Jul 2026 20:37:38 +0000 Subject: [PATCH 14/21] docs: update release notes with tool inventory --- .github/RELEASE_TEMPLATE.md | 41 +++++++++++++++++++++++++++++ .github/workflows/release-build.yml | 7 +++++ 2 files changed, 48 insertions(+) diff --git a/.github/RELEASE_TEMPLATE.md b/.github/RELEASE_TEMPLATE.md index 4f9cbc34e..434dea052 100644 --- a/.github/RELEASE_TEMPLATE.md +++ b/.github/RELEASE_TEMPLATE.md @@ -16,3 +16,44 @@ | amp-devcontainer-embedded-cpp | ghcr.io/philips-software/amp-devcontainer-embedded-cpp:{{ amp-devcontainer-embedded-cpp-version }}@{{ amp-devcontainer-embedded-cpp-sha }} | | amp-devcontainer-rust | ghcr.io/philips-software/amp-devcontainer-rust:{{ amp-devcontainer-rust-version }}@{{ amp-devcontainer-rust-sha }} | +#### :hammer_and_wrench: Included tools + +The tables below list the most relevant tools per flavor and their versions, as +recorded in the image's Software Bill of Materials (SBOM). + +
+amp-devcontainer-base + +{{ amp-devcontainer-base-tools }} + +
+ +
+amp-devcontainer-cpp + +{{ amp-devcontainer-cpp-tools }} + +
+ +
+amp-devcontainer-docs + +{{ amp-devcontainer-docs-tools }} + +
+ +
+amp-devcontainer-embedded-cpp + +{{ amp-devcontainer-embedded-cpp-tools }} + +
+ +
+amp-devcontainer-rust + +{{ amp-devcontainer-rust-tools }} + +
+ + diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 2fe54273d..c3bc00381 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -66,6 +66,7 @@ jobs: permissions: # Please note that this is an overly broad scope, but GitHub does not # currently provide a more fine-grained permission for release modification. + actions: read # is needed by actions/download-artifact to fetch the tool inventory contents: write # is needed to modify a release needs: [build-push-test, apply-release-notes-template] env: @@ -97,12 +98,18 @@ jobs: GH_TOKEN: ${{ github.token }} REPOSITORY_OWNER: ${{ github.repository_owner }} REPOSITORY_NAME: ${{ github.event.repository.name }} + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: tool-inventory-${{ github.event.repository.name }}-${{ matrix.flavor }} + path: tool-inventory - name: Update package details in release run: | set -Eeuo pipefail + TOOLS_TABLE=$(jq -r '"| Tool | Version |\n| --- | --- |\n" + ([.tools[] | "| \(.name) | \(.version) |"] | join("\n"))' tool-inventory/tool-inventory.json) UPDATED_NOTES=$(gh release view "${REF_NAME}" --json body -q '.body') UPDATED_NOTES=${UPDATED_NOTES//"{{ amp-devcontainer-${CONTAINER_FLAVOR}-version }}"/"${REF_NAME}"} UPDATED_NOTES=${UPDATED_NOTES//"{{ amp-devcontainer-${CONTAINER_FLAVOR}-sha }}"/"${DIGEST}"} + UPDATED_NOTES=${UPDATED_NOTES//"{{ amp-devcontainer-${CONTAINER_FLAVOR}-tools }}"/"${TOOLS_TABLE}"} gh release edit "${REF_NAME}" --notes "${UPDATED_NOTES}" env: DIGEST: ${{ steps.inspect-manifest.outputs.digest }} From 4022d604cc9d9a1be724a8c86cc9674f61a78b62 Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Thu, 9 Jul 2026 20:40:31 +0000 Subject: [PATCH 15/21] feat: uploade tool-inventory as release asset --- .github/workflows/release-build.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index c3bc00381..93554f67e 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -115,6 +115,18 @@ jobs: DIGEST: ${{ steps.inspect-manifest.outputs.digest }} GH_REPO: ${{ github.repository }} GH_TOKEN: ${{ github.token }} + - name: Upload tool inventory to release + run: | + set -Eeuo pipefail + FORMATTED_DIGEST=${DIGEST//:/_} + INVENTORY="${REPOSITORY_OWNER}-${REPOSITORY_NAME}-${CONTAINER_FLAVOR}_${FORMATTED_DIGEST}.tool-inventory.json" + cp tool-inventory/tool-inventory.json "${INVENTORY}" + gh release upload "${REF_NAME}" "${INVENTORY}" + env: + DIGEST: ${{ steps.inspect-manifest.outputs.digest }} + GH_TOKEN: ${{ github.token }} + REPOSITORY_OWNER: ${{ github.repository_owner }} + REPOSITORY_NAME: ${{ github.event.repository.name }} publish-devcontainer-templates: name: ๐Ÿ“ Publish templates From 5b6832f58f9c69aac886379b4c66da3205dc8ad4 Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Fri, 10 Jul 2026 12:38:54 +0000 Subject: [PATCH 16/21] chore: add sbom helper to base image --- .devcontainer/base/Dockerfile | 7 ++++++- .devcontainer/{scripts => base}/add-sbom-note.sh | 0 .devcontainer/base/apt-requirements.json | 1 + .devcontainer/cpp/Dockerfile | 14 ++++++-------- .devcontainer/rust/Dockerfile | 16 +++++++--------- 5 files changed, 20 insertions(+), 18 deletions(-) rename .devcontainer/{scripts => base}/add-sbom-note.sh (100%) diff --git a/.devcontainer/base/Dockerfile b/.devcontainer/base/Dockerfile index af4966fc2..0baf0e5c6 100644 --- a/.devcontainer/base/Dockerfile +++ b/.devcontainer/base/Dockerfile @@ -31,7 +31,8 @@ HEALTHCHECK NONE SHELL ["/bin/bash", "-Eeuo", "pipefail", "-c"] # hadolint ignore=DL3008 -RUN --mount=type=bind,source=.devcontainer/base/apt-requirements.json,target=/tmp/apt-requirements.json \ +RUN --mount=type=bind,source=.devcontainer/base/add-sbom-note.sh,target=/tmp/add-sbom-note.sh \ + --mount=type=bind,source=.devcontainer/base/apt-requirements.json,target=/tmp/apt-requirements.json \ --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt,sharing=locked \ --mount=type=cache,target=/var/log,sharing=locked \ @@ -55,6 +56,10 @@ RUN --mount=type=bind,source=.devcontainer/base/apt-requirements.json,target=/tm mkdir -p /usr/local/lib/bats-core cp /src/bats-core/package.json /usr/local/lib/bats-core/package.json + # Install the SBOM annotation helper so downstream flavors can annotate tools + # that Syft cannot detect on its own (see .devcontainer/base/add-sbom-note.sh) + install -m 0755 /tmp/add-sbom-note.sh /usr/local/bin/add-sbom-note + # Generate locale sed -i '/C.UTF-8/s/^# //' /etc/locale.gen locale-gen diff --git a/.devcontainer/scripts/add-sbom-note.sh b/.devcontainer/base/add-sbom-note.sh similarity index 100% rename from .devcontainer/scripts/add-sbom-note.sh rename to .devcontainer/base/add-sbom-note.sh diff --git a/.devcontainer/base/apt-requirements.json b/.devcontainer/base/apt-requirements.json index 8b9457089..795a91059 100644 --- a/.devcontainer/base/apt-requirements.json +++ b/.devcontainer/base/apt-requirements.json @@ -1,5 +1,6 @@ { "bash-completion": "1:2.16.0-8build1", + "binutils": "2.46-3ubuntu2", "ca-certificates": "20260601~26.04.1", "git": "1:2.53.0-1ubuntu1", "gnupg2": "2.4.8-4ubuntu3", diff --git a/.devcontainer/cpp/Dockerfile b/.devcontainer/cpp/Dockerfile index 99c4cbe3a..67b4f364a 100644 --- a/.devcontainer/cpp/Dockerfile +++ b/.devcontainer/cpp/Dockerfile @@ -200,19 +200,17 @@ RUN --mount=type=cache,target=/cache,sharing=locked \ # Annotate tools that Syft cannot detect on its own with a `.note.package` ELF # section (systemd ELF package metadata convention, see # https://systemd.io/ELF_PACKAGE_METADATA/) so they are included in the SBOM -# generated for the image. -RUN --mount=type=bind,source=.devcontainer/scripts/add-sbom-note.sh,target=/tmp/add-sbom-note.sh < Date: Fri, 10 Jul 2026 13:30:28 +0000 Subject: [PATCH 17/21] chore: add tests for add-sbom-note --- test/base/integration-tests.bats | 46 ++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/test/base/integration-tests.bats b/test/base/integration-tests.bats index 39b82d41f..f9ed43bbc 100644 --- a/test/base/integration-tests.bats +++ b/test/base/integration-tests.bats @@ -12,3 +12,49 @@ setup() { assert_success assert_output --partial "OK" } + +# bats test_tags=Sbom +@test "add-sbom-note tool is installed and executable" { + run command -v add-sbom-note + assert_success + + assert [ -x /usr/local/bin/add-sbom-note ] +} + +# bats test_tags=Sbom +@test "add-sbom-note fails when required arguments are missing" { + run add-sbom-note only-a-name + assert_failure +} + +# bats test_tags=Sbom +@test "add-sbom-note fails when none of the given binaries exist" { + run add-sbom-note example 1.2.3 pkg:generic/example@1.2.3 /nonexistent/binary + + assert_failure + assert_output --partial "no binaries found to annotate for example" +} + +# bats test_tags=Sbom +@test "add-sbom-note annotates an ELF binary with a .note.package section" { + BINARY=$(mktemp) + cp "$(command -v bash)" "$BINARY" + chmod +x "$BINARY" + # Ubuntu ships some binaries with a pre-existing .note.package section; start + # from a clean binary so the test exercises add-sbom-note's own annotation. + objcopy --remove-section .note.package "$BINARY" 2> /dev/null || true + + run add-sbom-note example 1.2.3 pkg:generic/example@1.2.3 "$BINARY" + assert_success + + SECTION=$(mktemp) + objcopy --dump-section .note.package="$SECTION" "$BINARY" /dev/null + + run cat "$SECTION" + assert_success + assert_output --partial '"name":"example"' + assert_output --partial '"version":"1.2.3"' + assert_output --partial '"purl":"pkg:generic/example@1.2.3"' + + rm -f "$BINARY" "$SECTION" +} From 2dfeff272bd2c364c938a70a573a4835ac28e41c Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Sat, 11 Jul 2026 06:16:11 +0000 Subject: [PATCH 18/21] chore: process review comments --- .devcontainer/base/add-sbom-note.sh | 2 +- .devcontainer/cpp/Dockerfile | 1 - .github/workflows/release-build.yml | 4 ++-- .github/workflows/wc-build-push.yml | 10 +++++----- test/base/integration-tests.bats | 6 +++--- 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/.devcontainer/base/add-sbom-note.sh b/.devcontainer/base/add-sbom-note.sh index 803e4a1f0..602020c3a 100644 --- a/.devcontainer/base/add-sbom-note.sh +++ b/.devcontainer/base/add-sbom-note.sh @@ -17,7 +17,7 @@ shift 3 note="$(mktemp)" trap 'rm -f "${note}"' EXIT -printf '{"name":"%s","version":"%s","purl":"%s"}' "${name}" "${version}" "${purl}" > "${note}" +jq -n --arg name "${name}" --arg version "${version}" --arg purl "${purl}" '{name: $name, version: $version, purl: $purl}' > "${note}" tagged=0 for binary in "$@"; do diff --git a/.devcontainer/cpp/Dockerfile b/.devcontainer/cpp/Dockerfile index 67b4f364a..09ff22e28 100644 --- a/.devcontainer/cpp/Dockerfile +++ b/.devcontainer/cpp/Dockerfile @@ -9,7 +9,6 @@ ARG CCACHE_VERSION=4.13.1 ARG XWIN_VERSION=0.8.0 # Whether or not to build the embedded flavor of this container ARG BUILD_EMBEDDED_FLAVOR=false -# Version of the Arm GNU toolchain used for the embedded flavor ARG ARM_GNU_TOOLCHAIN_VERSION=15.2.rel1 # Downloader stage for AMD64 architecture diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 93554f67e..406300a19 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -91,7 +91,7 @@ jobs: set -Eeuo pipefail FORMATTED_DIGEST=${DIGEST//:/_} gh attestation verify --repo "${GH_REPO}" "oci://${REGISTRY}/${GH_REPO}-${CONTAINER_FLAVOR}@${DIGEST}" --format json --jq '.[] | .attestation.bundle.dsseEnvelope | select(.payloadType == "application/vnd.in-toto+json").payload' | base64 -d | jq . > "${REPOSITORY_OWNER}-${REPOSITORY_NAME}-${CONTAINER_FLAVOR}_${FORMATTED_DIGEST}.intoto.jsonl" - gh release upload "${REF_NAME}" ./*.intoto.jsonl + gh release upload --clobber "${REF_NAME}" ./*.intoto.jsonl env: DIGEST: ${{ steps.inspect-manifest.outputs.digest }} GH_REPO: ${{ github.repository }} @@ -121,7 +121,7 @@ jobs: FORMATTED_DIGEST=${DIGEST//:/_} INVENTORY="${REPOSITORY_OWNER}-${REPOSITORY_NAME}-${CONTAINER_FLAVOR}_${FORMATTED_DIGEST}.tool-inventory.json" cp tool-inventory/tool-inventory.json "${INVENTORY}" - gh release upload "${REF_NAME}" "${INVENTORY}" + gh release upload --clobber "${REF_NAME}" "${INVENTORY}" env: DIGEST: ${{ steps.inspect-manifest.outputs.digest }} GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/wc-build-push.yml b/.github/workflows/wc-build-push.yml index 7ff04935d..babf43225 100644 --- a/.github/workflows/wc-build-push.yml +++ b/.github/workflows/wc-build-push.yml @@ -15,7 +15,7 @@ on: workflow_call: inputs: build-args: - required: false + required: true type: string build-test-runner-labels: required: true @@ -39,7 +39,7 @@ on: required: true type: string tool-inventory-file: - required: false + required: true type: string outputs: digest: @@ -253,7 +253,7 @@ jobs: dependency-snapshot: true output-file: ${{ runner.temp }}/sbom.spdx.json - name: Derive tool inventory from SBOM - if: ${{ inputs.tool-inventory-file != '' }} + if: ${{ inputs.tool-inventory-file }} uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false @@ -262,7 +262,7 @@ jobs: ${{ inputs.tool-inventory-file }} sparse-checkout-cone-mode: false - name: Generate tool inventory - if: ${{ inputs.tool-inventory-file != '' }} + if: ${{ inputs.tool-inventory-file }} run: | set -Eeuo pipefail bash .github/scripts/generate-tool-inventory.sh \ @@ -272,7 +272,7 @@ jobs: ALLOWLIST: ${{ inputs.tool-inventory-file }} FLAVOR: ${{ needs.sanitize-image-name.outputs.image-basename }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - if: ${{ inputs.tool-inventory-file != '' }} + if: ${{ inputs.tool-inventory-file }} with: name: tool-inventory-${{ needs.sanitize-image-name.outputs.image-basename }} path: tool-inventory.json diff --git a/test/base/integration-tests.bats b/test/base/integration-tests.bats index f9ed43bbc..1442ce727 100644 --- a/test/base/integration-tests.bats +++ b/test/base/integration-tests.bats @@ -52,9 +52,9 @@ setup() { run cat "$SECTION" assert_success - assert_output --partial '"name":"example"' - assert_output --partial '"version":"1.2.3"' - assert_output --partial '"purl":"pkg:generic/example@1.2.3"' + assert_output --partial '"name": "example"' + assert_output --partial '"version": "1.2.3"' + assert_output --partial '"purl": "pkg:generic/example@1.2.3"' rm -f "$BINARY" "$SECTION" } From 7748c80fca11b4f5657029dec30afa8394f79e0b Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Sun, 12 Jul 2026 11:02:12 +0000 Subject: [PATCH 19/21] docs: add a manual PR sign-off for image size --- .github/PULL_REQUEST_TEMPLATE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index b023d79d1..ff254825a 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -14,3 +14,4 @@ - [ ] I have added tests for new behavior, and have not broken any existing tests - [ ] I have added or updated relevant documentation - [ ] I have verified that all added components are accounted for in the SBOM +- [ ] I understand the image size delta and agree the functionality justifies it From 2a6b72df1a9a43dee835f31dfb65bf42aae039c9 Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Sun, 12 Jul 2026 11:14:35 +0000 Subject: [PATCH 20/21] chore: add sbom annotations in the same layer as tool installation --- .devcontainer/cpp/Dockerfile | 84 ++++++++++++++++------------------- .devcontainer/rust/Dockerfile | 44 +++++++++--------- 2 files changed, 61 insertions(+), 67 deletions(-) diff --git a/.devcontainer/cpp/Dockerfile b/.devcontainer/cpp/Dockerfile index 09ff22e28..1124e2775 100644 --- a/.devcontainer/cpp/Dockerfile +++ b/.devcontainer/cpp/Dockerfile @@ -116,6 +116,7 @@ ENV CCACHE_DIR=/cache/.ccache \ PATH="$PATH:/usr/lib/llvm-${CLANG_VERSION}/bin:/opt/gcc-arm-none-eabi/bin" \ PYTHONPYCACHEPREFIX=/cache/.python +# hadolint ignore=DL3008 RUN --mount=type=bind,source=.devcontainer/cpp/apt-requirements-base.json,target=/tmp/apt-requirements-base.json \ --mount=type=bind,source=.devcontainer/cpp/apt-requirements-clang.json,target=/tmp/apt-requirements-clang.json \ --mount=type=bind,source=.devcontainer/cpp/requirements.txt,target=/tmp/requirements.txt \ @@ -153,54 +154,43 @@ RUN --mount=type=bind,source=.devcontainer/cpp/apt-requirements-base.json,target # Install arm-gcc toolchain cp -a /src/arm-gnu-toolchain-*-arm-none-eabi /opt/gcc-arm-none-eabi fi -EOF -# Install include-what-you-use (iwyu) from source -# hadolint ignore=DL3008 -RUN --mount=type=cache,target=/cache,sharing=locked \ - --mount=type=cache,target=/var/cache/apt,sharing=locked \ - --mount=type=cache,target=/var/lib/apt,sharing=locked \ - apt-get update && apt-get install -y --no-install-recommends libclang-${CLANG_VERSION}-dev llvm-${CLANG_VERSION}-dev \ - && wget --no-hsts -qO - https://github.com/include-what-you-use/include-what-you-use/archive/refs/tags/${INCLUDE_WHAT_YOU_USE_VERSION}.tar.gz | tar xz -C /tmp \ - && CC=clang CXX=clang++ cmake -S /tmp/include-what-you-use-${INCLUDE_WHAT_YOU_USE_VERSION} -B /tmp/include-what-you-use-${INCLUDE_WHAT_YOU_USE_VERSION}/build \ - && cmake --build /tmp/include-what-you-use-${INCLUDE_WHAT_YOU_USE_VERSION}/build --target install \ - && rm -rf /tmp/include-what-you-use-${INCLUDE_WHAT_YOU_USE_VERSION} \ - && apt-get purge -y libclang-${CLANG_VERSION}-dev llvm-${CLANG_VERSION}-dev \ - && apt-get autoremove -y \ - && apt-get clean - -# Update all tool alternatives to the correct version -RUN --mount=type=cache,target=/var/log,sharing=locked \ - update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-15 10 \ + # Install include-what-you-use (iwyu) from source + apt-get update && apt-get install -y --no-install-recommends libclang-${CLANG_VERSION}-dev llvm-${CLANG_VERSION}-dev + wget --no-hsts -qO - https://github.com/include-what-you-use/include-what-you-use/archive/refs/tags/${INCLUDE_WHAT_YOU_USE_VERSION}.tar.gz | tar xz -C /tmp + CC=clang CXX=clang++ cmake -S /tmp/include-what-you-use-${INCLUDE_WHAT_YOU_USE_VERSION} -B /tmp/include-what-you-use-${INCLUDE_WHAT_YOU_USE_VERSION}/build + cmake --build /tmp/include-what-you-use-${INCLUDE_WHAT_YOU_USE_VERSION}/build --target install + rm -rf /tmp/include-what-you-use-${INCLUDE_WHAT_YOU_USE_VERSION} + apt-get purge -y libclang-${CLANG_VERSION}-dev llvm-${CLANG_VERSION}-dev + apt-get autoremove -y + apt-get clean + + # Update all tool alternatives to the correct version + update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-15 10 \ --slave /usr/bin/g++ g++ /usr/bin/g++-15 \ - --slave /usr/bin/gcov gcov /usr/bin/gcov-15 \ - && update-alternatives --install /usr/bin/cc cc /usr/bin/gcc-15 10 \ - --slave /usr/bin/c++ c++ /usr/bin/g++-15 \ - && update-alternatives --install /usr/bin/iwyu iwyu /usr/local/bin/include-what-you-use 10 \ - && update-alternatives --install /usr/bin/mull-runner mull-runner /usr/bin/mull-runner-${CLANG_VERSION} 10 \ + --slave /usr/bin/gcov gcov /usr/bin/gcov-15 + update-alternatives --install /usr/bin/cc cc /usr/bin/gcc-15 10 \ + --slave /usr/bin/c++ c++ /usr/bin/g++-15 + update-alternatives --install /usr/bin/iwyu iwyu /usr/local/bin/include-what-you-use 10 + update-alternatives --install /usr/bin/mull-runner mull-runner /usr/bin/mull-runner-${CLANG_VERSION} 10 \ --slave /usr/bin/mull-reporter mull-reporter /usr/bin/mull-reporter-${CLANG_VERSION} \ - --slave /usr/lib/mull-ir-frontend mull-ir-frontend /usr/lib/mull-ir-frontend-${CLANG_VERSION} \ - && update-alternatives --install /usr/bin/python python /usr/bin/python3 10 \ - && mkdir /root/.amp - -# Set up package managers CPM and Conan -# - Install CPM.cmake to the CMake module path -# - Configure a default profile for Conan and set the CMake generator to Ninja -# -# NOTE: CPM.cmake is intentionally absent from the generated SBOM. It is a single -# CMake module (not an ELF binary), so it cannot carry a `.note.package` ELF -# section and Syft has no cataloger for standalone `.cmake` files. Its version is -# pinned and tracked via the CPM_VERSION build argument above. -RUN --mount=type=cache,target=/cache,sharing=locked \ - wget --no-hsts -qP /usr/local/lib/python*/dist-packages/cmake/data/share/cmake-*/Modules/ https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_VERSION}/CPM.cmake \ - && conan profile detect \ - && echo -e "\n[conf]\ntools.cmake.cmaketoolchain:generator=Ninja" >> "$(conan profile path default)" - -# Annotate tools that Syft cannot detect on its own with a `.note.package` ELF -# section (systemd ELF package metadata convention, see -# https://systemd.io/ELF_PACKAGE_METADATA/) so they are included in the SBOM -# generated for the image. `add-sbom-note` is provided by the base image. -RUN <> "$(conan profile path default)" + add-sbom-note ccache "${CCACHE_VERSION}" "pkg:generic/ccache@${CCACHE_VERSION}" \ /usr/local/bin/ccache add-sbom-note xwin "${XWIN_VERSION}" "pkg:cargo/xwin@${XWIN_VERSION}" \ @@ -208,6 +198,10 @@ RUN < Date: Sun, 12 Jul 2026 11:19:21 +0000 Subject: [PATCH 21/21] chore: remove missed continuation --- .devcontainer/rust/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/rust/Dockerfile b/.devcontainer/rust/Dockerfile index 19b15239b..f1565b325 100644 --- a/.devcontainer/rust/Dockerfile +++ b/.devcontainer/rust/Dockerfile @@ -40,7 +40,7 @@ RUN --mount=type=bind,source=.devcontainer/rust/apt-requirements.json,target=/tm rustup target add thumbv7em-none-eabi rustup target add thumbv7em-none-eabihf - wget -qO - "https://github.com/cargo-bins/cargo-binstall/releases/download/v${CARGO_BINSTALL_VERSION}/cargo-binstall-$(uname -m)-unknown-linux-gnu.tgz" | tar xz -C "/usr/bin" \ + wget -qO - "https://github.com/cargo-bins/cargo-binstall/releases/download/v${CARGO_BINSTALL_VERSION}/cargo-binstall-$(uname -m)-unknown-linux-gnu.tgz" | tar xz -C "/usr/bin" cargo-binstall -y --locked "cargo-binutils@${CARGO_BINUTILS_VERSION}" \ "cargo-mutants@${CARGO_MUTANTS_VERSION}" \ "flip-link@${FLIP_LINK_VERSION}" \