blob: c3f8e4cea39bf62f140109e24d9b93d7915c8711 [file] [log] [blame]
Bob Badourf8792242022-02-01 11:54:20 -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"
19 "compress/gzip"
20 "encoding/xml"
21 "flag"
22 "fmt"
23 "io"
24 "io/fs"
25 "os"
26 "path/filepath"
Bob Badourab5cfbd2022-10-17 17:40:04 -070027 "sort"
Bob Badourf8792242022-02-01 11:54:20 -080028 "strings"
29
Bob Badour986a8392022-06-03 10:42:27 -070030 "android/soong/response"
Bob Badourf8792242022-02-01 11:54:20 -080031 "android/soong/tools/compliance"
32
33 "github.com/google/blueprint/deptools"
34)
35
36var (
Bob Badourf8792242022-02-01 11:54:20 -080037 failNoneRequested = fmt.Errorf("\nNo license metadata files requested")
38 failNoLicenses = fmt.Errorf("No licenses found")
39)
40
41type context struct {
42 stdout io.Writer
43 stderr io.Writer
44 rootFS fs.FS
Bob Badour49dd4f72022-02-04 14:49:01 -080045 product string
Bob Badour682e1ba2022-02-02 15:15:56 -080046 stripPrefix []string
47 title string
Bob Badourf8792242022-02-01 11:54:20 -080048 deps *[]string
49}
50
Bob Badour682e1ba2022-02-02 15:15:56 -080051func (ctx context) strip(installPath string) string {
52 for _, prefix := range ctx.stripPrefix {
53 if strings.HasPrefix(installPath, prefix) {
54 p := strings.TrimPrefix(installPath, prefix)
55 if 0 == len(p) {
Bob Badour49dd4f72022-02-04 14:49:01 -080056 p = ctx.product
Bob Badour682e1ba2022-02-02 15:15:56 -080057 }
58 if 0 == len(p) {
59 continue
60 }
61 return p
62 }
63 }
64 return installPath
65}
66
Bob Badour682e1ba2022-02-02 15:15:56 -080067// newMultiString creates a flag that allows multiple values in an array.
Bob Badour986a8392022-06-03 10:42:27 -070068func newMultiString(flags *flag.FlagSet, name, usage string) *multiString {
Bob Badour682e1ba2022-02-02 15:15:56 -080069 var f multiString
Bob Badour986a8392022-06-03 10:42:27 -070070 flags.Var(&f, name, usage)
Bob Badour682e1ba2022-02-02 15:15:56 -080071 return &f
72}
73
74// multiString implements the flag `Value` interface for multiple strings.
75type multiString []string
76
77func (ms *multiString) String() string { return strings.Join(*ms, ", ") }
78func (ms *multiString) Set(s string) error { *ms = append(*ms, s); return nil }
79
Bob Badourf8792242022-02-01 11:54:20 -080080func main() {
Bob Badour986a8392022-06-03 10:42:27 -070081 var expandedArgs []string
82 for _, arg := range os.Args[1:] {
83 if strings.HasPrefix(arg, "@") {
84 f, err := os.Open(strings.TrimPrefix(arg, "@"))
85 if err != nil {
86 fmt.Fprintln(os.Stderr, err.Error())
87 os.Exit(1)
88 }
89
90 respArgs, err := response.ReadRspFile(f)
91 f.Close()
92 if err != nil {
93 fmt.Fprintln(os.Stderr, err.Error())
94 os.Exit(1)
95 }
96 expandedArgs = append(expandedArgs, respArgs...)
97 } else {
98 expandedArgs = append(expandedArgs, arg)
99 }
100 }
101
102 flags := flag.NewFlagSet("flags", flag.ExitOnError)
103
104 flags.Usage = func() {
105 fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...}
106
107Outputs an xml NOTICE.xml or gzipped NOTICE.xml.gz file if the -o filename ends
108with ".gz".
109
110Options:
111`, filepath.Base(os.Args[0]))
112 flags.PrintDefaults()
113 }
114
115 outputFile := flags.String("o", "-", "Where to write the NOTICE xml or xml.gz file. (default stdout)")
116 depsFile := flags.String("d", "", "Where to write the deps file")
117 product := flags.String("product", "", "The name of the product for which the notice is generated.")
118 stripPrefix := newMultiString(flags, "strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)")
119 title := flags.String("title", "", "The title of the notice file.")
120
121 flags.Parse(expandedArgs)
Bob Badourf8792242022-02-01 11:54:20 -0800122
123 // Must specify at least one root target.
Bob Badour986a8392022-06-03 10:42:27 -0700124 if flags.NArg() == 0 {
125 flags.Usage()
Bob Badourf8792242022-02-01 11:54:20 -0800126 os.Exit(2)
127 }
128
129 if len(*outputFile) == 0 {
Bob Badour986a8392022-06-03 10:42:27 -0700130 flags.Usage()
Bob Badourf8792242022-02-01 11:54:20 -0800131 fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n")
132 os.Exit(2)
133 } else {
134 dir, err := filepath.Abs(filepath.Dir(*outputFile))
135 if err != nil {
136 fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err)
137 os.Exit(1)
138 }
139 fi, err := os.Stat(dir)
140 if err != nil {
141 fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err)
142 os.Exit(1)
143 }
144 if !fi.IsDir() {
145 fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile)
146 os.Exit(1)
147 }
148 }
149
150 var ofile io.Writer
151 var closer io.Closer
152 ofile = os.Stdout
153 var obuf *bytes.Buffer
154 if *outputFile != "-" {
155 obuf = &bytes.Buffer{}
156 ofile = obuf
157 }
158 if strings.HasSuffix(*outputFile, ".gz") {
159 ofile, _ = gzip.NewWriterLevel(obuf, gzip.BestCompression)
160 closer = ofile.(io.Closer)
161 }
162
163 var deps []string
164
Bob Badourc778e4c2022-03-22 13:05:19 -0700165 ctx := &context{ofile, os.Stderr, compliance.FS, *product, *stripPrefix, *title, &deps}
Bob Badourf8792242022-02-01 11:54:20 -0800166
Bob Badour986a8392022-06-03 10:42:27 -0700167 err := xmlNotice(ctx, flags.Args()...)
Bob Badourf8792242022-02-01 11:54:20 -0800168 if err != nil {
169 if err == failNoneRequested {
Bob Badour986a8392022-06-03 10:42:27 -0700170 flags.Usage()
Bob Badourf8792242022-02-01 11:54:20 -0800171 }
172 fmt.Fprintf(os.Stderr, "%s\n", err.Error())
173 os.Exit(1)
174 }
175 if closer != nil {
176 closer.Close()
177 }
178
179 if *outputFile != "-" {
180 err := os.WriteFile(*outputFile, obuf.Bytes(), 0666)
181 if err != nil {
182 fmt.Fprintf(os.Stderr, "could not write output to %q: %s\n", *outputFile, err)
183 os.Exit(1)
184 }
185 }
186 if *depsFile != "" {
187 err := deptools.WriteDepFile(*depsFile, *outputFile, deps)
188 if err != nil {
189 fmt.Fprintf(os.Stderr, "could not write deps to %q: %s\n", *depsFile, err)
190 os.Exit(1)
191 }
192 }
193 os.Exit(0)
194}
195
196// xmlNotice implements the xmlnotice utility.
197func xmlNotice(ctx *context, files ...string) error {
198 // Must be at least one root file.
199 if len(files) < 1 {
200 return failNoneRequested
201 }
202
203 // Read the license graph from the license metadata files (*.meta_lic).
204 licenseGraph, err := compliance.ReadLicenseGraph(ctx.rootFS, ctx.stderr, files)
205 if err != nil {
206 return fmt.Errorf("Unable to read license metadata file(s) %q: %v\n", files, err)
207 }
208 if licenseGraph == nil {
209 return failNoLicenses
210 }
211
212 // rs contains all notice resolutions.
213 rs := compliance.ResolveNotices(licenseGraph)
214
215 ni, err := compliance.IndexLicenseTexts(ctx.rootFS, licenseGraph, rs)
216 if err != nil {
217 return fmt.Errorf("Unable to read license text file(s) for %q: %v\n", files, err)
218 }
219
220 fmt.Fprintln(ctx.stdout, "<?xml version=\"1.0\" encoding=\"utf-8\"?>")
221 fmt.Fprintln(ctx.stdout, "<licenses>")
222
223 for installPath := range ni.InstallPaths() {
Bob Badour682e1ba2022-02-02 15:15:56 -0800224 p := ctx.strip(installPath)
Bob Badourf8792242022-02-01 11:54:20 -0800225 for _, h := range ni.InstallHashes(installPath) {
226 for _, lib := range ni.InstallHashLibs(installPath, h) {
227 fmt.Fprintf(ctx.stdout, "<file-name contentId=\"%s\" lib=\"", h.String())
228 xml.EscapeText(ctx.stdout, []byte(lib))
229 fmt.Fprintf(ctx.stdout, "\">")
230 xml.EscapeText(ctx.stdout, []byte(p))
231 fmt.Fprintln(ctx.stdout, "</file-name>")
232 }
233 }
234 }
235 for h := range ni.Hashes() {
236 fmt.Fprintf(ctx.stdout, "<file-content contentId=\"%s\"><![CDATA[", h)
237 xml.EscapeText(ctx.stdout, ni.HashText(h))
238 fmt.Fprintf(ctx.stdout, "]]></file-content>\n\n")
239 }
240 fmt.Fprintln(ctx.stdout, "</licenses>")
241
Bob Badourab5cfbd2022-10-17 17:40:04 -0700242 *ctx.deps = ni.InputFiles()
243 sort.Strings(*ctx.deps)
Bob Badourf8792242022-02-01 11:54:20 -0800244
245 return nil
246}