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 "sort" 26 "strings" 27 28 "android/soong/response" 29 "android/soong/tools/compliance" 30) 31 32var ( 33 failNoneRequested = fmt.Errorf("\nNo license metadata files requested") 34 failNoSources = fmt.Errorf("\nNo projects or metadata files to trace back from") 35 failNoLicenses = fmt.Errorf("No licenses found") 36) 37 38type context struct { 39 sources []string 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 96Calculates the source-sharing requirements in reverse starting at the 97-rtrace projects or metadata files that inherited source-sharing and 98working back to the targets where the source-sharing requirmements 99originate. 100 101Outputs a space-separated pair where the first field is an originating 102target with one or more restricted conditions and where the second 103field is a colon-separated list of the restricted conditions. 104 105Outputs a count of the originating targets, and if the count is zero, 106outputs a warning to check the -rtrace projects and/or filenames. 107 108Options: 109`, filepath.Base(os.Args[0])) 110 flags.PrintDefaults() 111 } 112 113 outputFile := flags.String("o", "-", "Where to write the output. (default stdout)") 114 sources := newMultiString(flags, "rtrace", "Projects or metadata files to trace back from. (required; multiple allowed)") 115 stripPrefix := newMultiString(flags, "strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)") 116 117 flags.Parse(expandedArgs) 118 119 // Must specify at least one root target. 120 if flags.NArg() == 0 { 121 flags.Usage() 122 os.Exit(2) 123 } 124 125 if len(*sources) == 0 { 126 flags.Usage() 127 fmt.Fprintf(os.Stderr, "\nMust specify at least 1 --rtrace source.\n") 128 os.Exit(2) 129 } 130 131 if len(*outputFile) == 0 { 132 flags.Usage() 133 fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n") 134 os.Exit(2) 135 } else { 136 dir, err := filepath.Abs(filepath.Dir(*outputFile)) 137 if err != nil { 138 fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err) 139 os.Exit(1) 140 } 141 fi, err := os.Stat(dir) 142 if err != nil { 143 fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err) 144 os.Exit(1) 145 } 146 if !fi.IsDir() { 147 fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile) 148 os.Exit(1) 149 } 150 } 151 152 var ofile io.Writer 153 ofile = os.Stdout 154 var obuf *bytes.Buffer 155 if *outputFile != "-" { 156 obuf = &bytes.Buffer{} 157 ofile = obuf 158 } 159 160 ctx := &context{ 161 sources: *sources, 162 stripPrefix: *stripPrefix, 163 } 164 _, err := traceRestricted(ctx, ofile, os.Stderr, compliance.FS, 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 *outputFile != "-" { 173 err := os.WriteFile(*outputFile, obuf.Bytes(), 0666) 174 if err != nil { 175 fmt.Fprintf(os.Stderr, "could not write output to %q from %q: %s\n", *outputFile, os.Getenv("PWD"), err) 176 os.Exit(1) 177 } 178 } 179 os.Exit(0) 180} 181 182// traceRestricted implements the rtrace utility. 183func traceRestricted(ctx *context, stdout, stderr io.Writer, rootFS fs.FS, files ...string) (*compliance.LicenseGraph, error) { 184 if len(files) < 1 { 185 return nil, failNoneRequested 186 } 187 188 if len(ctx.sources) < 1 { 189 return nil, failNoSources 190 } 191 192 // Read the license graph from the license metadata files (*.meta_lic). 193 licenseGraph, err := compliance.ReadLicenseGraph(rootFS, stderr, files) 194 if err != nil { 195 return nil, fmt.Errorf("Unable to read license metadata file(s) %q: %v\n", files, err) 196 } 197 if licenseGraph == nil { 198 return nil, failNoLicenses 199 } 200 201 sourceMap := make(map[string]struct{}) 202 for _, source := range ctx.sources { 203 sourceMap[source] = struct{}{} 204 } 205 206 compliance.TraceTopDownConditions(licenseGraph, func(tn *compliance.TargetNode) compliance.LicenseConditionSet { 207 if _, isPresent := sourceMap[tn.Name()]; isPresent { 208 return compliance.ImpliesRestricted 209 } 210 for _, project := range tn.Projects() { 211 if _, isPresent := sourceMap[project]; isPresent { 212 return compliance.ImpliesRestricted 213 } 214 } 215 return compliance.NewLicenseConditionSet() 216 }) 217 218 // targetOut calculates the string to output for `target` adding `sep`-separated conditions as needed. 219 targetOut := func(target *compliance.TargetNode, sep string) string { 220 tOut := ctx.strip(target.Name()) 221 return tOut 222 } 223 224 // outputResolution prints a resolution in the requested format to `stdout`, where one can read 225 // a resolution as `tname` resolves conditions named in `cnames`. 226 // `tname` is the name of the target the resolution traces back to. 227 // `cnames` is the list of conditions to resolve. 228 outputResolution := func(tname string, cnames []string) { 229 // ... one edge per line with names in a colon-separated tuple. 230 fmt.Fprintf(stdout, "%s %s\n", tname, strings.Join(cnames, ":")) 231 } 232 233 // Sort the resolutions by targetname for repeatability/stability. 234 actions := compliance.WalkResolutionsForCondition(licenseGraph, compliance.ImpliesShared).AllActions() 235 targets := make(compliance.TargetNodeList, 0, len(actions)) 236 for tn := range actions { 237 if tn.LicenseConditions().MatchesAnySet(compliance.ImpliesRestricted) { 238 targets = append(targets, tn) 239 } 240 } 241 sort.Sort(targets) 242 243 // Output the sorted targets. 244 for _, target := range targets { 245 var tname string 246 tname = targetOut(target, ":") 247 248 // cnames accumulates the list of condition names originating at a single origin that apply to `target`. 249 cnames := target.LicenseConditions().Names() 250 251 // Output 1 line for each attachesTo+actsOn combination. 252 outputResolution(tname, cnames) 253 } 254 fmt.Fprintf(stdout, "restricted conditions trace to %d targets\n", len(targets)) 255 if 0 == len(targets) { 256 fmt.Fprintln(stdout, " (check for typos in project names or metadata files)") 257 } 258 return licenseGraph, nil 259} 260