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 "flag" 20 "fmt" 21 "io" 22 "io/fs" 23 "os" 24 "path/filepath" 25 "strings" 26 27 "android/soong/response" 28 "android/soong/tools/compliance" 29) 30 31var ( 32 failNoneRequested = fmt.Errorf("\nNo license metadata files requested") 33 failNoLicenses = fmt.Errorf("No licenses found") 34) 35 36type context struct { 37 stdout io.Writer 38 stderr io.Writer 39 rootFS fs.FS 40 stripPrefix []string 41} 42 43func (ctx context) strip(installPath string) string { 44 for _, prefix := range ctx.stripPrefix { 45 if strings.HasPrefix(installPath, prefix) { 46 p := strings.TrimPrefix(installPath, prefix) 47 if 0 == len(p) { 48 continue 49 } 50 return p 51 } 52 } 53 return installPath 54} 55 56// newMultiString creates a flag that allows multiple values in an array. 57func newMultiString(flags *flag.FlagSet, name, usage string) *multiString { 58 var f multiString 59 flags.Var(&f, name, usage) 60 return &f 61} 62 63// multiString implements the flag `Value` interface for multiple strings. 64type multiString []string 65 66func (ms *multiString) String() string { return strings.Join(*ms, ", ") } 67func (ms *multiString) Set(s string) error { *ms = append(*ms, s); return nil } 68 69func main() { 70 var expandedArgs []string 71 for _, arg := range os.Args[1:] { 72 if strings.HasPrefix(arg, "@") { 73 f, err := os.Open(strings.TrimPrefix(arg, "@")) 74 if err != nil { 75 fmt.Fprintln(os.Stderr, err.Error()) 76 os.Exit(1) 77 } 78 79 respArgs, err := response.ReadRspFile(f) 80 f.Close() 81 if err != nil { 82 fmt.Fprintln(os.Stderr, err.Error()) 83 os.Exit(1) 84 } 85 expandedArgs = append(expandedArgs, respArgs...) 86 } else { 87 expandedArgs = append(expandedArgs, arg) 88 } 89 } 90 91 flags := flag.NewFlagSet("flags", flag.ExitOnError) 92 93 flags.Usage = func() { 94 fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...} 95 96Outputs a bill of materials. i.e. the list of installed paths. 97 98Options: 99`, filepath.Base(os.Args[0])) 100 flags.PrintDefaults() 101 } 102 103 outputFile := flags.String("o", "-", "Where to write the bill of materials. (default stdout)") 104 stripPrefix := newMultiString(flags, "strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)") 105 106 flags.Parse(expandedArgs) 107 108 // Must specify at least one root target. 109 if flags.NArg() == 0 { 110 flags.Usage() 111 os.Exit(2) 112 } 113 114 if len(*outputFile) == 0 { 115 flags.Usage() 116 fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n") 117 os.Exit(2) 118 } else { 119 dir, err := filepath.Abs(filepath.Dir(*outputFile)) 120 if err != nil { 121 fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err) 122 os.Exit(1) 123 } 124 fi, err := os.Stat(dir) 125 if err != nil { 126 fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err) 127 os.Exit(1) 128 } 129 if !fi.IsDir() { 130 fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile) 131 os.Exit(1) 132 } 133 } 134 135 var ofile io.Writer 136 ofile = os.Stdout 137 if *outputFile != "-" { 138 ofile = &bytes.Buffer{} 139 } 140 141 ctx := &context{ofile, os.Stderr, compliance.FS, *stripPrefix} 142 143 err := billOfMaterials(ctx, flags.Args()...) 144 if err != nil { 145 if err == failNoneRequested { 146 flags.Usage() 147 } 148 fmt.Fprintf(os.Stderr, "%s\n", err.Error()) 149 os.Exit(1) 150 } 151 if *outputFile != "-" { 152 err := os.WriteFile(*outputFile, ofile.(*bytes.Buffer).Bytes(), 0666) 153 if err != nil { 154 fmt.Fprintf(os.Stderr, "could not write output to %q: %s\n", *outputFile, err) 155 os.Exit(1) 156 } 157 } 158 os.Exit(0) 159} 160 161// billOfMaterials implements the bom utility. 162func billOfMaterials(ctx *context, files ...string) error { 163 // Must be at least one root file. 164 if len(files) < 1 { 165 return failNoneRequested 166 } 167 168 // Read the license graph from the license metadata files (*.meta_lic). 169 licenseGraph, err := compliance.ReadLicenseGraph(ctx.rootFS, ctx.stderr, files) 170 if err != nil { 171 return fmt.Errorf("Unable to read license metadata file(s) %q: %v\n", files, err) 172 } 173 if licenseGraph == nil { 174 return failNoLicenses 175 } 176 177 // rs contains all notice resolutions. 178 rs := compliance.ResolveNotices(licenseGraph) 179 180 ni, err := compliance.IndexLicenseTexts(ctx.rootFS, licenseGraph, rs) 181 if err != nil { 182 return fmt.Errorf("Unable to read license text file(s) for %q: %v\n", files, err) 183 } 184 185 for path := range ni.InstallPaths() { 186 fmt.Fprintln(ctx.stdout, ctx.strip(path)) 187 } 188 return nil 189} 190