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 failNoLicenses = fmt.Errorf("No licenses found") 35) 36 37func main() { 38 var expandedArgs []string 39 for _, arg := range os.Args[1:] { 40 if strings.HasPrefix(arg, "@") { 41 f, err := os.Open(strings.TrimPrefix(arg, "@")) 42 if err != nil { 43 fmt.Fprintln(os.Stderr, err.Error()) 44 os.Exit(1) 45 } 46 47 respArgs, err := response.ReadRspFile(f) 48 f.Close() 49 if err != nil { 50 fmt.Fprintln(os.Stderr, err.Error()) 51 os.Exit(1) 52 } 53 expandedArgs = append(expandedArgs, respArgs...) 54 } else { 55 expandedArgs = append(expandedArgs, arg) 56 } 57 } 58 59 flags := flag.NewFlagSet("flags", flag.ExitOnError) 60 61 flags.Usage = func() { 62 fmt.Fprintf(os.Stderr, `Usage: %s {-o outfile} file.meta_lic {file.meta_lic...} 63 64Outputs a csv file with 1 project per line in the first field followed 65by target:condition pairs describing why the project must be shared. 66 67Each target is the path to a generated license metadata file for a 68Soong module or Make target, and the license condition is either 69restricted (e.g. GPL) or reciprocal (e.g. MPL). 70`, filepath.Base(os.Args[0])) 71 } 72 73 outputFile := flags.String("o", "-", "Where to write the list of projects to share. (default stdout)") 74 75 flags.Parse(expandedArgs) 76 77 // Must specify at least one root target. 78 if flags.NArg() == 0 { 79 flags.Usage() 80 os.Exit(2) 81 } 82 83 if len(*outputFile) == 0 { 84 flags.Usage() 85 fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n") 86 os.Exit(2) 87 } else { 88 dir, err := filepath.Abs(filepath.Dir(*outputFile)) 89 if err != nil { 90 fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err) 91 os.Exit(1) 92 } 93 fi, err := os.Stat(dir) 94 if err != nil { 95 fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err) 96 os.Exit(1) 97 } 98 if !fi.IsDir() { 99 fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile) 100 os.Exit(1) 101 } 102 } 103 104 var ofile io.Writer 105 ofile = os.Stdout 106 var obuf *bytes.Buffer 107 if *outputFile != "-" { 108 obuf = &bytes.Buffer{} 109 ofile = obuf 110 } 111 112 err := listShare(ofile, os.Stderr, compliance.FS, flags.Args()...) 113 if err != nil { 114 if err == failNoneRequested { 115 flags.Usage() 116 } 117 fmt.Fprintf(os.Stderr, "%s\n", err.Error()) 118 os.Exit(1) 119 } 120 if *outputFile != "-" { 121 err := os.WriteFile(*outputFile, obuf.Bytes(), 0666) 122 if err != nil { 123 fmt.Fprintf(os.Stderr, "could not write output to %q from %q: %s\n", *outputFile, os.Getenv("PWD"), err) 124 os.Exit(1) 125 } 126 } 127 os.Exit(0) 128} 129 130// listShare implements the listshare utility. 131func listShare(stdout, stderr io.Writer, rootFS fs.FS, files ...string) error { 132 // Must be at least one root file. 133 if len(files) < 1 { 134 return failNoneRequested 135 } 136 137 // Read the license graph from the license metadata files (*.meta_lic). 138 licenseGraph, err := compliance.ReadLicenseGraph(rootFS, stderr, files) 139 if err != nil { 140 return fmt.Errorf("Unable to read license metadata file(s) %q from %q: %v\n", files, os.Getenv("PWD"), err) 141 } 142 if licenseGraph == nil { 143 return failNoLicenses 144 } 145 146 // shareSource contains all source-sharing resolutions. 147 shareSource := compliance.ResolveSourceSharing(licenseGraph) 148 149 // Group the resolutions by project. 150 presolution := make(map[string]compliance.LicenseConditionSet) 151 for _, target := range shareSource.AttachesTo() { 152 if shareSource.IsPureAggregate(target) && !target.LicenseConditions().MatchesAnySet(compliance.ImpliesShared) { 153 continue 154 } 155 rl := shareSource.Resolutions(target) 156 sort.Sort(rl) 157 for _, r := range rl { 158 for _, p := range r.ActsOn().Projects() { 159 if _, ok := presolution[p]; !ok { 160 presolution[p] = r.Resolves() 161 continue 162 } 163 presolution[p] = presolution[p].Union(r.Resolves()) 164 } 165 } 166 } 167 168 // Sort the projects for repeatability/stability. 169 projects := make([]string, 0, len(presolution)) 170 for p := range presolution { 171 projects = append(projects, p) 172 } 173 sort.Strings(projects) 174 175 // Output the sorted projects and the source-sharing license conditions that each project resolves. 176 for _, p := range projects { 177 if presolution[p].IsEmpty() { 178 fmt.Fprintf(stdout, "%s\n", p) 179 } else { 180 fmt.Fprintf(stdout, "%s,%s\n", p, strings.Join(presolution[p].Names(), ",")) 181 } 182 } 183 184 return nil 185} 186