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 graphViz bool 39*9e94795aSAndroid Build Coastguard Worker labelConditions bool 40*9e94795aSAndroid Build Coastguard Worker stripPrefix []string 41*9e94795aSAndroid Build Coastguard Worker} 42*9e94795aSAndroid Build Coastguard Worker 43*9e94795aSAndroid Build Coastguard Workerfunc (ctx context) strip(installPath string) string { 44*9e94795aSAndroid Build Coastguard Worker for _, prefix := range ctx.stripPrefix { 45*9e94795aSAndroid Build Coastguard Worker if strings.HasPrefix(installPath, prefix) { 46*9e94795aSAndroid Build Coastguard Worker p := strings.TrimPrefix(installPath, prefix) 47*9e94795aSAndroid Build Coastguard Worker if 0 == len(p) { 48*9e94795aSAndroid Build Coastguard Worker continue 49*9e94795aSAndroid Build Coastguard Worker } 50*9e94795aSAndroid Build Coastguard Worker return p 51*9e94795aSAndroid Build Coastguard Worker } 52*9e94795aSAndroid Build Coastguard Worker } 53*9e94795aSAndroid Build Coastguard Worker return installPath 54*9e94795aSAndroid Build Coastguard Worker} 55*9e94795aSAndroid Build Coastguard Worker 56*9e94795aSAndroid Build Coastguard Worker// newMultiString creates a flag that allows multiple values in an array. 57*9e94795aSAndroid Build Coastguard Workerfunc newMultiString(flags *flag.FlagSet, name, usage string) *multiString { 58*9e94795aSAndroid Build Coastguard Worker var f multiString 59*9e94795aSAndroid Build Coastguard Worker flags.Var(&f, name, usage) 60*9e94795aSAndroid Build Coastguard Worker return &f 61*9e94795aSAndroid Build Coastguard Worker} 62*9e94795aSAndroid Build Coastguard Worker 63*9e94795aSAndroid Build Coastguard Worker// multiString implements the flag `Value` interface for multiple strings. 64*9e94795aSAndroid Build Coastguard Workertype multiString []string 65*9e94795aSAndroid Build Coastguard Worker 66*9e94795aSAndroid Build Coastguard Workerfunc (ms *multiString) String() string { return strings.Join(*ms, ", ") } 67*9e94795aSAndroid Build Coastguard Workerfunc (ms *multiString) Set(s string) error { *ms = append(*ms, s); return nil } 68*9e94795aSAndroid Build Coastguard Worker 69*9e94795aSAndroid Build Coastguard Workerfunc main() { 70*9e94795aSAndroid Build Coastguard Worker var expandedArgs []string 71*9e94795aSAndroid Build Coastguard Worker for _, arg := range os.Args[1:] { 72*9e94795aSAndroid Build Coastguard Worker if strings.HasPrefix(arg, "@") { 73*9e94795aSAndroid Build Coastguard Worker f, err := os.Open(strings.TrimPrefix(arg, "@")) 74*9e94795aSAndroid Build Coastguard Worker if err != nil { 75*9e94795aSAndroid Build Coastguard Worker fmt.Fprintln(os.Stderr, err.Error()) 76*9e94795aSAndroid Build Coastguard Worker os.Exit(1) 77*9e94795aSAndroid Build Coastguard Worker } 78*9e94795aSAndroid Build Coastguard Worker 79*9e94795aSAndroid Build Coastguard Worker respArgs, err := response.ReadRspFile(f) 80*9e94795aSAndroid Build Coastguard Worker f.Close() 81*9e94795aSAndroid Build Coastguard Worker if err != nil { 82*9e94795aSAndroid Build Coastguard Worker fmt.Fprintln(os.Stderr, err.Error()) 83*9e94795aSAndroid Build Coastguard Worker os.Exit(1) 84*9e94795aSAndroid Build Coastguard Worker } 85*9e94795aSAndroid Build Coastguard Worker expandedArgs = append(expandedArgs, respArgs...) 86*9e94795aSAndroid Build Coastguard Worker } else { 87*9e94795aSAndroid Build Coastguard Worker expandedArgs = append(expandedArgs, arg) 88*9e94795aSAndroid Build Coastguard Worker } 89*9e94795aSAndroid Build Coastguard Worker } 90*9e94795aSAndroid Build Coastguard Worker 91*9e94795aSAndroid Build Coastguard Worker flags := flag.NewFlagSet("flags", flag.ExitOnError) 92*9e94795aSAndroid Build Coastguard Worker 93*9e94795aSAndroid Build Coastguard Worker flags.Usage = func() { 94*9e94795aSAndroid Build Coastguard Worker fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...} 95*9e94795aSAndroid Build Coastguard Worker 96*9e94795aSAndroid Build Coastguard WorkerOutputs space-separated Target Dependency Annotations tuples for each 97*9e94795aSAndroid Build Coastguard Workeredge in the license graph. When -dot flag given, outputs the nodes and 98*9e94795aSAndroid Build Coastguard Workeredges in graphViz directed graph format. 99*9e94795aSAndroid Build Coastguard Worker 100*9e94795aSAndroid Build Coastguard WorkerIn plain text mode, multiple values within a field are colon-separated. 101*9e94795aSAndroid Build Coastguard Workere.g. multiple annotations appear as annotation1:annotation2:annotation3 102*9e94795aSAndroid Build Coastguard Workeror when -label_conditions is requested, Target and Dependency become 103*9e94795aSAndroid Build Coastguard Workertarget:condition1:condition2 etc. 104*9e94795aSAndroid Build Coastguard Worker 105*9e94795aSAndroid Build Coastguard WorkerOptions: 106*9e94795aSAndroid Build Coastguard Worker`, filepath.Base(os.Args[0])) 107*9e94795aSAndroid Build Coastguard Worker flags.PrintDefaults() 108*9e94795aSAndroid Build Coastguard Worker } 109*9e94795aSAndroid Build Coastguard Worker 110*9e94795aSAndroid Build Coastguard Worker graphViz := flags.Bool("dot", false, "Whether to output graphviz (i.e. dot) format.") 111*9e94795aSAndroid Build Coastguard Worker labelConditions := flags.Bool("label_conditions", false, "Whether to label target nodes with conditions.") 112*9e94795aSAndroid Build Coastguard Worker outputFile := flags.String("o", "-", "Where to write the output. (default stdout)") 113*9e94795aSAndroid Build Coastguard Worker stripPrefix := newMultiString(flags, "strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)") 114*9e94795aSAndroid Build Coastguard Worker 115*9e94795aSAndroid Build Coastguard Worker flags.Parse(expandedArgs) 116*9e94795aSAndroid Build Coastguard Worker 117*9e94795aSAndroid Build Coastguard Worker // Must specify at least one root target. 118*9e94795aSAndroid Build Coastguard Worker if flags.NArg() == 0 { 119*9e94795aSAndroid Build Coastguard Worker flags.Usage() 120*9e94795aSAndroid Build Coastguard Worker os.Exit(2) 121*9e94795aSAndroid Build Coastguard Worker } 122*9e94795aSAndroid Build Coastguard Worker 123*9e94795aSAndroid Build Coastguard Worker if len(*outputFile) == 0 { 124*9e94795aSAndroid Build Coastguard Worker flags.Usage() 125*9e94795aSAndroid Build Coastguard Worker fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n") 126*9e94795aSAndroid Build Coastguard Worker os.Exit(2) 127*9e94795aSAndroid Build Coastguard Worker } else { 128*9e94795aSAndroid Build Coastguard Worker dir, err := filepath.Abs(filepath.Dir(*outputFile)) 129*9e94795aSAndroid Build Coastguard Worker if err != nil { 130*9e94795aSAndroid Build Coastguard Worker fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err) 131*9e94795aSAndroid Build Coastguard Worker os.Exit(1) 132*9e94795aSAndroid Build Coastguard Worker } 133*9e94795aSAndroid Build Coastguard Worker fi, err := os.Stat(dir) 134*9e94795aSAndroid Build Coastguard Worker if err != nil { 135*9e94795aSAndroid Build Coastguard Worker fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err) 136*9e94795aSAndroid Build Coastguard Worker os.Exit(1) 137*9e94795aSAndroid Build Coastguard Worker } 138*9e94795aSAndroid Build Coastguard Worker if !fi.IsDir() { 139*9e94795aSAndroid Build Coastguard Worker fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile) 140*9e94795aSAndroid Build Coastguard Worker os.Exit(1) 141*9e94795aSAndroid Build Coastguard Worker } 142*9e94795aSAndroid Build Coastguard Worker } 143*9e94795aSAndroid Build Coastguard Worker 144*9e94795aSAndroid Build Coastguard Worker var ofile io.Writer 145*9e94795aSAndroid Build Coastguard Worker ofile = os.Stdout 146*9e94795aSAndroid Build Coastguard Worker var obuf *bytes.Buffer 147*9e94795aSAndroid Build Coastguard Worker if *outputFile != "-" { 148*9e94795aSAndroid Build Coastguard Worker obuf = &bytes.Buffer{} 149*9e94795aSAndroid Build Coastguard Worker ofile = obuf 150*9e94795aSAndroid Build Coastguard Worker } 151*9e94795aSAndroid Build Coastguard Worker 152*9e94795aSAndroid Build Coastguard Worker ctx := &context{*graphViz, *labelConditions, *stripPrefix} 153*9e94795aSAndroid Build Coastguard Worker 154*9e94795aSAndroid Build Coastguard Worker err := dumpGraph(ctx, ofile, os.Stderr, compliance.FS, flags.Args()...) 155*9e94795aSAndroid Build Coastguard Worker if err != nil { 156*9e94795aSAndroid Build Coastguard Worker if err == failNoneRequested { 157*9e94795aSAndroid Build Coastguard Worker flags.Usage() 158*9e94795aSAndroid Build Coastguard Worker } 159*9e94795aSAndroid Build Coastguard Worker fmt.Fprintf(os.Stderr, "%s\n", err.Error()) 160*9e94795aSAndroid Build Coastguard Worker os.Exit(1) 161*9e94795aSAndroid Build Coastguard Worker } 162*9e94795aSAndroid Build Coastguard Worker if *outputFile != "-" { 163*9e94795aSAndroid Build Coastguard Worker err := os.WriteFile(*outputFile, obuf.Bytes(), 0666) 164*9e94795aSAndroid Build Coastguard Worker if err != nil { 165*9e94795aSAndroid Build Coastguard Worker fmt.Fprintf(os.Stderr, "could not write output to %q from %q: %s\n", *outputFile, os.Getenv("PWD"), err) 166*9e94795aSAndroid Build Coastguard Worker os.Exit(1) 167*9e94795aSAndroid Build Coastguard Worker } 168*9e94795aSAndroid Build Coastguard Worker } 169*9e94795aSAndroid Build Coastguard Worker os.Exit(0) 170*9e94795aSAndroid Build Coastguard Worker} 171*9e94795aSAndroid Build Coastguard Worker 172*9e94795aSAndroid Build Coastguard Worker// dumpGraph implements the dumpgraph utility. 173*9e94795aSAndroid Build Coastguard Workerfunc dumpGraph(ctx *context, stdout, stderr io.Writer, rootFS fs.FS, files ...string) error { 174*9e94795aSAndroid Build Coastguard Worker if len(files) < 1 { 175*9e94795aSAndroid Build Coastguard Worker return failNoneRequested 176*9e94795aSAndroid Build Coastguard Worker } 177*9e94795aSAndroid Build Coastguard Worker 178*9e94795aSAndroid Build Coastguard Worker // Read the license graph from the license metadata files (*.meta_lic). 179*9e94795aSAndroid Build Coastguard Worker licenseGraph, err := compliance.ReadLicenseGraph(rootFS, stderr, files) 180*9e94795aSAndroid Build Coastguard Worker if err != nil { 181*9e94795aSAndroid Build Coastguard Worker return fmt.Errorf("Unable to read license metadata file(s) %q: %w\n", files, err) 182*9e94795aSAndroid Build Coastguard Worker } 183*9e94795aSAndroid Build Coastguard Worker if licenseGraph == nil { 184*9e94795aSAndroid Build Coastguard Worker return failNoLicenses 185*9e94795aSAndroid Build Coastguard Worker } 186*9e94795aSAndroid Build Coastguard Worker 187*9e94795aSAndroid Build Coastguard Worker // Sort the edges of the graph. 188*9e94795aSAndroid Build Coastguard Worker edges := licenseGraph.Edges() 189*9e94795aSAndroid Build Coastguard Worker sort.Sort(edges) 190*9e94795aSAndroid Build Coastguard Worker 191*9e94795aSAndroid Build Coastguard Worker // nodes maps license metadata file names to graphViz node names when ctx.graphViz is true. 192*9e94795aSAndroid Build Coastguard Worker var nodes map[string]string 193*9e94795aSAndroid Build Coastguard Worker n := 0 194*9e94795aSAndroid Build Coastguard Worker 195*9e94795aSAndroid Build Coastguard Worker // targetOut calculates the string to output for `target` separating conditions as needed using `sep`. 196*9e94795aSAndroid Build Coastguard Worker targetOut := func(target *compliance.TargetNode, sep string) string { 197*9e94795aSAndroid Build Coastguard Worker tOut := ctx.strip(target.Name()) 198*9e94795aSAndroid Build Coastguard Worker if ctx.labelConditions { 199*9e94795aSAndroid Build Coastguard Worker conditions := target.LicenseConditions().Names() 200*9e94795aSAndroid Build Coastguard Worker sort.Strings(conditions) 201*9e94795aSAndroid Build Coastguard Worker if len(conditions) > 0 { 202*9e94795aSAndroid Build Coastguard Worker tOut += sep + strings.Join(conditions, sep) 203*9e94795aSAndroid Build Coastguard Worker } 204*9e94795aSAndroid Build Coastguard Worker } 205*9e94795aSAndroid Build Coastguard Worker return tOut 206*9e94795aSAndroid Build Coastguard Worker } 207*9e94795aSAndroid Build Coastguard Worker 208*9e94795aSAndroid Build Coastguard Worker // makeNode maps `target` to a graphViz node name. 209*9e94795aSAndroid Build Coastguard Worker makeNode := func(target *compliance.TargetNode) { 210*9e94795aSAndroid Build Coastguard Worker tName := target.Name() 211*9e94795aSAndroid Build Coastguard Worker if _, ok := nodes[tName]; !ok { 212*9e94795aSAndroid Build Coastguard Worker nodeName := fmt.Sprintf("n%d", n) 213*9e94795aSAndroid Build Coastguard Worker nodes[tName] = nodeName 214*9e94795aSAndroid Build Coastguard Worker fmt.Fprintf(stdout, "\t%s [label=\"%s\"];\n", nodeName, targetOut(target, "\\n")) 215*9e94795aSAndroid Build Coastguard Worker n++ 216*9e94795aSAndroid Build Coastguard Worker } 217*9e94795aSAndroid Build Coastguard Worker } 218*9e94795aSAndroid Build Coastguard Worker 219*9e94795aSAndroid Build Coastguard Worker // If graphviz output, map targets to node names, and start the directed graph. 220*9e94795aSAndroid Build Coastguard Worker if ctx.graphViz { 221*9e94795aSAndroid Build Coastguard Worker nodes = make(map[string]string) 222*9e94795aSAndroid Build Coastguard Worker targets := licenseGraph.Targets() 223*9e94795aSAndroid Build Coastguard Worker sort.Sort(targets) 224*9e94795aSAndroid Build Coastguard Worker 225*9e94795aSAndroid Build Coastguard Worker fmt.Fprintf(stdout, "strict digraph {\n\trankdir=RL;\n") 226*9e94795aSAndroid Build Coastguard Worker for _, target := range targets { 227*9e94795aSAndroid Build Coastguard Worker makeNode(target) 228*9e94795aSAndroid Build Coastguard Worker } 229*9e94795aSAndroid Build Coastguard Worker } 230*9e94795aSAndroid Build Coastguard Worker 231*9e94795aSAndroid Build Coastguard Worker // Print the sorted edges to stdout ... 232*9e94795aSAndroid Build Coastguard Worker for _, e := range edges { 233*9e94795aSAndroid Build Coastguard Worker // sort the annotations for repeatability/stability 234*9e94795aSAndroid Build Coastguard Worker annotations := e.Annotations().AsList() 235*9e94795aSAndroid Build Coastguard Worker sort.Strings(annotations) 236*9e94795aSAndroid Build Coastguard Worker 237*9e94795aSAndroid Build Coastguard Worker tName := e.Target().Name() 238*9e94795aSAndroid Build Coastguard Worker dName := e.Dependency().Name() 239*9e94795aSAndroid Build Coastguard Worker 240*9e94795aSAndroid Build Coastguard Worker if ctx.graphViz { 241*9e94795aSAndroid Build Coastguard Worker // ... one edge per line labelled with \\n-separated annotations. 242*9e94795aSAndroid Build Coastguard Worker tNode := nodes[tName] 243*9e94795aSAndroid Build Coastguard Worker dNode := nodes[dName] 244*9e94795aSAndroid Build Coastguard Worker fmt.Fprintf(stdout, "\t%s -> %s [label=\"%s\"];\n", dNode, tNode, strings.Join(annotations, "\\n")) 245*9e94795aSAndroid Build Coastguard Worker } else { 246*9e94795aSAndroid Build Coastguard Worker // ... one edge per line with annotations in a colon-separated tuple. 247*9e94795aSAndroid Build Coastguard Worker fmt.Fprintf(stdout, "%s %s %s\n", targetOut(e.Target(), ":"), targetOut(e.Dependency(), ":"), strings.Join(annotations, ":")) 248*9e94795aSAndroid Build Coastguard Worker } 249*9e94795aSAndroid Build Coastguard Worker } 250*9e94795aSAndroid Build Coastguard Worker 251*9e94795aSAndroid Build Coastguard Worker // If graphViz output, rank the root nodes together, and complete the directed graph. 252*9e94795aSAndroid Build Coastguard Worker if ctx.graphViz { 253*9e94795aSAndroid Build Coastguard Worker fmt.Fprintf(stdout, "\t{rank=same;") 254*9e94795aSAndroid Build Coastguard Worker for _, f := range files { 255*9e94795aSAndroid Build Coastguard Worker fName := f 256*9e94795aSAndroid Build Coastguard Worker if !strings.HasSuffix(fName, ".meta_lic") { 257*9e94795aSAndroid Build Coastguard Worker fName += ".meta_lic" 258*9e94795aSAndroid Build Coastguard Worker } 259*9e94795aSAndroid Build Coastguard Worker if fNode, ok := nodes[fName]; ok { 260*9e94795aSAndroid Build Coastguard Worker fmt.Fprintf(stdout, " %s", fNode) 261*9e94795aSAndroid Build Coastguard Worker } 262*9e94795aSAndroid Build Coastguard Worker } 263*9e94795aSAndroid Build Coastguard Worker fmt.Fprintf(stdout, "}\n}\n") 264*9e94795aSAndroid Build Coastguard Worker } 265*9e94795aSAndroid Build Coastguard Worker return nil 266*9e94795aSAndroid Build Coastguard Worker} 267