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