diff --git a/.devcontainer/cuda12.9-conda/devcontainer.json b/.devcontainer/cuda12.9-conda/devcontainer.json index de9a4d769..8522b6cce 100644 --- a/.devcontainer/cuda12.9-conda/devcontainer.json +++ b/.devcontainer/cuda12.9-conda/devcontainer.json @@ -40,12 +40,14 @@ "pruneStaticLibs": true }, "./features/src/utils": {}, - "./features/src/rapids-build-utils": {} + "./features/src/rapids-build-utils": {}, + "./features/src/sbom": {} }, "overrideFeatureInstallOrder": [ "./features/src/cuda", "./features/src/utils", - "./features/src/rapids-build-utils" + "./features/src/rapids-build-utils", + "./features/src/sbom" ], "initializeCommand": ["/bin/bash", "-c", "mkdir -m 0755 -p ${localWorkspaceFolder}/../.{aws,cache,config,conda/pkgs,conda/${localWorkspaceFolderBasename}-cuda12.9-envs,log/devcontainer-utils} ${localWorkspaceFolder}/../{rmm,dask-cuda,kvikio,ucxx,cudf,rapidsmpf,raft,cuvs,cumlprims_mg,cuml,cugraph,cugraph-gnn,nx-cugraph}"], "postAttachCommand": ["/bin/bash", "-c", "if [ ${CODESPACES:-false} = 'true' ]; then . devcontainer-utils-post-attach-command; fi"], diff --git a/.devcontainer/cuda12.9-pip/devcontainer.json b/.devcontainer/cuda12.9-pip/devcontainer.json index 7d437f507..cea41314f 100644 --- a/.devcontainer/cuda12.9-pip/devcontainer.json +++ b/.devcontainer/cuda12.9-pip/devcontainer.json @@ -28,12 +28,14 @@ "installProfilers": true }, "./features/src/utils": {}, - "./features/src/rapids-build-utils": {} + "./features/src/rapids-build-utils": {}, + "./features/src/sbom": {} }, "overrideFeatureInstallOrder": [ "./features/src/cuda", "./features/src/utils", - "./features/src/rapids-build-utils" + "./features/src/rapids-build-utils", + "./features/src/sbom" ], "initializeCommand": ["/bin/bash", "-c", "mkdir -m 0755 -p ${localWorkspaceFolder}/../.{aws,cache,config/pip,local/share/${localWorkspaceFolderBasename}-cuda12.9-venvs,log/devcontainer-utils} ${localWorkspaceFolder}/../{rmm,dask-cuda,kvikio,ucxx,cudf,rapidsmpf,raft,cuvs,cumlprims_mg,cuml,cugraph,cugraph-gnn,nx-cugraph}"], "postAttachCommand": ["/bin/bash", "-c", "if [ ${CODESPACES:-false} = 'true' ]; then . devcontainer-utils-post-attach-command; fi"], diff --git a/.devcontainer/cuda13.0-conda/devcontainer.json b/.devcontainer/cuda13.0-conda/devcontainer.json index 4197a7d10..3e02184cb 100644 --- a/.devcontainer/cuda13.0-conda/devcontainer.json +++ b/.devcontainer/cuda13.0-conda/devcontainer.json @@ -40,12 +40,14 @@ "pruneStaticLibs": true }, "./features/src/utils": {}, - "./features/src/rapids-build-utils": {} + "./features/src/rapids-build-utils": {}, + "./features/src/sbom": {} }, "overrideFeatureInstallOrder": [ "./features/src/cuda", "./features/src/utils", - "./features/src/rapids-build-utils" + "./features/src/rapids-build-utils", + "./features/src/sbom" ], "initializeCommand": ["/bin/bash", "-c", "mkdir -m 0755 -p ${localWorkspaceFolder}/../.{aws,cache,config,conda/pkgs,conda/${localWorkspaceFolderBasename}-cuda13.0-envs,log/devcontainer-utils} ${localWorkspaceFolder}/../{rmm,dask-cuda,kvikio,ucxx,cudf,rapidsmpf,raft,cuvs,cumlprims_mg,cuml,cugraph,cugraph-gnn,nx-cugraph}"], "postAttachCommand": ["/bin/bash", "-c", "if [ ${CODESPACES:-false} = 'true' ]; then . devcontainer-utils-post-attach-command; fi"], diff --git a/.devcontainer/cuda13.0-pip/devcontainer.json b/.devcontainer/cuda13.0-pip/devcontainer.json index 495cbca08..78dc2a979 100644 --- a/.devcontainer/cuda13.0-pip/devcontainer.json +++ b/.devcontainer/cuda13.0-pip/devcontainer.json @@ -28,12 +28,14 @@ "installProfilers": true }, "./features/src/utils": {}, - "./features/src/rapids-build-utils": {} + "./features/src/rapids-build-utils": {}, + "./features/src/sbom": {} }, "overrideFeatureInstallOrder": [ "./features/src/cuda", "./features/src/utils", - "./features/src/rapids-build-utils" + "./features/src/rapids-build-utils", + "./features/src/sbom" ], "initializeCommand": ["/bin/bash", "-c", "mkdir -m 0755 -p ${localWorkspaceFolder}/../.{aws,cache,config/pip,local/share/${localWorkspaceFolderBasename}-cuda13.0-venvs,log/devcontainer-utils} ${localWorkspaceFolder}/../{rmm,dask-cuda,kvikio,ucxx,cudf,rapidsmpf,raft,cuvs,cumlprims_mg,cuml,cugraph,cugraph-gnn,nx-cugraph}"], "postAttachCommand": ["/bin/bash", "-c", "if [ ${CODESPACES:-false} = 'true' ]; then . devcontainer-utils-post-attach-command; fi"], diff --git a/.github/actions/build-linux-image/action.yml b/.github/actions/build-linux-image/action.yml index 5ed505286..910d6578f 100644 --- a/.github/actions/build-linux-image/action.yml +++ b/.github/actions/build-linux-image/action.yml @@ -8,6 +8,7 @@ inputs: repo: {type: string, required: true, description: Image repo} push: {type: string, required: true, description: Whether to push the image} retries: {type: string, default: '10', description: Number of times to retry the container build} + syft_ver: {type: string, required: false, description: Syft version for SBOM feature (default 'latest')} outputs: hash_amd64: @@ -35,6 +36,7 @@ runs: repo: "${{ inputs.repo }}" retries: "${{ inputs.retries }}" tag: "${{ inputs.tag }}" + syft_ver: "${{ inputs.syft_ver }}" run: | set -euo pipefail; @@ -52,6 +54,7 @@ runs: --no-cache \ --image-name "${repo,,}:${tag}" \ --workspace-folder "$(realpath -m ./image)" \ + ${syft_ver:+--build-arg SYFT_VER=${syft_ver}} \ "${outputs[@]}" 2>&1 \ | tee "${{ runner.temp }}/${arch}.log" 1>&2 do diff --git a/.github/actions/build-windows-image/action.yml b/.github/actions/build-windows-image/action.yml index a22088bf2..2af2a2e0b 100644 --- a/.github/actions/build-windows-image/action.yml +++ b/.github/actions/build-windows-image/action.yml @@ -32,6 +32,11 @@ inputs: required: false default: process description: Windows isolation mode to target (hyperv or process isolation) + syft_ver: + type: string + required: false + default: latest + description: Syft version for SBOM generation runs: using: composite @@ -45,4 +50,5 @@ runs: -cudaVersion ${{ inputs.cuda }} ` -repoVersion ${{ inputs.version }} ` -isolation ${{ inputs.isolation }} ` - -edition windows${{ inputs.edition }} + -edition windows${{ inputs.edition }} ` + -syftVer ${{ inputs.syft_ver }} diff --git a/.github/actions/devcontainer-json/action.sh b/.github/actions/devcontainer-json/action.sh index b22f71b89..edbee3cca 100755 --- a/.github/actions/devcontainer-json/action.sh +++ b/.github/actions/devcontainer-json/action.sh @@ -6,6 +6,7 @@ cd "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/../../../"; os="${1:-"ubuntu:22.04"}"; features="${2:-"[]"}"; container_env="${3:-"null"}"; +syft_ver="${4:-"latest"}"; VERSION="$(git describe --abbrev=0 --tags | sed 's/[a-zA-Z]//g' | cut -d '.' -f -2)"; tag="$(node -p "$(cat <> "$GITHUB_OUTPUT" \ 4> image/.devcontainer/devcontainer.json.out ; diff --git a/.github/workflows/build-test-and-push-linux-image.yml b/.github/workflows/build-test-and-push-linux-image.yml index 5218882bc..689d58ebc 100644 --- a/.github/workflows/build-test-and-push-linux-image.yml +++ b/.github/workflows/build-test-and-push-linux-image.yml @@ -24,6 +24,11 @@ on: type: string required: false description: JSON map of default envvars to build into the devcontainer + syft_ver: + type: string + required: false + default: latest + description: Syft version for SBOM generation jobs: @@ -55,6 +60,7 @@ jobs: os: "${{ inputs.os }}" features: "${{ inputs.features }}" container_env: "${{ inputs.container_env }}" + syft_ver: "${{ inputs.syft_ver }}" - name: Login to Docker Hub uses: docker/login-action@v3 @@ -70,6 +76,7 @@ jobs: repo: "${{ inputs.repo }}" push: "${{ inputs.push }}" tag: "${{ steps.json.outputs.tag }}" + syft_ver: "${{ inputs.syft_ver }}" push-to-dockerhub: if: inputs.push == 'true' diff --git a/.github/workflows/build-test-and-push-windows-image.yml b/.github/workflows/build-test-and-push-windows-image.yml index 0d947ac3e..ac31ef53f 100644 --- a/.github/workflows/build-test-and-push-windows-image.yml +++ b/.github/workflows/build-test-and-push-windows-image.yml @@ -24,6 +24,11 @@ on: type: string required: false description: JSON map of default envvars to build into the devcontainer + syft_ver: + type: string + required: false + default: latest + description: Syft version for SBOM generation jobs: @@ -75,6 +80,7 @@ jobs: repo: "${{ steps.info.outputs.repo }}" version: "${{ steps.info.outputs.version }}" edition: "${{ matrix.edition }}" + syft_ver: "${{ inputs.syft_ver }}" - name: Test ${{ steps.info.outputs.tag }} uses: ./.github/actions/test-windows-image diff --git a/.github/workflows/release-linux.yml b/.github/workflows/release-linux.yml index a8daee82e..d105e2582 100644 --- a/.github/workflows/release-linux.yml +++ b/.github/workflows/release-linux.yml @@ -6,6 +6,11 @@ concurrency: on: workflow_dispatch: + inputs: + syft_ver: + description: "Syft version for SBOM generation" + required: false + default: latest jobs: @@ -67,3 +72,4 @@ jobs: features: "${{ toJSON(matrix.features) }}" container_env: "${{ toJSON(matrix.env) }}" repo: "${{ vars.DOCKERHUB_REPOSITORY || github.repository }}" + syft_ver: "${{ inputs.syft_ver || 'latest' }}" diff --git a/.github/workflows/release-windows.yml b/.github/workflows/release-windows.yml index 3c29f7561..ff1a8ac40 100644 --- a/.github/workflows/release-windows.yml +++ b/.github/workflows/release-windows.yml @@ -6,6 +6,11 @@ concurrency: on: workflow_dispatch: + inputs: + syft_ver: + description: "Syft version for SBOM generation" + required: false + default: latest jobs: @@ -42,3 +47,4 @@ jobs: features: "${{ toJSON(matrix.features) }}" container_env: "${{ toJSON(matrix.env) }}" repo: "${{ vars.DOCKERHUB_REPOSITORY || github.repository }}" + syft_ver: "${{ inputs.syft_ver || 'latest' }}" \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 61495ae5b..cb2fe1728 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,6 +6,11 @@ concurrency: on: workflow_dispatch: + inputs: + syft_ver: + description: "Syft version for SBOM generation" + required: false + default: latest push: branches: - "branch-[0-9][0-9].[0-9][0-9]" @@ -73,6 +78,7 @@ jobs: features: "${{ toJSON(matrix.features) }}" container_env: "${{ toJSON(matrix.env) }}" repo: "${{ vars.DOCKERHUB_REPOSITORY || github.repository }}" + syft_ver: "${{ inputs.syft_ver || 'latest' }}" release-windows: if: needs.image-matrix.outputs.windows != '{"include":[]}' @@ -89,6 +95,7 @@ jobs: features: "${{ toJSON(matrix.features) }}" container_env: "${{ toJSON(matrix.env) }}" repo: "${{ vars.DOCKERHUB_REPOSITORY || github.repository }}" + syft_ver: "${{ inputs.syft_ver || 'latest' }}" release-features: if: needs.features-matrix.outputs.features != '[]' || needs.features-matrix.outputs.scenarios != '[]' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7e0beedac..7db822415 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -175,6 +175,7 @@ jobs: features: "${{ toJSON(matrix.features) }}" container_env: "${{ toJSON(matrix.env) }}" repo: "${{ vars.DOCKERHUB_REPOSITORY || github.repository }}" + syft_ver: "latest" build-and-test-windows-images: if: needs.image-matrix.outputs.windows != '{"include":[]}' @@ -191,3 +192,4 @@ jobs: features: "${{ toJSON(matrix.features) }}" container_env: "${{ toJSON(matrix.env) }}" repo: "${{ vars.DOCKERHUB_REPOSITORY || github.repository }}" + syft_ver: "latest" diff --git a/features/src/sbom/common/etc/bash.bash_env b/features/src/sbom/common/etc/bash.bash_env new file mode 100755 index 000000000..0005efebc --- /dev/null +++ b/features/src/sbom/common/etc/bash.bash_env @@ -0,0 +1,37 @@ +#! /usr/bin/env bash + +# Respect --noprofile and --norc +if [[ ! $(ps -o args= -p $$) =~ (--noprofile) ]]; then + # Otherwise, initialize non-login shells like login shells + if ! shopt -q login_shell; then + if [ -f /etc/profile ]; then + . /etc/profile + fi + for x in "$HOME"/.{bash_profile,bash_login,profile}; do + if [ -f "$x" ]; then + . "$x" + break + fi + done + fi +elif [[ ! $(ps -o args= -p $$) =~ (--norc|--rcfile|--init-file) ]]; then + if [ -f /etc/bash.bashrc ]; then + . /etc/bash.bashrc + fi + for x in "$HOME"/.bashrc; do + if [ -f "$x" ]; then + . "$x" + break + fi + done +fi + +export BASH_ENV=/etc/bash.bash_env + +if [ -n "${BASH_ENV_ETC_PROFILE:-}" ]; then + if [ -f "$BASH_ENV_ETC_PROFILE" ] \ + && [ "$BASH_ENV_ETC_PROFILE" != "$BASH_ENV" ]; \ + then + . "$BASH_ENV_ETC_PROFILE" + fi +fi diff --git a/features/src/sbom/common/etc/skel/.config/clangd/config.yaml b/features/src/sbom/common/etc/skel/.config/clangd/config.yaml new file mode 100644 index 000000000..52c36cb9d --- /dev/null +++ b/features/src/sbom/common/etc/skel/.config/clangd/config.yaml @@ -0,0 +1,63 @@ +# https://clangd.llvm.org/config + +# Apply a config conditionally to all C files +If: + PathMatch: .*\.(c|h)$ + +--- + +# Apply a config conditionally to all C++ files +If: + PathMatch: .*\.(c|h)pp + +--- + +# Apply a config conditionally to all CUDA files +If: + PathMatch: .*\.cuh? +CompileFlags: + Add: + - "-x" + - "cuda" + # No error on unknown CUDA versions + - "-Wno-unknown-cuda-version" + # Allow variadic CUDA functions + - "-Xclang=-fcuda-allow-variadic-functions" + +--- + +# Tweak the clangd parse settings for all files +CompileFlags: + Add: + # report all errors + - "-ferror-limit=0" + - "-fmacro-backtrace-limit=0" + - "-ftemplate-backtrace-limit=0" + # Skip the CUDA version check + - "--no-cuda-version-check" + Remove: + # remove gcc's -fcoroutines + - -fcoroutines + # remove nvc++ flags unknown to clang + - "-gpu=*" + - "-stdpar*" + # remove nvcc flags unknown to clang + - "-arch*" + - "-gencode*" + - "--generate-code*" + - "-ccbin*" + - "-t=*" + - "--threads*" + - "-Xptxas*" + - "-Xcudafe*" + - "-Xfatbin*" + - "-Xcompiler*" + - "--diag-suppress*" + - "--diag_suppress*" + - "--compiler-options*" + - "--expt-extended-lambda" + - "--expt-relaxed-constexpr" + - "-forward-unknown-to-host-compiler" + - "-Werror=cross-execution-space-call" + - "--compress-mode*" + - "-static-global-template-stub*" diff --git a/features/src/sbom/common/find-version-from-git-tags.sh b/features/src/sbom/common/find-version-from-git-tags.sh new file mode 100755 index 000000000..b959c1310 --- /dev/null +++ b/features/src/sbom/common/find-version-from-git-tags.sh @@ -0,0 +1,129 @@ +#! /usr/bin/env bash + +# Assign variable one scope above the caller +# Usage: local "$1" && _upvar $1 "value(s)" +# Param: $1 Variable name to assign value to +# Param: $* Value(s) to assign. If multiple values, an array is +# assigned, otherwise a single value is assigned. +# See: http://fvue.nl/wiki/Bash:_Passing_variables_by_reference +_upvar() { + if unset -v "$1"; then + if (( $# == 2 )); then + eval $1=\"\$2\"; + else + eval $1=\(\"\${@:2}\"\); + fi; + fi +} + +declare -Ag _find_version_from_git_tags_cache=(); + +# Figure out correct version of a three part version number is not passed +_find_version_from_git_tags() { + local variable_name="$1" + local requested_version="${!variable_name}" + if [ "${requested_version}" = "none" ]; then return; fi + local repository="$2" + local prefix="${3:-"tags/v"}" + local separator="${4:-"."}" + local suffix="${5:-}" + local last_part_optional="${6:-"false"}" + local after_version="${7:-""}" + if [ "$(echo "${requested_version}" | grep -o "." | wc -l)" != "2" ]; then + local escaped_separator="${separator//./\\.}" + local last_part="" + if [ "${last_part_optional}" = "true" ]; then + last_part+="(${escaped_separator}[0-9]+)?" + last_part+="(${escaped_separator}[0-9]+)?" + if [ -n "${suffix:+x}" ]; then + last_part+="(${suffix})?" + fi + else + last_part+="${escaped_separator}[0-9]+" + last_part+="${escaped_separator}[0-9]+" + if [ -n "${suffix:+x}" ]; then + last_part+="(${suffix})" + fi + fi + local regex="${prefix}\\K[0-9]+${last_part}$" + + if ! test -v _find_version_from_git_tags_cache["$variable_name"]; then + local remote_upstream_fetch="$(git --no-pager config get remote.upstream.fetch)" + if test -n "${remote_upstream_fetch:+x}"; then + git config unset --global remote.upstream.fetch || true + fi + readarray -t version_list < <( + git ls-remote --tags "${repository}" \ + | grep -oP "${regex}" \ + | tr -d ' ' \ + | tr "${separator}" "." \ + | sort -rV + ) + if test -n "${remote_upstream_fetch:+x}"; then + git config set --global remote.upstream.fetch "${remote_upstream_fetch}" + fi + _upvar _find_version_from_git_tags_cache["$variable_name"] "${version_list[*]}" + else + readarray -d' ' -t version_list <<< "${_find_version_from_git_tags_cache["$variable_name"]}" + fi + local version_list="$(IFS=$'\n'; echo "${version_list[*]}")" + if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "lts" ]; then + requested_version="$(head -n 1 <<< "${version_list}")" + elif test -n "${after_version:+x}"; then + set +e + requested_version="$(grep -A 1 -m 1 "${after_version}" <<< "${version_list}" | tail -n 1)" + set -e + else + set +e + requested_version="$(grep -E -m 1 "^${requested_version//./\\.}([\\.\\s]|$)" <<< "${version_list}")" + set -e + fi + fi + if [ ! -n "${requested_version:+x}" ] || ! grep "^${requested_version//./\\.}$" <<< "${version_list}" > /dev/null 2>&1; then + echo -e "Invalid ${variable_name} value: ${requested_version}\nValid values:\n${version_list}" >&2 + return 1 + fi + _upvar "${variable_name}" "${requested_version}" + echo "${variable_name}=${requested_version}" +} + +# Use semver logic to decrement a version number then look for the closest match +_find_prev_version_from_git_tags() { + local variable_name="$1" + local current_version="${!variable_name}" + local repository="$2" + # Normally a "v" is used before the version number, but support alternate cases + local prefix="${3:-"tags/v"}" + # Some repositories use "_" instead of "." for version number part separation, support that + local separator="${4:-"."}" + # Some repositories may have tags that include a suffix (e.g. actions/node-versions) + local version_suffix_regex="${5:-}" + # Some tools release versions that omit the last digit (e.g. go) + local last_part_optional="${6:-"false"}" + # Try one break fix version number less if we get a failure. Use "set +e" since "set -e" can cause failures in valid scenarios. + set +e + local major="$(echo "${current_version}" | grep -oE '^[0-9]+' || echo '')" + local minor="$(echo "${current_version}" | grep -oP '^[0-9]+\.\K[0-9]+' || echo '')" + local breakfix="$(echo "${current_version}" | grep -oP '^[0-9]+\.[0-9]+\.\K[0-9]+' 2>/dev/null || echo '')" + + if [ "${minor}" = "0" ] && [ "${breakfix}" = "0" ]; then + ((major=major-1)) + _upvar "${variable_name}" "${major}" + # Look for latest version from previous major release + _find_version_from_git_tags "${variable_name}" "${repository}" "${prefix}" "${separator}" "${version_suffix_regex}" "${last_part_optional}" + # Handle situations like Go's odd version pattern where "0" releases omit the last part + elif [ "${breakfix}" = "" ] || [ "${breakfix}" = "0" ]; then + ((minor=minor-1)) + _upvar "${variable_name}" "${major}.${minor}" + # Look for latest version from previous minor release + _find_version_from_git_tags "${variable_name}" "${repository}" "${prefix}" "${separator}" "${version_suffix_regex}" "${last_part_optional}" + else + ((breakfix=breakfix-1)) + if [ "${breakfix}" = "0" ] && [ "${last_part_optional}" = "true" ]; then + _upvar "${variable_name}" "${major}.${minor}" + else + _upvar "${variable_name}" "${major}.${minor}.${breakfix}" + fi + fi + set -e +} diff --git a/features/src/sbom/common/install.sh b/features/src/sbom/common/install.sh new file mode 100755 index 000000000..62c7cf810 --- /dev/null +++ b/features/src/sbom/common/install.sh @@ -0,0 +1,64 @@ +#! /usr/bin/env bash +set -e + +if [ "$(id -u)" -ne 0 ]; then + echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.' + exit 1 +fi + +src="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +# export bash utility functions +# shellcheck disable=SC1091 +source "$src/utilities.sh"; + +rm -f /etc/profile.d/00-restore-env.sh; + +# install /etc/skel +cp -r "$src/etc/skel" /etc/; +# install /etc/bash.bash_env +cp "$src/etc/bash.bash_env" /etc/; +chown root:root /etc/bash.bash_env; +chmod u+rwx,g+rwx,o+rx /etc/bash.bash_env; + +unset src; + +# Store and reset BASH_ENV in /etc/profile so lmod doesn't steal it from us. +# Our `/etc/bash.bash_env` will source lmod's $BASH_ENV at the end. +append_to_etc_profile "$(cat <> /etc/environment; +fi + +# Remove unnecessary "$HOME/.local/bin" at the end of the path +# shellcheck disable=SC2016 +if grep -qxF 'if [[ "${PATH}" != *"$HOME/.local/bin"* ]]; then export PATH="${PATH}:$HOME/.local/bin"; fi' /etc/bash.bashrc; then + grep -vxF \ + 'if [[ "${PATH}" != *"$HOME/.local/bin"* ]]; then export PATH="${PATH}:$HOME/.local/bin"; fi' \ + /etc/bash.bashrc \ + > /etc/bash.bashrc.new \ + && mv /etc/bash.bashrc{.new,}; +fi + +cp /etc/skel/.profile /root/.profile; +echo 'mesg n 2> /dev/null || true' >> /root/.profile; + +for_each_user_profile "$(cat <<"EOF" +sed -i 's@if \[ -d "$HOME/bin" \]@if [ -n "${PATH##*"$HOME/bin"*}" ] \&\& [ -d "$HOME/bin" ]@' $0; +sed -i 's@if \[ -d "$HOME/.local/bin" \]@if [ -n "${PATH##*"$HOME/.local/bin"*}" ] \&\& [ -d "$HOME/.local/bin" ]@' $0; +EOF +)"; + +# Fix the devcontainers/features/common-utils __bash_prompt fn +# shellcheck disable=SC2016 +for_each_user_bashrc ' +if [[ "$(grep -qE "^__bash_prompt\(\) \{$" "$0"; echo $?)" == 0 ]]; then + sed -i "s/\${BRANCH}/\${BRANCH:-}/g" "$0"; + sed -i "s/\${GITHUB_USER}/\${GITHUB_USER:-}/g" "$0"; +fi +'; diff --git a/features/src/sbom/common/utilities.sh b/features/src/sbom/common/utilities.sh new file mode 100644 index 000000000..49a044b95 --- /dev/null +++ b/features/src/sbom/common/utilities.sh @@ -0,0 +1,186 @@ +#! /usr/bin/env bash + +# Define common shell functions + +# Assign variable one scope above the caller +# Usage: local "$1" && _upvar $1 "value(s)" +# Param: $1 Variable name to assign value to +# Param: $* Value(s) to assign. If multiple values, an array is +# assigned, otherwise a single value is assigned. +# See: http://fvue.nl/wiki/Bash:_Passing_variables_by_reference +_upvar() { + if unset -v "$1"; then + if (( $# == 2 )); then + eval $1=\"\$2\"; + else + eval $1=\(\"\${@:2}\"\); + fi; + fi +} + +# Run apt update if apt lists aren't populated +apt_get_update() { + if [ "$(find /var/lib/apt/lists -mindepth 1 | head -n1 | wc -l)" = "0" ]; then + echo "Running apt-get update..."; + apt-get update -y; + fi +} + +export -f apt_get_update; + +# Checks if packages are installed and installs them if not +check_packages() { + if ! dpkg -s "$@" > /dev/null 2>&1; then + apt_get_update; + echo "Installing packages: $*"; + DEBIAN_FRONTEND=noninteractive \ + apt-get -y install --no-install-recommends "$@"; + fi +} + +export -f check_packages; + +for_each_user_bashrc() { + # Update all bashrc files + # shellcheck disable=SC2086 + find / /etc /home ${_REMOTE_USER_HOME} ${_CONTAINER_USER_HOME} -maxdepth 2 -type f -name .bashrc \ + | sort | uniq | xargs -r -d'\n' -n1 bash -c "${@}"; +} + +export -f for_each_user_bashrc; + +for_each_user_profile() { + # Update all .profile files + # shellcheck disable=SC2086 + find / /etc /home ${_REMOTE_USER_HOME} ${_CONTAINER_USER_HOME} -maxdepth 2 -type f -name .profile \ + | sort | uniq | xargs -r -d'\n' -n1 bash -c "${@}"; +} + +export -f for_each_user_profile; + +append_to_all_bashrcs() { + # Update all bashrc files + # shellcheck disable=SC2086 + for bashrc in $(find / /etc /home ${_REMOTE_USER_HOME} ${_CONTAINER_USER_HOME} -maxdepth 2 -type f -name .bashrc | sort | uniq); do + if [[ "$(cat "$bashrc")" != *"$1"* ]]; then + echo "Appending to $bashrc..."; + echo -e "$1" >> "$bashrc"; + fi + done +} + +export -f append_to_all_bashrcs; + +prepend_to_all_bashrcs() { + # Update all bashrc files + # shellcheck disable=SC2086 + for bashrc in $(find / /etc /home ${_REMOTE_USER_HOME} ${_CONTAINER_USER_HOME} -maxdepth 2 -type f -name .bashrc | sort | uniq); do + if [[ "$(cat "$bashrc")" != *"$1"* ]]; then + echo "Prepending to $bashrc..."; + echo -e "$1\n$(cat "$bashrc")" > "$bashrc"; + fi + done +} + +export -f prepend_to_all_bashrcs; + +append_to_etc_profile() { + if [[ "$(cat /etc/profile)" != *"$1"* ]]; then + echo "Appending to /etc/profile..."; + echo -e "$1" >> /etc/profile; + fi +} + +export -f append_to_etc_profile; + +prepend_to_etc_profile() { + if [[ "$(cat /etc/profile)" != *"$1"* ]]; then + echo "Prepending to /etc/profile..."; + echo -e "$1\n$(cat /etc/profile)" > /etc/profile; + fi +} + +export -f prepend_to_etc_profile; + +append_to_etc_bashrc() { + if [[ "$(cat /etc/bash.bashrc)" != *"$1"* ]]; then + echo "Appending to /etc/bash.bashrc..."; + echo -e "$1" >> /etc/bash.bashrc; + fi +} + +export -f append_to_etc_bashrc; + +prepend_to_etc_bashrc() { + if [[ "$(cat /etc/bash.bashrc)" != *"$1"* ]]; then + echo "Prepending to /etc/bash.bashrc..."; + echo -e "$1\n$(cat /etc/bash.bashrc)" > /etc/bash.bashrc; + fi +} + +export -f prepend_to_etc_bashrc; + +append_etc_zshrc() { + if [ -f "/etc/zsh/zshrc" ] && [[ "$(cat /etc/zsh/zshrc)" != *"$1"* ]]; then + echo "Appending to /etc/zsh/zshrc..."; + echo -e "$1" >> /etc/zsh/zshrc; + fi +} + +export -f append_etc_zshrc; + +prepend_to_etc_zshrc() { + if [ -f "/etc/zsh/zshrc" ] && [[ "$(cat /etc/zsh/zshrc)" != *"$1"* ]]; then + echo "Prepending to /etc/zsh/zshrc..."; + echo -e "$1\n$(cat /etc/zsh/zshrc)" > /etc/zsh/zshrc; + fi +} + +export -f prepend_to_etc_zshrc; + +add_etc_profile_d_script() { + # shellcheck disable=SC2012 + local name="$(($(ls -1q /etc/profile.d/*.sh | wc -l) + 20))-${1}.sh"; + echo -e "#! /usr/bin/env bash\n${*:2}" > "/etc/profile.d/${name}"; + chmod +x "/etc/profile.d/${name}"; +} + +export -f add_etc_profile_d_script; + +# Determine the appropriate non-root user +find_non_root_user() { + USERNAME="${USERNAME:-"${_REMOTE_USER:-"auto"}"}"; + if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then + USERNAME="" + POSSIBLE_USERS=("vscode" "node" "codespace" "coder" "$(awk -v val=1001 -F ":" '$3==val{print $1}' /etc/passwd)") + for CURRENT_USER in "${POSSIBLE_USERS[@]}"; do + if id -u "${CURRENT_USER}" > /dev/null 2>&1; then + USERNAME=${CURRENT_USER} + break + fi + done + elif [ "${USERNAME}" = "none" ] || ! id -u "${USERNAME}" > /dev/null 2>&1; then + USERNAME=root + fi + if [ "${USERNAME}" = "" ]; then + USERNAME=root + fi +} + +export -f find_non_root_user; + +# shellcheck disable=SC1091 +. "$(dirname "$(realpath -m "${BASH_SOURCE[0]}")")/find-version-from-git-tags.sh"; + +find_version_from_git_tags() { + check_packages git; + _find_version_from_git_tags "$@"; +} + +export -f find_version_from_git_tags; + +find_prev_version_from_git_tags() { + _find_prev_version_from_git_tags "$@"; +} + +export -f find_prev_version_from_git_tags; diff --git a/features/src/sbom/devcontainer-feature.json b/features/src/sbom/devcontainer-feature.json new file mode 100644 index 000000000..f75f45208 --- /dev/null +++ b/features/src/sbom/devcontainer-feature.json @@ -0,0 +1,19 @@ +{ + "name": "SBOM", + "id": "sbom", + "version": "0.1.0", + "description": "Generate a CycloneDX SBOM with Syft and store it inside the image.", + "options": { + "syftVer": { + "type": "string", + "default": "latest", + "description": "Syft version to install (e.g. 1.32.0). Use 'latest' to install the newest release." + }, + "outputPath": { + "type": "string", + "default": "/sbom/sbom.json", + "description": "Absolute path where the SBOM will be written." + } + } +} + diff --git a/features/src/sbom/install.sh b/features/src/sbom/install.sh new file mode 100644 index 000000000..6fafb830b --- /dev/null +++ b/features/src/sbom/install.sh @@ -0,0 +1,48 @@ +#! /usr/bin/env bash +set -e + +# Ensure we're in this feature's directory during build +cd "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"; + +# install global/common scripts +. ./common/install.sh; + +check_packages curl tar ca-certificates; + +SYFT_VER="${SYFTVER:-latest}"; +OUTPUT_PATH="${OUTPUTPATH:-/sbom/sbom.json}"; + +if [[ "${SYFT_VER}" == "latest" ]]; then + find_version_from_git_tags SYFT_VER https://github.com/anchore/syft; +fi + +case "${TARGETARCH:-$(dpkg --print-architecture | awk -F'-' '{print $NF}')}" in + amd64|x86_64) + SYFT_ARCH="linux_amd64"; + ;; + arm64|aarch64) + SYFT_ARCH="linux_arm64"; + ;; + *) + echo "Unsupported architecture: ${TARGETARCH:-$(uname -m)}" >&2; + exit 1; + ;; +esac + +TMP_DIR="$(mktemp -d)"; +trap 'rm -rf "${TMP_DIR}"' EXIT; + +curl -sSfL "https://github.com/anchore/syft/releases/download/v${SYFT_VER}/syft_${SYFT_VER}_${SYFT_ARCH}.tar.gz" \ + | tar -xz -C "${TMP_DIR}" syft; + +install -m 0755 "${TMP_DIR}/syft" /usr/local/bin/syft; + +mkdir -p "$(dirname "${OUTPUT_PATH}")"; + +syft scan \ + --scope all-layers \ + --output "cyclonedx-json@1.6=${OUTPUT_PATH}" \ + dir:/; + +chmod 0644 "${OUTPUT_PATH}"; + diff --git a/image/.devcontainer/devcontainer.json b/image/.devcontainer/devcontainer.json index 0781c91cd..47f38af36 100644 --- a/image/.devcontainer/devcontainer.json +++ b/image/.devcontainer/devcontainer.json @@ -37,7 +37,8 @@ "./features/src/sccache": { "version": "0.7.7" }, - "./features/src/utils": {} + "./features/src/utils": {}, + "./features/src/sbom": {} }, "overrideFeatureInstallOrder": [ "ghcr.io/devcontainers/features/common-utils", @@ -48,7 +49,8 @@ "./features/src/cmake", "./features/src/ninja", "./features/src/sccache", - "./features/src/utils" + "./features/src/utils", + "./features/src/sbom" ], "updateContentCommand": [ "/bin/bash", diff --git a/matrix.yml b/matrix.yml index a7679471a..2669bc279 100644 --- a/matrix.yml +++ b/matrix.yml @@ -38,6 +38,7 @@ x-python: &python { name: "ghcr.io/devcontainers/features/python:1.7.1", version x-python-rapids: &python_rapids { name: "ghcr.io/devcontainers/features/python:1.7.1", version: "3.13", installTools: false, enableShared: true, optimize: true, hide: true } x-ucx-rapids: &ucx_rapids { name: "ucx", version: "1.19.0" } x-openmpi: &openmpi { name: "openmpi", version: "5.0.7" } +x-sbom: &sbom { name: "sbom", version: "latest" } x-cccl-dev: &cccl_dev { name: "cccl-dev", hide: true, doxygenVersion: "1.9.6" } x-clangd-dev: &clangd_dev { name: "llvm", version: "dev", packages: "clangd", hide: true } @@ -77,117 +78,117 @@ include: - os: "ubuntu:20.04" images: - - { features: [*python, *dood, *gcc_7, { <<: *cuda_prev_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *gcc_env } - - { features: [*python, *dood, *gcc_7, { <<: *cuda_prev_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *gcc_env } - - { features: [*python, *dood, *gcc_8, { <<: *cuda_prev_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *gcc_env } - - { features: [*python, *dood, *gcc_8, { <<: *cuda_prev_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *gcc_env } - - { features: [*python, *dood, *gcc_9, { <<: *cuda_prev_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *gcc_env } - - { features: [*python, *dood, *gcc_9, { <<: *cuda_prev_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *gcc_env } - - { features: [*python, *dood, *gcc_10, { <<: *cuda_prev_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *gcc_env } - - { features: [*python, *dood, *gcc_10, { <<: *cuda_prev_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *gcc_env } - - - { features: [*python, *dood, *llvm_14, { <<: *cuda_prev_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *llvm_env } - - { features: [*python, *dood, *llvm_14, { <<: *cuda_prev_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *llvm_env } + - { features: [*python, *dood, *gcc_7, { <<: *cuda_prev_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *gcc_env } + - { features: [*python, *dood, *gcc_7, { <<: *cuda_prev_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *gcc_env } + - { features: [*python, *dood, *gcc_8, { <<: *cuda_prev_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *gcc_env } + - { features: [*python, *dood, *gcc_8, { <<: *cuda_prev_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *gcc_env } + - { features: [*python, *dood, *gcc_9, { <<: *cuda_prev_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *gcc_env } + - { features: [*python, *dood, *gcc_9, { <<: *cuda_prev_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *gcc_env } + - { features: [*python, *dood, *gcc_10, { <<: *cuda_prev_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *gcc_env } + - { features: [*python, *dood, *gcc_10, { <<: *cuda_prev_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *gcc_env } + + - { features: [*python, *dood, *llvm_14, { <<: *cuda_prev_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *llvm_env } + - { features: [*python, *dood, *llvm_14, { <<: *cuda_prev_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *llvm_env } - os: "ubuntu:22.04" images: - - { features: [*python, *dood, *nvhpc_prev, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *nvhpc_env } - - { features: [*python, *dood, *nvhpc_curr, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *nvhpc_env } - - - { features: [*python, *dood, *gcc_11, { <<: *cuda_prev_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *gcc_env } - - { features: [*python, *dood, *gcc_11, { <<: *cuda_prev_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *gcc_env } - - { features: [*python, *dood, *gcc_11, { <<: *cuda_curr_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *gcc_env } - - { features: [*python, *dood, *gcc_11, { <<: *cuda_curr_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *gcc_env } - - { features: [*python, *dood, *gcc_12, { <<: *cuda_prev_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *gcc_env } - - { features: [*python, *dood, *gcc_12, { <<: *cuda_prev_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *gcc_env } - - { features: [*python, *dood, *gcc_12, { <<: *cuda_curr_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *gcc_env } - - { features: [*python, *dood, *gcc_12, { <<: *cuda_curr_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *gcc_env } + - { features: [*python, *dood, *nvhpc_prev, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *nvhpc_env } + - { features: [*python, *dood, *nvhpc_curr, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *nvhpc_env } + + - { features: [*python, *dood, *gcc_11, { <<: *cuda_prev_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *gcc_env } + - { features: [*python, *dood, *gcc_11, { <<: *cuda_prev_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *gcc_env } + - { features: [*python, *dood, *gcc_11, { <<: *cuda_curr_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *gcc_env } + - { features: [*python, *dood, *gcc_11, { <<: *cuda_curr_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *gcc_env } + - { features: [*python, *dood, *gcc_12, { <<: *cuda_prev_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *gcc_env } + - { features: [*python, *dood, *gcc_12, { <<: *cuda_prev_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *gcc_env } + - { features: [*python, *dood, *gcc_12, { <<: *cuda_curr_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *gcc_env } + - { features: [*python, *dood, *gcc_12, { <<: *cuda_curr_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *gcc_env } # This is only used for testing python -- CTKk 12.0 + gcc13 don't play nice together, but # that doesn't matter for running python tests, and pinning all python jobs at 13 simplifies # things significantly. - - { features: [*python, *dood, *gcc_13, { <<: *cuda_prev_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *gcc_env } - - - { features: [*python, *dood, *llvm_15, { <<: *cuda_prev_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *llvm_env } - - { features: [*python, *dood, *llvm_15, { <<: *cuda_prev_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *llvm_env } - - { features: [*python, *dood, *llvm_15, { <<: *cuda_curr_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *llvm_env } - - { features: [*python, *dood, *llvm_15, { <<: *cuda_curr_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *llvm_env } - - { features: [*python, *dood, *llvm_16, { <<: *cuda_prev_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *llvm_env } - - { features: [*python, *dood, *llvm_16, { <<: *cuda_prev_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *llvm_env } - - { features: [*python, *dood, *llvm_16, { <<: *cuda_curr_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *llvm_env } - - { features: [*python, *dood, *llvm_16, { <<: *cuda_curr_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *llvm_env } - - { features: [*python, *dood, *llvm_17, { <<: *cuda_prev_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *llvm_env } - - { features: [*python, *dood, *llvm_17, { <<: *cuda_prev_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *llvm_env } - - { features: [*python, *dood, *llvm_17, { <<: *cuda_curr_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *llvm_env } - - { features: [*python, *dood, *llvm_17, { <<: *cuda_curr_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *llvm_env } - - { features: [*python, *dood, *llvm_18, { <<: *cuda_prev_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *llvm_env } - - { features: [*python, *dood, *llvm_18, { <<: *cuda_prev_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *llvm_env } - - { features: [*python, *dood, *llvm_18, { <<: *cuda_curr_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *llvm_env } - - { features: [*python, *dood, *llvm_18, { <<: *cuda_curr_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *llvm_env } - - { features: [*python, *dood, *llvm_19, { <<: *cuda_prev_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *llvm_env } - - { features: [*python, *dood, *llvm_19, { <<: *cuda_prev_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *llvm_env } - - { features: [*python, *dood, *llvm_19, { <<: *cuda_curr_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *llvm_env } - - { features: [*python, *dood, *llvm_19, { <<: *cuda_curr_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *llvm_env } - - { features: [*python, *dood, *llvm_20, { <<: *cuda_prev_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *llvm_env } - - { features: [*python, *dood, *llvm_21, { <<: *cuda_prev_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *llvm_env } + - { features: [*python, *dood, *gcc_13, { <<: *cuda_prev_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *gcc_env } + + - { features: [*python, *dood, *llvm_15, { <<: *cuda_prev_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *llvm_env } + - { features: [*python, *dood, *llvm_15, { <<: *cuda_prev_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *llvm_env } + - { features: [*python, *dood, *llvm_15, { <<: *cuda_curr_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *llvm_env } + - { features: [*python, *dood, *llvm_15, { <<: *cuda_curr_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *llvm_env } + - { features: [*python, *dood, *llvm_16, { <<: *cuda_prev_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *llvm_env } + - { features: [*python, *dood, *llvm_16, { <<: *cuda_prev_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *llvm_env } + - { features: [*python, *dood, *llvm_16, { <<: *cuda_curr_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *llvm_env } + - { features: [*python, *dood, *llvm_16, { <<: *cuda_curr_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *llvm_env } + - { features: [*python, *dood, *llvm_17, { <<: *cuda_prev_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *llvm_env } + - { features: [*python, *dood, *llvm_17, { <<: *cuda_prev_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *llvm_env } + - { features: [*python, *dood, *llvm_17, { <<: *cuda_curr_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *llvm_env } + - { features: [*python, *dood, *llvm_17, { <<: *cuda_curr_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *llvm_env } + - { features: [*python, *dood, *llvm_18, { <<: *cuda_prev_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *llvm_env } + - { features: [*python, *dood, *llvm_18, { <<: *cuda_prev_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *llvm_env } + - { features: [*python, *dood, *llvm_18, { <<: *cuda_curr_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *llvm_env } + - { features: [*python, *dood, *llvm_18, { <<: *cuda_curr_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *llvm_env } + - { features: [*python, *dood, *llvm_19, { <<: *cuda_prev_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *llvm_env } + - { features: [*python, *dood, *llvm_19, { <<: *cuda_prev_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *llvm_env } + - { features: [*python, *dood, *llvm_19, { <<: *cuda_curr_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *llvm_env } + - { features: [*python, *dood, *llvm_19, { <<: *cuda_curr_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *llvm_env } + - { features: [*python, *dood, *llvm_20, { <<: *cuda_prev_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *llvm_env } + - { features: [*python, *dood, *llvm_21, { <<: *cuda_prev_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *llvm_env } - os: "ubuntu:24.04" images: - - { features: [*python, *dood, *gcc_13, { <<: *cuda_prev_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *gcc_env } - - { features: [*python, *dood, *gcc_13, { <<: *cuda_curr_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *gcc_env } - - { features: [*python, *dood, *gcc_13, { <<: *cuda_curr_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *gcc_env } + - { features: [*python, *dood, *gcc_13, { <<: *cuda_prev_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *gcc_env } + - { features: [*python, *dood, *gcc_13, { <<: *cuda_curr_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *gcc_env } + - { features: [*python, *dood, *gcc_13, { <<: *cuda_curr_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *gcc_env } - - { features: [*python, *dood, *gcc_14, { <<: *cuda_prev_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *gcc_env } - - { features: [*python, *dood, *gcc_14, { <<: *cuda_curr_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *gcc_env } - - { features: [*python, *dood, *gcc_14, { <<: *cuda_curr_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *gcc_env } + - { features: [*python, *dood, *gcc_14, { <<: *cuda_prev_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *gcc_env } + - { features: [*python, *dood, *gcc_14, { <<: *cuda_curr_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *gcc_env } + - { features: [*python, *dood, *gcc_14, { <<: *cuda_curr_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *gcc_env } - - { features: [*python, *dood, *llvm_20, { <<: *cuda_prev_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *llvm_env } - - { features: [*python, *dood, *llvm_20, { <<: *cuda_curr_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *llvm_env } - - { features: [*python, *dood, *llvm_20, { <<: *cuda_curr_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *llvm_env } - - { features: [*python, *dood, *llvm_21, { <<: *cuda_prev_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *llvm_env } - - { features: [*python, *dood, *llvm_21, { <<: *cuda_curr_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *llvm_env } - - { features: [*python, *dood, *llvm_21, { <<: *cuda_curr_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *llvm_env } + - { features: [*python, *dood, *llvm_20, { <<: *cuda_prev_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *llvm_env } + - { features: [*python, *dood, *llvm_20, { <<: *cuda_curr_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *llvm_env } + - { features: [*python, *dood, *llvm_20, { <<: *cuda_curr_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *llvm_env } + - { features: [*python, *dood, *llvm_21, { <<: *cuda_prev_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *llvm_env } + - { features: [*python, *dood, *llvm_21, { <<: *cuda_curr_min, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *llvm_env } + - { features: [*python, *dood, *llvm_21, { <<: *cuda_curr_max, <<: *cccl_cuda_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *llvm_env } # cuda-ext images: - - { features: [*python, *dood, *gcc_14, { <<: *cuda_prev_max, <<: *cccl_cuda_ext_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *gcc_env } - - { features: [*python, *dood, *gcc_14, { <<: *cuda_curr_min, <<: *cccl_cuda_ext_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *gcc_env } - - { features: [*python, *dood, *gcc_14, { <<: *cuda_curr_max, <<: *cccl_cuda_ext_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *gcc_env } - - { features: [*python, *dood, *llvm_20, { <<: *cuda_prev_max, <<: *cccl_cuda_ext_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *llvm_env } - - { features: [*python, *dood, *llvm_20, { <<: *cuda_curr_min, <<: *cccl_cuda_ext_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *llvm_env } - - { features: [*python, *dood, *llvm_20, { <<: *cuda_curr_max, <<: *cccl_cuda_ext_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *llvm_env } - - { features: [*python, *dood, *llvm_21, { <<: *cuda_prev_max, <<: *cccl_cuda_ext_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *llvm_env } - - { features: [*python, *dood, *llvm_21, { <<: *cuda_curr_min, <<: *cccl_cuda_ext_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *llvm_env } - - { features: [*python, *dood, *llvm_21, { <<: *cuda_curr_max, <<: *cccl_cuda_ext_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev], env: *llvm_env } + - { features: [*python, *dood, *gcc_14, { <<: *cuda_prev_max, <<: *cccl_cuda_ext_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *gcc_env } + - { features: [*python, *dood, *gcc_14, { <<: *cuda_curr_min, <<: *cccl_cuda_ext_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *gcc_env } + - { features: [*python, *dood, *gcc_14, { <<: *cuda_curr_max, <<: *cccl_cuda_ext_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *gcc_env } + - { features: [*python, *dood, *llvm_20, { <<: *cuda_prev_max, <<: *cccl_cuda_ext_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *llvm_env } + - { features: [*python, *dood, *llvm_20, { <<: *cuda_curr_min, <<: *cccl_cuda_ext_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *llvm_env } + - { features: [*python, *dood, *llvm_20, { <<: *cuda_curr_max, <<: *cccl_cuda_ext_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *llvm_env } + - { features: [*python, *dood, *llvm_21, { <<: *cuda_prev_max, <<: *cccl_cuda_ext_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *llvm_env } + - { features: [*python, *dood, *llvm_21, { <<: *cuda_curr_min, <<: *cccl_cuda_ext_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *llvm_env } + - { features: [*python, *dood, *llvm_21, { <<: *cuda_curr_max, <<: *cccl_cuda_ext_opts }, *clang_format_cccl, *clangd_dev, *cccl_dev, *sbom], env: *llvm_env } - os: "windows" images: - - { features: [{ <<: *cuda_prev_min, <<: *cccl_cuda_opts }, { name: "cl", version: "14.29" }] } # MSVC 2019 - - { features: [{ <<: *cuda_prev_max, <<: *cccl_cuda_opts }, { name: "cl", version: "14.29" }] } # MSVC 2019 - - { features: [{ <<: *cuda_curr_min, <<: *cccl_cuda_opts }, { name: "cl", version: "14.29" }] } # MSVC 2019 - - { features: [{ <<: *cuda_curr_max, <<: *cccl_cuda_opts }, { name: "cl", version: "14.29" }] } # MSVC 2019 + - { features: [{ <<: *cuda_prev_min, <<: *cccl_cuda_opts }, { name: "cl", version: "14.29" }, *sbom] } # MSVC 2019 + - { features: [{ <<: *cuda_prev_max, <<: *cccl_cuda_opts }, { name: "cl", version: "14.29" }, *sbom] } # MSVC 2019 + - { features: [{ <<: *cuda_curr_min, <<: *cccl_cuda_opts }, { name: "cl", version: "14.29" }, *sbom] } # MSVC 2019 + - { features: [{ <<: *cuda_curr_max, <<: *cccl_cuda_opts }, { name: "cl", version: "14.29" }, *sbom] } # MSVC 2019 # CTK 12.0 doesn't know that it supports 14.40+, use 14.39: - - { features: [{ <<: *cuda_prev_min, <<: *cccl_cuda_opts }, { name: "cl", version: "14.39" }] } # MSVC 2022 + - { features: [{ <<: *cuda_prev_min, <<: *cccl_cuda_opts }, { name: "cl", version: "14.39" }, *sbom] } # MSVC 2022 # Still, we build a 12.0 + 14.4X image for python testing infra: - - { features: [{ <<: *cuda_prev_min, <<: *cccl_cuda_opts }, { name: "cl", version: "14.44" }] } # MSVC 2022 - - { features: [{ <<: *cuda_prev_max, <<: *cccl_cuda_opts }, { name: "cl", version: "14.44" }] } # MSVC 2022 - - { features: [{ <<: *cuda_curr_min, <<: *cccl_cuda_opts }, { name: "cl", version: "14.44" }] } # MSVC 2022 - - { features: [{ <<: *cuda_curr_max, <<: *cccl_cuda_opts }, { name: "cl", version: "14.44" }] } # MSVC 2022 + - { features: [{ <<: *cuda_prev_min, <<: *cccl_cuda_opts }, { name: "cl", version: "14.44" }, *sbom] } # MSVC 2022 + - { features: [{ <<: *cuda_prev_max, <<: *cccl_cuda_opts }, { name: "cl", version: "14.44" }, *sbom] } # MSVC 2022 + - { features: [{ <<: *cuda_curr_min, <<: *cccl_cuda_opts }, { name: "cl", version: "14.44" }, *sbom] } # MSVC 2022 + - { features: [{ <<: *cuda_curr_max, <<: *cccl_cuda_opts }, { name: "cl", version: "14.44" }, *sbom] } # MSVC 2022 # RAPIDS devcontainers - os: "ubuntu:24.04" images: # cuda - - { features: [*python_rapids, *clang_format_rapids, *clangd_dev, *cuda_prev_max_rapids], env: *gcc_env_rapids } - - { features: [*python_rapids, *clang_format_rapids, *clangd_dev, *cuda_curr_min], env: *gcc_env_rapids } - - { features: [*python_rapids, *clang_format_rapids, *clangd_dev, *cuda_curr_max], env: *gcc_env_rapids } - - { features: [*python_rapids, *clang_format_rapids, *clangd_dev, *cuda_curr_max_rapids], env: *gcc_env_rapids } - - { features: [*python_rapids, *clang_format_rapids, *clangd_dev, *cuda_prev_max_rapids, *ucx_rapids, *openmpi], env: *gcc_env_rapids } - - { features: [*python_rapids, *clang_format_rapids, *clangd_dev, *cuda_curr_min, *ucx_rapids, *openmpi], env: *gcc_env_rapids } - - { features: [*python_rapids, *clang_format_rapids, *clangd_dev, *cuda_curr_max, *ucx_rapids, *openmpi], env: *gcc_env_rapids } - - { features: [*python_rapids, *clang_format_rapids, *clangd_dev, *cuda_curr_max_rapids, *ucx_rapids, *openmpi], env: *gcc_env_rapids } + - { features: [*python_rapids, *clang_format_rapids, *clangd_dev, *cuda_prev_max_rapids, *sbom], env: *gcc_env_rapids } + - { features: [*python_rapids, *clang_format_rapids, *clangd_dev, *cuda_curr_min, *sbom], env: *gcc_env_rapids } + - { features: [*python_rapids, *clang_format_rapids, *clangd_dev, *cuda_curr_max, *sbom], env: *gcc_env_rapids } + - { features: [*python_rapids, *clang_format_rapids, *clangd_dev, *cuda_curr_max_rapids, *sbom], env: *gcc_env_rapids } + - { features: [*python_rapids, *clang_format_rapids, *clangd_dev, *cuda_prev_max_rapids, *ucx_rapids, *openmpi, *sbom], env: *gcc_env_rapids } + - { features: [*python_rapids, *clang_format_rapids, *clangd_dev, *cuda_curr_min, *ucx_rapids, *openmpi, *sbom], env: *gcc_env_rapids } + - { features: [*python_rapids, *clang_format_rapids, *clangd_dev, *cuda_curr_max, *ucx_rapids, *openmpi, *sbom], env: *gcc_env_rapids } + - { features: [*python_rapids, *clang_format_rapids, *clangd_dev, *cuda_curr_max_rapids, *ucx_rapids, *openmpi, *sbom], env: *gcc_env_rapids } # mambaforge - - { features: [*conda], env: { PYTHON_VERSION: "3.13" } } + - { features: [*conda, *sbom], env: { PYTHON_VERSION: "3.13" } } diff --git a/windows/build-windows-image.ps1 b/windows/build-windows-image.ps1 index 425998238..078c0be22 100644 --- a/windows/build-windows-image.ps1 +++ b/windows/build-windows-image.ps1 @@ -19,7 +19,10 @@ Param( $repo="local", [Parameter(Mandatory=$false)] [string] - $repoVersion="latest" + $repoVersion="latest", + [Parameter(Mandatory=$false)] + [string] + $syftVer="latest" ) function TestReturnCode { @@ -35,6 +38,13 @@ $rootWindowsImage = @{ "windows2019" = "mcr.microsoft.com/windows/servercore:ltsc2019" }[$edition] +if ($syftVer -eq "latest") { + $latest = Invoke-RestMethod -UseBasicParsing -Uri "https://api.github.com/repos/anchore/syft/releases/latest" + $syftVer = $latest.tag_name.TrimStart('v') +} + +$syftArch = if ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture -eq [System.Runtime.InteropServices.Architecture]::Arm64) { 'windows_arm64' } else { 'windows_amd64' } + try { # Source version matrix .\vs-version-matrix.ps1 @@ -58,7 +68,7 @@ try { Write-Output "ENV:CUDA_VER $ENV:CUDA_VER" Write-Output "ENV:ROOT_IMAGE $ENV:ROOT_IMAGE" - docker build --file .\windows.Dockerfile --tag "$ENV:IMAGE_NAME" --isolation "$ENV:ISOLATION" --build-arg MSVC_VER="$ENV:MSVC_VER" --build-arg MSVC_COMPILER_VER="$ENV:MSVC_COMPILER_VER" --build-arg CUDA_VER="$ENV:CUDA_VER" --build-arg ROOT_IMAGE="$ENV:ROOT_IMAGE" .\image + docker build --file .\windows.Dockerfile --tag "$ENV:IMAGE_NAME" --isolation "$ENV:ISOLATION" --build-arg MSVC_VER="$ENV:MSVC_VER" --build-arg MSVC_COMPILER_VER="$ENV:MSVC_COMPILER_VER" --build-arg CUDA_VER="$ENV:CUDA_VER" --build-arg ROOT_IMAGE="$ENV:ROOT_IMAGE" --build-arg SYFT_VER="$syftVer" --build-arg SYFT_ARCH="$syftArch" .\image } catch { Pop-Location diff --git a/windows/windows.Dockerfile b/windows/windows.Dockerfile index b6e27b353..9f7d000dc 100644 --- a/windows/windows.Dockerfile +++ b/windows/windows.Dockerfile @@ -1,6 +1,6 @@ ARG ROOT_IMAGE -FROM $ROOT_IMAGE +FROM ${ROOT_IMAGE} AS base SHELL ["powershell.exe"] @@ -14,3 +14,24 @@ RUN Set-ExecutionPolicy Unrestricted -Scope CurrentUser RUN /tools/install-compiler.ps1 -msvcVersion $ENV:MSVC_VER -clversion $ENV:MSVC_COMPILER_VER RUN /tools/install-tools.ps1 -cudaVersion $ENV:CUDA_VER + +FROM base AS sbom + +ARG SYFT_VER=latest +ARG SYFT_ARCH=windows_amd64 + +RUN $ErrorActionPreference = 'Stop'; \ + New-Item -ItemType Directory -Path C:\sbom -Force | Out-Null; \ + $zipPath = Join-Path $env:TEMP 'syft.zip'; \ + $extractPath = Join-Path $env:TEMP 'syft'; \ + Invoke-WebRequest -UseBasicParsing -Uri ("https://github.com/anchore/syft/releases/download/v{0}/syft_{0}_{1}.zip" -f $SYFT_VER, $SYFT_ARCH) -OutFile $zipPath; \ + if (Test-Path $extractPath) { Remove-Item $extractPath -Recurse -Force; } \ + Expand-Archive -Path $zipPath -DestinationPath $extractPath -Force; \ + $exePath = Get-ChildItem -Path $extractPath -Recurse -Filter 'syft.exe' | Select-Object -First 1 | ForEach-Object { $_.FullName }; \ + & $exePath scan --scope all-layers --output 'cyclonedx-json@1.6=C:/sbom/sbom.json' dir:C:/; \ + Remove-Item $zipPath -Force; \ + Remove-Item $extractPath -Recurse -Force + +FROM base + +COPY --from=sbom C:\sbom\sbom.json C:\sbom\sbom.json