| 
									
										
										
										
											2024-08-12 10:30:59 +01:00
										 |  |  | # typed: true # rubocop:todo Sorbet/StrictSigil | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  | # frozen_string_literal: true | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | require "cxxstdlib" | 
					
						
							|  |  |  | require "json" | 
					
						
							|  |  |  | require "development_tools" | 
					
						
							| 
									
										
										
										
											2025-06-09 19:06:16 +01:00
										 |  |  | require "cachable" | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  | require "utils/curl" | 
					
						
							| 
									
										
										
										
											2025-08-20 19:20:19 +01:00
										 |  |  | require "utils/output" | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | # Rather than calling `new` directly, use one of the class methods like {SBOM.create}. | 
					
						
							|  |  |  | class SBOM | 
					
						
							| 
									
										
										
										
											2025-08-20 19:20:19 +01:00
										 |  |  |   include Utils::Output::Mixin | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |   FILENAME = "sbom.spdx.json" | 
					
						
							| 
									
										
										
										
											2024-08-08 02:20:03 +01:00
										 |  |  |   SCHEMA_FILE = (HOMEBREW_LIBRARY_PATH/"data/schemas/sbom.json").freeze | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |   # Instantiates a {SBOM} for a new installation of a formula. | 
					
						
							| 
									
										
										
										
											2024-05-09 13:10:35 +01:00
										 |  |  |   sig { params(formula: Formula, tab: Tab).returns(T.attached_class) } | 
					
						
							|  |  |  |   def self.create(formula, tab) | 
					
						
							| 
									
										
										
										
											2024-05-20 09:41:29 +01:00
										 |  |  |     active_spec = if formula.stable? | 
					
						
							|  |  |  |       T.must(formula.stable) | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |       T.must(formula.head) | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |     active_spec_sym = formula.active_spec_sym | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |     attributes = { | 
					
						
							| 
									
										
										
										
											2024-05-09 13:10:35 +01:00
										 |  |  |       name:                 formula.name, | 
					
						
							| 
									
										
										
										
											2024-08-08 09:34:32 +01:00
										 |  |  |       homebrew_version:     HOMEBREW_VERSION, | 
					
						
							| 
									
										
										
										
											2024-05-09 13:10:35 +01:00
										 |  |  |       spdxfile:             SBOM.spdxfile(formula), | 
					
						
							| 
									
										
										
										
											2024-08-08 09:34:32 +01:00
										 |  |  |       time:                 tab.time || Time.now, | 
					
						
							| 
									
										
										
										
											2024-05-09 13:10:35 +01:00
										 |  |  |       source_modified_time: tab.source_modified_time.to_i, | 
					
						
							|  |  |  |       compiler:             tab.compiler, | 
					
						
							|  |  |  |       stdlib:               tab.stdlib, | 
					
						
							|  |  |  |       runtime_dependencies: SBOM.runtime_deps_hash(Array(tab.runtime_dependencies)), | 
					
						
							|  |  |  |       license:              SPDX.license_expression_to_string(formula.license), | 
					
						
							|  |  |  |       built_on:             DevelopmentTools.build_system_info, | 
					
						
							|  |  |  |       source:               { | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |         path:         formula.specified_path.to_s, | 
					
						
							|  |  |  |         tap:          formula.tap&.name, | 
					
						
							|  |  |  |         tap_git_head: nil, # Filled in later if possible | 
					
						
							| 
									
										
										
										
											2024-05-20 09:41:29 +01:00
										 |  |  |         spec:         active_spec_sym.to_s, | 
					
						
							|  |  |  |         patches:      active_spec.patches, | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |         bottle:       formula.bottle_hash, | 
					
						
							| 
									
										
										
										
											2024-05-20 09:41:29 +01:00
										 |  |  |         active_spec_sym =>       { | 
					
						
							|  |  |  |           version:  active_spec.version, | 
					
						
							|  |  |  |           url:      active_spec.url, | 
					
						
							|  |  |  |           checksum: active_spec.checksum, | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |         }, | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # 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? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     new(attributes) | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-09 13:10:35 +01:00
										 |  |  |   sig { params(formula: Formula).returns(Pathname) } | 
					
						
							|  |  |  |   def self.spdxfile(formula) | 
					
						
							|  |  |  |     formula.prefix/FILENAME | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-09 13:10:35 +01:00
										 |  |  |   sig { params(deps: T::Array[T::Hash[String, String]]).returns(T::Array[T::Hash[String, String]]) } | 
					
						
							|  |  |  |   def self.runtime_deps_hash(deps) | 
					
						
							|  |  |  |     deps.map do |dep| | 
					
						
							|  |  |  |       full_name = dep.fetch("full_name") | 
					
						
							|  |  |  |       dep_formula = Formula[full_name] | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         "full_name"           => full_name, | 
					
						
							|  |  |  |         "pkg_version"         => dep.fetch("pkg_version"), | 
					
						
							|  |  |  |         "name"                => dep_formula.name, | 
					
						
							|  |  |  |         "license"             => SPDX.license_expression_to_string(dep_formula.license), | 
					
						
							|  |  |  |         "bottle"              => dep_formula.bottle_hash, | 
					
						
							|  |  |  |         "formula_pkg_version" => dep_formula.pkg_version.to_s, | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-09 13:10:35 +01:00
										 |  |  |   sig { params(formula: Formula).returns(T::Boolean) } | 
					
						
							|  |  |  |   def self.exist?(formula) | 
					
						
							|  |  |  |     spdxfile(formula).exist? | 
					
						
							|  |  |  |   end | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-07 17:24:31 +01:00
										 |  |  |   sig { returns(T::Hash[String, T.untyped]) } | 
					
						
							|  |  |  |   def self.schema | 
					
						
							|  |  |  |     @schema ||= JSON.parse(SCHEMA_FILE.read, freeze: true) | 
					
						
							| 
									
										
										
										
											2024-05-09 13:10:35 +01:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-08 09:34:32 +01:00
										 |  |  |   sig { params(bottling: T::Boolean).returns(T::Array[T::Hash[String, T.untyped]]) } | 
					
						
							|  |  |  |   def schema_validation_errors(bottling: false) | 
					
						
							| 
									
										
										
										
											2025-08-20 19:20:19 +01:00
										 |  |  |     unless Homebrew.require? "json_schemer" | 
					
						
							| 
									
										
										
										
											2024-05-09 13:10:35 +01:00
										 |  |  |       error_message = "Need json_schemer to validate SBOM, run `brew install-bundler-gems --add-groups=bottle`!" | 
					
						
							|  |  |  |       odie error_message if ENV["HOMEBREW_ENFORCE_SBOM"] | 
					
						
							| 
									
										
										
										
											2024-08-08 09:34:32 +01:00
										 |  |  |       return [] | 
					
						
							| 
									
										
										
										
											2024-05-09 13:10:35 +01:00
										 |  |  |     end | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-07 17:24:31 +01:00
										 |  |  |     schemer = JSONSchemer.schema(SBOM.schema) | 
					
						
							| 
									
										
										
										
											2024-05-13 07:36:51 +01:00
										 |  |  |     data = to_spdx_sbom(bottling:) | 
					
						
							| 
									
										
										
										
											2024-08-08 09:34:32 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     schemer.validate(data).map { |error| error["error"] } | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   sig { params(bottling: T::Boolean).returns(T::Boolean) } | 
					
						
							|  |  |  |   def valid?(bottling: false) | 
					
						
							|  |  |  |     validation_errors = schema_validation_errors(bottling:) | 
					
						
							|  |  |  |     return true if validation_errors.empty? | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     opoo "SBOM validation errors:" | 
					
						
							| 
									
										
										
										
											2024-08-08 09:34:32 +01:00
										 |  |  |     validation_errors.each(&:puts) | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-09 13:10:35 +01:00
										 |  |  |     odie "Failed to validate SBOM against JSON schema!" if ENV["HOMEBREW_ENFORCE_SBOM"] | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     false | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-13 07:36:51 +01:00
										 |  |  |   sig { params(validate: T::Boolean, bottling: T::Boolean).void } | 
					
						
							|  |  |  |   def write(validate: true, bottling: false) | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |     # If this is a new installation, the cache of installed formulae | 
					
						
							|  |  |  |     # will no longer be valid. | 
					
						
							|  |  |  |     Formula.clear_cache unless spdxfile.exist? | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-26 15:28:53 +01:00
										 |  |  |     if validate && !valid?(bottling:) | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |       opoo "SBOM is not valid, not writing to disk!" | 
					
						
							|  |  |  |       return | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-13 07:36:51 +01:00
										 |  |  |     spdxfile.atomic_write(JSON.pretty_generate(to_spdx_sbom(bottling:))) | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-09 13:10:35 +01:00
										 |  |  |   private | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   attr_reader :name, :homebrew_version, :time, :stdlib, :source, :built_on, :license | 
					
						
							|  |  |  |   attr_accessor :spdxfile | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   sig { params(attributes: Hash).void } | 
					
						
							|  |  |  |   def initialize(attributes = {}) | 
					
						
							|  |  |  |     attributes.each { |key, value| instance_variable_set(:"@#{key}", value) } | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-13 07:36:51 +01:00
										 |  |  |   sig { | 
					
						
							|  |  |  |     params( | 
					
						
							|  |  |  |       runtime_dependency_declaration: T::Array[Hash], | 
					
						
							|  |  |  |       compiler_declaration:           Hash, | 
					
						
							|  |  |  |       bottling:                       T::Boolean, | 
					
						
							|  |  |  |     ).returns(T::Array[Hash]) | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   def generate_relations_json(runtime_dependency_declaration, compiler_declaration, bottling:) | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |     runtime = runtime_dependency_declaration.map do |dependency| | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         spdxElementId:      dependency[:SPDXID], | 
					
						
							|  |  |  |         relationshipType:   "RUNTIME_DEPENDENCY_OF", | 
					
						
							|  |  |  |         relatedSpdxElement: "SPDXRef-Bottle-#{name}", | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2024-05-13 07:36:51 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |     patches = source[:patches].each_with_index.map do |_patch, index| | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         spdxElementId:      "SPDXRef-Patch-#{name}-#{index}", | 
					
						
							|  |  |  |         relationshipType:   "PATCH_APPLIED", | 
					
						
							|  |  |  |         relatedSpdxElement: "SPDXRef-Archive-#{name}-src", | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-13 07:36:51 +01:00
										 |  |  |     base = T.let([{ | 
					
						
							|  |  |  |       spdxElementId:      "SPDXRef-File-#{name}", | 
					
						
							|  |  |  |       relationshipType:   "PACKAGE_OF", | 
					
						
							|  |  |  |       relatedSpdxElement: "SPDXRef-Archive-#{name}-src", | 
					
						
							|  |  |  |     }], T::Array[Hash]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     unless bottling | 
					
						
							|  |  |  |       base << { | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |         spdxElementId:      "SPDXRef-Compiler", | 
					
						
							|  |  |  |         relationshipType:   "BUILD_TOOL_OF", | 
					
						
							|  |  |  |         relatedSpdxElement: "SPDXRef-Package-#{name}-src", | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2024-05-13 07:36:51 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |       if compiler_declaration["SPDXRef-Stdlib"].present? | 
					
						
							|  |  |  |         base << { | 
					
						
							|  |  |  |           spdxElementId:      "SPDXRef-Stdlib", | 
					
						
							|  |  |  |           relationshipType:   "DEPENDENCY_OF", | 
					
						
							|  |  |  |           relatedSpdxElement: "SPDXRef-Bottle-#{name}", | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     runtime + patches + base | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   sig { | 
					
						
							|  |  |  |     params(runtime_dependency_declaration: T::Array[Hash], | 
					
						
							| 
									
										
										
										
											2024-05-13 07:36:51 +01:00
										 |  |  |            compiler_declaration:           Hash, | 
					
						
							|  |  |  |            bottling:                       T::Boolean).returns( | 
					
						
							|  |  |  |              T::Array[ | 
					
						
							|  |  |  |               T::Hash[ | 
					
						
							|  |  |  |                 Symbol, | 
					
						
							| 
									
										
										
										
											2025-05-05 14:35:08 -07:00
										 |  |  |                 T.any(String, T::Array[T::Hash[Symbol, String]]), | 
					
						
							| 
									
										
										
										
											2024-05-13 07:36:51 +01:00
										 |  |  |               ], | 
					
						
							|  |  |  |             ], | 
					
						
							|  |  |  |            ) | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2024-05-13 07:36:51 +01:00
										 |  |  |   def generate_packages_json(runtime_dependency_declaration, compiler_declaration, bottling:) | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |     bottle = [] | 
					
						
							| 
									
										
										
										
											2024-05-20 09:41:29 +01:00
										 |  |  |     if !bottling && (bottle_info = get_bottle_info(source[:bottle])) && | 
					
						
							|  |  |  |        (stable_version = source.dig(:stable, :version)) | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |       bottle << { | 
					
						
							|  |  |  |         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, | 
					
						
							| 
									
										
										
										
											2024-08-08 09:34:32 +01:00
										 |  |  |         licenseConcluded: assert_value(license), | 
					
						
							| 
									
										
										
										
											2024-05-09 13:10:35 +01:00
										 |  |  |         downloadLocation: bottle_info.fetch("url"), | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |         copyrightText:    assert_value(nil), | 
					
						
							|  |  |  |         externalRefs:     [ | 
					
						
							|  |  |  |           { | 
					
						
							|  |  |  |             referenceCategory: "PACKAGE-MANAGER", | 
					
						
							|  |  |  |             referenceLocator:  "pkg:brew/#{tap}/#{name}@#{stable_version}", | 
					
						
							|  |  |  |             referenceType:     "purl", | 
					
						
							|  |  |  |           }, | 
					
						
							|  |  |  |         ], | 
					
						
							|  |  |  |         checksums:        [ | 
					
						
							|  |  |  |           { | 
					
						
							|  |  |  |             algorithm:     "SHA256", | 
					
						
							| 
									
										
										
										
											2024-05-09 13:10:35 +01:00
										 |  |  |             checksumValue: bottle_info.fetch("sha256"), | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |           }, | 
					
						
							|  |  |  |         ], | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-13 07:36:51 +01:00
										 |  |  |     compiler_declarations = if bottling | 
					
						
							|  |  |  |       [] | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |       compiler_declaration.values | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |     [ | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         SPDXID:           "SPDXRef-Archive-#{name}-src", | 
					
						
							|  |  |  |         name:             name.to_s, | 
					
						
							| 
									
										
										
										
											2024-05-20 09:41:29 +01:00
										 |  |  |         versionInfo:      spec_version.to_s, | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |         filesAnalyzed:    false, | 
					
						
							|  |  |  |         licenseDeclared:  assert_value(nil), | 
					
						
							|  |  |  |         builtDate:        source_modified_time.to_s, | 
					
						
							|  |  |  |         licenseConcluded: assert_value(license), | 
					
						
							| 
									
										
										
										
											2024-05-20 09:41:29 +01:00
										 |  |  |         downloadLocation: source[spec_symbol][:url], | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |         copyrightText:    assert_value(nil), | 
					
						
							|  |  |  |         externalRefs:     [], | 
					
						
							|  |  |  |         checksums:        [ | 
					
						
							|  |  |  |           { | 
					
						
							|  |  |  |             algorithm:     "SHA256", | 
					
						
							| 
									
										
										
										
											2024-05-20 09:41:29 +01:00
										 |  |  |             checksumValue: source[spec_symbol][:checksum].to_s, | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |           }, | 
					
						
							|  |  |  |         ], | 
					
						
							|  |  |  |       }, | 
					
						
							| 
									
										
										
										
											2024-05-13 07:36:51 +01:00
										 |  |  |     ] + runtime_dependency_declaration + compiler_declarations + bottle | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-26 16:48:48 +01:00
										 |  |  |   sig { | 
					
						
							|  |  |  |     params(bottling: T::Boolean).returns(T::Array[T::Hash[Symbol, | 
					
						
							|  |  |  |                                                           T.any(T::Boolean, String, | 
					
						
							|  |  |  |                                                                 T::Array[T::Hash[Symbol, String]])]]) | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   def full_spdx_runtime_dependencies(bottling:) | 
					
						
							| 
									
										
										
										
											2024-08-02 11:42:35 +01:00
										 |  |  |     return [] if bottling || @runtime_dependencies.blank? | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     @runtime_dependencies.compact.filter_map do |dependency| | 
					
						
							|  |  |  |       next unless dependency.present? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       bottle_info = get_bottle_info(dependency["bottle"]) | 
					
						
							|  |  |  |       next unless bottle_info.present? | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-09 13:10:35 +01:00
										 |  |  |       # Only set bottle URL if the dependency is the same version as the formula/bottle. | 
					
						
							|  |  |  |       bottle_url = bottle_info["url"] if dependency["pkg_version"] == dependency["formula_pkg_version"] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-26 16:48:48 +01:00
										 |  |  |       dependency_json = { | 
					
						
							| 
									
										
										
										
											2024-05-09 13:10:35 +01:00
										 |  |  |         SPDXID:           "SPDXRef-Package-SPDXRef-#{dependency["name"].tr("/", "-")}-#{dependency["pkg_version"]}", | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |         name:             dependency["name"], | 
					
						
							|  |  |  |         versionInfo:      dependency["pkg_version"], | 
					
						
							|  |  |  |         filesAnalyzed:    false, | 
					
						
							|  |  |  |         licenseDeclared:  assert_value(nil), | 
					
						
							|  |  |  |         licenseConcluded: assert_value(dependency["license"]), | 
					
						
							| 
									
										
										
										
											2024-05-09 13:10:35 +01:00
										 |  |  |         downloadLocation: assert_value(bottle_url), | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |         copyrightText:    assert_value(nil), | 
					
						
							|  |  |  |         checksums:        [ | 
					
						
							|  |  |  |           { | 
					
						
							|  |  |  |             algorithm:     "SHA256", | 
					
						
							| 
									
										
										
										
											2024-05-09 13:10:35 +01:00
										 |  |  |             checksumValue: assert_value(bottle_info["sha256"]), | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |           }, | 
					
						
							|  |  |  |         ], | 
					
						
							|  |  |  |         externalRefs:     [ | 
					
						
							|  |  |  |           { | 
					
						
							|  |  |  |             referenceCategory: "PACKAGE-MANAGER", | 
					
						
							| 
									
										
										
										
											2024-05-09 13:10:35 +01:00
										 |  |  |             referenceLocator:  "pkg:brew/#{dependency["full_name"]}@#{dependency["pkg_version"]}", | 
					
						
							| 
									
										
										
										
											2024-08-08 02:20:03 +01:00
										 |  |  |             referenceType:     "purl", | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |           }, | 
					
						
							|  |  |  |         ], | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2024-05-26 16:48:48 +01:00
										 |  |  |       dependency_json | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-13 07:36:51 +01:00
										 |  |  |   sig { params(bottling: T::Boolean).returns(T::Hash[Symbol, T.any(String, T::Array[T::Hash[Symbol, String]])]) } | 
					
						
							|  |  |  |   def to_spdx_sbom(bottling:) | 
					
						
							| 
									
										
										
										
											2024-05-26 16:48:48 +01:00
										 |  |  |     runtime_full = full_spdx_runtime_dependencies(bottling:) | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     compiler_info = { | 
					
						
							|  |  |  |       "SPDXRef-Compiler" => { | 
					
						
							|  |  |  |         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", | 
					
						
							| 
									
										
										
										
											2024-08-08 09:34:32 +01:00
										 |  |  |         name:             stdlib.to_s, | 
					
						
							|  |  |  |         versionInfo:      stdlib.to_s, | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |         filesAnalyzed:    false, | 
					
						
							|  |  |  |         licenseDeclared:  assert_value(nil), | 
					
						
							|  |  |  |         licenseConcluded: assert_value(nil), | 
					
						
							|  |  |  |         copyrightText:    assert_value(nil), | 
					
						
							|  |  |  |         downloadLocation: assert_value(nil), | 
					
						
							|  |  |  |         checksums:        [], | 
					
						
							|  |  |  |         externalRefs:     [], | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-08 09:34:32 +01:00
										 |  |  |     # Improve reproducibility when bottling. | 
					
						
							|  |  |  |     if bottling | 
					
						
							|  |  |  |       created = source_modified_time.iso8601 | 
					
						
							|  |  |  |       creators = ["Tool: https://github.com/Homebrew/brew"] | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |       created = Time.at(time).utc.iso8601 | 
					
						
							|  |  |  |       creators = ["Tool: https://github.com/Homebrew/brew@#{homebrew_version}"] | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-13 07:36:51 +01:00
										 |  |  |     packages = generate_packages_json(runtime_full, compiler_info, bottling:) | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |     { | 
					
						
							|  |  |  |       SPDXID:            "SPDXRef-DOCUMENT", | 
					
						
							|  |  |  |       spdxVersion:       "SPDX-2.3", | 
					
						
							| 
									
										
										
										
											2024-05-20 09:41:29 +01:00
										 |  |  |       name:              "SBOM-SPDX-#{name}-#{spec_version}", | 
					
						
							| 
									
										
										
										
											2024-08-08 09:34:32 +01:00
										 |  |  |       creationInfo:      { created:, creators: }, | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |       dataLicense:       "CC0-1.0", | 
					
						
							| 
									
										
										
										
											2024-05-20 09:41:29 +01:00
										 |  |  |       documentNamespace: "https://formulae.brew.sh/spdx/#{name}-#{spec_version}.json", | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |       documentDescribes: packages.map { |dependency| dependency[:SPDXID] }, | 
					
						
							|  |  |  |       files:             [], | 
					
						
							|  |  |  |       packages:, | 
					
						
							| 
									
										
										
										
											2024-05-13 07:36:51 +01:00
										 |  |  |       relationships:     generate_relations_json(runtime_full, compiler_info, bottling:), | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |     } | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   sig { params(base: T.nilable(T::Hash[String, Hash])).returns(T.nilable(T::Hash[String, String])) } | 
					
						
							|  |  |  |   def get_bottle_info(base) | 
					
						
							|  |  |  |     return unless base.present? | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-09 13:10:35 +01:00
										 |  |  |     files = base["files"].presence | 
					
						
							|  |  |  |     return unless files | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-09 13:10:35 +01:00
										 |  |  |     files[Utils::Bottles.tag.to_sym] || files[:all] | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   sig { returns(Symbol) } | 
					
						
							|  |  |  |   def compiler | 
					
						
							| 
									
										
										
										
											2024-05-09 13:10:35 +01:00
										 |  |  |     @compiler.presence&.to_sym || DevelopmentTools.default_compiler | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   sig { returns(T.nilable(Tap)) } | 
					
						
							|  |  |  |   def tap | 
					
						
							|  |  |  |     tap_name = source[:tap] | 
					
						
							|  |  |  |     Tap.fetch(tap_name) if tap_name | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-20 09:41:29 +01:00
										 |  |  |   sig { returns(Symbol) } | 
					
						
							|  |  |  |   def spec_symbol | 
					
						
							|  |  |  |     source.fetch(:spec).to_sym | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |   sig { returns(T.nilable(Version)) } | 
					
						
							| 
									
										
										
										
											2024-05-20 09:41:29 +01:00
										 |  |  |   def spec_version | 
					
						
							|  |  |  |     source.fetch(spec_symbol)[:version] | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   sig { returns(Time) } | 
					
						
							|  |  |  |   def source_modified_time | 
					
						
							| 
									
										
										
										
											2024-05-13 07:36:51 +01:00
										 |  |  |     Time.at(@source_modified_time).utc | 
					
						
							| 
									
										
										
										
											2024-02-05 17:42:27 +01:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   sig { params(val: T.untyped).returns(T.any(String, Symbol)) } | 
					
						
							|  |  |  |   def assert_value(val) | 
					
						
							|  |  |  |     return :NOASSERTION.to_s unless val.present? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     val | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | end |