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