blob: 5363a59192cf7966bbbc2f2f77b4ee2d24d7f4ec [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)")
Bob Badour682e1ba2022-02-02 15:15:56 -080032 stripPrefix = newMultiString("strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)")
Bob Badour2546feb2022-01-26 20:58:24 -080033
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
Bob Badour682e1ba2022-02-02 15:15:56 -080042 stripPrefix []string
43}
44
45func (ctx context) strip(installPath string) string {
46 for _, prefix := range ctx.stripPrefix {
47 if strings.HasPrefix(installPath, prefix) {
48 p := strings.TrimPrefix(installPath, prefix)
49 if 0 == len(p) {
50 continue
51 }
52 return p
53 }
54 }
55 return installPath
Bob Badour2546feb2022-01-26 20:58:24 -080056}
57
58func init() {
59 flag.Usage = func() {
60 fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...}
61
62Outputs a bill of materials. i.e. the list of installed paths.
63
64Options:
65`, filepath.Base(os.Args[0]))
66 flag.PrintDefaults()
67 }
68}
69
Bob Badour682e1ba2022-02-02 15:15:56 -080070// newMultiString creates a flag that allows multiple values in an array.
71func newMultiString(name, usage string) *multiString {
72 var f multiString
73 flag.Var(&f, name, usage)
74 return &f
75}
76
77// multiString implements the flag `Value` interface for multiple strings.
78type multiString []string
79
80func (ms *multiString) String() string { return strings.Join(*ms, ", ") }
81func (ms *multiString) Set(s string) error { *ms = append(*ms, s); return nil }
82
Bob Badour2546feb2022-01-26 20:58:24 -080083func main() {
84 flag.Parse()
85
86 // Must specify at least one root target.
87 if flag.NArg() == 0 {
88 flag.Usage()
89 os.Exit(2)
90 }
91
92 if len(*outputFile) == 0 {
93 flag.Usage()
94 fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n")
95 os.Exit(2)
96 } else {
97 dir, err := filepath.Abs(filepath.Dir(*outputFile))
98 if err != nil {
Colin Cross179ec3e2022-01-27 15:47:09 -080099 fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err)
Bob Badour2546feb2022-01-26 20:58:24 -0800100 os.Exit(1)
101 }
102 fi, err := os.Stat(dir)
103 if err != nil {
Colin Cross179ec3e2022-01-27 15:47:09 -0800104 fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err)
Bob Badour2546feb2022-01-26 20:58:24 -0800105 os.Exit(1)
106 }
107 if !fi.IsDir() {
108 fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile)
109 os.Exit(1)
110 }
111 }
112
113 var ofile io.Writer
114 ofile = os.Stdout
115 if *outputFile != "-" {
116 ofile = &bytes.Buffer{}
117 }
118
119 ctx := &context{ofile, os.Stderr, os.DirFS("."), *stripPrefix}
120
121 err := billOfMaterials(ctx, flag.Args()...)
122 if err != nil {
123 if err == failNoneRequested {
124 flag.Usage()
125 }
126 fmt.Fprintf(os.Stderr, "%s\n", err.Error())
127 os.Exit(1)
128 }
129 if *outputFile != "-" {
130 err := os.WriteFile(*outputFile, ofile.(*bytes.Buffer).Bytes(), 0666)
131 if err != nil {
Colin Cross179ec3e2022-01-27 15:47:09 -0800132 fmt.Fprintf(os.Stderr, "could not write output to %q: %s\n", *outputFile, err)
Bob Badour2546feb2022-01-26 20:58:24 -0800133 os.Exit(1)
134 }
135 }
136 os.Exit(0)
137}
138
139// billOfMaterials implements the bom utility.
140func billOfMaterials(ctx *context, files ...string) error {
141 // Must be at least one root file.
142 if len(files) < 1 {
143 return failNoneRequested
144 }
145
146 // Read the license graph from the license metadata files (*.meta_lic).
147 licenseGraph, err := compliance.ReadLicenseGraph(ctx.rootFS, ctx.stderr, files)
148 if err != nil {
149 return fmt.Errorf("Unable to read license metadata file(s) %q: %v\n", files, err)
150 }
151 if licenseGraph == nil {
152 return failNoLicenses
153 }
154
155 // rs contains all notice resolutions.
156 rs := compliance.ResolveNotices(licenseGraph)
157
158 ni, err := compliance.IndexLicenseTexts(ctx.rootFS, licenseGraph, rs)
159 if err != nil {
160 return fmt.Errorf("Unable to read license text file(s) for %q: %v\n", files, err)
161 }
162
163 for path := range ni.InstallPaths() {
Bob Badour682e1ba2022-02-02 15:15:56 -0800164 fmt.Fprintln(ctx.stdout, ctx.strip(path))
Bob Badour2546feb2022-01-26 20:58:24 -0800165 }
166 return nil
167}