blob: aaec786da4bb376d73b196a13147277380491c22 [file] [log] [blame]
Bob Badour2546feb2022-01-26 20:58:24 -08001// Copyright 2021 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package main
16
17import (
18 "bytes"
Bob Badour2546feb2022-01-26 20:58:24 -080019 "flag"
20 "fmt"
21 "io"
22 "io/fs"
23 "os"
24 "path/filepath"
25 "strings"
Colin Cross38a61932022-01-27 15:26:49 -080026
27 "android/soong/tools/compliance"
Bob Badour2546feb2022-01-26 20:58:24 -080028)
29
30var (
31 outputFile = flag.String("o", "-", "Where to write the bill of materials. (default stdout)")
32 stripPrefix = flag.String("strip_prefix", "", "Prefix to remove from paths. i.e. path to root")
33
34 failNoneRequested = fmt.Errorf("\nNo license metadata files requested")
35 failNoLicenses = fmt.Errorf("No licenses found")
36)
37
38type context struct {
39 stdout io.Writer
40 stderr io.Writer
41 rootFS fs.FS
42 stripPrefix string
43}
44
45func init() {
46 flag.Usage = func() {
47 fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...}
48
49Outputs a bill of materials. i.e. the list of installed paths.
50
51Options:
52`, filepath.Base(os.Args[0]))
53 flag.PrintDefaults()
54 }
55}
56
57func main() {
58 flag.Parse()
59
60 // Must specify at least one root target.
61 if flag.NArg() == 0 {
62 flag.Usage()
63 os.Exit(2)
64 }
65
66 if len(*outputFile) == 0 {
67 flag.Usage()
68 fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n")
69 os.Exit(2)
70 } else {
71 dir, err := filepath.Abs(filepath.Dir(*outputFile))
72 if err != nil {
Colin Cross179ec3e2022-01-27 15:47:09 -080073 fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err)
Bob Badour2546feb2022-01-26 20:58:24 -080074 os.Exit(1)
75 }
76 fi, err := os.Stat(dir)
77 if err != nil {
Colin Cross179ec3e2022-01-27 15:47:09 -080078 fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err)
Bob Badour2546feb2022-01-26 20:58:24 -080079 os.Exit(1)
80 }
81 if !fi.IsDir() {
82 fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile)
83 os.Exit(1)
84 }
85 }
86
87 var ofile io.Writer
88 ofile = os.Stdout
89 if *outputFile != "-" {
90 ofile = &bytes.Buffer{}
91 }
92
93 ctx := &context{ofile, os.Stderr, os.DirFS("."), *stripPrefix}
94
95 err := billOfMaterials(ctx, flag.Args()...)
96 if err != nil {
97 if err == failNoneRequested {
98 flag.Usage()
99 }
100 fmt.Fprintf(os.Stderr, "%s\n", err.Error())
101 os.Exit(1)
102 }
103 if *outputFile != "-" {
104 err := os.WriteFile(*outputFile, ofile.(*bytes.Buffer).Bytes(), 0666)
105 if err != nil {
Colin Cross179ec3e2022-01-27 15:47:09 -0800106 fmt.Fprintf(os.Stderr, "could not write output to %q: %s\n", *outputFile, err)
Bob Badour2546feb2022-01-26 20:58:24 -0800107 os.Exit(1)
108 }
109 }
110 os.Exit(0)
111}
112
113// billOfMaterials implements the bom utility.
114func billOfMaterials(ctx *context, files ...string) error {
115 // Must be at least one root file.
116 if len(files) < 1 {
117 return failNoneRequested
118 }
119
120 // Read the license graph from the license metadata files (*.meta_lic).
121 licenseGraph, err := compliance.ReadLicenseGraph(ctx.rootFS, ctx.stderr, files)
122 if err != nil {
123 return fmt.Errorf("Unable to read license metadata file(s) %q: %v\n", files, err)
124 }
125 if licenseGraph == nil {
126 return failNoLicenses
127 }
128
129 // rs contains all notice resolutions.
130 rs := compliance.ResolveNotices(licenseGraph)
131
132 ni, err := compliance.IndexLicenseTexts(ctx.rootFS, licenseGraph, rs)
133 if err != nil {
134 return fmt.Errorf("Unable to read license text file(s) for %q: %v\n", files, err)
135 }
136
137 for path := range ni.InstallPaths() {
138 if 0 < len(ctx.stripPrefix) && strings.HasPrefix(path, ctx.stripPrefix) {
139 fmt.Fprintln(ctx.stdout, path[len(ctx.stripPrefix):])
140 } else {
141 fmt.Fprintln(ctx.stdout, path)
142 }
143 }
144 return nil
145}