From b066ac414ca51f53246576bcf0e561319601ad70 Mon Sep 17 00:00:00 2001 From: Mike McQuaid Date: Mon, 20 May 2024 09:41:29 +0100 Subject: [PATCH] SBOM: various fixes. - be a bit stricter with SBOM handling with the test default formula flow in CI by making it raise errors if SBOM's aren't generated and validated as expected - fix handling of HEAD installations of formulae so SBOM generation is both correct and doesn't raise errors - make `Formula#bottle_hash` more accepting of edge cases e.g. HEAD-only formulae without a stable spec Fixes #17333 --- .github/workflows/tests.yml | 1 + Library/Homebrew/formula.rb | 8 ++++++-- Library/Homebrew/sbom.rb | 41 ++++++++++++++++++++++++------------- 3 files changed, 34 insertions(+), 16 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a74ce1736c58c..7a8dfa391d4cd 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -405,6 +405,7 @@ jobs: runs-on: macos-14 env: HOMEBREW_TEST_BOT_ANALYTICS: 1 + HOMEBREW_ENFORCE_SBOM: 1 steps: - name: Set up Homebrew id: set-up-homebrew diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index 9a884e963fd99..8820939caf841 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -2579,9 +2579,13 @@ def to_hash_with_variations(hash_method: :to_hash) # Returns the bottle information for a formula. def bottle_hash(compact_for_api: false) - bottle_spec = T.must(stable).bottle_specification - hash = {} + stable_spec = stable + return hash unless stable_spec + return hash unless bottle_defined? + + bottle_spec = stable_spec.bottle_specification + hash["rebuild"] = bottle_spec.rebuild if !compact_for_api || !bottle_spec.rebuild.zero? hash["root_url"] = bottle_spec.root_url unless compact_for_api hash["files"] = {} diff --git a/Library/Homebrew/sbom.rb b/Library/Homebrew/sbom.rb index 3148269251307..c57193f1a3836 100644 --- a/Library/Homebrew/sbom.rb +++ b/Library/Homebrew/sbom.rb @@ -17,6 +17,13 @@ class SBOM # Instantiates a {SBOM} for a new installation of a formula. sig { params(formula: Formula, tab: Tab).returns(T.attached_class) } def self.create(formula, tab) + active_spec = if formula.stable? + T.must(formula.stable) + else + T.must(formula.head) + end + active_spec_sym = formula.active_spec_sym + attributes = { name: formula.name, homebrew_version: HOMEBREW_VERSION, @@ -32,13 +39,13 @@ def self.create(formula, tab) path: formula.specified_path.to_s, tap: formula.tap&.name, tap_git_head: nil, # Filled in later if possible - spec: formula.active_spec_sym.to_s, - patches: formula.stable&.patches, + spec: active_spec_sym.to_s, + patches: active_spec.patches, bottle: formula.bottle_hash, - stable: { - version: formula.stable&.version, - url: formula.stable&.url, - checksum: formula.stable&.checksum, + active_spec_sym => { + version: active_spec.version, + url: active_spec.url, + checksum: active_spec.checksum, }, }, } @@ -230,7 +237,8 @@ def generate_relations_json(runtime_dependency_declaration, compiler_declaration } def generate_packages_json(runtime_dependency_declaration, compiler_declaration, bottling:) bottle = [] - if !bottling && (bottle_info = get_bottle_info(source[:bottle])) + if !bottling && (bottle_info = get_bottle_info(source[:bottle])) && + (stable_version = source.dig(:stable, :version)) bottle << { SPDXID: "SPDXRef-Bottle-#{name}", name: name.to_s, @@ -267,18 +275,18 @@ def generate_packages_json(runtime_dependency_declaration, compiler_declaration, { SPDXID: "SPDXRef-Archive-#{name}-src", name: name.to_s, - versionInfo: stable_version.to_s, + versionInfo: spec_version.to_s, filesAnalyzed: false, licenseDeclared: assert_value(nil), builtDate: source_modified_time.to_s, licenseConcluded: assert_value(license), - downloadLocation: source[:stable][:url], + downloadLocation: source[spec_symbol][:url], copyrightText: assert_value(nil), externalRefs: [], checksums: [ { algorithm: "SHA256", - checksumValue: source[:stable][:checksum].to_s, + checksumValue: source[spec_symbol][:checksum].to_s, }, ], }, @@ -362,13 +370,13 @@ def to_spdx_sbom(bottling:) { SPDXID: "SPDXRef-DOCUMENT", spdxVersion: "SPDX-2.3", - name: "SBOM-SPDX-#{name}-#{stable_version}", + name: "SBOM-SPDX-#{name}-#{spec_version}", creationInfo: { created: (Time.at(time).utc if time.present? && !bottling), creators: ["Tool: https://github.com/homebrew/brew@#{homebrew_version}"], }, dataLicense: "CC0-1.0", - documentNamespace: "https://formulae.brew.sh/spdx/#{name}-#{stable_version}.json", + documentNamespace: "https://formulae.brew.sh/spdx/#{name}-#{spec_version}.json", documentDescribes: packages.map { |dependency| dependency[:SPDXID] }, files: [], packages:, @@ -397,9 +405,14 @@ def tap Tap.fetch(tap_name) if tap_name end + sig { returns(Symbol) } + def spec_symbol + source.fetch(:spec).to_sym + end + sig { returns(T.nilable(Version)) } - def stable_version - source[:stable][:version] + def spec_version + source.fetch(spec_symbol)[:version] end sig { returns(Time) }