xref: /aosp_15_r20/build/make/tools/compliance/cmd/sbom/sbom.go (revision 9e94795a3d4ef5c1d47486f9a02bb378756cea8a)
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