xref: /aosp_15_r20/build/make/tools/compliance/cmd/dumpresolutions/dumpresolutions.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 main
16*9e94795aSAndroid Build Coastguard Worker
17*9e94795aSAndroid Build Coastguard Workerimport (
18*9e94795aSAndroid Build Coastguard Worker	"bytes"
19*9e94795aSAndroid Build Coastguard Worker	"flag"
20*9e94795aSAndroid Build Coastguard Worker	"fmt"
21*9e94795aSAndroid Build Coastguard Worker	"io"
22*9e94795aSAndroid Build Coastguard Worker	"io/fs"
23*9e94795aSAndroid Build Coastguard Worker	"os"
24*9e94795aSAndroid Build Coastguard Worker	"path/filepath"
25*9e94795aSAndroid Build Coastguard Worker	"sort"
26*9e94795aSAndroid Build Coastguard Worker	"strings"
27*9e94795aSAndroid Build Coastguard Worker
28*9e94795aSAndroid Build Coastguard Worker	"android/soong/response"
29*9e94795aSAndroid Build Coastguard Worker	"android/soong/tools/compliance"
30*9e94795aSAndroid Build Coastguard Worker)
31*9e94795aSAndroid Build Coastguard Worker
32*9e94795aSAndroid Build Coastguard Workervar (
33*9e94795aSAndroid Build Coastguard Worker	failNoneRequested = fmt.Errorf("\nNo license metadata files requested")
34*9e94795aSAndroid Build Coastguard Worker	failNoLicenses    = fmt.Errorf("No licenses found")
35*9e94795aSAndroid Build Coastguard Worker)
36*9e94795aSAndroid Build Coastguard Worker
37*9e94795aSAndroid Build Coastguard Workertype context struct {
38*9e94795aSAndroid Build Coastguard Worker	conditions      []compliance.LicenseCondition
39*9e94795aSAndroid Build Coastguard Worker	graphViz        bool
40*9e94795aSAndroid Build Coastguard Worker	labelConditions bool
41*9e94795aSAndroid Build Coastguard Worker	stripPrefix     []string
42*9e94795aSAndroid Build Coastguard Worker}
43*9e94795aSAndroid Build Coastguard Worker
44*9e94795aSAndroid Build Coastguard Workerfunc (ctx context) strip(installPath string) string {
45*9e94795aSAndroid Build Coastguard Worker	for _, prefix := range ctx.stripPrefix {
46*9e94795aSAndroid Build Coastguard Worker		if strings.HasPrefix(installPath, prefix) {
47*9e94795aSAndroid Build Coastguard Worker			p := strings.TrimPrefix(installPath, prefix)
48*9e94795aSAndroid Build Coastguard Worker			if 0 == len(p) {
49*9e94795aSAndroid Build Coastguard Worker				continue
50*9e94795aSAndroid Build Coastguard Worker			}
51*9e94795aSAndroid Build Coastguard Worker			return p
52*9e94795aSAndroid Build Coastguard Worker		}
53*9e94795aSAndroid Build Coastguard Worker	}
54*9e94795aSAndroid Build Coastguard Worker	return installPath
55*9e94795aSAndroid Build Coastguard Worker}
56*9e94795aSAndroid Build Coastguard Worker
57*9e94795aSAndroid Build Coastguard Worker// newMultiString creates a flag that allows multiple values in an array.
58*9e94795aSAndroid Build Coastguard Workerfunc newMultiString(flags *flag.FlagSet, name, usage string) *multiString {
59*9e94795aSAndroid Build Coastguard Worker	var f multiString
60*9e94795aSAndroid Build Coastguard Worker	flags.Var(&f, name, usage)
61*9e94795aSAndroid Build Coastguard Worker	return &f
62*9e94795aSAndroid Build Coastguard Worker}
63*9e94795aSAndroid Build Coastguard Worker
64*9e94795aSAndroid Build Coastguard Worker// multiString implements the flag `Value` interface for multiple strings.
65*9e94795aSAndroid Build Coastguard Workertype multiString []string
66*9e94795aSAndroid Build Coastguard Worker
67*9e94795aSAndroid Build Coastguard Workerfunc (ms *multiString) String() string     { return strings.Join(*ms, ", ") }
68*9e94795aSAndroid Build Coastguard Workerfunc (ms *multiString) Set(s string) error { *ms = append(*ms, s); return nil }
69*9e94795aSAndroid Build Coastguard Worker
70*9e94795aSAndroid Build Coastguard Workerfunc main() {
71*9e94795aSAndroid Build Coastguard Worker	var expandedArgs []string
72*9e94795aSAndroid Build Coastguard Worker	for _, arg := range os.Args[1:] {
73*9e94795aSAndroid Build Coastguard Worker		if strings.HasPrefix(arg, "@") {
74*9e94795aSAndroid Build Coastguard Worker			f, err := os.Open(strings.TrimPrefix(arg, "@"))
75*9e94795aSAndroid Build Coastguard Worker			if err != nil {
76*9e94795aSAndroid Build Coastguard Worker				fmt.Fprintln(os.Stderr, err.Error())
77*9e94795aSAndroid Build Coastguard Worker				os.Exit(1)
78*9e94795aSAndroid Build Coastguard Worker			}
79*9e94795aSAndroid Build Coastguard Worker
80*9e94795aSAndroid Build Coastguard Worker			respArgs, err := response.ReadRspFile(f)
81*9e94795aSAndroid Build Coastguard Worker			f.Close()
82*9e94795aSAndroid Build Coastguard Worker			if err != nil {
83*9e94795aSAndroid Build Coastguard Worker				fmt.Fprintln(os.Stderr, err.Error())
84*9e94795aSAndroid Build Coastguard Worker				os.Exit(1)
85*9e94795aSAndroid Build Coastguard Worker			}
86*9e94795aSAndroid Build Coastguard Worker			expandedArgs = append(expandedArgs, respArgs...)
87*9e94795aSAndroid Build Coastguard Worker		} else {
88*9e94795aSAndroid Build Coastguard Worker			expandedArgs = append(expandedArgs, arg)
89*9e94795aSAndroid Build Coastguard Worker		}
90*9e94795aSAndroid Build Coastguard Worker	}
91*9e94795aSAndroid Build Coastguard Worker
92*9e94795aSAndroid Build Coastguard Worker	flags := flag.NewFlagSet("flags", flag.ExitOnError)
93*9e94795aSAndroid Build Coastguard Worker
94*9e94795aSAndroid Build Coastguard Worker	flags.Usage = func() {
95*9e94795aSAndroid Build Coastguard Worker		fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...}
96*9e94795aSAndroid Build Coastguard Worker
97*9e94795aSAndroid Build Coastguard WorkerOutputs a space-separated Target ActsOn Origin Condition tuple for each
98*9e94795aSAndroid Build Coastguard Workerresolution in the graph. When -dot flag given, outputs nodes and edges
99*9e94795aSAndroid Build Coastguard Workerin graphviz directed graph format.
100*9e94795aSAndroid Build Coastguard Worker
101*9e94795aSAndroid Build Coastguard WorkerIf one or more '-c condition' conditions are given, outputs the
102*9e94795aSAndroid Build Coastguard Workerresolution for the union of the conditions. Otherwise, outputs the
103*9e94795aSAndroid Build Coastguard Workerresolution for all conditions.
104*9e94795aSAndroid Build Coastguard Worker
105*9e94795aSAndroid Build Coastguard WorkerIn plain text mode, when '-label_conditions' is requested, the Target
106*9e94795aSAndroid Build Coastguard Workerand Origin have colon-separated license conditions appended:
107*9e94795aSAndroid Build Coastguard Workeri.e. target:condition1:condition2 etc.
108*9e94795aSAndroid Build Coastguard Worker
109*9e94795aSAndroid Build Coastguard WorkerOptions:
110*9e94795aSAndroid Build Coastguard Worker`, filepath.Base(os.Args[0]))
111*9e94795aSAndroid Build Coastguard Worker		flags.PrintDefaults()
112*9e94795aSAndroid Build Coastguard Worker	}
113*9e94795aSAndroid Build Coastguard Worker
114*9e94795aSAndroid Build Coastguard Worker	conditions := newMultiString(flags, "c", "License condition to resolve. (may be given multiple times)")
115*9e94795aSAndroid Build Coastguard Worker	graphViz := flags.Bool("dot", false, "Whether to output graphviz (i.e. dot) format.")
116*9e94795aSAndroid Build Coastguard Worker	labelConditions := flags.Bool("label_conditions", false, "Whether to label target nodes with conditions.")
117*9e94795aSAndroid Build Coastguard Worker	outputFile := flags.String("o", "-", "Where to write the output. (default stdout)")
118*9e94795aSAndroid Build Coastguard Worker	stripPrefix := newMultiString(flags, "strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)")
119*9e94795aSAndroid Build Coastguard Worker
120*9e94795aSAndroid Build Coastguard Worker	flags.Parse(expandedArgs)
121*9e94795aSAndroid Build Coastguard Worker
122*9e94795aSAndroid Build Coastguard Worker	// Must specify at least one root target.
123*9e94795aSAndroid Build Coastguard Worker	if flags.NArg() == 0 {
124*9e94795aSAndroid Build Coastguard Worker		flags.Usage()
125*9e94795aSAndroid Build Coastguard Worker		os.Exit(2)
126*9e94795aSAndroid Build Coastguard Worker	}
127*9e94795aSAndroid Build Coastguard Worker
128*9e94795aSAndroid Build Coastguard Worker	if len(*outputFile) == 0 {
129*9e94795aSAndroid Build Coastguard Worker		flags.Usage()
130*9e94795aSAndroid Build Coastguard Worker		fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n")
131*9e94795aSAndroid Build Coastguard Worker		os.Exit(2)
132*9e94795aSAndroid Build Coastguard Worker	} else {
133*9e94795aSAndroid Build Coastguard Worker		dir, err := filepath.Abs(filepath.Dir(*outputFile))
134*9e94795aSAndroid Build Coastguard Worker		if err != nil {
135*9e94795aSAndroid Build Coastguard Worker			fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err)
136*9e94795aSAndroid Build Coastguard Worker			os.Exit(1)
137*9e94795aSAndroid Build Coastguard Worker		}
138*9e94795aSAndroid Build Coastguard Worker		fi, err := os.Stat(dir)
139*9e94795aSAndroid Build Coastguard Worker		if err != nil {
140*9e94795aSAndroid Build Coastguard Worker			fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err)
141*9e94795aSAndroid Build Coastguard Worker			os.Exit(1)
142*9e94795aSAndroid Build Coastguard Worker		}
143*9e94795aSAndroid Build Coastguard Worker		if !fi.IsDir() {
144*9e94795aSAndroid Build Coastguard Worker			fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile)
145*9e94795aSAndroid Build Coastguard Worker			os.Exit(1)
146*9e94795aSAndroid Build Coastguard Worker		}
147*9e94795aSAndroid Build Coastguard Worker	}
148*9e94795aSAndroid Build Coastguard Worker
149*9e94795aSAndroid Build Coastguard Worker	var ofile io.Writer
150*9e94795aSAndroid Build Coastguard Worker	ofile = os.Stdout
151*9e94795aSAndroid Build Coastguard Worker	var obuf *bytes.Buffer
152*9e94795aSAndroid Build Coastguard Worker	if *outputFile != "-" {
153*9e94795aSAndroid Build Coastguard Worker		obuf = &bytes.Buffer{}
154*9e94795aSAndroid Build Coastguard Worker		ofile = obuf
155*9e94795aSAndroid Build Coastguard Worker	}
156*9e94795aSAndroid Build Coastguard Worker
157*9e94795aSAndroid Build Coastguard Worker	lcs := make([]compliance.LicenseCondition, 0, len(*conditions))
158*9e94795aSAndroid Build Coastguard Worker	for _, name := range *conditions {
159*9e94795aSAndroid Build Coastguard Worker		lcs = append(lcs, compliance.RecognizedConditionNames[name])
160*9e94795aSAndroid Build Coastguard Worker	}
161*9e94795aSAndroid Build Coastguard Worker	ctx := &context{
162*9e94795aSAndroid Build Coastguard Worker		conditions:      lcs,
163*9e94795aSAndroid Build Coastguard Worker		graphViz:        *graphViz,
164*9e94795aSAndroid Build Coastguard Worker		labelConditions: *labelConditions,
165*9e94795aSAndroid Build Coastguard Worker		stripPrefix:     *stripPrefix,
166*9e94795aSAndroid Build Coastguard Worker	}
167*9e94795aSAndroid Build Coastguard Worker	_, err := dumpResolutions(ctx, ofile, os.Stderr, compliance.FS, flags.Args()...)
168*9e94795aSAndroid Build Coastguard Worker	if err != nil {
169*9e94795aSAndroid Build Coastguard Worker		if err == failNoneRequested {
170*9e94795aSAndroid Build Coastguard Worker			flags.Usage()
171*9e94795aSAndroid Build Coastguard Worker		}
172*9e94795aSAndroid Build Coastguard Worker		fmt.Fprintf(os.Stderr, "%s\n", err.Error())
173*9e94795aSAndroid Build Coastguard Worker		os.Exit(1)
174*9e94795aSAndroid Build Coastguard Worker	}
175*9e94795aSAndroid Build Coastguard Worker	if *outputFile != "-" {
176*9e94795aSAndroid Build Coastguard Worker		err := os.WriteFile(*outputFile, obuf.Bytes(), 0666)
177*9e94795aSAndroid Build Coastguard Worker		if err != nil {
178*9e94795aSAndroid Build Coastguard Worker			fmt.Fprintf(os.Stderr, "could not write output to %q from %q: %s\n", *outputFile, os.Getenv("PWD"), err)
179*9e94795aSAndroid Build Coastguard Worker			os.Exit(1)
180*9e94795aSAndroid Build Coastguard Worker		}
181*9e94795aSAndroid Build Coastguard Worker	}
182*9e94795aSAndroid Build Coastguard Worker	os.Exit(0)
183*9e94795aSAndroid Build Coastguard Worker}
184*9e94795aSAndroid Build Coastguard Worker
185*9e94795aSAndroid Build Coastguard Worker// dumpResolutions implements the dumpresolutions utility.
186*9e94795aSAndroid Build Coastguard Workerfunc dumpResolutions(ctx *context, stdout, stderr io.Writer, rootFS fs.FS, files ...string) (*compliance.LicenseGraph, error) {
187*9e94795aSAndroid Build Coastguard Worker	if len(files) < 1 {
188*9e94795aSAndroid Build Coastguard Worker		return nil, failNoneRequested
189*9e94795aSAndroid Build Coastguard Worker	}
190*9e94795aSAndroid Build Coastguard Worker
191*9e94795aSAndroid Build Coastguard Worker	// Read the license graph from the license metadata files (*.meta_lic).
192*9e94795aSAndroid Build Coastguard Worker	licenseGraph, err := compliance.ReadLicenseGraph(rootFS, stderr, files)
193*9e94795aSAndroid Build Coastguard Worker	if err != nil {
194*9e94795aSAndroid Build Coastguard Worker		return nil, fmt.Errorf("Unable to read license metadata file(s) %q: %v\n", files, err)
195*9e94795aSAndroid Build Coastguard Worker	}
196*9e94795aSAndroid Build Coastguard Worker	if licenseGraph == nil {
197*9e94795aSAndroid Build Coastguard Worker		return nil, failNoLicenses
198*9e94795aSAndroid Build Coastguard Worker	}
199*9e94795aSAndroid Build Coastguard Worker
200*9e94795aSAndroid Build Coastguard Worker	compliance.ResolveTopDownConditions(licenseGraph)
201*9e94795aSAndroid Build Coastguard Worker	cs := compliance.AllLicenseConditions
202*9e94795aSAndroid Build Coastguard Worker	if len(ctx.conditions) > 0 {
203*9e94795aSAndroid Build Coastguard Worker		cs = compliance.NewLicenseConditionSet()
204*9e94795aSAndroid Build Coastguard Worker		for _, c := range ctx.conditions {
205*9e94795aSAndroid Build Coastguard Worker			cs = cs.Plus(c)
206*9e94795aSAndroid Build Coastguard Worker		}
207*9e94795aSAndroid Build Coastguard Worker	}
208*9e94795aSAndroid Build Coastguard Worker
209*9e94795aSAndroid Build Coastguard Worker	resolutions := compliance.WalkResolutionsForCondition(licenseGraph, cs)
210*9e94795aSAndroid Build Coastguard Worker
211*9e94795aSAndroid Build Coastguard Worker	// nodes maps license metadata file names to graphViz node names when graphViz requested.
212*9e94795aSAndroid Build Coastguard Worker	nodes := make(map[string]string)
213*9e94795aSAndroid Build Coastguard Worker	n := 0
214*9e94795aSAndroid Build Coastguard Worker
215*9e94795aSAndroid Build Coastguard Worker	// targetOut calculates the string to output for `target` adding `sep`-separated conditions as needed.
216*9e94795aSAndroid Build Coastguard Worker	targetOut := func(target *compliance.TargetNode, sep string) string {
217*9e94795aSAndroid Build Coastguard Worker		tOut := ctx.strip(target.Name())
218*9e94795aSAndroid Build Coastguard Worker		if ctx.labelConditions {
219*9e94795aSAndroid Build Coastguard Worker			conditions := target.LicenseConditions().Names()
220*9e94795aSAndroid Build Coastguard Worker			if len(conditions) > 0 {
221*9e94795aSAndroid Build Coastguard Worker				tOut += sep + strings.Join(conditions, sep)
222*9e94795aSAndroid Build Coastguard Worker			}
223*9e94795aSAndroid Build Coastguard Worker		}
224*9e94795aSAndroid Build Coastguard Worker		return tOut
225*9e94795aSAndroid Build Coastguard Worker	}
226*9e94795aSAndroid Build Coastguard Worker
227*9e94795aSAndroid Build Coastguard Worker	// makeNode maps `target` to a graphViz node name.
228*9e94795aSAndroid Build Coastguard Worker	makeNode := func(target *compliance.TargetNode) {
229*9e94795aSAndroid Build Coastguard Worker		tName := target.Name()
230*9e94795aSAndroid Build Coastguard Worker		if _, ok := nodes[tName]; !ok {
231*9e94795aSAndroid Build Coastguard Worker			nodeName := fmt.Sprintf("n%d", n)
232*9e94795aSAndroid Build Coastguard Worker			nodes[tName] = nodeName
233*9e94795aSAndroid Build Coastguard Worker			fmt.Fprintf(stdout, "\t%s [label=\"%s\"];\n", nodeName, targetOut(target, "\\n"))
234*9e94795aSAndroid Build Coastguard Worker			n++
235*9e94795aSAndroid Build Coastguard Worker		}
236*9e94795aSAndroid Build Coastguard Worker	}
237*9e94795aSAndroid Build Coastguard Worker
238*9e94795aSAndroid Build Coastguard Worker	// outputResolution prints a resolution in the requested format to `stdout`, where one can read
239*9e94795aSAndroid Build Coastguard Worker	// a resolution as `tname` resolves `oname`'s conditions named in `cnames`.
240*9e94795aSAndroid Build Coastguard Worker	// `tname` is the name of the target the resolution applies to.
241*9e94795aSAndroid Build Coastguard Worker	// `cnames` is the list of conditions to resolve.
242*9e94795aSAndroid Build Coastguard Worker	outputResolution := func(tname, aname string, cnames []string) {
243*9e94795aSAndroid Build Coastguard Worker		if ctx.graphViz {
244*9e94795aSAndroid Build Coastguard Worker			// ... one edge per line labelled with \\n-separated annotations.
245*9e94795aSAndroid Build Coastguard Worker			tNode := nodes[tname]
246*9e94795aSAndroid Build Coastguard Worker			aNode := nodes[aname]
247*9e94795aSAndroid Build Coastguard Worker			fmt.Fprintf(stdout, "\t%s -> %s [label=\"%s\"];\n", tNode, aNode, strings.Join(cnames, "\\n"))
248*9e94795aSAndroid Build Coastguard Worker		} else {
249*9e94795aSAndroid Build Coastguard Worker			// ... one edge per line with names in a colon-separated tuple.
250*9e94795aSAndroid Build Coastguard Worker			fmt.Fprintf(stdout, "%s %s %s\n", tname, aname, strings.Join(cnames, ":"))
251*9e94795aSAndroid Build Coastguard Worker		}
252*9e94795aSAndroid Build Coastguard Worker	}
253*9e94795aSAndroid Build Coastguard Worker
254*9e94795aSAndroid Build Coastguard Worker	// Sort the resolutions by targetname for repeatability/stability.
255*9e94795aSAndroid Build Coastguard Worker	targets := resolutions.AttachesTo()
256*9e94795aSAndroid Build Coastguard Worker	sort.Sort(targets)
257*9e94795aSAndroid Build Coastguard Worker
258*9e94795aSAndroid Build Coastguard Worker	// If graphviz output, start the directed graph.
259*9e94795aSAndroid Build Coastguard Worker	if ctx.graphViz {
260*9e94795aSAndroid Build Coastguard Worker		fmt.Fprintf(stdout, "strict digraph {\n\trankdir=LR;\n")
261*9e94795aSAndroid Build Coastguard Worker		for _, target := range targets {
262*9e94795aSAndroid Build Coastguard Worker			makeNode(target)
263*9e94795aSAndroid Build Coastguard Worker			rl := resolutions.Resolutions(target)
264*9e94795aSAndroid Build Coastguard Worker			sort.Sort(rl)
265*9e94795aSAndroid Build Coastguard Worker			for _, r := range rl {
266*9e94795aSAndroid Build Coastguard Worker				makeNode(r.ActsOn())
267*9e94795aSAndroid Build Coastguard Worker			}
268*9e94795aSAndroid Build Coastguard Worker		}
269*9e94795aSAndroid Build Coastguard Worker	}
270*9e94795aSAndroid Build Coastguard Worker
271*9e94795aSAndroid Build Coastguard Worker	// Output the sorted targets.
272*9e94795aSAndroid Build Coastguard Worker	for _, target := range targets {
273*9e94795aSAndroid Build Coastguard Worker		var tname string
274*9e94795aSAndroid Build Coastguard Worker		if ctx.graphViz {
275*9e94795aSAndroid Build Coastguard Worker			tname = target.Name()
276*9e94795aSAndroid Build Coastguard Worker		} else {
277*9e94795aSAndroid Build Coastguard Worker			tname = targetOut(target, ":")
278*9e94795aSAndroid Build Coastguard Worker		}
279*9e94795aSAndroid Build Coastguard Worker
280*9e94795aSAndroid Build Coastguard Worker		rl := resolutions.Resolutions(target)
281*9e94795aSAndroid Build Coastguard Worker		sort.Sort(rl)
282*9e94795aSAndroid Build Coastguard Worker		for _, r := range rl {
283*9e94795aSAndroid Build Coastguard Worker			var aname string
284*9e94795aSAndroid Build Coastguard Worker			if ctx.graphViz {
285*9e94795aSAndroid Build Coastguard Worker				aname = r.ActsOn().Name()
286*9e94795aSAndroid Build Coastguard Worker			} else {
287*9e94795aSAndroid Build Coastguard Worker				aname = targetOut(r.ActsOn(), ":")
288*9e94795aSAndroid Build Coastguard Worker			}
289*9e94795aSAndroid Build Coastguard Worker
290*9e94795aSAndroid Build Coastguard Worker			// cnames accumulates the list of condition names originating at a single origin that apply to `target`.
291*9e94795aSAndroid Build Coastguard Worker			cnames := r.Resolves().Names()
292*9e94795aSAndroid Build Coastguard Worker
293*9e94795aSAndroid Build Coastguard Worker			// Output 1 line for each attachesTo+actsOn combination.
294*9e94795aSAndroid Build Coastguard Worker			outputResolution(tname, aname, cnames)
295*9e94795aSAndroid Build Coastguard Worker		}
296*9e94795aSAndroid Build Coastguard Worker	}
297*9e94795aSAndroid Build Coastguard Worker	// If graphViz output, rank the root nodes together, and complete the directed graph.
298*9e94795aSAndroid Build Coastguard Worker	if ctx.graphViz {
299*9e94795aSAndroid Build Coastguard Worker		fmt.Fprintf(stdout, "\t{rank=same;")
300*9e94795aSAndroid Build Coastguard Worker		for _, f := range files {
301*9e94795aSAndroid Build Coastguard Worker			fName := f
302*9e94795aSAndroid Build Coastguard Worker			if !strings.HasSuffix(fName, ".meta_lic") {
303*9e94795aSAndroid Build Coastguard Worker				fName += ".meta_lic"
304*9e94795aSAndroid Build Coastguard Worker			}
305*9e94795aSAndroid Build Coastguard Worker			if fNode, ok := nodes[fName]; ok {
306*9e94795aSAndroid Build Coastguard Worker				fmt.Fprintf(stdout, " %s", fNode)
307*9e94795aSAndroid Build Coastguard Worker			}
308*9e94795aSAndroid Build Coastguard Worker		}
309*9e94795aSAndroid Build Coastguard Worker		fmt.Fprintf(stdout, "}\n}\n")
310*9e94795aSAndroid Build Coastguard Worker	}
311*9e94795aSAndroid Build Coastguard Worker	return licenseGraph, nil
312*9e94795aSAndroid Build Coastguard Worker}
313