blob: 187f8280577ba05060a7bbc4c4370db9bbc96969 [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
Bob Badour986a8392022-06-03 10:42:27 -070027 "android/soong/response"
Colin Cross38a61932022-01-27 15:26:49 -080028 "android/soong/tools/compliance"
Bob Badour2546feb2022-01-26 20:58:24 -080029)
30
31var (
Bob Badour2546feb2022-01-26 20:58:24 -080032 failNoneRequested = fmt.Errorf("\nNo license metadata files requested")
33 failNoLicenses = fmt.Errorf("No licenses found")
34)
35
36type context struct {
37 stdout io.Writer
38 stderr io.Writer
39 rootFS fs.FS
Bob Badour682e1ba2022-02-02 15:15:56 -080040 stripPrefix []string
41}
42
43func (ctx context) strip(installPath string) string {
44 for _, prefix := range ctx.stripPrefix {
45 if strings.HasPrefix(installPath, prefix) {
46 p := strings.TrimPrefix(installPath, prefix)
47 if 0 == len(p) {
48 continue
49 }
50 return p
51 }
52 }
53 return installPath
Bob Badour2546feb2022-01-26 20:58:24 -080054}
55
Bob Badour682e1ba2022-02-02 15:15:56 -080056// newMultiString creates a flag that allows multiple values in an array.
Bob Badour986a8392022-06-03 10:42:27 -070057func newMultiString(flags *flag.FlagSet, name, usage string) *multiString {
Bob Badour682e1ba2022-02-02 15:15:56 -080058 var f multiString
Bob Badour986a8392022-06-03 10:42:27 -070059 flags.Var(&f, name, usage)
Bob Badour682e1ba2022-02-02 15:15:56 -080060 return &f
61}
62
63// multiString implements the flag `Value` interface for multiple strings.
64type multiString []string
65
66func (ms *multiString) String() string { return strings.Join(*ms, ", ") }
67func (ms *multiString) Set(s string) error { *ms = append(*ms, s); return nil }
68
Bob Badour2546feb2022-01-26 20:58:24 -080069func main() {
Bob Badour986a8392022-06-03 10:42:27 -070070 var expandedArgs []string
71 for _, arg := range os.Args[1:] {
72 if strings.HasPrefix(arg, "@") {
73 f, err := os.Open(strings.TrimPrefix(arg, "@"))
74 if err != nil {
75 fmt.Fprintln(os.Stderr, err.Error())
76 os.Exit(1)
77 }
78
79 respArgs, err := response.ReadRspFile(f)
80 f.Close()
81 if err != nil {
82 fmt.Fprintln(os.Stderr, err.Error())
83 os.Exit(1)
84 }
85 expandedArgs = append(expandedArgs, respArgs...)
86 } else {
87 expandedArgs = append(expandedArgs, arg)
88 }
89 }
90
91 flags := flag.NewFlagSet("flags", flag.ExitOnError)
92
93 flags.Usage = func() {
94 fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...}
95
96Outputs a bill of materials. i.e. the list of installed paths.
97
98Options:
99`, filepath.Base(os.Args[0]))
100 flags.PrintDefaults()
101 }
102
103 outputFile := flags.String("o", "-", "Where to write the bill of materials. (default stdout)")
104 stripPrefix := newMultiString(flags, "strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)")
105
106 flags.Parse(expandedArgs)
Bob Badour2546feb2022-01-26 20:58:24 -0800107
108 // Must specify at least one root target.
Bob Badour986a8392022-06-03 10:42:27 -0700109 if flags.NArg() == 0 {
110 flags.Usage()
Bob Badour2546feb2022-01-26 20:58:24 -0800111 os.Exit(2)
112 }
113
114 if len(*outputFile) == 0 {
Bob Badour986a8392022-06-03 10:42:27 -0700115 flags.Usage()
Bob Badour2546feb2022-01-26 20:58:24 -0800116 fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n")
117 os.Exit(2)
118 } else {
119 dir, err := filepath.Abs(filepath.Dir(*outputFile))
120 if err != nil {
Colin Cross179ec3e2022-01-27 15:47:09 -0800121 fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err)
Bob Badour2546feb2022-01-26 20:58:24 -0800122 os.Exit(1)
123 }
124 fi, err := os.Stat(dir)
125 if err != nil {
Colin Cross179ec3e2022-01-27 15:47:09 -0800126 fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err)
Bob Badour2546feb2022-01-26 20:58:24 -0800127 os.Exit(1)
128 }
129 if !fi.IsDir() {
130 fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile)
131 os.Exit(1)
132 }
133 }
134
135 var ofile io.Writer
136 ofile = os.Stdout
137 if *outputFile != "-" {
138 ofile = &bytes.Buffer{}
139 }
140
Bob Badourc778e4c2022-03-22 13:05:19 -0700141 ctx := &context{ofile, os.Stderr, compliance.FS, *stripPrefix}
Bob Badour2546feb2022-01-26 20:58:24 -0800142
Bob Badour986a8392022-06-03 10:42:27 -0700143 err := billOfMaterials(ctx, flags.Args()...)
Bob Badour2546feb2022-01-26 20:58:24 -0800144 if err != nil {
145 if err == failNoneRequested {
Bob Badour986a8392022-06-03 10:42:27 -0700146 flags.Usage()
Bob Badour2546feb2022-01-26 20:58:24 -0800147 }
148 fmt.Fprintf(os.Stderr, "%s\n", err.Error())
149 os.Exit(1)
150 }
151 if *outputFile != "-" {
152 err := os.WriteFile(*outputFile, ofile.(*bytes.Buffer).Bytes(), 0666)
153 if err != nil {
Colin Cross179ec3e2022-01-27 15:47:09 -0800154 fmt.Fprintf(os.Stderr, "could not write output to %q: %s\n", *outputFile, err)
Bob Badour2546feb2022-01-26 20:58:24 -0800155 os.Exit(1)
156 }
157 }
158 os.Exit(0)
159}
160
161// billOfMaterials implements the bom utility.
162func billOfMaterials(ctx *context, files ...string) error {
163 // Must be at least one root file.
164 if len(files) < 1 {
165 return failNoneRequested
166 }
167
168 // Read the license graph from the license metadata files (*.meta_lic).
169 licenseGraph, err := compliance.ReadLicenseGraph(ctx.rootFS, ctx.stderr, files)
170 if err != nil {
171 return fmt.Errorf("Unable to read license metadata file(s) %q: %v\n", files, err)
172 }
173 if licenseGraph == nil {
174 return failNoLicenses
175 }
176
177 // rs contains all notice resolutions.
178 rs := compliance.ResolveNotices(licenseGraph)
179
180 ni, err := compliance.IndexLicenseTexts(ctx.rootFS, licenseGraph, rs)
181 if err != nil {
182 return fmt.Errorf("Unable to read license text file(s) for %q: %v\n", files, err)
183 }
184
185 for path := range ni.InstallPaths() {
Bob Badour682e1ba2022-02-02 15:15:56 -0800186 fmt.Fprintln(ctx.stdout, ctx.strip(path))
Bob Badour2546feb2022-01-26 20:58:24 -0800187 }
188 return nil
189}