diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7e7ada18e3799..7f23e008d0957 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -73,6 +73,7 @@ jobs: get_file ci/runner_env.sh get_file ci/symbols_depend.py get_file ci/touched_kconfig.awk + get_file ci/get_cherry_picks.sh get_file ci/gen_sbom.py if [[ "${{ inputs.defconfig }}" == "adi_ci_defconfig" ]]; then @@ -163,6 +164,7 @@ jobs: - name: Prepare dist run: | + [[ "${{ runner.debug }}" == "1" ]] && set -x kversion=$(cat "$KBUILD_OUTPUT/include/config/kernel.release") dist=$(mktemp -d -t linux.$kversion.dist.XXX) @@ -193,14 +195,26 @@ jobs: echo "git_sha=${{ github.sha }}" >> "$dist/context.txt" echo "git_sha_at=$(git --no-pager log ${{ github.sha }} -1 --pretty=format:%at)" >> "$dist/context.txt" echo "git_sha_ct=$(git --no-pager log ${{ github.sha }} -1 --pretty=format:%ct)" >> "$dist/context.txt" + + source ci/get_cherry_picks.sh + + tag=$(echo "$kversion" | sed -E 's/^([0-9]+\.[0-9]+(\.[0-9]+)?(-rc[0-9]+)?).*/\1/; s/\.0($|-rc)/\1/; s/^/v/') + get_cherry_picks "${{ secrets.GITHUB_TOKEN }}" \ + "${{ github.repository }}" \ + "$tag" \ + "${{ github.sha }}" \ + "$dist/cherry-picks.txt" + [[ -f compile_commands.json ]] && cp compile_commands.json "$dist" || true [[ -d "$dist_headers/usr" ]] && cp "$dist/context.txt" "$dist_headers/context.txt" || true - name: Generate SBOM run: | [[ -f "$DIST/compile_commands.json" ]] || exit 0 + export GIT_URL="${{ github.repositoryUrl }}" python3 ci/gen_sbom.py + rm "$DIST/compile_commands.json" - name: Assert state diff --git a/ci/gen_sbom.py b/ci/gen_sbom.py index 2974aff6a8a86..027c18869cc2b 100644 --- a/ci/gen_sbom.py +++ b/ci/gen_sbom.py @@ -117,7 +117,7 @@ def get_name(ctx): def get_description(ctx): return "The Linux kernel is the core of any Linux operating system" -def build_cdx(dist, ctx, source_files, src_root, main_c_command=None): +def build_cdx(dist, ctx, source_files, src_root, main_c_command=None, cherry_picks=None): """Return a CycloneDX 1.6 dict.""" kernel_release = ctx.get("kernel_release") kernel = ctx.get("kernel") @@ -141,6 +141,10 @@ def build_cdx(dist, ctx, source_files, src_root, main_c_command=None): git_url = environ.get('GIT_URL', '') purl_out = get_purl_out(ctx) + pedigree = {} + if cherry_picks: + pedigree["commits"] = [{"uid": sha} for sha in cherry_picks] + return { "bomFormat": "CycloneDX", "specVersion": "1.7", @@ -182,6 +186,7 @@ def build_cdx(dist, ctx, source_files, src_root, main_c_command=None): "name": "hub.analog.com/component-id", "value": get_component(ctx), }], + "pedigree": pedigree }, }, "components": [], @@ -202,7 +207,7 @@ def build_cdx(dist, ctx, source_files, src_root, main_c_command=None): } -def build_spdx(dist, ctx, source_files, src_root, main_c_command=None): +def build_spdx(dist, ctx, source_files, src_root, main_c_command=None, cherry_picks=None): """Return an SPDX 3.0.1 JSON-LD dict. Profiles: Core, Software, SimpleLicensing, Build (light). @@ -411,6 +416,20 @@ def _lic_id(expr): "value": main_c_command, }] + if cherry_picks: + # ref: https://github.com/spdx/Spdx-Java-Library/issues/302 + build_elem.setdefault("extension", []).append({ + "type": "extension_CdxPropertiesExtension", + "extension_cdxProperty": [ + { + "type": "extension_CdxPropertyEntry", + "extension_cdxPropName": "git:cherry-pick", + "extension_cdxPropValue": sha, + } + for sha in cherry_picks + ], + }) + build_input_rel = { "type": "Relationship", "spdxId": _id_src("rel/build-input"), @@ -513,14 +532,23 @@ def main(): rel = fp[len(src_root):] source_files.add(rel) + cherry_picks = None + cherry_picks_file = path.join(dist, "cherry-picks.txt") + if path.isfile(cherry_picks_file): + with open(cherry_picks_file) as f: + cherry_picks = set(line for line in f.read().splitlines() if line) + + cdx = build_cdx(dist, ctx, source_files, src_root, main_c_command, cherry_picks) + spdx = build_spdx(dist, ctx, source_files, src_root, main_c_command, cherry_picks) + cdx_path = path.join(dist, "sbom.cdx.json") with open(cdx_path, "w") as f: - json.dump(build_cdx(dist, ctx, source_files, src_root, main_c_command), f) + json.dump(cdx, f) print(f"sbom written to {cdx_path}", file=sys.stderr) spdx_path = path.join(dist, "sbom.spdx.json") with open(spdx_path, "w") as f: - json.dump(build_spdx(dist, ctx, source_files, src_root, main_c_command), f) + json.dump(spdx, f) print(f"sbom written to {spdx_path}", file=sys.stderr) diff --git a/ci/get_cherry_picks.sh b/ci/get_cherry_picks.sh new file mode 100755 index 0000000000000..b87ad9f43065e --- /dev/null +++ b/ci/get_cherry_picks.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# Collect cherry-pick original SHA from commit messages line: +# cherry picked from commit ... +# The commit must have been created with `git cherry-pick -x ` + +gh-compare() +{ + curl -fL \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer $1" \ + -H "X-GitHub-Api-Version: 2026-03-10" \ + "https://api.github.com/repos/$2/compare/$3...$4?per_page=250&page=$5" +} + +get_cherry_picks() +{ + local github_token="$1" + local org_repository="$2" + local base_sha="$3" + local head_sha="$4" + local output_file="$5" + local tag json total_commits pages page shas + + json=$(gh-compare "$github_token" "$org_repository" "$base_sha" "$head_sha" 1) + + status=$(echo "$json" | jq '.status // empty') + [ "$status" == "404" ] && { echo "Range $base_sha...$head_sha not found (404), won't collect cherry-picks" ; return 0 ; } || : + total_commits=$(echo "$json" | jq '.total_commits // empty') + [ -n "$total_commits" ] || { echo "$json" ; return 1 ; } + [ "$total_commits" -eq 10000 ] && { echo "More than 10000 commits, won't collect cherry-picks" ; return 0 ; } || : + + shas=$(echo "$json" | jq -r '.commits[].commit.message | scan("cherry picked from commit ([0-9a-f]{40})") | .[0]') + + pages=$(( (total_commits + 249) / 250 )) + for page in $(seq 2 "$pages"); do + json=$(gh-compare "$github_token" "$org_repository" "$base_sha" "$head_sha" "$page") + shas=$(printf '%s\n%s' "$shas" "$(echo "$json" | jq -r '.commits[].commit.message | scan("cherry picked from commit ([0-9a-f]{40})") | .[0]')") + done + + shas=$(printf '%s' "$shas" | sort -u | grep -v '^$') + + [ -n "$output_file" ] && echo "$shas" > "$output_file" || echo "$shas" +}