blob: 463dfadb6514a25191ff3ef32c18435410de2605 [file] [log] [blame]
Bob Badour1ded0a12021-12-03 17:16:14 -08001// 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 (
Bob Badour1ded0a12021-12-03 17:16:14 -080018 "flag"
19 "fmt"
20 "io"
21 "os"
22 "path/filepath"
23 "sort"
24 "strings"
Colin Cross38a61932022-01-27 15:26:49 -080025
26 "android/soong/tools/compliance"
Bob Badour1ded0a12021-12-03 17:16:14 -080027)
28
29var (
30 conditions = newMultiString("c", "License condition to resolve. (may be given multiple times)")
31 graphViz = flag.Bool("dot", false, "Whether to output graphviz (i.e. dot) format.")
32 labelConditions = flag.Bool("label_conditions", false, "Whether to label target nodes with conditions.")
33 stripPrefix = flag.String("strip_prefix", "", "Prefix to remove from paths. i.e. path to root")
34
35 failNoneRequested = fmt.Errorf("\nNo license metadata files requested")
Colin Cross35f79c32022-01-27 15:18:52 -080036 failNoLicenses = fmt.Errorf("No licenses found")
Bob Badour1ded0a12021-12-03 17:16:14 -080037)
38
39type context struct {
Bob Badour103eb0f2022-01-10 13:50:57 -080040 conditions []compliance.LicenseCondition
Bob Badour1ded0a12021-12-03 17:16:14 -080041 graphViz bool
42 labelConditions bool
43 stripPrefix string
44}
45
46func init() {
47 flag.Usage = func() {
48 fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...}
49
50Outputs a space-separated Target ActsOn Origin Condition tuple for each
51resolution in the graph. When -dot flag given, outputs nodes and edges
52in graphviz directed graph format.
53
Bob Badour103eb0f2022-01-10 13:50:57 -080054If one or more '-c condition' conditions are given, outputs the
55resolution for the union of the conditions. Otherwise, outputs the
56resolution for all conditions.
Bob Badour1ded0a12021-12-03 17:16:14 -080057
58In plain text mode, when '-label_conditions' is requested, the Target
59and Origin have colon-separated license conditions appended:
60i.e. target:condition1:condition2 etc.
61
62Options:
63`, filepath.Base(os.Args[0]))
64 flag.PrintDefaults()
65 }
66}
67
68// newMultiString creates a flag that allows multiple values in an array.
69func newMultiString(name, usage string) *multiString {
70 var f multiString
71 flag.Var(&f, name, usage)
72 return &f
73}
74
75// multiString implements the flag `Value` interface for multiple strings.
76type multiString []string
77
78func (ms *multiString) String() string { return strings.Join(*ms, ", ") }
79func (ms *multiString) Set(s string) error { *ms = append(*ms, s); return nil }
80
81func main() {
82 flag.Parse()
83
84 // Must specify at least one root target.
85 if flag.NArg() == 0 {
86 flag.Usage()
87 os.Exit(2)
88 }
89
Bob Badour103eb0f2022-01-10 13:50:57 -080090 lcs := make([]compliance.LicenseCondition, 0, len(*conditions))
91 for _, name := range *conditions {
92 lcs = append(lcs, compliance.RecognizedConditionNames[name])
93 }
Bob Badour1ded0a12021-12-03 17:16:14 -080094 ctx := &context{
Bob Badour103eb0f2022-01-10 13:50:57 -080095 conditions: lcs,
Bob Badour1ded0a12021-12-03 17:16:14 -080096 graphViz: *graphViz,
97 labelConditions: *labelConditions,
98 stripPrefix: *stripPrefix,
99 }
Bob Badour103eb0f2022-01-10 13:50:57 -0800100 _, err := dumpResolutions(ctx, os.Stdout, os.Stderr, flag.Args()...)
Bob Badour1ded0a12021-12-03 17:16:14 -0800101 if err != nil {
102 if err == failNoneRequested {
103 flag.Usage()
104 }
105 fmt.Fprintf(os.Stderr, "%s\n", err.Error())
106 os.Exit(1)
107 }
108 os.Exit(0)
109}
110
111// dumpResolutions implements the dumpresolutions utility.
Bob Badour103eb0f2022-01-10 13:50:57 -0800112func dumpResolutions(ctx *context, stdout, stderr io.Writer, files ...string) (*compliance.LicenseGraph, error) {
Bob Badour1ded0a12021-12-03 17:16:14 -0800113 if len(files) < 1 {
Bob Badour103eb0f2022-01-10 13:50:57 -0800114 return nil, failNoneRequested
Bob Badour1ded0a12021-12-03 17:16:14 -0800115 }
116
117 // Read the license graph from the license metadata files (*.meta_lic).
118 licenseGraph, err := compliance.ReadLicenseGraph(os.DirFS("."), stderr, files)
119 if err != nil {
Bob Badour103eb0f2022-01-10 13:50:57 -0800120 return nil, fmt.Errorf("Unable to read license metadata file(s) %q: %v\n", files, err)
Bob Badour1ded0a12021-12-03 17:16:14 -0800121 }
122 if licenseGraph == nil {
Bob Badour103eb0f2022-01-10 13:50:57 -0800123 return nil, failNoLicenses
Bob Badour1ded0a12021-12-03 17:16:14 -0800124 }
125
Bob Badour103eb0f2022-01-10 13:50:57 -0800126 compliance.ResolveTopDownConditions(licenseGraph)
127 cs := compliance.AllLicenseConditions
Bob Badour1ded0a12021-12-03 17:16:14 -0800128 if len(ctx.conditions) > 0 {
Bob Badour103eb0f2022-01-10 13:50:57 -0800129 cs = compliance.NewLicenseConditionSet()
Bob Badour1ded0a12021-12-03 17:16:14 -0800130 for _, c := range ctx.conditions {
Bob Badour103eb0f2022-01-10 13:50:57 -0800131 cs = cs.Plus(c)
Bob Badour1ded0a12021-12-03 17:16:14 -0800132 }
133 }
134
Bob Badour103eb0f2022-01-10 13:50:57 -0800135 resolutions := compliance.WalkResolutionsForCondition(licenseGraph, cs)
136
Bob Badour1ded0a12021-12-03 17:16:14 -0800137 // nodes maps license metadata file names to graphViz node names when graphViz requested.
138 nodes := make(map[string]string)
139 n := 0
140
141 // targetOut calculates the string to output for `target` adding `sep`-separated conditions as needed.
142 targetOut := func(target *compliance.TargetNode, sep string) string {
143 tOut := strings.TrimPrefix(target.Name(), ctx.stripPrefix)
144 if ctx.labelConditions {
Bob Badour103eb0f2022-01-10 13:50:57 -0800145 conditions := target.LicenseConditions().Names()
Bob Badour1ded0a12021-12-03 17:16:14 -0800146 if len(conditions) > 0 {
147 tOut += sep + strings.Join(conditions, sep)
148 }
149 }
150 return tOut
151 }
152
153 // makeNode maps `target` to a graphViz node name.
154 makeNode := func(target *compliance.TargetNode) {
155 tName := target.Name()
156 if _, ok := nodes[tName]; !ok {
157 nodeName := fmt.Sprintf("n%d", n)
158 nodes[tName] = nodeName
159 fmt.Fprintf(stdout, "\t%s [label=\"%s\"];\n", nodeName, targetOut(target, "\\n"))
160 n++
161 }
162 }
163
164 // outputResolution prints a resolution in the requested format to `stdout`, where one can read
165 // a resolution as `tname` resolves `oname`'s conditions named in `cnames`.
166 // `tname` is the name of the target the resolution applies to.
Bob Badour1ded0a12021-12-03 17:16:14 -0800167 // `cnames` is the list of conditions to resolve.
Bob Badour103eb0f2022-01-10 13:50:57 -0800168 outputResolution := func(tname, aname string, cnames []string) {
Bob Badour1ded0a12021-12-03 17:16:14 -0800169 if ctx.graphViz {
170 // ... one edge per line labelled with \\n-separated annotations.
171 tNode := nodes[tname]
172 aNode := nodes[aname]
Bob Badour103eb0f2022-01-10 13:50:57 -0800173 fmt.Fprintf(stdout, "\t%s -> %s [label=\"%s\"];\n", tNode, aNode, strings.Join(cnames, "\\n"))
Bob Badour1ded0a12021-12-03 17:16:14 -0800174 } else {
175 // ... one edge per line with names in a colon-separated tuple.
Bob Badour103eb0f2022-01-10 13:50:57 -0800176 fmt.Fprintf(stdout, "%s %s %s\n", tname, aname, strings.Join(cnames, ":"))
Bob Badour1ded0a12021-12-03 17:16:14 -0800177 }
178 }
179
180 // Sort the resolutions by targetname for repeatability/stability.
181 targets := resolutions.AttachesTo()
182 sort.Sort(targets)
183
184 // If graphviz output, start the directed graph.
185 if ctx.graphViz {
186 fmt.Fprintf(stdout, "strict digraph {\n\trankdir=LR;\n")
187 for _, target := range targets {
188 makeNode(target)
Bob Badour103eb0f2022-01-10 13:50:57 -0800189 rl := resolutions.Resolutions(target)
Bob Badour1ded0a12021-12-03 17:16:14 -0800190 sort.Sort(rl)
191 for _, r := range rl {
192 makeNode(r.ActsOn())
193 }
Bob Badour1ded0a12021-12-03 17:16:14 -0800194 }
195 }
196
197 // Output the sorted targets.
198 for _, target := range targets {
199 var tname string
200 if ctx.graphViz {
201 tname = target.Name()
202 } else {
203 tname = targetOut(target, ":")
204 }
205
Bob Badour103eb0f2022-01-10 13:50:57 -0800206 rl := resolutions.Resolutions(target)
Bob Badour1ded0a12021-12-03 17:16:14 -0800207 sort.Sort(rl)
208 for _, r := range rl {
209 var aname string
210 if ctx.graphViz {
211 aname = r.ActsOn().Name()
212 } else {
213 aname = targetOut(r.ActsOn(), ":")
214 }
215
Bob Badour1ded0a12021-12-03 17:16:14 -0800216 // cnames accumulates the list of condition names originating at a single origin that apply to `target`.
Bob Badour103eb0f2022-01-10 13:50:57 -0800217 cnames := r.Resolves().Names()
Bob Badour1ded0a12021-12-03 17:16:14 -0800218
Bob Badour103eb0f2022-01-10 13:50:57 -0800219 // Output 1 line for each attachesTo+actsOn combination.
220 outputResolution(tname, aname, cnames)
Bob Badour1ded0a12021-12-03 17:16:14 -0800221 }
222 }
223 // If graphViz output, rank the root nodes together, and complete the directed graph.
224 if ctx.graphViz {
225 fmt.Fprintf(stdout, "\t{rank=same;")
226 for _, f := range files {
227 fName := f
228 if !strings.HasSuffix(fName, ".meta_lic") {
229 fName += ".meta_lic"
230 }
231 if fNode, ok := nodes[fName]; ok {
232 fmt.Fprintf(stdout, " %s", fNode)
233 }
234 }
235 fmt.Fprintf(stdout, "}\n}\n")
236 }
Bob Badour103eb0f2022-01-10 13:50:57 -0800237 return licenseGraph, nil
Bob Badour1ded0a12021-12-03 17:16:14 -0800238}