Skip to content

Commit

Permalink
feat: add generated SPDX file on bottling
Browse files Browse the repository at this point in the history
  • Loading branch information
SMillerDev committed Feb 5, 2024
1 parent dbb10da commit 0e3db6e
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 10 deletions.
1 change: 1 addition & 0 deletions Library/Homebrew/dev-cmd/bottle.rb
Expand Up @@ -437,6 +437,7 @@ def self.bottle_formula(formula, args:)
tab.tabfile.unlink
else
tab.write
tab.write_sbom
end

keg.consistent_reproducible_symlink_permissions!
Expand Down
190 changes: 180 additions & 10 deletions Library/Homebrew/tab.rb
Expand Up @@ -12,10 +12,11 @@ class Tab
extend Cachable

FILENAME = "INSTALL_RECEIPT.json"
SPDX_FILENAME = "spdx.sbom.json"

attr_accessor :homebrew_version, :tabfile, :built_as_bottle, :installed_as_dependency, :installed_on_request,
:changed_files, :poured_from_bottle, :loaded_from_api, :time, :stdlib, :aliases, :arch, :source,
:built_on
:built_on, :license
attr_writer :used_options, :unused_options, :compiler, :runtime_dependencies, :source_modified_time

# Instantiates a {Tab} for a new installation of a formula.
Expand All @@ -39,12 +40,17 @@ def self.create(formula, compiler, stdlib)
"aliases" => formula.aliases,
"runtime_dependencies" => Tab.runtime_deps_hash(formula, runtime_deps),
"arch" => Hardware::CPU.arch,
"license" => SPDX.license_expression_to_string(formula.license),
"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,
"versions" => {
"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,
"stable_url" => formula.stable&.url,
"stable_checksum" => formula.stable&.checksum,
"patches" => formula.stable&.patches,
"bottle" => formula.bottle_hash["files"],
"versions" => {
"stable" => formula.stable&.version&.to_s,
"head" => formula.head&.version&.to_s,
"version_scheme" => formula.version_scheme,
Expand Down Expand Up @@ -172,10 +178,14 @@ def self.for_formula(formula)
tab = empty
tab.unused_options = formula.options.as_flags
tab.source = {
"path" => formula.specified_path.to_s,
"tap" => formula.tap&.name,
"spec" => formula.active_spec_sym.to_s,
"versions" => {
"path" => formula.specified_path.to_s,
"tap" => formula.tap&.name,
"spec" => formula.active_spec_sym.to_s,
"stable_url" => formula.stable&.url,
"stable_checksum" => formula.stable&.checksum,
"patches" => formula.stable&.patches,
"bottle" => formula.bottle_hash["files"],
"versions" => {
"stable" => formula.stable&.version&.to_s,
"head" => formula.head&.version&.to_s,
"version_scheme" => formula.version_scheme,
Expand Down Expand Up @@ -203,6 +213,7 @@ def self.empty
"aliases" => [],
"runtime_dependencies" => nil,
"arch" => nil,
"license" => nil,
"source" => {
"path" => nil,
"tap" => nil,
Expand All @@ -223,12 +234,14 @@ def self.empty
def self.runtime_deps_hash(formula, deps)
deps.map do |dep|
f = dep.to_formula
odebug SPDX.license_expression_to_string(f.license)
{
"full_name" => f.full_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),
}
end
end
Expand Down Expand Up @@ -389,6 +402,16 @@ def write
tabfile.atomic_write(to_json)
end

def write_sbom
spdxfile = Pathname(tabfile.to_s.sub!(FILENAME, SPDX_FILENAME))
# If this is a new installation, the cache of installed formulae
# will no longer be valid.
Formula.clear_cache unless spdxfile.exist?

self.class.cache[spdxfile] = self
spdxfile.atomic_write(JSON.pretty_generate(to_spdx_sbom))
end

sig { returns(String) }
def to_s
s = []
Expand All @@ -407,4 +430,151 @@ def to_s
end
s.join(" ")
end

sig { returns(Hash) }
def to_spdx_sbom
name = tabfile.dirname.dirname.basename
runtime_full = runtime_dependencies.map do |dependency|
{
"SPDXID" => "SPDXRef-Package-SPDXRef-#{dependency["full_name"].split("/").last}-#{dependency["version"]}",
"name" => dependency["full_name"].split("/").last,
"versionInfo" => dependency["pkg_version"],
"filesAnalyzed" => false,
"licenseDeclared" => "NOASSERTION",
"licenseConcluded" => dependency["license"] || "NOASSERTION",
"downloadLocation" => "NOASSERTION",
"copyrightText" => "NOASSERTION",
"checksums" => [],
"externalRefs" => [
{
"referenceCategory" => "PACKAGE-MANAGER",
"referenceLocator" => "pkg:brew/#{dependency["full_name"]}@#{dependency["version"]}",
"referenceType" => "purl",
},
],
}
end

compiler_info = {
"SPDXRef-Compiler" => {
"SPDXID" => "SPDXRef-Compiler",
"name" => compiler,
"versionInfo" => built_on["xcode"],
"filesAnalyzed" => false,
"licenseDeclared" => "NOASSERTION",
"licenseConcluded" => "NOASSERTION",
"copyrightText" => "NOASSERTION",
"checksums" => [],
"externalRefs" => [],
},
}

if stdlib.present?
compiler_info["SPDXRef-Stdlib"] = {
"SPDXID" => "SPDXRef-Stdlib",
"name" => stdlib,
"versionInfo" => stdlib,
"filesAnalyzed" => false,
"licenseDeclared" => "NOASSERTION",
"licenseConcluded" => "NOASSERTION",
"copyrightText" => "NOASSERTION",
"checksums" => [],
"externalRefs" => [],
}
end

{
"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@q#{homebrew_version}"],
},
"dataLicense" => "CC0-1.0",
"documentNamespace" => "https://formulae.brew.sh/spdx/#{name}-#{stable_version}.json",
"documentDescribes" =>
runtime_full.map { |dependency| dependency["SPDXID"] } +
compiler_info.keys +
[
"SPDXRef-Package-#{name}",
"SPDXRef-Archive-#{name}-src",
],
"packages" => [
{
"SPDXID" => "SPDXRef-Bottle-#{name}",
"name" => name.to_s,
"versionInfo" => stable_version.to_s,
"filesAnalyzed" => false,
"licenseDeclared" => "NOASSERTION",
"builtDate" => source_modified_time,
"licenseConcluded" => license,
"downloadLocation" => source["bottle"][Utils::Bottles.tag.to_s]["url"],
"copyrightText" => "NOASSERTION",
"externalRefs" => [
{
"referenceCategory" => "PACKAGE-MANAGER",
"referenceLocator" => "pkg:brew/#{tap}/#{name}@#{stable_version}",
"referenceType" => "purl",
},
],
"checksums" => [
{
"algorithm" => "SHA256",
"checksumValue" => source["bottle"][Utils::Bottles.tag.to_s]["sha256"],
},
],
},
{
"SPDXID" => "SPDXRef-Archive-#{name}-src",
"name" => name.to_s,
"versionInfo" => stable_version.to_s,
"filesAnalyzed" => false,
"licenseDeclared" => "NOASSERTION",
"builtDate" => source_modified_time,
"licenseConcluded" => license || "NOASSERTION",
"downloadLocation" => source["stable_url"],
"copyrightText" => "NOASSERTION",
"externalRefs" => [],
"checksums" => [
{
"algorithm" => "SHA256",
"checksumValue" => source["stable_checksum"],
},
],
},
] + runtime_full + compiler_info.values,
files: [],
"relationships" => runtime_full.map { |dependency|
{
"spdxElementId" => dependency["SPDXID"],
"relationshipType" => "RUNTIME_DEPENDENCY_OF",
"relatedSpdxElement" => "SPDXRef-Bottle-#{name}",
}
} + [
{
"spdxElementId" => "SPDXRef-File-#{name}",
"relationshipType" => "PACKAGE_OF",
"relatedSpdxElement" => "SPDXRef-Archive-#{name}-src",
},
{
"spdxElementId" => "SPDXRef-Compiler",
"relationshipType" => "BUILD_TOOL_OF",
"relatedSpdxElement" => "SPDXRef-Package-#{name}-src",
},
(if compiler_info["SPDXRef-Stdlib"].present?
{
"spdxElementId" => "SPDXRef-Stdlib",
"relationshipType" => "DEPENDENCY_OF",
"relatedSpdxElement" => "SPDXRef-Bottle-#{name}",
}
end),
{
"spdxElementId" => "SPDXRef-Patch-#{name}",
"relationshipType" => "PATCH_APPLIED",
"relatedSpdxElement" => "SPDXRef-Archive-#{name}-src",
},
],
}
end
end

0 comments on commit 0e3db6e

Please sign in to comment.