Revert "Revert "Updated SBOM generator module to generate JSON spdx utility bill of""
This reverts commit 928ee9d9f7ca091054a14c7dcffb2cc406eda2b4.
Reason for revert: Fixed the initial cause of the revert. Added spdx-tools to the missing branches. See b/276427351
Change-Id: I7bd0b3f194b27dc9a255ccadeb2a9a12a3d59f66
diff --git a/tools/compliance/cmd/sbom/sbom.go b/tools/compliance/cmd/sbom/sbom.go
index 0f8a876..1477ca5 100644
--- a/tools/compliance/cmd/sbom/sbom.go
+++ b/tools/compliance/cmd/sbom/sbom.go
@@ -31,6 +31,11 @@
"android/soong/tools/compliance/projectmetadata"
"github.com/google/blueprint/deptools"
+
+ "github.com/spdx/tools-golang/builder/builder2v2"
+ "github.com/spdx/tools-golang/json"
+ "github.com/spdx/tools-golang/spdx/common"
+ spdx "github.com/spdx/tools-golang/spdx/v2_2"
)
var (
@@ -38,6 +43,8 @@
failNoLicenses = fmt.Errorf("No licenses found")
)
+const NOASSERTION = "NOASSERTION"
+
type context struct {
stdout io.Writer
stderr io.Writer
@@ -154,7 +161,8 @@
ctx := &context{ofile, os.Stderr, compliance.FS, *product, *stripPrefix, actualTime}
- deps, err := sbomGenerator(ctx, flags.Args()...)
+ spdxDoc, deps, err := sbomGenerator(ctx, flags.Args()...)
+
if err != nil {
if err == failNoneRequested {
flags.Usage()
@@ -163,6 +171,11 @@
os.Exit(1)
}
+ if err := spdx_json.Save2_2(spdxDoc, ofile); err != nil {
+ fmt.Fprintf(os.Stderr, "failed to write document to %v: %v", *outputFile, err)
+ os.Exit(1)
+ }
+
if *outputFile != "-" {
err := os.WriteFile(*outputFile, obuf.Bytes(), 0666)
if err != nil {
@@ -218,17 +231,17 @@
// or NOASSERTION if not available, none determined or ambiguous
func getDownloadUrl(_ *context, pm *projectmetadata.ProjectMetadata) string {
if pm == nil {
- return "NOASSERTION"
+ return NOASSERTION
}
urlsByTypeName := pm.UrlsByTypeName()
if urlsByTypeName == nil {
- return "NOASSERTION"
+ return NOASSERTION
}
url := urlsByTypeName.DownloadUrl()
if url == "" {
- return "NOASSERTION"
+ return NOASSERTION
}
return url
}
@@ -274,7 +287,7 @@
// inputFiles returns the complete list of files read
func inputFiles(lg *compliance.LicenseGraph, pmix *projectmetadata.Index, licenseTexts []string) []string {
projectMeta := pmix.AllMetadataFiles()
- targets := lg.TargetNames()
+ targets := lg.TargetNames()
files := make([]string, 0, len(licenseTexts)+len(targets)+len(projectMeta))
files = append(files, licenseTexts...)
files = append(files, targets...)
@@ -289,10 +302,10 @@
// sbomGenerator uses the SPDX standard, see the SPDX specification (https://spdx.github.io/spdx-spec/)
// sbomGenerator is also following the internal google SBOM styleguide (http://goto.google.com/spdx-style-guide)
-func sbomGenerator(ctx *context, files ...string) ([]string, error) {
+func sbomGenerator(ctx *context, files ...string) (*spdx.Document, []string, error) {
// Must be at least one root file.
if len(files) < 1 {
- return nil, failNoneRequested
+ return nil, nil, failNoneRequested
}
pmix := projectmetadata.NewIndex(ctx.rootFS)
@@ -300,9 +313,21 @@
lg, err := compliance.ReadLicenseGraph(ctx.rootFS, ctx.stderr, files)
if err != nil {
- return nil, fmt.Errorf("Unable to read license text file(s) for %q: %v\n", files, err)
+ return nil, nil, fmt.Errorf("Unable to read license text file(s) for %q: %v\n", files, err)
}
+ // creating the packages section
+ pkgs := []*spdx.Package{}
+
+ // creating the relationship section
+ relationships := []*spdx.Relationship{}
+
+ // creating the license section
+ otherLicenses := []*spdx.OtherLicense{}
+
+ // main package name
+ var mainPkgName string
+
// implementing the licenses references for the packages
licenses := make(map[string]string)
concludedLicenses := func(licenseTexts []string) string {
@@ -325,7 +350,6 @@
}
isMainPackage := true
- var mainPackage string
visitedNodes := make(map[*compliance.TargetNode]struct{})
// performing a Breadth-first top down walk of licensegraph and building package information
@@ -341,45 +365,50 @@
}
if isMainPackage {
- mainPackage = getDocumentName(ctx, tn, pm)
- fmt.Fprintf(ctx.stdout, "SPDXVersion: SPDX-2.2\n")
- fmt.Fprintf(ctx.stdout, "DataLicense: CC0-1.0\n")
- fmt.Fprintf(ctx.stdout, "DocumentName: %s\n", mainPackage)
- fmt.Fprintf(ctx.stdout, "SPDXID: SPDXRef-DOCUMENT\n")
- fmt.Fprintf(ctx.stdout, "DocumentNamespace: Android\n")
- fmt.Fprintf(ctx.stdout, "Creator: Organization: Google LLC\n")
- fmt.Fprintf(ctx.stdout, "Created: %s\n", ctx.creationTime().Format("2006-01-02T15:04:05Z"))
+ mainPkgName = replaceSlashes(getPackageName(ctx, tn))
isMainPackage = false
}
- relationships := make([]string, 0, 1)
- defer func() {
- if r := recover(); r != nil {
- panic(r)
- }
- for _, relationship := range relationships {
- fmt.Fprintln(ctx.stdout, relationship)
- }
- }()
if len(path) == 0 {
- relationships = append(relationships,
- fmt.Sprintf("Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-Package-%s",
- getPackageName(ctx, tn)))
+ // Add the describe relationship for the main package
+ rln := &spdx.Relationship{
+ RefA: common.MakeDocElementID("" /* this document */, "DOCUMENT"),
+ RefB: common.MakeDocElementID("", mainPkgName),
+ Relationship: "DESCRIBES",
+ }
+ relationships = append(relationships, rln)
+
} else {
// Check parent and identify annotation
parent := path[len(path)-1]
targetEdge := parent.Edge()
if targetEdge.IsRuntimeDependency() {
// Adding the dynamic link annotation RUNTIME_DEPENDENCY_OF relationship
- relationships = append(relationships, fmt.Sprintf("Relationship: SPDXRef-Package-%s RUNTIME_DEPENDENCY_OF SPDXRef-Package-%s", getPackageName(ctx, tn), getPackageName(ctx, targetEdge.Target())))
+ rln := &spdx.Relationship{
+ RefA: common.MakeDocElementID("", replaceSlashes(getPackageName(ctx, tn))),
+ RefB: common.MakeDocElementID("", replaceSlashes(getPackageName(ctx, targetEdge.Target()))),
+ Relationship: "RUNTIME_DEPENDENCY_OF",
+ }
+ relationships = append(relationships, rln)
} else if targetEdge.IsDerivation() {
// Adding the derivation annotation as a CONTAINS relationship
- relationships = append(relationships, fmt.Sprintf("Relationship: SPDXRef-Package-%s CONTAINS SPDXRef-Package-%s", getPackageName(ctx, targetEdge.Target()), getPackageName(ctx, tn)))
+ rln := &spdx.Relationship{
+ RefA: common.MakeDocElementID("", replaceSlashes(getPackageName(ctx, targetEdge.Target()))),
+ RefB: common.MakeDocElementID("", replaceSlashes(getPackageName(ctx, tn))),
+ Relationship: "CONTAINS",
+ }
+ relationships = append(relationships, rln)
} else if targetEdge.IsBuildTool() {
// Adding the toolchain annotation as a BUILD_TOOL_OF relationship
- relationships = append(relationships, fmt.Sprintf("Relationship: SPDXRef-Package-%s BUILD_TOOL_OF SPDXRef-Package-%s", getPackageName(ctx, tn), getPackageName(ctx, targetEdge.Target())))
+ rln := &spdx.Relationship{
+ RefA: common.MakeDocElementID("", replaceSlashes(getPackageName(ctx, tn))),
+ RefB: common.MakeDocElementID("", replaceSlashes(getPackageName(ctx, targetEdge.Target()))),
+ Relationship: "BUILD_TOOL_OF",
+ }
+ relationships = append(relationships, rln)
+
} else {
panic(fmt.Errorf("Unknown dependency type: %v", targetEdge.Annotations()))
}
@@ -390,18 +419,27 @@
}
visitedNodes[tn] = struct{}{}
pkgName := getPackageName(ctx, tn)
- fmt.Fprintf(ctx.stdout, "##### Package: %s\n", strings.Replace(pkgName, "-", "/", -2))
- fmt.Fprintf(ctx.stdout, "PackageName: %s\n", pkgName)
- if pm != nil && pm.Version() != "" {
- fmt.Fprintf(ctx.stdout, "PackageVersion: %s\n", pm.Version())
+
+ // Making an spdx package and adding it to pkgs
+ pkg := &spdx.Package{
+ PackageName: replaceSlashes(pkgName),
+ PackageDownloadLocation: getDownloadUrl(ctx, pm),
+ PackageSPDXIdentifier: common.ElementID(replaceSlashes(pkgName)),
+ PackageLicenseConcluded: concludedLicenses(tn.LicenseTexts()),
}
- fmt.Fprintf(ctx.stdout, "SPDXID: SPDXRef-Package-%s\n", pkgName)
- fmt.Fprintf(ctx.stdout, "PackageDownloadLocation: %s\n", getDownloadUrl(ctx, pm))
- fmt.Fprintf(ctx.stdout, "PackageLicenseConcluded: %s\n", concludedLicenses(tn.LicenseTexts()))
+
+ if pm != nil && pm.Version() != "" {
+ pkg.PackageVersion = pm.Version()
+ } else {
+ pkg.PackageVersion = NOASSERTION
+ }
+
+ pkgs = append(pkgs, pkg)
+
return true
})
- fmt.Fprintf(ctx.stdout, "##### Non-standard license:\n")
+ // Adding Non-standard licenses
licenseTexts := make([]string, 0, len(licenses))
@@ -412,23 +450,39 @@
sort.Strings(licenseTexts)
for _, licenseText := range licenseTexts {
- fmt.Fprintf(ctx.stdout, "LicenseID: %s\n", licenses[licenseText])
// open the file
f, err := ctx.rootFS.Open(filepath.Clean(licenseText))
if err != nil {
- return nil, fmt.Errorf("error opening license text file %q: %w", licenseText, err)
+ return nil, nil, fmt.Errorf("error opening license text file %q: %w", licenseText, err)
}
// read the file
text, err := io.ReadAll(f)
if err != nil {
- return nil, fmt.Errorf("error reading license text file %q: %w", licenseText, err)
+ return nil, nil, fmt.Errorf("error reading license text file %q: %w", licenseText, err)
}
- // adding the extracted license text
- fmt.Fprintf(ctx.stdout, "ExtractedText: <text>%v</text>\n", string(text))
+ // Making an spdx License and adding it to otherLicenses
+ otherLicenses = append(otherLicenses, &spdx.OtherLicense{
+ LicenseName: strings.Replace(licenses[licenseText], "LicenseRef-", "", -1),
+ LicenseIdentifier: string(licenses[licenseText]),
+ ExtractedText: string(text),
+ })
}
deps := inputFiles(lg, pmix, licenseTexts)
sort.Strings(deps)
- return deps, nil
+
+ // Making the SPDX doc
+ ci, err := builder2v2.BuildCreationInfoSection2_2("Organization", "Google LLC", nil)
+ if err != nil {
+ return nil, nil, fmt.Errorf("Unable to build creation info section for SPDX doc: %v\n", err)
+ }
+
+ return &spdx.Document{
+ SPDXIdentifier: "DOCUMENT",
+ CreationInfo: ci,
+ Packages: pkgs,
+ Relationships: relationships,
+ OtherLicenses: otherLicenses,
+ }, deps, nil
}