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