xref: /aosp_15_r20/build/make/tools/compliance/readgraph.go (revision 9e94795a3d4ef5c1d47486f9a02bb378756cea8a)
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