commit
b6d72309db
@ -494,7 +494,6 @@ module Homebrew
|
|||||||
Tab.clear_cache
|
Tab.clear_cache
|
||||||
Dependency.clear_cache
|
Dependency.clear_cache
|
||||||
Requirement.clear_cache
|
Requirement.clear_cache
|
||||||
SBOM.clear_cache
|
|
||||||
|
|
||||||
tab = keg.tab
|
tab = keg.tab
|
||||||
original_tab = tab.dup
|
original_tab = tab.dup
|
||||||
@ -509,7 +508,7 @@ module Homebrew
|
|||||||
end
|
end
|
||||||
|
|
||||||
sbom = SBOM.create(formula, tab)
|
sbom = SBOM.create(formula, tab)
|
||||||
sbom.write
|
sbom.write(bottling: true)
|
||||||
|
|
||||||
keg.consistent_reproducible_symlink_permissions!
|
keg.consistent_reproducible_symlink_permissions!
|
||||||
|
|
||||||
|
@ -829,8 +829,8 @@ on_request: installed_on_request?, options:)
|
|||||||
tab.runtime_dependencies = Tab.runtime_deps_hash(formula, f_runtime_deps)
|
tab.runtime_dependencies = Tab.runtime_deps_hash(formula, f_runtime_deps)
|
||||||
tab.write
|
tab.write
|
||||||
|
|
||||||
# write a SBOM file (if we don't already have one and aren't bottling)
|
# write/update a SBOM file (if we aren't bottling)
|
||||||
if !build_bottle? && !SBOM.exist?(formula)
|
unless build_bottle?
|
||||||
sbom = SBOM.create(formula, tab)
|
sbom = SBOM.create(formula, tab)
|
||||||
sbom.write(validate: Homebrew::EnvConfig.developer?)
|
sbom.write(validate: Homebrew::EnvConfig.developer?)
|
||||||
end
|
end
|
||||||
|
@ -9,8 +9,6 @@ require "utils/curl"
|
|||||||
|
|
||||||
# Rather than calling `new` directly, use one of the class methods like {SBOM.create}.
|
# Rather than calling `new` directly, use one of the class methods like {SBOM.create}.
|
||||||
class SBOM
|
class SBOM
|
||||||
extend Cachable
|
|
||||||
|
|
||||||
FILENAME = "sbom.spdx.json"
|
FILENAME = "sbom.spdx.json"
|
||||||
SCHEMA_URL = "https://spdx.github.io/spdx-3-model/model.jsonld"
|
SCHEMA_URL = "https://spdx.github.io/spdx-3-model/model.jsonld"
|
||||||
SCHEMA_FILENAME = "sbom.spdx.schema.3.json"
|
SCHEMA_FILENAME = "sbom.spdx.schema.3.json"
|
||||||
@ -23,7 +21,7 @@ class SBOM
|
|||||||
name: formula.name,
|
name: formula.name,
|
||||||
homebrew_version: HOMEBREW_VERSION,
|
homebrew_version: HOMEBREW_VERSION,
|
||||||
spdxfile: SBOM.spdxfile(formula),
|
spdxfile: SBOM.spdxfile(formula),
|
||||||
time: Time.now.to_i,
|
time: tab.time,
|
||||||
source_modified_time: tab.source_modified_time.to_i,
|
source_modified_time: tab.source_modified_time.to_i,
|
||||||
compiler: tab.compiler,
|
compiler: tab.compiler,
|
||||||
stdlib: tab.stdlib,
|
stdlib: tab.stdlib,
|
||||||
@ -116,8 +114,8 @@ class SBOM
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
sig { returns(T::Boolean) }
|
sig { params(bottling: T::Boolean).returns(T::Boolean) }
|
||||||
def valid?
|
def valid?(bottling: false)
|
||||||
unless require? "json_schemer"
|
unless require? "json_schemer"
|
||||||
error_message = "Need json_schemer to validate SBOM, run `brew install-bundler-gems --add-groups=bottle`!"
|
error_message = "Need json_schemer to validate SBOM, run `brew install-bundler-gems --add-groups=bottle`!"
|
||||||
odie error_message if ENV["HOMEBREW_ENFORCE_SBOM"]
|
odie error_message if ENV["HOMEBREW_ENFORCE_SBOM"]
|
||||||
@ -132,7 +130,7 @@ class SBOM
|
|||||||
end
|
end
|
||||||
|
|
||||||
schemer = JSONSchemer.schema(schema)
|
schemer = JSONSchemer.schema(schema)
|
||||||
data = to_spdx_sbom
|
data = to_spdx_sbom(bottling:)
|
||||||
return true if schemer.valid?(data)
|
return true if schemer.valid?(data)
|
||||||
|
|
||||||
opoo "SBOM validation errors:"
|
opoo "SBOM validation errors:"
|
||||||
@ -145,20 +143,18 @@ class SBOM
|
|||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
sig { params(validate: T::Boolean).void }
|
sig { params(validate: T::Boolean, bottling: T::Boolean).void }
|
||||||
def write(validate: true)
|
def write(validate: true, bottling: false)
|
||||||
# If this is a new installation, the cache of installed formulae
|
# If this is a new installation, the cache of installed formulae
|
||||||
# will no longer be valid.
|
# will no longer be valid.
|
||||||
Formula.clear_cache unless spdxfile.exist?
|
Formula.clear_cache unless spdxfile.exist?
|
||||||
|
|
||||||
self.class.cache[spdxfile] = self
|
if validate && !valid?(bottling:)
|
||||||
|
|
||||||
if validate && !valid?
|
|
||||||
opoo "SBOM is not valid, not writing to disk!"
|
opoo "SBOM is not valid, not writing to disk!"
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
spdxfile.atomic_write(JSON.pretty_generate(to_spdx_sbom))
|
spdxfile.atomic_write(JSON.pretty_generate(to_spdx_sbom(bottling:)))
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@ -171,8 +167,14 @@ class SBOM
|
|||||||
attributes.each { |key, value| instance_variable_set(:"@#{key}", value) }
|
attributes.each { |key, value| instance_variable_set(:"@#{key}", value) }
|
||||||
end
|
end
|
||||||
|
|
||||||
sig { params(runtime_dependency_declaration: T::Array[Hash], compiler_declaration: Hash).returns(T::Array[Hash]) }
|
sig {
|
||||||
def generate_relations_json(runtime_dependency_declaration, compiler_declaration)
|
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:)
|
||||||
runtime = runtime_dependency_declaration.map do |dependency|
|
runtime = runtime_dependency_declaration.map do |dependency|
|
||||||
{
|
{
|
||||||
spdxElementId: dependency[:SPDXID],
|
spdxElementId: dependency[:SPDXID],
|
||||||
@ -180,6 +182,7 @@ class SBOM
|
|||||||
relatedSpdxElement: "SPDXRef-Bottle-#{name}",
|
relatedSpdxElement: "SPDXRef-Bottle-#{name}",
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
patches = source[:patches].each_with_index.map do |_patch, index|
|
patches = source[:patches].each_with_index.map do |_patch, index|
|
||||||
{
|
{
|
||||||
spdxElementId: "SPDXRef-Patch-#{name}-#{index}",
|
spdxElementId: "SPDXRef-Patch-#{name}-#{index}",
|
||||||
@ -188,25 +191,26 @@ class SBOM
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
base = [
|
base = T.let([{
|
||||||
{
|
spdxElementId: "SPDXRef-File-#{name}",
|
||||||
spdxElementId: "SPDXRef-File-#{name}",
|
relationshipType: "PACKAGE_OF",
|
||||||
relationshipType: "PACKAGE_OF",
|
relatedSpdxElement: "SPDXRef-Archive-#{name}-src",
|
||||||
relatedSpdxElement: "SPDXRef-Archive-#{name}-src",
|
}], T::Array[Hash])
|
||||||
},
|
|
||||||
{
|
unless bottling
|
||||||
|
base << {
|
||||||
spdxElementId: "SPDXRef-Compiler",
|
spdxElementId: "SPDXRef-Compiler",
|
||||||
relationshipType: "BUILD_TOOL_OF",
|
relationshipType: "BUILD_TOOL_OF",
|
||||||
relatedSpdxElement: "SPDXRef-Package-#{name}-src",
|
relatedSpdxElement: "SPDXRef-Package-#{name}-src",
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
if compiler_declaration["SPDXRef-Stdlib"].present?
|
|
||||||
base << {
|
|
||||||
spdxElementId: "SPDXRef-Stdlib",
|
|
||||||
relationshipType: "DEPENDENCY_OF",
|
|
||||||
relatedSpdxElement: "SPDXRef-Bottle-#{name}",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if compiler_declaration["SPDXRef-Stdlib"].present?
|
||||||
|
base << {
|
||||||
|
spdxElementId: "SPDXRef-Stdlib",
|
||||||
|
relationshipType: "DEPENDENCY_OF",
|
||||||
|
relatedSpdxElement: "SPDXRef-Bottle-#{name}",
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
runtime + patches + base
|
runtime + patches + base
|
||||||
@ -214,13 +218,19 @@ class SBOM
|
|||||||
|
|
||||||
sig {
|
sig {
|
||||||
params(runtime_dependency_declaration: T::Array[Hash],
|
params(runtime_dependency_declaration: T::Array[Hash],
|
||||||
compiler_declaration: Hash).returns(T::Array[T::Hash[Symbol,
|
compiler_declaration: Hash,
|
||||||
T.any(String,
|
bottling: T::Boolean).returns(
|
||||||
T::Array[T::Hash[Symbol, String]])]])
|
T::Array[
|
||||||
|
T::Hash[
|
||||||
|
Symbol,
|
||||||
|
T.any(String, T::Array[T::Hash[Symbol, String]])
|
||||||
|
],
|
||||||
|
],
|
||||||
|
)
|
||||||
}
|
}
|
||||||
def generate_packages_json(runtime_dependency_declaration, compiler_declaration)
|
def generate_packages_json(runtime_dependency_declaration, compiler_declaration, bottling:)
|
||||||
bottle = []
|
bottle = []
|
||||||
if (bottle_info = get_bottle_info(source[:bottle]))
|
if !bottling && (bottle_info = get_bottle_info(source[:bottle]))
|
||||||
bottle << {
|
bottle << {
|
||||||
SPDXID: "SPDXRef-Bottle-#{name}",
|
SPDXID: "SPDXRef-Bottle-#{name}",
|
||||||
name: name.to_s,
|
name: name.to_s,
|
||||||
@ -247,6 +257,12 @@ class SBOM
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
compiler_declarations = if bottling
|
||||||
|
[]
|
||||||
|
else
|
||||||
|
compiler_declaration.values
|
||||||
|
end
|
||||||
|
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
SPDXID: "SPDXRef-Archive-#{name}-src",
|
SPDXID: "SPDXRef-Archive-#{name}-src",
|
||||||
@ -266,7 +282,7 @@ class SBOM
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
] + runtime_dependency_declaration + compiler_declaration.values + bottle
|
] + runtime_dependency_declaration + compiler_declarations + bottle
|
||||||
end
|
end
|
||||||
|
|
||||||
sig { returns(T::Array[T::Hash[Symbol, T.any(T::Boolean, String, T::Array[T::Hash[Symbol, String]])]]) }
|
sig { returns(T::Array[T::Hash[Symbol, T.any(T::Boolean, String, T::Array[T::Hash[Symbol, String]])]]) }
|
||||||
@ -308,8 +324,8 @@ class SBOM
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
sig { returns(T::Hash[Symbol, T.any(String, T::Array[T::Hash[Symbol, String]])]) }
|
sig { params(bottling: T::Boolean).returns(T::Hash[Symbol, T.any(String, T::Array[T::Hash[Symbol, String]])]) }
|
||||||
def to_spdx_sbom
|
def to_spdx_sbom(bottling:)
|
||||||
runtime_full = full_spdx_runtime_dependencies
|
runtime_full = full_spdx_runtime_dependencies
|
||||||
|
|
||||||
compiler_info = {
|
compiler_info = {
|
||||||
@ -342,13 +358,13 @@ class SBOM
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
packages = generate_packages_json(runtime_full, compiler_info)
|
packages = generate_packages_json(runtime_full, compiler_info, bottling:)
|
||||||
{
|
{
|
||||||
SPDXID: "SPDXRef-DOCUMENT",
|
SPDXID: "SPDXRef-DOCUMENT",
|
||||||
spdxVersion: "SPDX-2.3",
|
spdxVersion: "SPDX-2.3",
|
||||||
name: "SBOM-SPDX-#{name}-#{stable_version}",
|
name: "SBOM-SPDX-#{name}-#{stable_version}",
|
||||||
creationInfo: {
|
creationInfo: {
|
||||||
created: DateTime.now.to_s,
|
created: (Time.at(time).utc if time.present? && !bottling),
|
||||||
creators: ["Tool: https://github.com/homebrew/brew@#{homebrew_version}"],
|
creators: ["Tool: https://github.com/homebrew/brew@#{homebrew_version}"],
|
||||||
},
|
},
|
||||||
dataLicense: "CC0-1.0",
|
dataLicense: "CC0-1.0",
|
||||||
@ -356,7 +372,7 @@ class SBOM
|
|||||||
documentDescribes: packages.map { |dependency| dependency[:SPDXID] },
|
documentDescribes: packages.map { |dependency| dependency[:SPDXID] },
|
||||||
files: [],
|
files: [],
|
||||||
packages:,
|
packages:,
|
||||||
relationships: generate_relations_json(runtime_full, compiler_info),
|
relationships: generate_relations_json(runtime_full, compiler_info, bottling:),
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -388,7 +404,7 @@ class SBOM
|
|||||||
|
|
||||||
sig { returns(Time) }
|
sig { returns(Time) }
|
||||||
def source_modified_time
|
def source_modified_time
|
||||||
Time.at(@source_modified_time || 0).utc
|
Time.at(@source_modified_time).utc
|
||||||
end
|
end
|
||||||
|
|
||||||
sig { params(val: T.untyped).returns(T.any(String, Symbol)) }
|
sig { params(val: T.untyped).returns(T.any(String, Symbol)) }
|
||||||
|
Loading…
x
Reference in New Issue
Block a user