1// 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 "flag" 21 "fmt" 22 "io" 23 "io/fs" 24 "os" 25 "path/filepath" 26 "sort" 27 "strings" 28 29 "android/soong/response" 30 "android/soong/tools/compliance" 31 32 "github.com/google/blueprint/deptools" 33) 34 35var ( 36 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 44 product string 45 stripPrefix []string 46 title string 47 deps *[]string 48} 49 50func (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) { 55 p = ctx.product 56 } 57 if 0 == len(p) { 58 continue 59 } 60 return p 61 } 62 } 63 return installPath 64} 65 66// newMultiString creates a flag that allows multiple values in an array. 67func newMultiString(flags *flag.FlagSet, name, usage string) *multiString { 68 var f multiString 69 flags.Var(&f, name, usage) 70 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 79func main() { 80 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 a text NOTICE file. 107 108Options: 109`, filepath.Base(os.Args[0])) 110 flags.PrintDefaults() 111 } 112 113 outputFile := flags.String("o", "-", "Where to write the NOTICE text file. (default stdout)") 114 depsFile := flags.String("d", "", "Where to write the deps file") 115 product := flags.String("product", "", "The name of the product for which the notice is generated.") 116 stripPrefix := newMultiString(flags, "strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)") 117 title := flags.String("title", "", "The title of the notice file.") 118 119 flags.Parse(expandedArgs) 120 121 // Must specify at least one root target. 122 if flags.NArg() == 0 { 123 flags.Usage() 124 os.Exit(2) 125 } 126 127 if len(*outputFile) == 0 { 128 flags.Usage() 129 fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n") 130 os.Exit(2) 131 } else { 132 dir, err := filepath.Abs(filepath.Dir(*outputFile)) 133 if err != nil { 134 fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err) 135 os.Exit(1) 136 } 137 fi, err := os.Stat(dir) 138 if err != nil { 139 fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err) 140 os.Exit(1) 141 } 142 if !fi.IsDir() { 143 fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile) 144 os.Exit(1) 145 } 146 } 147 148 var ofile io.Writer 149 var closer io.Closer 150 ofile = os.Stdout 151 var obuf *bytes.Buffer 152 if *outputFile != "-" { 153 obuf = &bytes.Buffer{} 154 ofile = obuf 155 } 156 if strings.HasSuffix(*outputFile, ".gz") { 157 ofile, _ = gzip.NewWriterLevel(obuf, gzip.BestCompression) 158 closer = ofile.(io.Closer) 159 } 160 161 var deps []string 162 163 ctx := &context{ofile, os.Stderr, compliance.FS, *product, *stripPrefix, *title, &deps} 164 165 err := textNotice(ctx, flags.Args()...) 166 if err != nil { 167 if err == failNoneRequested { 168 flags.Usage() 169 } 170 fmt.Fprintf(os.Stderr, "%s\n", err.Error()) 171 os.Exit(1) 172 } 173 if closer != nil { 174 closer.Close() 175 } 176 177 if *outputFile != "-" { 178 err := os.WriteFile(*outputFile, obuf.Bytes(), 0666) 179 if err != nil { 180 fmt.Fprintf(os.Stderr, "could not write output to %q: %s\n", *outputFile, err) 181 os.Exit(1) 182 } 183 } 184 if *depsFile != "" { 185 err := deptools.WriteDepFile(*depsFile, *outputFile, deps) 186 if err != nil { 187 fmt.Fprintf(os.Stderr, "could not write deps to %q: %s\n", *depsFile, err) 188 os.Exit(1) 189 } 190 } 191 os.Exit(0) 192} 193 194// textNotice implements the textNotice utility. 195func textNotice(ctx *context, files ...string) error { 196 // Must be at least one root file. 197 if len(files) < 1 { 198 return failNoneRequested 199 } 200 201 // Read the license graph from the license metadata files (*.meta_lic). 202 licenseGraph, err := compliance.ReadLicenseGraph(ctx.rootFS, ctx.stderr, files) 203 if err != nil { 204 return fmt.Errorf("Unable to read license metadata file(s) %q: %v\n", files, err) 205 } 206 if licenseGraph == nil { 207 return failNoLicenses 208 } 209 210 // rs contains all notice resolutions. 211 rs := compliance.ResolveNotices(licenseGraph) 212 213 ni, err := compliance.IndexLicenseTexts(ctx.rootFS, licenseGraph, rs) 214 if err != nil { 215 return fmt.Errorf("Unable to read license text file(s) for %q: %v\n", files, err) 216 } 217 218 if len(ctx.title) > 0 { 219 fmt.Fprintf(ctx.stdout, "%s\n\n", ctx.title) 220 } 221 for h := range ni.Hashes() { 222 fmt.Fprintln(ctx.stdout, "==============================================================================") 223 for _, libName := range ni.HashLibs(h) { 224 fmt.Fprintf(ctx.stdout, "%s used by:\n", libName) 225 for _, installPath := range ni.HashLibInstalls(h, libName) { 226 fmt.Fprintf(ctx.stdout, " %s\n", ctx.strip(installPath)) 227 } 228 fmt.Fprintln(ctx.stdout) 229 } 230 ctx.stdout.Write(ni.HashText(h)) 231 fmt.Fprintln(ctx.stdout) 232 } 233 234 *ctx.deps = ni.InputFiles() 235 sort.Strings(*ctx.deps) 236 237 return nil 238} 239