blob: 35c5c24d60b3986446754a13ed9d3998a2f2edc8 [file] [log] [blame]
Bob Badoure6fdd142021-12-09 22:10:43 -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 Badour682e1ba2022-02-02 15:15:56 -080019 "compress/gzip"
Bob Badoure6fdd142021-12-09 22:10:43 -080020 "flag"
21 "fmt"
22 "io"
23 "io/fs"
24 "os"
25 "path/filepath"
26 "strings"
Colin Cross38a61932022-01-27 15:26:49 -080027
28 "android/soong/tools/compliance"
Colin Crossbb45f8c2022-01-28 15:18:19 -080029
30 "github.com/google/blueprint/deptools"
Bob Badoure6fdd142021-12-09 22:10:43 -080031)
32
33var (
34 outputFile = flag.String("o", "-", "Where to write the NOTICE text file. (default stdout)")
Colin Crossbb45f8c2022-01-28 15:18:19 -080035 depsFile = flag.String("d", "", "Where to write the deps file")
Bob Badour682e1ba2022-02-02 15:15:56 -080036 stripPrefix = newMultiString("strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)")
37 title = flag.String("title", "", "The title of the notice file.")
Bob Badoure6fdd142021-12-09 22:10:43 -080038
39 failNoneRequested = fmt.Errorf("\nNo license metadata files requested")
40 failNoLicenses = fmt.Errorf("No licenses found")
41)
42
43type context struct {
44 stdout io.Writer
45 stderr io.Writer
46 rootFS fs.FS
Bob Badour682e1ba2022-02-02 15:15:56 -080047 stripPrefix []string
48 title string
Colin Crossbb45f8c2022-01-28 15:18:19 -080049 deps *[]string
Bob Badoure6fdd142021-12-09 22:10:43 -080050}
51
Bob Badour682e1ba2022-02-02 15:15:56 -080052func (ctx context) strip(installPath string) string {
53 for _, prefix := range ctx.stripPrefix {
54 if strings.HasPrefix(installPath, prefix) {
55 p := strings.TrimPrefix(installPath, prefix)
56 if 0 == len(p) {
57 p = ctx.title
58 }
59 if 0 == len(p) {
60 continue
61 }
62 return p
63 }
64 }
65 return installPath
66}
67
Bob Badoure6fdd142021-12-09 22:10:43 -080068func init() {
69 flag.Usage = func() {
70 fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...}
71
72Outputs a text NOTICE file.
73
74Options:
75`, filepath.Base(os.Args[0]))
76 flag.PrintDefaults()
77 }
78}
79
Bob Badour682e1ba2022-02-02 15:15:56 -080080// newMultiString creates a flag that allows multiple values in an array.
81func newMultiString(name, usage string) *multiString {
82 var f multiString
83 flag.Var(&f, name, usage)
84 return &f
85}
86
87// multiString implements the flag `Value` interface for multiple strings.
88type multiString []string
89
90func (ms *multiString) String() string { return strings.Join(*ms, ", ") }
91func (ms *multiString) Set(s string) error { *ms = append(*ms, s); return nil }
92
Bob Badoure6fdd142021-12-09 22:10:43 -080093func main() {
94 flag.Parse()
95
96 // Must specify at least one root target.
97 if flag.NArg() == 0 {
98 flag.Usage()
99 os.Exit(2)
100 }
101
102 if len(*outputFile) == 0 {
103 flag.Usage()
104 fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n")
105 os.Exit(2)
106 } else {
107 dir, err := filepath.Abs(filepath.Dir(*outputFile))
108 if err != nil {
Colin Cross179ec3e2022-01-27 15:47:09 -0800109 fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err)
Bob Badoure6fdd142021-12-09 22:10:43 -0800110 os.Exit(1)
111 }
112 fi, err := os.Stat(dir)
113 if err != nil {
Colin Cross179ec3e2022-01-27 15:47:09 -0800114 fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err)
Bob Badoure6fdd142021-12-09 22:10:43 -0800115 os.Exit(1)
116 }
117 if !fi.IsDir() {
118 fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile)
119 os.Exit(1)
120 }
121 }
122
123 var ofile io.Writer
Bob Badour682e1ba2022-02-02 15:15:56 -0800124 var closer io.Closer
Bob Badoure6fdd142021-12-09 22:10:43 -0800125 ofile = os.Stdout
Bob Badour682e1ba2022-02-02 15:15:56 -0800126 var obuf *bytes.Buffer
Bob Badoure6fdd142021-12-09 22:10:43 -0800127 if *outputFile != "-" {
Bob Badour682e1ba2022-02-02 15:15:56 -0800128 obuf = &bytes.Buffer{}
129 ofile = obuf
130 }
131 if strings.HasSuffix(*outputFile, ".gz") {
132 ofile, _ = gzip.NewWriterLevel(obuf, gzip.BestCompression)
133 closer = ofile.(io.Closer)
Bob Badoure6fdd142021-12-09 22:10:43 -0800134 }
135
Colin Crossbb45f8c2022-01-28 15:18:19 -0800136 var deps []string
137
Bob Badour682e1ba2022-02-02 15:15:56 -0800138 ctx := &context{ofile, os.Stderr, os.DirFS("."), *stripPrefix, *title, &deps}
Bob Badoure6fdd142021-12-09 22:10:43 -0800139
140 err := textNotice(ctx, flag.Args()...)
141 if err != nil {
142 if err == failNoneRequested {
143 flag.Usage()
144 }
145 fmt.Fprintf(os.Stderr, "%s\n", err.Error())
146 os.Exit(1)
147 }
Bob Badour682e1ba2022-02-02 15:15:56 -0800148 if closer != nil {
149 closer.Close()
150 }
151
Bob Badoure6fdd142021-12-09 22:10:43 -0800152 if *outputFile != "-" {
Bob Badour682e1ba2022-02-02 15:15:56 -0800153 err := os.WriteFile(*outputFile, obuf.Bytes(), 0666)
Bob Badoure6fdd142021-12-09 22:10:43 -0800154 if err != nil {
Colin Cross179ec3e2022-01-27 15:47:09 -0800155 fmt.Fprintf(os.Stderr, "could not write output to %q: %s\n", *outputFile, err)
Bob Badoure6fdd142021-12-09 22:10:43 -0800156 os.Exit(1)
157 }
158 }
Colin Crossbb45f8c2022-01-28 15:18:19 -0800159 if *depsFile != "" {
160 err := deptools.WriteDepFile(*depsFile, *outputFile, deps)
161 if err != nil {
162 fmt.Fprintf(os.Stderr, "could not write deps to %q: %s\n", *depsFile, err)
163 os.Exit(1)
164 }
165 }
Bob Badoure6fdd142021-12-09 22:10:43 -0800166 os.Exit(0)
167}
168
169// textNotice implements the textNotice utility.
170func textNotice(ctx *context, files ...string) error {
171 // Must be at least one root file.
172 if len(files) < 1 {
173 return failNoneRequested
174 }
175
176 // Read the license graph from the license metadata files (*.meta_lic).
177 licenseGraph, err := compliance.ReadLicenseGraph(ctx.rootFS, ctx.stderr, files)
178 if err != nil {
179 return fmt.Errorf("Unable to read license metadata file(s) %q: %v\n", files, err)
180 }
181 if licenseGraph == nil {
182 return failNoLicenses
183 }
184
185 // rs contains all notice resolutions.
186 rs := compliance.ResolveNotices(licenseGraph)
187
188 ni, err := compliance.IndexLicenseTexts(ctx.rootFS, licenseGraph, rs)
189 if err != nil {
190 return fmt.Errorf("Unable to read license text file(s) for %q: %v\n", files, err)
191 }
192
193 for h := range ni.Hashes() {
194 fmt.Fprintln(ctx.stdout, "==============================================================================")
195 for _, libName := range ni.HashLibs(h) {
196 fmt.Fprintf(ctx.stdout, "%s used by:\n", libName)
197 for _, installPath := range ni.HashLibInstalls(h, libName) {
Bob Badour682e1ba2022-02-02 15:15:56 -0800198 fmt.Fprintf(ctx.stdout, " %s\n", ctx.strip(installPath))
Bob Badoure6fdd142021-12-09 22:10:43 -0800199 }
200 fmt.Fprintln(ctx.stdout)
201 }
202 ctx.stdout.Write(ni.HashText(h))
203 fmt.Fprintln(ctx.stdout)
204 }
Colin Crossbb45f8c2022-01-28 15:18:19 -0800205
206 *ctx.deps = ni.InputNoticeFiles()
207
Bob Badoure6fdd142021-12-09 22:10:43 -0800208 return nil
209}