blob: 02ab0258b63f420888ba4744be9995eec5eee9a7 [file] [log] [blame]
Bob Badourfa739da2021-10-25 16:21:00 -07001// 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 Badourfa739da2021-10-25 16:21:00 -070018 "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 Badourfa739da2021-10-25 16:21:00 -070027)
28
29var (
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")
Colin Cross35f79c32022-01-27 15:18:52 -080035 failNoLicenses = fmt.Errorf("No licenses found")
Bob Badourfa739da2021-10-25 16:21:00 -070036)
37
38type context struct {
39 graphViz bool
40 labelConditions bool
41 stripPrefix string
42}
43
44func init() {
45 flag.Usage = func() {
46 fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...}
47
48Outputs space-separated Target Dependency Annotations tuples for each
49edge in the license graph. When -dot flag given, outputs the nodes and
50edges in graphViz directed graph format.
51
52In plain text mode, multiple values within a field are colon-separated.
53e.g. multiple annotations appear as annotation1:annotation2:annotation3
54or when -label_conditions is requested, Target and Dependency become
55target:condition1:condition2 etc.
56
57Options:
58`, filepath.Base(os.Args[0]))
59 flag.PrintDefaults()
60 }
61}
62
63func main() {
64 flag.Parse()
65
66 // Must specify at least one root target.
67 if flag.NArg() == 0 {
68 flag.Usage()
69 os.Exit(2)
70 }
71
72 ctx := &context{*graphViz, *labelConditions, *stripPrefix}
73
74 err := dumpGraph(ctx, os.Stdout, os.Stderr, flag.Args()...)
75 if err != nil {
76 if err == failNoneRequested {
77 flag.Usage()
78 }
79 fmt.Fprintf(os.Stderr, "%s\n", err.Error())
80 os.Exit(1)
81 }
82 os.Exit(0)
83}
84
85// dumpGraph implements the dumpgraph utility.
86func dumpGraph(ctx *context, stdout, stderr io.Writer, files ...string) error {
87 if len(files) < 1 {
88 return failNoneRequested
89 }
90
91 // Read the license graph from the license metadata files (*.meta_lic).
92 licenseGraph, err := compliance.ReadLicenseGraph(os.DirFS("."), stderr, files)
93 if err != nil {
94 return fmt.Errorf("Unable to read license metadata file(s) %q: %w\n", files, err)
95 }
96 if licenseGraph == nil {
97 return failNoLicenses
98 }
99
100 // Sort the edges of the graph.
101 edges := licenseGraph.Edges()
102 sort.Sort(edges)
103
104 // nodes maps license metadata file names to graphViz node names when ctx.graphViz is true.
105 var nodes map[string]string
106 n := 0
107
108 // targetOut calculates the string to output for `target` separating conditions as needed using `sep`.
109 targetOut := func(target *compliance.TargetNode, sep string) string {
110 tOut := strings.TrimPrefix(target.Name(), ctx.stripPrefix)
111 if ctx.labelConditions {
112 conditions := target.LicenseConditions().Names()
113 sort.Strings(conditions)
114 if len(conditions) > 0 {
115 tOut += sep + strings.Join(conditions, sep)
116 }
117 }
118 return tOut
119 }
120
121 // makeNode maps `target` to a graphViz node name.
122 makeNode := func(target *compliance.TargetNode) {
123 tName := target.Name()
124 if _, ok := nodes[tName]; !ok {
125 nodeName := fmt.Sprintf("n%d", n)
126 nodes[tName] = nodeName
127 fmt.Fprintf(stdout, "\t%s [label=\"%s\"];\n", nodeName, targetOut(target, "\\n"))
128 n++
129 }
130 }
131
132 // If graphviz output, map targets to node names, and start the directed graph.
133 if ctx.graphViz {
134 nodes = make(map[string]string)
135 targets := licenseGraph.Targets()
136 sort.Sort(targets)
137
138 fmt.Fprintf(stdout, "strict digraph {\n\trankdir=RL;\n")
139 for _, target := range targets {
140 makeNode(target)
141 }
142 }
143
144 // Print the sorted edges to stdout ...
145 for _, e := range edges {
146 // sort the annotations for repeatability/stability
147 annotations := e.Annotations().AsList()
148 sort.Strings(annotations)
149
150 tName := e.Target().Name()
151 dName := e.Dependency().Name()
152
153 if ctx.graphViz {
154 // ... one edge per line labelled with \\n-separated annotations.
155 tNode := nodes[tName]
156 dNode := nodes[dName]
157 fmt.Fprintf(stdout, "\t%s -> %s [label=\"%s\"];\n", dNode, tNode, strings.Join(annotations, "\\n"))
158 } else {
159 // ... one edge per line with annotations in a colon-separated tuple.
160 fmt.Fprintf(stdout, "%s %s %s\n", targetOut(e.Target(), ":"), targetOut(e.Dependency(), ":"), strings.Join(annotations, ":"))
161 }
162 }
163
164 // If graphViz output, rank the root nodes together, and complete the directed graph.
165 if ctx.graphViz {
166 fmt.Fprintf(stdout, "\t{rank=same;")
167 for _, f := range files {
168 fName := f
169 if !strings.HasSuffix(fName, ".meta_lic") {
170 fName += ".meta_lic"
171 }
172 if fNode, ok := nodes[fName]; ok {
173 fmt.Fprintf(stdout, " %s", fNode)
174 }
175 }
176 fmt.Fprintf(stdout, "}\n}\n")
177 }
178 return nil
179}