1*9e94795aSAndroid Build Coastguard Worker// Copyright 2022 Google LLC 2*9e94795aSAndroid Build Coastguard Worker// 3*9e94795aSAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License"); 4*9e94795aSAndroid Build Coastguard Worker// you may not use this file except in compliance with the License. 5*9e94795aSAndroid Build Coastguard Worker// You may obtain a copy of the License at 6*9e94795aSAndroid Build Coastguard Worker// 7*9e94795aSAndroid Build Coastguard Worker// http://www.apache.org/licenses/LICENSE-2.0 8*9e94795aSAndroid Build Coastguard Worker// 9*9e94795aSAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software 10*9e94795aSAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS, 11*9e94795aSAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*9e94795aSAndroid Build Coastguard Worker// See the License for the specific language governing permissions and 13*9e94795aSAndroid Build Coastguard Worker// limitations under the License. 14*9e94795aSAndroid Build Coastguard Worker 15*9e94795aSAndroid Build Coastguard Workerpackage main 16*9e94795aSAndroid Build Coastguard Worker 17*9e94795aSAndroid Build Coastguard Workerimport ( 18*9e94795aSAndroid Build Coastguard Worker "bytes" 19*9e94795aSAndroid Build Coastguard Worker "crypto/sha1" 20*9e94795aSAndroid Build Coastguard Worker "encoding/hex" 21*9e94795aSAndroid Build Coastguard Worker "flag" 22*9e94795aSAndroid Build Coastguard Worker "fmt" 23*9e94795aSAndroid Build Coastguard Worker "io" 24*9e94795aSAndroid Build Coastguard Worker "io/fs" 25*9e94795aSAndroid Build Coastguard Worker "os" 26*9e94795aSAndroid Build Coastguard Worker "path/filepath" 27*9e94795aSAndroid Build Coastguard Worker "sort" 28*9e94795aSAndroid Build Coastguard Worker "strings" 29*9e94795aSAndroid Build Coastguard Worker "time" 30*9e94795aSAndroid Build Coastguard Worker 31*9e94795aSAndroid Build Coastguard Worker "android/soong/response" 32*9e94795aSAndroid Build Coastguard Worker "android/soong/tools/compliance" 33*9e94795aSAndroid Build Coastguard Worker "android/soong/tools/compliance/projectmetadata" 34*9e94795aSAndroid Build Coastguard Worker 35*9e94795aSAndroid Build Coastguard Worker "github.com/google/blueprint/deptools" 36*9e94795aSAndroid Build Coastguard Worker 37*9e94795aSAndroid Build Coastguard Worker "github.com/spdx/tools-golang/builder/builder2v2" 38*9e94795aSAndroid Build Coastguard Worker spdx_json "github.com/spdx/tools-golang/json" 39*9e94795aSAndroid Build Coastguard Worker "github.com/spdx/tools-golang/spdx/common" 40*9e94795aSAndroid Build Coastguard Worker spdx "github.com/spdx/tools-golang/spdx/v2_2" 41*9e94795aSAndroid Build Coastguard Worker "github.com/spdx/tools-golang/spdxlib" 42*9e94795aSAndroid Build Coastguard Worker) 43*9e94795aSAndroid Build Coastguard Worker 44*9e94795aSAndroid Build Coastguard Workervar ( 45*9e94795aSAndroid Build Coastguard Worker failNoneRequested = fmt.Errorf("\nNo license metadata files requested") 46*9e94795aSAndroid Build Coastguard Worker failNoLicenses = fmt.Errorf("No licenses found") 47*9e94795aSAndroid Build Coastguard Worker) 48*9e94795aSAndroid Build Coastguard Worker 49*9e94795aSAndroid Build Coastguard Workerconst NOASSERTION = "NOASSERTION" 50*9e94795aSAndroid Build Coastguard Worker 51*9e94795aSAndroid Build Coastguard Workertype context struct { 52*9e94795aSAndroid Build Coastguard Worker stdout io.Writer 53*9e94795aSAndroid Build Coastguard Worker stderr io.Writer 54*9e94795aSAndroid Build Coastguard Worker rootFS fs.FS 55*9e94795aSAndroid Build Coastguard Worker product string 56*9e94795aSAndroid Build Coastguard Worker stripPrefix []string 57*9e94795aSAndroid Build Coastguard Worker creationTime creationTimeGetter 58*9e94795aSAndroid Build Coastguard Worker buildid string 59*9e94795aSAndroid Build Coastguard Worker} 60*9e94795aSAndroid Build Coastguard Worker 61*9e94795aSAndroid Build Coastguard Workerfunc (ctx context) strip(installPath string) string { 62*9e94795aSAndroid Build Coastguard Worker for _, prefix := range ctx.stripPrefix { 63*9e94795aSAndroid Build Coastguard Worker if strings.HasPrefix(installPath, prefix) { 64*9e94795aSAndroid Build Coastguard Worker p := strings.TrimPrefix(installPath, prefix) 65*9e94795aSAndroid Build Coastguard Worker if 0 == len(p) { 66*9e94795aSAndroid Build Coastguard Worker p = ctx.product 67*9e94795aSAndroid Build Coastguard Worker } 68*9e94795aSAndroid Build Coastguard Worker if 0 == len(p) { 69*9e94795aSAndroid Build Coastguard Worker continue 70*9e94795aSAndroid Build Coastguard Worker } 71*9e94795aSAndroid Build Coastguard Worker return p 72*9e94795aSAndroid Build Coastguard Worker } 73*9e94795aSAndroid Build Coastguard Worker } 74*9e94795aSAndroid Build Coastguard Worker return installPath 75*9e94795aSAndroid Build Coastguard Worker} 76*9e94795aSAndroid Build Coastguard Worker 77*9e94795aSAndroid Build Coastguard Worker// newMultiString creates a flag that allows multiple values in an array. 78*9e94795aSAndroid Build Coastguard Workerfunc newMultiString(flags *flag.FlagSet, name, usage string) *multiString { 79*9e94795aSAndroid Build Coastguard Worker var f multiString 80*9e94795aSAndroid Build Coastguard Worker flags.Var(&f, name, usage) 81*9e94795aSAndroid Build Coastguard Worker return &f 82*9e94795aSAndroid Build Coastguard Worker} 83*9e94795aSAndroid Build Coastguard Worker 84*9e94795aSAndroid Build Coastguard Worker// multiString implements the flag `Value` interface for multiple strings. 85*9e94795aSAndroid Build Coastguard Workertype multiString []string 86*9e94795aSAndroid Build Coastguard Worker 87*9e94795aSAndroid Build Coastguard Workerfunc (ms *multiString) String() string { return strings.Join(*ms, ", ") } 88*9e94795aSAndroid Build Coastguard Workerfunc (ms *multiString) Set(s string) error { *ms = append(*ms, s); return nil } 89*9e94795aSAndroid Build Coastguard Worker 90*9e94795aSAndroid Build Coastguard Workerfunc main() { 91*9e94795aSAndroid Build Coastguard Worker var expandedArgs []string 92*9e94795aSAndroid Build Coastguard Worker for _, arg := range os.Args[1:] { 93*9e94795aSAndroid Build Coastguard Worker if strings.HasPrefix(arg, "@") { 94*9e94795aSAndroid Build Coastguard Worker f, err := os.Open(strings.TrimPrefix(arg, "@")) 95*9e94795aSAndroid Build Coastguard Worker if err != nil { 96*9e94795aSAndroid Build Coastguard Worker fmt.Fprintln(os.Stderr, err.Error()) 97*9e94795aSAndroid Build Coastguard Worker os.Exit(1) 98*9e94795aSAndroid Build Coastguard Worker } 99*9e94795aSAndroid Build Coastguard Worker 100*9e94795aSAndroid Build Coastguard Worker respArgs, err := response.ReadRspFile(f) 101*9e94795aSAndroid Build Coastguard Worker f.Close() 102*9e94795aSAndroid Build Coastguard Worker if err != nil { 103*9e94795aSAndroid Build Coastguard Worker fmt.Fprintln(os.Stderr, err.Error()) 104*9e94795aSAndroid Build Coastguard Worker os.Exit(1) 105*9e94795aSAndroid Build Coastguard Worker } 106*9e94795aSAndroid Build Coastguard Worker expandedArgs = append(expandedArgs, respArgs...) 107*9e94795aSAndroid Build Coastguard Worker } else { 108*9e94795aSAndroid Build Coastguard Worker expandedArgs = append(expandedArgs, arg) 109*9e94795aSAndroid Build Coastguard Worker } 110*9e94795aSAndroid Build Coastguard Worker } 111*9e94795aSAndroid Build Coastguard Worker 112*9e94795aSAndroid Build Coastguard Worker flags := flag.NewFlagSet("flags", flag.ExitOnError) 113*9e94795aSAndroid Build Coastguard Worker 114*9e94795aSAndroid Build Coastguard Worker flags.Usage = func() { 115*9e94795aSAndroid Build Coastguard Worker fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...} 116*9e94795aSAndroid Build Coastguard Worker 117*9e94795aSAndroid Build Coastguard WorkerOutputs an SBOM.spdx. 118*9e94795aSAndroid Build Coastguard Worker 119*9e94795aSAndroid Build Coastguard WorkerOptions: 120*9e94795aSAndroid Build Coastguard Worker`, filepath.Base(os.Args[0])) 121*9e94795aSAndroid Build Coastguard Worker flags.PrintDefaults() 122*9e94795aSAndroid Build Coastguard Worker } 123*9e94795aSAndroid Build Coastguard Worker 124*9e94795aSAndroid Build Coastguard Worker outputFile := flags.String("o", "-", "Where to write the SBOM spdx file. (default stdout)") 125*9e94795aSAndroid Build Coastguard Worker depsFile := flags.String("d", "", "Where to write the deps file") 126*9e94795aSAndroid Build Coastguard Worker product := flags.String("product", "", "The name of the product for which the notice is generated.") 127*9e94795aSAndroid Build Coastguard Worker stripPrefix := newMultiString(flags, "strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)") 128*9e94795aSAndroid Build Coastguard Worker buildid := flags.String("build_id", "", "Uniquely identifies the build. (default timestamp)") 129*9e94795aSAndroid Build Coastguard Worker 130*9e94795aSAndroid Build Coastguard Worker flags.Parse(expandedArgs) 131*9e94795aSAndroid Build Coastguard Worker 132*9e94795aSAndroid Build Coastguard Worker // Must specify at least one root target. 133*9e94795aSAndroid Build Coastguard Worker if flags.NArg() == 0 { 134*9e94795aSAndroid Build Coastguard Worker flags.Usage() 135*9e94795aSAndroid Build Coastguard Worker os.Exit(2) 136*9e94795aSAndroid Build Coastguard Worker } 137*9e94795aSAndroid Build Coastguard Worker 138*9e94795aSAndroid Build Coastguard Worker if len(*outputFile) == 0 { 139*9e94795aSAndroid Build Coastguard Worker flags.Usage() 140*9e94795aSAndroid Build Coastguard Worker fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n") 141*9e94795aSAndroid Build Coastguard Worker os.Exit(2) 142*9e94795aSAndroid Build Coastguard Worker } else { 143*9e94795aSAndroid Build Coastguard Worker dir, err := filepath.Abs(filepath.Dir(*outputFile)) 144*9e94795aSAndroid Build Coastguard Worker if err != nil { 145*9e94795aSAndroid Build Coastguard Worker fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err) 146*9e94795aSAndroid Build Coastguard Worker os.Exit(1) 147*9e94795aSAndroid Build Coastguard Worker } 148*9e94795aSAndroid Build Coastguard Worker fi, err := os.Stat(dir) 149*9e94795aSAndroid Build Coastguard Worker if err != nil { 150*9e94795aSAndroid Build Coastguard Worker fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err) 151*9e94795aSAndroid Build Coastguard Worker os.Exit(1) 152*9e94795aSAndroid Build Coastguard Worker } 153*9e94795aSAndroid Build Coastguard Worker if !fi.IsDir() { 154*9e94795aSAndroid Build Coastguard Worker fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile) 155*9e94795aSAndroid Build Coastguard Worker os.Exit(1) 156*9e94795aSAndroid Build Coastguard Worker } 157*9e94795aSAndroid Build Coastguard Worker } 158*9e94795aSAndroid Build Coastguard Worker 159*9e94795aSAndroid Build Coastguard Worker var ofile io.Writer 160*9e94795aSAndroid Build Coastguard Worker ofile = os.Stdout 161*9e94795aSAndroid Build Coastguard Worker var obuf *bytes.Buffer 162*9e94795aSAndroid Build Coastguard Worker if *outputFile != "-" { 163*9e94795aSAndroid Build Coastguard Worker obuf = &bytes.Buffer{} 164*9e94795aSAndroid Build Coastguard Worker ofile = obuf 165*9e94795aSAndroid Build Coastguard Worker } 166*9e94795aSAndroid Build Coastguard Worker 167*9e94795aSAndroid Build Coastguard Worker ctx := &context{ofile, os.Stderr, compliance.FS, *product, *stripPrefix, actualTime, *buildid} 168*9e94795aSAndroid Build Coastguard Worker 169*9e94795aSAndroid Build Coastguard Worker spdxDoc, deps, err := sbomGenerator(ctx, flags.Args()...) 170*9e94795aSAndroid Build Coastguard Worker 171*9e94795aSAndroid Build Coastguard Worker if err != nil { 172*9e94795aSAndroid Build Coastguard Worker if err == failNoneRequested { 173*9e94795aSAndroid Build Coastguard Worker flags.Usage() 174*9e94795aSAndroid Build Coastguard Worker } 175*9e94795aSAndroid Build Coastguard Worker fmt.Fprintf(os.Stderr, "%s\n", err.Error()) 176*9e94795aSAndroid Build Coastguard Worker os.Exit(1) 177*9e94795aSAndroid Build Coastguard Worker } 178*9e94795aSAndroid Build Coastguard Worker 179*9e94795aSAndroid Build Coastguard Worker // writing the spdx Doc created 180*9e94795aSAndroid Build Coastguard Worker if err := spdx_json.Save2_2(spdxDoc, ofile); err != nil { 181*9e94795aSAndroid Build Coastguard Worker fmt.Fprintf(os.Stderr, "failed to write document to %v: %v", *outputFile, err) 182*9e94795aSAndroid Build Coastguard Worker os.Exit(1) 183*9e94795aSAndroid Build Coastguard Worker } 184*9e94795aSAndroid Build Coastguard Worker 185*9e94795aSAndroid Build Coastguard Worker if *outputFile != "-" { 186*9e94795aSAndroid Build Coastguard Worker err := os.WriteFile(*outputFile, obuf.Bytes(), 0666) 187*9e94795aSAndroid Build Coastguard Worker if err != nil { 188*9e94795aSAndroid Build Coastguard Worker fmt.Fprintf(os.Stderr, "could not write output to %q: %s\n", *outputFile, err) 189*9e94795aSAndroid Build Coastguard Worker os.Exit(1) 190*9e94795aSAndroid Build Coastguard Worker } 191*9e94795aSAndroid Build Coastguard Worker } 192*9e94795aSAndroid Build Coastguard Worker 193*9e94795aSAndroid Build Coastguard Worker if *depsFile != "" { 194*9e94795aSAndroid Build Coastguard Worker err := deptools.WriteDepFile(*depsFile, *outputFile, deps) 195*9e94795aSAndroid Build Coastguard Worker if err != nil { 196*9e94795aSAndroid Build Coastguard Worker fmt.Fprintf(os.Stderr, "could not write deps to %q: %s\n", *depsFile, err) 197*9e94795aSAndroid Build Coastguard Worker os.Exit(1) 198*9e94795aSAndroid Build Coastguard Worker } 199*9e94795aSAndroid Build Coastguard Worker } 200*9e94795aSAndroid Build Coastguard Worker os.Exit(0) 201*9e94795aSAndroid Build Coastguard Worker} 202*9e94795aSAndroid Build Coastguard Worker 203*9e94795aSAndroid Build Coastguard Workertype creationTimeGetter func() string 204*9e94795aSAndroid Build Coastguard Worker 205*9e94795aSAndroid Build Coastguard Worker// actualTime returns current time in UTC 206*9e94795aSAndroid Build Coastguard Workerfunc actualTime() string { 207*9e94795aSAndroid Build Coastguard Worker t := time.Now().UTC() 208*9e94795aSAndroid Build Coastguard Worker return t.UTC().Format("2006-01-02T15:04:05Z") 209*9e94795aSAndroid Build Coastguard Worker} 210*9e94795aSAndroid Build Coastguard Worker 211*9e94795aSAndroid Build Coastguard Worker// replaceSlashes replaces "/" by "-" for the library path to be used for packages & files SPDXID 212*9e94795aSAndroid Build Coastguard Workerfunc replaceSlashes(x string) string { 213*9e94795aSAndroid Build Coastguard Worker return strings.ReplaceAll(x, "/", "-") 214*9e94795aSAndroid Build Coastguard Worker} 215*9e94795aSAndroid Build Coastguard Worker 216*9e94795aSAndroid Build Coastguard Worker// stripDocName removes the outdir prefix and meta_lic suffix from a target Name 217*9e94795aSAndroid Build Coastguard Workerfunc stripDocName(name string) string { 218*9e94795aSAndroid Build Coastguard Worker // remove outdir prefix 219*9e94795aSAndroid Build Coastguard Worker if strings.HasPrefix(name, "out/") { 220*9e94795aSAndroid Build Coastguard Worker name = name[4:] 221*9e94795aSAndroid Build Coastguard Worker } 222*9e94795aSAndroid Build Coastguard Worker 223*9e94795aSAndroid Build Coastguard Worker // remove suffix 224*9e94795aSAndroid Build Coastguard Worker if strings.HasSuffix(name, ".meta_lic") { 225*9e94795aSAndroid Build Coastguard Worker name = name[:len(name)-9] 226*9e94795aSAndroid Build Coastguard Worker } else if strings.HasSuffix(name, "/meta_lic") { 227*9e94795aSAndroid Build Coastguard Worker name = name[:len(name)-9] + "/" 228*9e94795aSAndroid Build Coastguard Worker } 229*9e94795aSAndroid Build Coastguard Worker 230*9e94795aSAndroid Build Coastguard Worker return name 231*9e94795aSAndroid Build Coastguard Worker} 232*9e94795aSAndroid Build Coastguard Worker 233*9e94795aSAndroid Build Coastguard Worker// getPackageName returns a package name of a target Node 234*9e94795aSAndroid Build Coastguard Workerfunc getPackageName(_ *context, tn *compliance.TargetNode) string { 235*9e94795aSAndroid Build Coastguard Worker return replaceSlashes(tn.Name()) 236*9e94795aSAndroid Build Coastguard Worker} 237*9e94795aSAndroid Build Coastguard Worker 238*9e94795aSAndroid Build Coastguard Worker// getDocumentName returns a package name of a target Node 239*9e94795aSAndroid Build Coastguard Workerfunc getDocumentName(ctx *context, tn *compliance.TargetNode, pm *projectmetadata.ProjectMetadata) string { 240*9e94795aSAndroid Build Coastguard Worker if len(ctx.product) > 0 { 241*9e94795aSAndroid Build Coastguard Worker return replaceSlashes(ctx.product) 242*9e94795aSAndroid Build Coastguard Worker } 243*9e94795aSAndroid Build Coastguard Worker if len(tn.ModuleName()) > 0 { 244*9e94795aSAndroid Build Coastguard Worker if pm != nil { 245*9e94795aSAndroid Build Coastguard Worker return replaceSlashes(pm.Name() + ":" + tn.ModuleName()) 246*9e94795aSAndroid Build Coastguard Worker } 247*9e94795aSAndroid Build Coastguard Worker return replaceSlashes(tn.ModuleName()) 248*9e94795aSAndroid Build Coastguard Worker } 249*9e94795aSAndroid Build Coastguard Worker 250*9e94795aSAndroid Build Coastguard Worker return stripDocName(replaceSlashes(tn.Name())) 251*9e94795aSAndroid Build Coastguard Worker} 252*9e94795aSAndroid Build Coastguard Worker 253*9e94795aSAndroid Build Coastguard Worker// getDownloadUrl returns the download URL if available (GIT, SVN, etc..), 254*9e94795aSAndroid Build Coastguard Worker// or NOASSERTION if not available, none determined or ambiguous 255*9e94795aSAndroid Build Coastguard Workerfunc getDownloadUrl(_ *context, pm *projectmetadata.ProjectMetadata) string { 256*9e94795aSAndroid Build Coastguard Worker if pm == nil { 257*9e94795aSAndroid Build Coastguard Worker return NOASSERTION 258*9e94795aSAndroid Build Coastguard Worker } 259*9e94795aSAndroid Build Coastguard Worker 260*9e94795aSAndroid Build Coastguard Worker urlsByTypeName := pm.UrlsByTypeName() 261*9e94795aSAndroid Build Coastguard Worker if urlsByTypeName == nil { 262*9e94795aSAndroid Build Coastguard Worker return NOASSERTION 263*9e94795aSAndroid Build Coastguard Worker } 264*9e94795aSAndroid Build Coastguard Worker 265*9e94795aSAndroid Build Coastguard Worker url := urlsByTypeName.DownloadUrl() 266*9e94795aSAndroid Build Coastguard Worker if url == "" { 267*9e94795aSAndroid Build Coastguard Worker return NOASSERTION 268*9e94795aSAndroid Build Coastguard Worker } 269*9e94795aSAndroid Build Coastguard Worker return url 270*9e94795aSAndroid Build Coastguard Worker} 271*9e94795aSAndroid Build Coastguard Worker 272*9e94795aSAndroid Build Coastguard Worker// getProjectMetadata returns the optimal project metadata for the target node 273*9e94795aSAndroid Build Coastguard Workerfunc getProjectMetadata(_ *context, pmix *projectmetadata.Index, 274*9e94795aSAndroid Build Coastguard Worker tn *compliance.TargetNode) (*projectmetadata.ProjectMetadata, error) { 275*9e94795aSAndroid Build Coastguard Worker pms, err := pmix.MetadataForProjects(tn.Projects()...) 276*9e94795aSAndroid Build Coastguard Worker if err != nil { 277*9e94795aSAndroid Build Coastguard Worker return nil, fmt.Errorf("Unable to read projects for %q: %w\n", tn.Name(), err) 278*9e94795aSAndroid Build Coastguard Worker } 279*9e94795aSAndroid Build Coastguard Worker if len(pms) == 0 { 280*9e94795aSAndroid Build Coastguard Worker return nil, nil 281*9e94795aSAndroid Build Coastguard Worker } 282*9e94795aSAndroid Build Coastguard Worker 283*9e94795aSAndroid Build Coastguard Worker // Getting the project metadata that contains most of the info needed for sbomGenerator 284*9e94795aSAndroid Build Coastguard Worker score := -1 285*9e94795aSAndroid Build Coastguard Worker index := -1 286*9e94795aSAndroid Build Coastguard Worker for i := 0; i < len(pms); i++ { 287*9e94795aSAndroid Build Coastguard Worker tempScore := 0 288*9e94795aSAndroid Build Coastguard Worker if pms[i].Name() != "" { 289*9e94795aSAndroid Build Coastguard Worker tempScore += 1 290*9e94795aSAndroid Build Coastguard Worker } 291*9e94795aSAndroid Build Coastguard Worker if pms[i].Version() != "" { 292*9e94795aSAndroid Build Coastguard Worker tempScore += 1 293*9e94795aSAndroid Build Coastguard Worker } 294*9e94795aSAndroid Build Coastguard Worker if pms[i].UrlsByTypeName().DownloadUrl() != "" { 295*9e94795aSAndroid Build Coastguard Worker tempScore += 1 296*9e94795aSAndroid Build Coastguard Worker } 297*9e94795aSAndroid Build Coastguard Worker 298*9e94795aSAndroid Build Coastguard Worker if tempScore == score { 299*9e94795aSAndroid Build Coastguard Worker if pms[i].Project() < pms[index].Project() { 300*9e94795aSAndroid Build Coastguard Worker index = i 301*9e94795aSAndroid Build Coastguard Worker } 302*9e94795aSAndroid Build Coastguard Worker } else if tempScore > score { 303*9e94795aSAndroid Build Coastguard Worker score = tempScore 304*9e94795aSAndroid Build Coastguard Worker index = i 305*9e94795aSAndroid Build Coastguard Worker } 306*9e94795aSAndroid Build Coastguard Worker } 307*9e94795aSAndroid Build Coastguard Worker return pms[index], nil 308*9e94795aSAndroid Build Coastguard Worker} 309*9e94795aSAndroid Build Coastguard Worker 310*9e94795aSAndroid Build Coastguard Worker// inputFiles returns the complete list of files read 311*9e94795aSAndroid Build Coastguard Workerfunc inputFiles(lg *compliance.LicenseGraph, pmix *projectmetadata.Index, licenseTexts []string) []string { 312*9e94795aSAndroid Build Coastguard Worker projectMeta := pmix.AllMetadataFiles() 313*9e94795aSAndroid Build Coastguard Worker targets := lg.TargetNames() 314*9e94795aSAndroid Build Coastguard Worker files := make([]string, 0, len(licenseTexts)+len(targets)+len(projectMeta)) 315*9e94795aSAndroid Build Coastguard Worker files = append(files, licenseTexts...) 316*9e94795aSAndroid Build Coastguard Worker files = append(files, targets...) 317*9e94795aSAndroid Build Coastguard Worker files = append(files, projectMeta...) 318*9e94795aSAndroid Build Coastguard Worker return files 319*9e94795aSAndroid Build Coastguard Worker} 320*9e94795aSAndroid Build Coastguard Worker 321*9e94795aSAndroid Build Coastguard Worker// generateSPDXNamespace generates a unique SPDX Document Namespace using a SHA1 checksum 322*9e94795aSAndroid Build Coastguard Workerfunc generateSPDXNamespace(buildid string, created string, files ...string) string { 323*9e94795aSAndroid Build Coastguard Worker 324*9e94795aSAndroid Build Coastguard Worker seed := strings.Join(files, "") 325*9e94795aSAndroid Build Coastguard Worker 326*9e94795aSAndroid Build Coastguard Worker if buildid == "" { 327*9e94795aSAndroid Build Coastguard Worker seed += created 328*9e94795aSAndroid Build Coastguard Worker } else { 329*9e94795aSAndroid Build Coastguard Worker seed += buildid 330*9e94795aSAndroid Build Coastguard Worker } 331*9e94795aSAndroid Build Coastguard Worker 332*9e94795aSAndroid Build Coastguard Worker // Compute a SHA1 checksum of the seed. 333*9e94795aSAndroid Build Coastguard Worker hash := sha1.Sum([]byte(seed)) 334*9e94795aSAndroid Build Coastguard Worker uuid := hex.EncodeToString(hash[:]) 335*9e94795aSAndroid Build Coastguard Worker 336*9e94795aSAndroid Build Coastguard Worker namespace := fmt.Sprintf("SPDXRef-DOCUMENT-%s", uuid) 337*9e94795aSAndroid Build Coastguard Worker 338*9e94795aSAndroid Build Coastguard Worker return namespace 339*9e94795aSAndroid Build Coastguard Worker} 340*9e94795aSAndroid Build Coastguard Worker 341*9e94795aSAndroid Build Coastguard Worker// sbomGenerator implements the spdx bom utility 342*9e94795aSAndroid Build Coastguard Worker 343*9e94795aSAndroid Build Coastguard Worker// SBOM is part of the new government regulation issued to improve national cyber security 344*9e94795aSAndroid Build Coastguard Worker// and enhance software supply chain and transparency, see https://www.cisa.gov/sbom 345*9e94795aSAndroid Build Coastguard Worker 346*9e94795aSAndroid Build Coastguard Worker// sbomGenerator uses the SPDX standard, see the SPDX specification (https://spdx.github.io/spdx-spec/) 347*9e94795aSAndroid Build Coastguard Worker// sbomGenerator is also following the internal google SBOM styleguide (http://goto.google.com/spdx-style-guide) 348*9e94795aSAndroid Build Coastguard Workerfunc sbomGenerator(ctx *context, files ...string) (*spdx.Document, []string, error) { 349*9e94795aSAndroid Build Coastguard Worker // Must be at least one root file. 350*9e94795aSAndroid Build Coastguard Worker if len(files) < 1 { 351*9e94795aSAndroid Build Coastguard Worker return nil, nil, failNoneRequested 352*9e94795aSAndroid Build Coastguard Worker } 353*9e94795aSAndroid Build Coastguard Worker 354*9e94795aSAndroid Build Coastguard Worker pmix := projectmetadata.NewIndex(ctx.rootFS) 355*9e94795aSAndroid Build Coastguard Worker 356*9e94795aSAndroid Build Coastguard Worker lg, err := compliance.ReadLicenseGraph(ctx.rootFS, ctx.stderr, files) 357*9e94795aSAndroid Build Coastguard Worker 358*9e94795aSAndroid Build Coastguard Worker if err != nil { 359*9e94795aSAndroid Build Coastguard Worker return nil, nil, fmt.Errorf("Unable to read license text file(s) for %q: %v\n", files, err) 360*9e94795aSAndroid Build Coastguard Worker } 361*9e94795aSAndroid Build Coastguard Worker 362*9e94795aSAndroid Build Coastguard Worker // creating the packages section 363*9e94795aSAndroid Build Coastguard Worker pkgs := []*spdx.Package{} 364*9e94795aSAndroid Build Coastguard Worker 365*9e94795aSAndroid Build Coastguard Worker // creating the relationship section 366*9e94795aSAndroid Build Coastguard Worker relationships := []*spdx.Relationship{} 367*9e94795aSAndroid Build Coastguard Worker 368*9e94795aSAndroid Build Coastguard Worker // creating the license section 369*9e94795aSAndroid Build Coastguard Worker otherLicenses := []*spdx.OtherLicense{} 370*9e94795aSAndroid Build Coastguard Worker 371*9e94795aSAndroid Build Coastguard Worker // spdx document name 372*9e94795aSAndroid Build Coastguard Worker var docName string 373*9e94795aSAndroid Build Coastguard Worker 374*9e94795aSAndroid Build Coastguard Worker // main package name 375*9e94795aSAndroid Build Coastguard Worker var mainPkgName string 376*9e94795aSAndroid Build Coastguard Worker 377*9e94795aSAndroid Build Coastguard Worker // implementing the licenses references for the packages 378*9e94795aSAndroid Build Coastguard Worker licenses := make(map[string]string) 379*9e94795aSAndroid Build Coastguard Worker concludedLicenses := func(licenseTexts []string) string { 380*9e94795aSAndroid Build Coastguard Worker licenseRefs := make([]string, 0, len(licenseTexts)) 381*9e94795aSAndroid Build Coastguard Worker for _, licenseText := range licenseTexts { 382*9e94795aSAndroid Build Coastguard Worker license := strings.SplitN(licenseText, ":", 2)[0] 383*9e94795aSAndroid Build Coastguard Worker if _, ok := licenses[license]; !ok { 384*9e94795aSAndroid Build Coastguard Worker licenseRef := "LicenseRef-" + replaceSlashes(license) 385*9e94795aSAndroid Build Coastguard Worker licenses[license] = licenseRef 386*9e94795aSAndroid Build Coastguard Worker } 387*9e94795aSAndroid Build Coastguard Worker 388*9e94795aSAndroid Build Coastguard Worker licenseRefs = append(licenseRefs, licenses[license]) 389*9e94795aSAndroid Build Coastguard Worker } 390*9e94795aSAndroid Build Coastguard Worker if len(licenseRefs) > 1 { 391*9e94795aSAndroid Build Coastguard Worker return "(" + strings.Join(licenseRefs, " AND ") + ")" 392*9e94795aSAndroid Build Coastguard Worker } else if len(licenseRefs) == 1 { 393*9e94795aSAndroid Build Coastguard Worker return licenseRefs[0] 394*9e94795aSAndroid Build Coastguard Worker } 395*9e94795aSAndroid Build Coastguard Worker return "NONE" 396*9e94795aSAndroid Build Coastguard Worker } 397*9e94795aSAndroid Build Coastguard Worker 398*9e94795aSAndroid Build Coastguard Worker isMainPackage := true 399*9e94795aSAndroid Build Coastguard Worker visitedNodes := make(map[*compliance.TargetNode]struct{}) 400*9e94795aSAndroid Build Coastguard Worker 401*9e94795aSAndroid Build Coastguard Worker // performing a Breadth-first top down walk of licensegraph and building package information 402*9e94795aSAndroid Build Coastguard Worker compliance.WalkTopDownBreadthFirst(nil, lg, 403*9e94795aSAndroid Build Coastguard Worker func(lg *compliance.LicenseGraph, tn *compliance.TargetNode, path compliance.TargetEdgePath) bool { 404*9e94795aSAndroid Build Coastguard Worker if err != nil { 405*9e94795aSAndroid Build Coastguard Worker return false 406*9e94795aSAndroid Build Coastguard Worker } 407*9e94795aSAndroid Build Coastguard Worker var pm *projectmetadata.ProjectMetadata 408*9e94795aSAndroid Build Coastguard Worker pm, err = getProjectMetadata(ctx, pmix, tn) 409*9e94795aSAndroid Build Coastguard Worker if err != nil { 410*9e94795aSAndroid Build Coastguard Worker return false 411*9e94795aSAndroid Build Coastguard Worker } 412*9e94795aSAndroid Build Coastguard Worker 413*9e94795aSAndroid Build Coastguard Worker if isMainPackage { 414*9e94795aSAndroid Build Coastguard Worker docName = getDocumentName(ctx, tn, pm) 415*9e94795aSAndroid Build Coastguard Worker mainPkgName = replaceSlashes(getPackageName(ctx, tn)) 416*9e94795aSAndroid Build Coastguard Worker isMainPackage = false 417*9e94795aSAndroid Build Coastguard Worker } 418*9e94795aSAndroid Build Coastguard Worker 419*9e94795aSAndroid Build Coastguard Worker if len(path) == 0 { 420*9e94795aSAndroid Build Coastguard Worker // Add the describe relationship for the main package 421*9e94795aSAndroid Build Coastguard Worker rln := &spdx.Relationship{ 422*9e94795aSAndroid Build Coastguard Worker RefA: common.MakeDocElementID("" /* this document */, "DOCUMENT"), 423*9e94795aSAndroid Build Coastguard Worker RefB: common.MakeDocElementID("", mainPkgName), 424*9e94795aSAndroid Build Coastguard Worker Relationship: "DESCRIBES", 425*9e94795aSAndroid Build Coastguard Worker } 426*9e94795aSAndroid Build Coastguard Worker relationships = append(relationships, rln) 427*9e94795aSAndroid Build Coastguard Worker 428*9e94795aSAndroid Build Coastguard Worker } else { 429*9e94795aSAndroid Build Coastguard Worker // Check parent and identify annotation 430*9e94795aSAndroid Build Coastguard Worker parent := path[len(path)-1] 431*9e94795aSAndroid Build Coastguard Worker targetEdge := parent.Edge() 432*9e94795aSAndroid Build Coastguard Worker if targetEdge.IsRuntimeDependency() { 433*9e94795aSAndroid Build Coastguard Worker // Adding the dynamic link annotation RUNTIME_DEPENDENCY_OF relationship 434*9e94795aSAndroid Build Coastguard Worker rln := &spdx.Relationship{ 435*9e94795aSAndroid Build Coastguard Worker RefA: common.MakeDocElementID("", replaceSlashes(getPackageName(ctx, tn))), 436*9e94795aSAndroid Build Coastguard Worker RefB: common.MakeDocElementID("", replaceSlashes(getPackageName(ctx, targetEdge.Target()))), 437*9e94795aSAndroid Build Coastguard Worker Relationship: "RUNTIME_DEPENDENCY_OF", 438*9e94795aSAndroid Build Coastguard Worker } 439*9e94795aSAndroid Build Coastguard Worker relationships = append(relationships, rln) 440*9e94795aSAndroid Build Coastguard Worker 441*9e94795aSAndroid Build Coastguard Worker } else if targetEdge.IsDerivation() { 442*9e94795aSAndroid Build Coastguard Worker // Adding the derivation annotation as a CONTAINS relationship 443*9e94795aSAndroid Build Coastguard Worker rln := &spdx.Relationship{ 444*9e94795aSAndroid Build Coastguard Worker RefA: common.MakeDocElementID("", replaceSlashes(getPackageName(ctx, targetEdge.Target()))), 445*9e94795aSAndroid Build Coastguard Worker RefB: common.MakeDocElementID("", replaceSlashes(getPackageName(ctx, tn))), 446*9e94795aSAndroid Build Coastguard Worker Relationship: "CONTAINS", 447*9e94795aSAndroid Build Coastguard Worker } 448*9e94795aSAndroid Build Coastguard Worker relationships = append(relationships, rln) 449*9e94795aSAndroid Build Coastguard Worker 450*9e94795aSAndroid Build Coastguard Worker } else if targetEdge.IsBuildTool() { 451*9e94795aSAndroid Build Coastguard Worker // Adding the toolchain annotation as a BUILD_TOOL_OF relationship 452*9e94795aSAndroid Build Coastguard Worker rln := &spdx.Relationship{ 453*9e94795aSAndroid Build Coastguard Worker RefA: common.MakeDocElementID("", replaceSlashes(getPackageName(ctx, tn))), 454*9e94795aSAndroid Build Coastguard Worker RefB: common.MakeDocElementID("", replaceSlashes(getPackageName(ctx, targetEdge.Target()))), 455*9e94795aSAndroid Build Coastguard Worker Relationship: "BUILD_TOOL_OF", 456*9e94795aSAndroid Build Coastguard Worker } 457*9e94795aSAndroid Build Coastguard Worker relationships = append(relationships, rln) 458*9e94795aSAndroid Build Coastguard Worker 459*9e94795aSAndroid Build Coastguard Worker } else { 460*9e94795aSAndroid Build Coastguard Worker panic(fmt.Errorf("Unknown dependency type: %v", targetEdge.Annotations())) 461*9e94795aSAndroid Build Coastguard Worker } 462*9e94795aSAndroid Build Coastguard Worker } 463*9e94795aSAndroid Build Coastguard Worker 464*9e94795aSAndroid Build Coastguard Worker if _, alreadyVisited := visitedNodes[tn]; alreadyVisited { 465*9e94795aSAndroid Build Coastguard Worker return false 466*9e94795aSAndroid Build Coastguard Worker } 467*9e94795aSAndroid Build Coastguard Worker visitedNodes[tn] = struct{}{} 468*9e94795aSAndroid Build Coastguard Worker pkgName := getPackageName(ctx, tn) 469*9e94795aSAndroid Build Coastguard Worker 470*9e94795aSAndroid Build Coastguard Worker // Making an spdx package and adding it to pkgs 471*9e94795aSAndroid Build Coastguard Worker pkg := &spdx.Package{ 472*9e94795aSAndroid Build Coastguard Worker PackageName: replaceSlashes(pkgName), 473*9e94795aSAndroid Build Coastguard Worker PackageDownloadLocation: getDownloadUrl(ctx, pm), 474*9e94795aSAndroid Build Coastguard Worker PackageSPDXIdentifier: common.ElementID(replaceSlashes(pkgName)), 475*9e94795aSAndroid Build Coastguard Worker PackageLicenseConcluded: concludedLicenses(tn.LicenseTexts()), 476*9e94795aSAndroid Build Coastguard Worker } 477*9e94795aSAndroid Build Coastguard Worker 478*9e94795aSAndroid Build Coastguard Worker if pm != nil && pm.Version() != "" { 479*9e94795aSAndroid Build Coastguard Worker pkg.PackageVersion = pm.Version() 480*9e94795aSAndroid Build Coastguard Worker } else { 481*9e94795aSAndroid Build Coastguard Worker pkg.PackageVersion = NOASSERTION 482*9e94795aSAndroid Build Coastguard Worker } 483*9e94795aSAndroid Build Coastguard Worker 484*9e94795aSAndroid Build Coastguard Worker pkgs = append(pkgs, pkg) 485*9e94795aSAndroid Build Coastguard Worker 486*9e94795aSAndroid Build Coastguard Worker return true 487*9e94795aSAndroid Build Coastguard Worker }) 488*9e94795aSAndroid Build Coastguard Worker 489*9e94795aSAndroid Build Coastguard Worker // Adding Non-standard licenses 490*9e94795aSAndroid Build Coastguard Worker 491*9e94795aSAndroid Build Coastguard Worker licenseTexts := make([]string, 0, len(licenses)) 492*9e94795aSAndroid Build Coastguard Worker 493*9e94795aSAndroid Build Coastguard Worker for licenseText := range licenses { 494*9e94795aSAndroid Build Coastguard Worker licenseTexts = append(licenseTexts, licenseText) 495*9e94795aSAndroid Build Coastguard Worker } 496*9e94795aSAndroid Build Coastguard Worker 497*9e94795aSAndroid Build Coastguard Worker sort.Strings(licenseTexts) 498*9e94795aSAndroid Build Coastguard Worker 499*9e94795aSAndroid Build Coastguard Worker for _, licenseText := range licenseTexts { 500*9e94795aSAndroid Build Coastguard Worker // open the file 501*9e94795aSAndroid Build Coastguard Worker f, err := ctx.rootFS.Open(filepath.Clean(licenseText)) 502*9e94795aSAndroid Build Coastguard Worker if err != nil { 503*9e94795aSAndroid Build Coastguard Worker return nil, nil, fmt.Errorf("error opening license text file %q: %w", licenseText, err) 504*9e94795aSAndroid Build Coastguard Worker } 505*9e94795aSAndroid Build Coastguard Worker 506*9e94795aSAndroid Build Coastguard Worker // read the file 507*9e94795aSAndroid Build Coastguard Worker text, err := io.ReadAll(f) 508*9e94795aSAndroid Build Coastguard Worker if err != nil { 509*9e94795aSAndroid Build Coastguard Worker return nil, nil, fmt.Errorf("error reading license text file %q: %w", licenseText, err) 510*9e94795aSAndroid Build Coastguard Worker } 511*9e94795aSAndroid Build Coastguard Worker // Making an spdx License and adding it to otherLicenses 512*9e94795aSAndroid Build Coastguard Worker otherLicenses = append(otherLicenses, &spdx.OtherLicense{ 513*9e94795aSAndroid Build Coastguard Worker LicenseName: strings.Replace(licenses[licenseText], "LicenseRef-", "", -1), 514*9e94795aSAndroid Build Coastguard Worker LicenseIdentifier: string(licenses[licenseText]), 515*9e94795aSAndroid Build Coastguard Worker ExtractedText: string(text), 516*9e94795aSAndroid Build Coastguard Worker }) 517*9e94795aSAndroid Build Coastguard Worker } 518*9e94795aSAndroid Build Coastguard Worker 519*9e94795aSAndroid Build Coastguard Worker deps := inputFiles(lg, pmix, licenseTexts) 520*9e94795aSAndroid Build Coastguard Worker sort.Strings(deps) 521*9e94795aSAndroid Build Coastguard Worker 522*9e94795aSAndroid Build Coastguard Worker // Making the SPDX doc 523*9e94795aSAndroid Build Coastguard Worker ci, err := builder2v2.BuildCreationInfoSection2_2("Organization", "Google LLC", nil) 524*9e94795aSAndroid Build Coastguard Worker if err != nil { 525*9e94795aSAndroid Build Coastguard Worker return nil, nil, fmt.Errorf("Unable to build creation info section for SPDX doc: %v\n", err) 526*9e94795aSAndroid Build Coastguard Worker } 527*9e94795aSAndroid Build Coastguard Worker 528*9e94795aSAndroid Build Coastguard Worker ci.Created = ctx.creationTime() 529*9e94795aSAndroid Build Coastguard Worker 530*9e94795aSAndroid Build Coastguard Worker doc := &spdx.Document{ 531*9e94795aSAndroid Build Coastguard Worker SPDXVersion: "SPDX-2.2", 532*9e94795aSAndroid Build Coastguard Worker DataLicense: "CC0-1.0", 533*9e94795aSAndroid Build Coastguard Worker SPDXIdentifier: "DOCUMENT", 534*9e94795aSAndroid Build Coastguard Worker DocumentName: docName, 535*9e94795aSAndroid Build Coastguard Worker DocumentNamespace: generateSPDXNamespace(ctx.buildid, ci.Created, files...), 536*9e94795aSAndroid Build Coastguard Worker CreationInfo: ci, 537*9e94795aSAndroid Build Coastguard Worker Packages: pkgs, 538*9e94795aSAndroid Build Coastguard Worker Relationships: relationships, 539*9e94795aSAndroid Build Coastguard Worker OtherLicenses: otherLicenses, 540*9e94795aSAndroid Build Coastguard Worker } 541*9e94795aSAndroid Build Coastguard Worker 542*9e94795aSAndroid Build Coastguard Worker if err := spdxlib.ValidateDocument2_2(doc); err != nil { 543*9e94795aSAndroid Build Coastguard Worker return nil, nil, fmt.Errorf("Unable to validate the SPDX doc: %v\n", err) 544*9e94795aSAndroid Build Coastguard Worker } 545*9e94795aSAndroid Build Coastguard Worker 546*9e94795aSAndroid Build Coastguard Worker return doc, deps, nil 547*9e94795aSAndroid Build Coastguard Worker} 548