diff --git a/Library/Homebrew/sbom.rb b/Library/Homebrew/sbom.rb index 8b19e6945742b2..d92e022d70785b 100644 --- a/Library/Homebrew/sbom.rb +++ b/Library/Homebrew/sbom.rb @@ -25,40 +25,40 @@ def self.create(formula, compiler: nil, stdlib: nil) build = formula.build runtime_deps = formula.runtime_dependencies(undeclared: false) attributes = { - "name" => formula.name, - "homebrew_version" => HOMEBREW_VERSION, - "spdxfile" => formula.prefix/FILENAME, - "built_as_bottle" => build.bottle?, - "installed_as_dependency" => false, - "installed_on_request" => false, - "poured_from_bottle" => false, - "loaded_from_api" => false, - "time" => Time.now.to_i, - "source_modified_time" => formula.source_modified_time.to_i, - "compiler" => compiler, - "stdlib" => stdlib, - "aliases" => formula.aliases, - "runtime_dependencies" => SBOM.runtime_deps_hash(formula, runtime_deps), - "arch" => Hardware::CPU.arch, - "license" => SPDX.license_expression_to_string(formula.license), - "built_on" => DevelopmentTools.build_system_info, - "source" => { - "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, - "bottle" => formula.bottle_hash, - "stable" => { - "version" => formula.stable&.version, - "url" => formula.stable&.url, - "checksum" => formula.stable&.checksum, + :name => formula.name, + :homebrew_version => HOMEBREW_VERSION, + :spdxfile => formula.prefix/FILENAME, + :built_as_bottle => build.bottle?, + :installed_as_dependency => false, + :installed_on_request => false, + :poured_from_bottle => false, + :loaded_from_api => false, + :time => Time.now.to_i, + :source_modified_time => formula.source_modified_time.to_i, + :compiler => compiler, + :stdlib => stdlib, + :aliases => formula.aliases, + :runtime_dependencies => SBOM.runtime_deps_hash(formula, runtime_deps), + :arch => Hardware::CPU.arch, + :license => SPDX.license_expression_to_string(formula.license), + :built_on => DevelopmentTools.build_system_info, + :source => { + :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, + :bottle => formula.bottle_hash, + :stable => { + :version => formula.stable&.version, + :url => formula.stable&.url, + :checksum => formula.stable&.checksum, }, }, } # We can only get `tap_git_head` if the tap is installed locally - attributes["source"]["tap_git_head"] = T.must(formula.tap).git_head if formula.tap&.installed? + attributes[:source][:tap_git_head] = T.must(formula.tap).git_head if formula.tap&.installed? new(attributes) end @@ -108,37 +108,37 @@ def write def generate_relations_json(runtime_dependency_declaration, compiler_declaration) runtime = runtime_dependency_declaration.map do |dependency| { - "spdxElementId" => dependency["SPDXID"], - "relationshipType" => "RUNTIME_DEPENDENCY_OF", - "relatedSpdxElement" => "SPDXRef-Bottle-#{name}", + :spdxElementId => dependency[:SPDXID], + :relationshipType => :RUNTIME_DEPENDENCY_OF, + :relatedSpdxElement => "SPDXRef-Bottle-#{name}", } end - patches = source["patches"].map do |_patch| + patches = source[:patches].map do |_patch| { - "spdxElementId" => "SPDXRef-Patch-#{name}", - "relationshipType" => "PATCH_APPLIED", - "relatedSpdxElement" => "SPDXRef-Archive-#{name}-src", + :spdxElementId => "SPDXRef-Patch-#{name}", + :relationshipType => :PATCH_APPLIED, + :relatedSpdxElement => "SPDXRef-Archive-#{name}-src", } end base = [ { - "spdxElementId" => "SPDXRef-File-#{name}", - "relationshipType" => "PACKAGE_OF", - "relatedSpdxElement" => "SPDXRef-Archive-#{name}-src", + :spdxElementId => "SPDXRef-File-#{name}", + :relationshipType => :PACKAGE_OF, + :relatedSpdxElement => "SPDXRef-Archive-#{name}-src", }, { - "spdxElementId" => "SPDXRef-Compiler", - "relationshipType" => "BUILD_TOOL_OF", - "relatedSpdxElement" => "SPDXRef-Package-#{name}-src", + :spdxElementId => "SPDXRef-Compiler", + :relationshipType => :BUILD_TOOL_OF, + :relatedSpdxElement => "SPDXRef-Package-#{name}-src", }, ] if compiler_declaration["SPDXRef-Stdlib"].present? base += { - "spdxElementId" => "SPDXRef-Stdlib", - "relationshipType" => "DEPENDENCY_OF", - "relatedSpdxElement" => "SPDXRef-Bottle-#{name}", + :spdxElementId => "SPDXRef-Stdlib", + :relationshipType => :DEPENDENCY_OF, + :relatedSpdxElement => "SPDXRef-Bottle-#{name}", } end @@ -150,26 +150,26 @@ def generate_packages_json(runtime_dependency_declaration, compiler_declaration) bottle = [] if get_bottle_info(source["bottle"]) bottle << { - "SPDXID" => "SPDXRef-Bottle-#{name}", - "name" => name.to_s, - "versionInfo" => stable_version.to_s, - "filesAnalyzed" => false, - "licenseDeclared" => "NOASSERTION", - "builtDate" => source_modified_time.to_s, - "licenseConcluded" => license, - "downloadLocation" => T.must(get_bottle_info(source["bottle"]))["url"], - "copyrightText" => "NOASSERTION", - "externalRefs" => [ + :SPDXID => "SPDXRef-Bottle-#{name}", + :name => name.to_s, + :versionInfo => stable_version.to_s, + :filesAnalyzed => false, + :licenseDeclared => assert_value(nil), + :builtDate => source_modified_time.to_s, + :licenseConcluded => license, + :downloadLocation => T.must(get_bottle_info(source["bottle"]))["url"], + :copyrightText => assert_value(nil), + :externalRefs => [ { - "referenceCategory" => "PACKAGE-MANAGER", - "referenceLocator" => "pkg:brew/#{tap}/#{name}@#{stable_version}", - "referenceType" => "purl", + :referenceCategory => "PACKAGE-MANAGER", + :referenceLocator => "pkg:brew/#{tap}/#{name}@#{stable_version}", + :referenceType => "purl", }, ], - "checksums" => [ + :checksums => [ { - "algorithm" => "SHA256", - "checksumValue" => T.must(get_bottle_info(source["bottle"]))["sha256"], + :algorithm => "SHA256", + :checksumValue => T.must(get_bottle_info(source["bottle"]))["sha256"], }, ], } @@ -181,11 +181,11 @@ def generate_packages_json(runtime_dependency_declaration, compiler_declaration) "name" => name.to_s, "versionInfo" => stable_version.to_s, "filesAnalyzed" => false, - "licenseDeclared" => "NOASSERTION", + "licenseDeclared" => assert_value(nil), "builtDate" => source_modified_time.to_s, - "licenseConcluded" => license || "NOASSERTION", + "licenseConcluded" => assert_value(license), "downloadLocation" => source["stable"]["url"], - "copyrightText" => "NOASSERTION", + "copyrightText" => assert_value(nil), "externalRefs" => [], "checksums" => [ { @@ -209,14 +209,14 @@ def to_spdx_sbom "name" => dependency["name"], "versionInfo" => dependency["pkg_version"], "filesAnalyzed" => false, - "licenseDeclared" => "NOASSERTION", - "licenseConcluded" => dependency["license"] || "NOASSERTION", - "downloadLocation" => bottle_info.present? ? bottle_info["url"] : "NOASSERTION", - "copyrightText" => "NOASSERTION", + "licenseDeclared" => assert_value(nil), + "licenseConcluded" => assert_value(dependency["license"]), + "downloadLocation" => assert_value(bottle_info.present? ? bottle_info["url"] : nil), + "copyrightText" => assert_value(nil), "checksums" => [ { "algorithm" => "SHA256", - "checksumValue" => bottle_info.present? ? bottle_info["sha256"] : "NOASSERTION", + "checksumValue" => assert_value(bottle_info.present? ? bottle_info["sha256"] : nil), }, ], "externalRefs" => [ @@ -232,49 +232,49 @@ def to_spdx_sbom compiler_info = { "SPDXRef-Compiler" => { - "SPDXID" => "SPDXRef-Compiler", - "name" => compiler.to_s, - "versionInfo" => T.unsafe(built_on["xcode"]) || "NOASSERTION", - "filesAnalyzed" => false, - "licenseDeclared" => "NOASSERTION", - "licenseConcluded" => "NOASSERTION", - "copyrightText" => "NOASSERTION", - "downloadLocation" => "NOASSERTION", - "checksums" => [], - "externalRefs" => [], + :SPDXID => "SPDXRef-Compiler", + :name => compiler.to_s, + :versionInfo => assert_value(built_on["xcode"]), + :filesAnalyzed => false, + :licenseDeclared => assert_value(nil), + :licenseConcluded => assert_value(nil), + :copyrightText => assert_value(nil), + :downloadLocation => assert_value(nil), + :checksums => [], + :externalRefs => [], }, } if stdlib.present? compiler_info["SPDXRef-Stdlib"] = { - "SPDXID" => "SPDXRef-Stdlib", - "name" => stdlib, - "versionInfo" => stdlib, - "filesAnalyzed" => false, - "licenseDeclared" => "NOASSERTION", - "licenseConcluded" => "NOASSERTION", - "copyrightText" => "NOASSERTION", - "downloadLocation" => "NOASSERTION", - "checksums" => [], - "externalRefs" => [], + :SPDXID => "SPDXRef-Stdlib", + :name => stdlib, + :versionInfo => stdlib, + :filesAnalyzed => false, + :licenseDeclared => assert_value(nil), + :licenseConcluded => assert_value(nil), + :copyrightText => assert_value(nil), + :downloadLocation => assert_value(nil), + :checksums => [], + :externalRefs => [], } end packages = generate_packages_json(runtime_full, compiler_info) { - "SPDXID" => "SPDXRef-DOCUMENT", - "spdxVersion" => "SPDX-2.3", - "name" => "SBOM-SPDX-#{name}-#{stable_version}", - "creationInfo" => { - "created" => DateTime.now.to_s, - "creators" => ["Tool: https://github.com/homebrew/brew@#{homebrew_version}"], + :SPDXID => "SPDXRef-DOCUMENT", + :spdxVersion => "SPDX-2.3", + :name => "SBOM-SPDX-#{name}-#{stable_version}", + :creationInfo => { + :created => DateTime.now.to_s, + :creators => ["Tool: https://github.com/homebrew/brew@#{homebrew_version}"], }, - "dataLicense" => "CC0-1.0", - "documentNamespace" => "https://formulae.brew.sh/spdx/#{name}-#{stable_version}.json", - "documentDescribes" => packages.map { |dependency| dependency["SPDXID"] }, - "files" => [], - "packages" => packages, - "relationships" => generate_relations_json(runtime_full, compiler_info), + :dataLicense => "CC0-1.0", + :documentNamespace => "https://formulae.brew.sh/spdx/#{name}-#{stable_version}.json", + :documentDescribes => packages.map { |dependency| dependency["SPDXID"] }, + :files => [], + :packages => packages, + :relationships => generate_relations_json(runtime_full, compiler_info), } end @@ -283,14 +283,14 @@ def self.runtime_deps_hash(formula, deps) deps.map do |dep| f = dep.to_formula { - "full_name" => f.full_name, - "name" => f.name, - "version" => f.version.to_s, - "revision" => f.revision, - "pkg_version" => f.pkg_version.to_s, - "declared_directly" => formula.deps.include?(dep), - "license" => SPDX.license_expression_to_string(f.license), - "bottle" => f.bottle_hash, + :full_name => f.full_name, + :name => f.name, + :version => f.version.to_s, + :revision => f.revision, + :pkg_version => f.pkg_version.to_s, + :declared_directly => formula.deps.include?(dep), + :license => SPDX.license_expression_to_string(f.license), + :bottle => f.bottle_hash, } end end @@ -333,22 +333,29 @@ def bottle? sig { returns(T.nilable(Tap)) } def tap - tap_name = source["tap"] + tap_name = source[:tap] Tap.fetch(tap_name) if tap_name end sig { returns(Symbol) } def spec - source["spec"].to_sym + source[:spec].to_sym end sig { returns(T.nilable(Version)) } def stable_version - source["stable"]["version"] + source[:stable][:version] end sig { returns(Time) } def source_modified_time Time.at(@source_modified_time || 0) end + + sig { params(val: T.nilable(T.untyped)).returns(T.any(String,Symbol)) } + def assert_value(val) + return :NOASSERTION unless val.present? + + val + end end