1*9e94795aSAndroid Build Coastguard Worker// Copyright 2021 Google LLC 2*9e94795aSAndroid Build Coastguard Worker// 3*9e94795aSAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License"); 4*9e94795aSAndroid Build Coastguard Worker// you may not use this file except in compliance with the License. 5*9e94795aSAndroid Build Coastguard Worker// You may obtain a copy of the License at 6*9e94795aSAndroid Build Coastguard Worker// 7*9e94795aSAndroid Build Coastguard Worker// http://www.apache.org/licenses/LICENSE-2.0 8*9e94795aSAndroid Build Coastguard Worker// 9*9e94795aSAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software 10*9e94795aSAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS, 11*9e94795aSAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*9e94795aSAndroid Build Coastguard Worker// See the License for the specific language governing permissions and 13*9e94795aSAndroid Build Coastguard Worker// limitations under the License. 14*9e94795aSAndroid Build Coastguard Worker 15*9e94795aSAndroid Build Coastguard Workerpackage compliance 16*9e94795aSAndroid Build Coastguard Worker 17*9e94795aSAndroid Build Coastguard Workerimport ( 18*9e94795aSAndroid Build Coastguard Worker "fmt" 19*9e94795aSAndroid Build Coastguard Worker "io" 20*9e94795aSAndroid Build Coastguard Worker "io/fs" 21*9e94795aSAndroid Build Coastguard Worker "os" 22*9e94795aSAndroid Build Coastguard Worker "strings" 23*9e94795aSAndroid Build Coastguard Worker "sync" 24*9e94795aSAndroid Build Coastguard Worker 25*9e94795aSAndroid Build Coastguard Worker "android/soong/compliance/license_metadata_proto" 26*9e94795aSAndroid Build Coastguard Worker 27*9e94795aSAndroid Build Coastguard Worker "google.golang.org/protobuf/encoding/prototext" 28*9e94795aSAndroid Build Coastguard Worker) 29*9e94795aSAndroid Build Coastguard Worker 30*9e94795aSAndroid Build Coastguard Workervar ( 31*9e94795aSAndroid Build Coastguard Worker // ConcurrentReaders is the size of the task pool for limiting resource usage e.g. open files. 32*9e94795aSAndroid Build Coastguard Worker ConcurrentReaders = 5 33*9e94795aSAndroid Build Coastguard Worker) 34*9e94795aSAndroid Build Coastguard Worker 35*9e94795aSAndroid Build Coastguard Workertype globalFS struct{} 36*9e94795aSAndroid Build Coastguard Worker 37*9e94795aSAndroid Build Coastguard Workervar _ fs.FS = globalFS{} 38*9e94795aSAndroid Build Coastguard Workervar _ fs.StatFS = globalFS{} 39*9e94795aSAndroid Build Coastguard Worker 40*9e94795aSAndroid Build Coastguard Workerfunc (s globalFS) Open(name string) (fs.File, error) { 41*9e94795aSAndroid Build Coastguard Worker return os.Open(name) 42*9e94795aSAndroid Build Coastguard Worker} 43*9e94795aSAndroid Build Coastguard Worker 44*9e94795aSAndroid Build Coastguard Workerfunc (s globalFS) Stat(name string) (fs.FileInfo, error) { 45*9e94795aSAndroid Build Coastguard Worker return os.Stat(name) 46*9e94795aSAndroid Build Coastguard Worker} 47*9e94795aSAndroid Build Coastguard Worker 48*9e94795aSAndroid Build Coastguard Workervar FS globalFS 49*9e94795aSAndroid Build Coastguard Worker 50*9e94795aSAndroid Build Coastguard Worker// GetFS returns a filesystem for accessing files under the OUT_DIR environment variable. 51*9e94795aSAndroid Build Coastguard Workerfunc GetFS(outDir string) fs.FS { 52*9e94795aSAndroid Build Coastguard Worker if len(outDir) > 0 { 53*9e94795aSAndroid Build Coastguard Worker return os.DirFS(outDir) 54*9e94795aSAndroid Build Coastguard Worker } 55*9e94795aSAndroid Build Coastguard Worker return os.DirFS(".") 56*9e94795aSAndroid Build Coastguard Worker} 57*9e94795aSAndroid Build Coastguard Worker 58*9e94795aSAndroid Build Coastguard Worker// result describes the outcome of reading and parsing a single license metadata file. 59*9e94795aSAndroid Build Coastguard Workertype result struct { 60*9e94795aSAndroid Build Coastguard Worker // file identifies the path to the license metadata file 61*9e94795aSAndroid Build Coastguard Worker file string 62*9e94795aSAndroid Build Coastguard Worker 63*9e94795aSAndroid Build Coastguard Worker // target contains the parsed metadata or nil if an error 64*9e94795aSAndroid Build Coastguard Worker target *TargetNode 65*9e94795aSAndroid Build Coastguard Worker 66*9e94795aSAndroid Build Coastguard Worker // err is nil unless an error occurs 67*9e94795aSAndroid Build Coastguard Worker err error 68*9e94795aSAndroid Build Coastguard Worker} 69*9e94795aSAndroid Build Coastguard Worker 70*9e94795aSAndroid Build Coastguard Worker// receiver coordinates the tasks for reading and parsing license metadata files. 71*9e94795aSAndroid Build Coastguard Workertype receiver struct { 72*9e94795aSAndroid Build Coastguard Worker // lg accumulates the read metadata and becomes the final resulting LicenseGraph. 73*9e94795aSAndroid Build Coastguard Worker lg *LicenseGraph 74*9e94795aSAndroid Build Coastguard Worker 75*9e94795aSAndroid Build Coastguard Worker // rootFS locates the root of the file system from which to read the files. 76*9e94795aSAndroid Build Coastguard Worker rootFS fs.FS 77*9e94795aSAndroid Build Coastguard Worker 78*9e94795aSAndroid Build Coastguard Worker // stderr identifies the error output writer. 79*9e94795aSAndroid Build Coastguard Worker stderr io.Writer 80*9e94795aSAndroid Build Coastguard Worker 81*9e94795aSAndroid Build Coastguard Worker // task provides a fixed-size task pool to limit concurrent open files etc. 82*9e94795aSAndroid Build Coastguard Worker task chan bool 83*9e94795aSAndroid Build Coastguard Worker 84*9e94795aSAndroid Build Coastguard Worker // results returns one license metadata file result at a time. 85*9e94795aSAndroid Build Coastguard Worker results chan *result 86*9e94795aSAndroid Build Coastguard Worker 87*9e94795aSAndroid Build Coastguard Worker // wg detects when done 88*9e94795aSAndroid Build Coastguard Worker wg sync.WaitGroup 89*9e94795aSAndroid Build Coastguard Worker} 90*9e94795aSAndroid Build Coastguard Worker 91*9e94795aSAndroid Build Coastguard Worker// ReadLicenseGraph reads and parses `files` and their dependencies into a LicenseGraph. 92*9e94795aSAndroid Build Coastguard Worker// 93*9e94795aSAndroid Build Coastguard Worker// `files` become the root files of the graph for top-down walks of the graph. 94*9e94795aSAndroid Build Coastguard Workerfunc ReadLicenseGraph(rootFS fs.FS, stderr io.Writer, files []string) (*LicenseGraph, error) { 95*9e94795aSAndroid Build Coastguard Worker if len(files) == 0 { 96*9e94795aSAndroid Build Coastguard Worker return nil, fmt.Errorf("no license metadata to analyze") 97*9e94795aSAndroid Build Coastguard Worker } 98*9e94795aSAndroid Build Coastguard Worker if ConcurrentReaders < 1 { 99*9e94795aSAndroid Build Coastguard Worker return nil, fmt.Errorf("need at least one task in pool") 100*9e94795aSAndroid Build Coastguard Worker } 101*9e94795aSAndroid Build Coastguard Worker 102*9e94795aSAndroid Build Coastguard Worker lg := newLicenseGraph() 103*9e94795aSAndroid Build Coastguard Worker for _, f := range files { 104*9e94795aSAndroid Build Coastguard Worker if strings.HasSuffix(f, "meta_lic") { 105*9e94795aSAndroid Build Coastguard Worker lg.rootFiles = append(lg.rootFiles, f) 106*9e94795aSAndroid Build Coastguard Worker } else { 107*9e94795aSAndroid Build Coastguard Worker lg.rootFiles = append(lg.rootFiles, f+".meta_lic") 108*9e94795aSAndroid Build Coastguard Worker } 109*9e94795aSAndroid Build Coastguard Worker } 110*9e94795aSAndroid Build Coastguard Worker 111*9e94795aSAndroid Build Coastguard Worker recv := &receiver{ 112*9e94795aSAndroid Build Coastguard Worker lg: lg, 113*9e94795aSAndroid Build Coastguard Worker rootFS: rootFS, 114*9e94795aSAndroid Build Coastguard Worker stderr: stderr, 115*9e94795aSAndroid Build Coastguard Worker task: make(chan bool, ConcurrentReaders), 116*9e94795aSAndroid Build Coastguard Worker results: make(chan *result, ConcurrentReaders), 117*9e94795aSAndroid Build Coastguard Worker wg: sync.WaitGroup{}, 118*9e94795aSAndroid Build Coastguard Worker } 119*9e94795aSAndroid Build Coastguard Worker for i := 0; i < ConcurrentReaders; i++ { 120*9e94795aSAndroid Build Coastguard Worker recv.task <- true 121*9e94795aSAndroid Build Coastguard Worker } 122*9e94795aSAndroid Build Coastguard Worker 123*9e94795aSAndroid Build Coastguard Worker readFiles := func() { 124*9e94795aSAndroid Build Coastguard Worker lg.mu.Lock() 125*9e94795aSAndroid Build Coastguard Worker // identify the metadata files to schedule reading tasks for 126*9e94795aSAndroid Build Coastguard Worker for _, f := range lg.rootFiles { 127*9e94795aSAndroid Build Coastguard Worker lg.targets[f] = nil 128*9e94795aSAndroid Build Coastguard Worker } 129*9e94795aSAndroid Build Coastguard Worker lg.mu.Unlock() 130*9e94795aSAndroid Build Coastguard Worker 131*9e94795aSAndroid Build Coastguard Worker // schedule tasks to read the files 132*9e94795aSAndroid Build Coastguard Worker for _, f := range lg.rootFiles { 133*9e94795aSAndroid Build Coastguard Worker readFile(recv, f) 134*9e94795aSAndroid Build Coastguard Worker } 135*9e94795aSAndroid Build Coastguard Worker 136*9e94795aSAndroid Build Coastguard Worker // schedule a task to wait until finished and close the channel. 137*9e94795aSAndroid Build Coastguard Worker go func() { 138*9e94795aSAndroid Build Coastguard Worker recv.wg.Wait() 139*9e94795aSAndroid Build Coastguard Worker close(recv.task) 140*9e94795aSAndroid Build Coastguard Worker close(recv.results) 141*9e94795aSAndroid Build Coastguard Worker }() 142*9e94795aSAndroid Build Coastguard Worker } 143*9e94795aSAndroid Build Coastguard Worker go readFiles() 144*9e94795aSAndroid Build Coastguard Worker 145*9e94795aSAndroid Build Coastguard Worker // tasks to read license metadata files are scheduled; read and process results from channel 146*9e94795aSAndroid Build Coastguard Worker var err error 147*9e94795aSAndroid Build Coastguard Worker for recv.results != nil { 148*9e94795aSAndroid Build Coastguard Worker select { 149*9e94795aSAndroid Build Coastguard Worker case r, ok := <-recv.results: 150*9e94795aSAndroid Build Coastguard Worker if ok { 151*9e94795aSAndroid Build Coastguard Worker // handle errors by nil'ing ls, setting err, and clobbering results channel 152*9e94795aSAndroid Build Coastguard Worker if r.err != nil { 153*9e94795aSAndroid Build Coastguard Worker err = r.err 154*9e94795aSAndroid Build Coastguard Worker fmt.Fprintf(recv.stderr, "%s\n", err.Error()) 155*9e94795aSAndroid Build Coastguard Worker lg = nil 156*9e94795aSAndroid Build Coastguard Worker recv.results = nil 157*9e94795aSAndroid Build Coastguard Worker continue 158*9e94795aSAndroid Build Coastguard Worker } 159*9e94795aSAndroid Build Coastguard Worker 160*9e94795aSAndroid Build Coastguard Worker // record the parsed metadata (guarded by mutex) 161*9e94795aSAndroid Build Coastguard Worker recv.lg.mu.Lock() 162*9e94795aSAndroid Build Coastguard Worker lg.targets[r.target.name] = r.target 163*9e94795aSAndroid Build Coastguard Worker recv.lg.mu.Unlock() 164*9e94795aSAndroid Build Coastguard Worker } else { 165*9e94795aSAndroid Build Coastguard Worker // finished -- nil the results channel 166*9e94795aSAndroid Build Coastguard Worker recv.results = nil 167*9e94795aSAndroid Build Coastguard Worker } 168*9e94795aSAndroid Build Coastguard Worker } 169*9e94795aSAndroid Build Coastguard Worker } 170*9e94795aSAndroid Build Coastguard Worker 171*9e94795aSAndroid Build Coastguard Worker if lg != nil { 172*9e94795aSAndroid Build Coastguard Worker esize := 0 173*9e94795aSAndroid Build Coastguard Worker for _, tn := range lg.targets { 174*9e94795aSAndroid Build Coastguard Worker esize += len(tn.proto.Deps) 175*9e94795aSAndroid Build Coastguard Worker } 176*9e94795aSAndroid Build Coastguard Worker lg.edges = make(TargetEdgeList, 0, esize) 177*9e94795aSAndroid Build Coastguard Worker for _, tn := range lg.targets { 178*9e94795aSAndroid Build Coastguard Worker tn.licenseConditions = LicenseConditionSetFromNames(tn.proto.LicenseConditions...) 179*9e94795aSAndroid Build Coastguard Worker err = addDependencies(lg, tn) 180*9e94795aSAndroid Build Coastguard Worker if err != nil { 181*9e94795aSAndroid Build Coastguard Worker return nil, fmt.Errorf("error indexing dependencies for %q: %w", tn.name, err) 182*9e94795aSAndroid Build Coastguard Worker } 183*9e94795aSAndroid Build Coastguard Worker tn.proto.Deps = []*license_metadata_proto.AnnotatedDependency{} 184*9e94795aSAndroid Build Coastguard Worker } 185*9e94795aSAndroid Build Coastguard Worker } 186*9e94795aSAndroid Build Coastguard Worker return lg, err 187*9e94795aSAndroid Build Coastguard Worker 188*9e94795aSAndroid Build Coastguard Worker} 189*9e94795aSAndroid Build Coastguard Worker 190*9e94795aSAndroid Build Coastguard Worker// targetNode contains the license metadata for a node in the license graph. 191*9e94795aSAndroid Build Coastguard Workertype targetNode struct { 192*9e94795aSAndroid Build Coastguard Worker proto license_metadata_proto.LicenseMetadata 193*9e94795aSAndroid Build Coastguard Worker 194*9e94795aSAndroid Build Coastguard Worker // name is the path to the metadata file. 195*9e94795aSAndroid Build Coastguard Worker name string 196*9e94795aSAndroid Build Coastguard Worker 197*9e94795aSAndroid Build Coastguard Worker // lg is the license graph the node belongs to. 198*9e94795aSAndroid Build Coastguard Worker lg *LicenseGraph 199*9e94795aSAndroid Build Coastguard Worker 200*9e94795aSAndroid Build Coastguard Worker // edges identifies the dependencies of the target. 201*9e94795aSAndroid Build Coastguard Worker edges TargetEdgeList 202*9e94795aSAndroid Build Coastguard Worker 203*9e94795aSAndroid Build Coastguard Worker // licenseConditions identifies the set of license conditions originating at the target node. 204*9e94795aSAndroid Build Coastguard Worker licenseConditions LicenseConditionSet 205*9e94795aSAndroid Build Coastguard Worker 206*9e94795aSAndroid Build Coastguard Worker // resolution identifies the set of conditions resolved by acting on the target node. 207*9e94795aSAndroid Build Coastguard Worker resolution LicenseConditionSet 208*9e94795aSAndroid Build Coastguard Worker 209*9e94795aSAndroid Build Coastguard Worker // pure indicates whether to treat the node as a pure aggregate (no internal linkage) 210*9e94795aSAndroid Build Coastguard Worker pure bool 211*9e94795aSAndroid Build Coastguard Worker} 212*9e94795aSAndroid Build Coastguard Worker 213*9e94795aSAndroid Build Coastguard Worker// addDependencies converts the proto AnnotatedDependencies into `edges` 214*9e94795aSAndroid Build Coastguard Workerfunc addDependencies(lg *LicenseGraph, tn *TargetNode) error { 215*9e94795aSAndroid Build Coastguard Worker tn.edges = make(TargetEdgeList, 0, len(tn.proto.Deps)) 216*9e94795aSAndroid Build Coastguard Worker for _, ad := range tn.proto.Deps { 217*9e94795aSAndroid Build Coastguard Worker dependency := ad.GetFile() 218*9e94795aSAndroid Build Coastguard Worker if len(dependency) == 0 { 219*9e94795aSAndroid Build Coastguard Worker return fmt.Errorf("missing dependency name") 220*9e94795aSAndroid Build Coastguard Worker } 221*9e94795aSAndroid Build Coastguard Worker dtn, ok := lg.targets[dependency] 222*9e94795aSAndroid Build Coastguard Worker if !ok { 223*9e94795aSAndroid Build Coastguard Worker return fmt.Errorf("unknown dependency name %q", dependency) 224*9e94795aSAndroid Build Coastguard Worker } 225*9e94795aSAndroid Build Coastguard Worker if dtn == nil { 226*9e94795aSAndroid Build Coastguard Worker return fmt.Errorf("nil dependency for name %q", dependency) 227*9e94795aSAndroid Build Coastguard Worker } 228*9e94795aSAndroid Build Coastguard Worker annotations := newEdgeAnnotations() 229*9e94795aSAndroid Build Coastguard Worker for _, a := range ad.Annotations { 230*9e94795aSAndroid Build Coastguard Worker // look up a common constant annotation string from a small map 231*9e94795aSAndroid Build Coastguard Worker // instead of creating 1000's of copies of the same 3 strings. 232*9e94795aSAndroid Build Coastguard Worker if ann, ok := RecognizedAnnotations[a]; ok { 233*9e94795aSAndroid Build Coastguard Worker annotations.annotations[ann] = struct{}{} 234*9e94795aSAndroid Build Coastguard Worker } 235*9e94795aSAndroid Build Coastguard Worker } 236*9e94795aSAndroid Build Coastguard Worker edge := &TargetEdge{tn, dtn, annotations} 237*9e94795aSAndroid Build Coastguard Worker lg.edges = append(lg.edges, edge) 238*9e94795aSAndroid Build Coastguard Worker tn.edges = append(tn.edges, edge) 239*9e94795aSAndroid Build Coastguard Worker } 240*9e94795aSAndroid Build Coastguard Worker return nil 241*9e94795aSAndroid Build Coastguard Worker} 242*9e94795aSAndroid Build Coastguard Worker 243*9e94795aSAndroid Build Coastguard Worker// readFile is a task to read and parse a single license metadata file, and to schedule 244*9e94795aSAndroid Build Coastguard Worker// additional tasks for reading and parsing dependencies as necessary. 245*9e94795aSAndroid Build Coastguard Workerfunc readFile(recv *receiver, file string) { 246*9e94795aSAndroid Build Coastguard Worker recv.wg.Add(1) 247*9e94795aSAndroid Build Coastguard Worker <-recv.task 248*9e94795aSAndroid Build Coastguard Worker go func() { 249*9e94795aSAndroid Build Coastguard Worker f, err := recv.rootFS.Open(file) 250*9e94795aSAndroid Build Coastguard Worker if err != nil { 251*9e94795aSAndroid Build Coastguard Worker recv.results <- &result{file, nil, fmt.Errorf("error opening license metadata %q: %w", file, err)} 252*9e94795aSAndroid Build Coastguard Worker return 253*9e94795aSAndroid Build Coastguard Worker } 254*9e94795aSAndroid Build Coastguard Worker 255*9e94795aSAndroid Build Coastguard Worker // read the file 256*9e94795aSAndroid Build Coastguard Worker data, err := io.ReadAll(f) 257*9e94795aSAndroid Build Coastguard Worker if err != nil { 258*9e94795aSAndroid Build Coastguard Worker recv.results <- &result{file, nil, fmt.Errorf("error reading license metadata %q: %w", file, err)} 259*9e94795aSAndroid Build Coastguard Worker return 260*9e94795aSAndroid Build Coastguard Worker } 261*9e94795aSAndroid Build Coastguard Worker f.Close() 262*9e94795aSAndroid Build Coastguard Worker 263*9e94795aSAndroid Build Coastguard Worker tn := &TargetNode{lg: recv.lg, name: file} 264*9e94795aSAndroid Build Coastguard Worker 265*9e94795aSAndroid Build Coastguard Worker err = prototext.Unmarshal(data, &tn.proto) 266*9e94795aSAndroid Build Coastguard Worker if err != nil { 267*9e94795aSAndroid Build Coastguard Worker recv.results <- &result{file, nil, fmt.Errorf("error license metadata %q: %w", file, err)} 268*9e94795aSAndroid Build Coastguard Worker return 269*9e94795aSAndroid Build Coastguard Worker } 270*9e94795aSAndroid Build Coastguard Worker 271*9e94795aSAndroid Build Coastguard Worker // send result for this file and release task before scheduling dependencies, 272*9e94795aSAndroid Build Coastguard Worker // but do not signal done to WaitGroup until dependencies are scheduled. 273*9e94795aSAndroid Build Coastguard Worker recv.results <- &result{file, tn, nil} 274*9e94795aSAndroid Build Coastguard Worker recv.task <- true 275*9e94795aSAndroid Build Coastguard Worker 276*9e94795aSAndroid Build Coastguard Worker // schedule tasks as necessary to read dependencies 277*9e94795aSAndroid Build Coastguard Worker for _, ad := range tn.proto.Deps { 278*9e94795aSAndroid Build Coastguard Worker dependency := ad.GetFile() 279*9e94795aSAndroid Build Coastguard Worker // decide, signal and record whether to schedule task in critical section 280*9e94795aSAndroid Build Coastguard Worker recv.lg.mu.Lock() 281*9e94795aSAndroid Build Coastguard Worker _, alreadyScheduled := recv.lg.targets[dependency] 282*9e94795aSAndroid Build Coastguard Worker if !alreadyScheduled { 283*9e94795aSAndroid Build Coastguard Worker recv.lg.targets[dependency] = nil 284*9e94795aSAndroid Build Coastguard Worker } 285*9e94795aSAndroid Build Coastguard Worker recv.lg.mu.Unlock() 286*9e94795aSAndroid Build Coastguard Worker // schedule task to read dependency file outside critical section 287*9e94795aSAndroid Build Coastguard Worker if !alreadyScheduled { 288*9e94795aSAndroid Build Coastguard Worker readFile(recv, dependency) 289*9e94795aSAndroid Build Coastguard Worker } 290*9e94795aSAndroid Build Coastguard Worker } 291*9e94795aSAndroid Build Coastguard Worker 292*9e94795aSAndroid Build Coastguard Worker // signal task done after scheduling dependencies 293*9e94795aSAndroid Build Coastguard Worker recv.wg.Done() 294*9e94795aSAndroid Build Coastguard Worker }() 295*9e94795aSAndroid Build Coastguard Worker} 296