blob: a2ff5941cccf550e56a50f116c315a3fac75a3bf [file] [log] [blame]
Bob Badourc8178452022-01-31 13:05:53 -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 "flag"
19 "fmt"
20 "io"
21 "os"
22 "path/filepath"
23 "sort"
24 "strings"
25
26 "android/soong/tools/compliance"
27)
28
29var (
30 sources = newMultiString("rtrace", "Projects or metadata files to trace back from. (required; multiple allowed)")
31 stripPrefix = flag.String("strip_prefix", "", "Prefix to remove from paths. i.e. path to root")
32
33 failNoneRequested = fmt.Errorf("\nNo license metadata files requested")
34 failNoSources = fmt.Errorf("\nNo projects or metadata files to trace back from")
35 failNoLicenses = fmt.Errorf("No licenses found")
36)
37
38type context struct {
39 sources []string
40 stripPrefix string
41}
42
43func init() {
44 flag.Usage = func() {
45 fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...}
46
47Outputs a space-separated Target ActsOn Origin Condition tuple for each
48resolution in the graph. When -dot flag given, outputs nodes and edges
49in graphviz directed graph format.
50
51If one or more '-c condition' conditions are given, outputs the
52resolution for the union of the conditions. Otherwise, outputs the
53resolution for all conditions.
54
55In plain text mode, when '-label_conditions' is requested, the Target
56and Origin have colon-separated license conditions appended:
57i.e. target:condition1:condition2 etc.
58
59Options:
60`, filepath.Base(os.Args[0]))
61 flag.PrintDefaults()
62 }
63}
64
65// newMultiString creates a flag that allows multiple values in an array.
66func newMultiString(name, usage string) *multiString {
67 var f multiString
68 flag.Var(&f, name, usage)
69 return &f
70}
71
72// multiString implements the flag `Value` interface for multiple strings.
73type multiString []string
74
75func (ms *multiString) String() string { return strings.Join(*ms, ", ") }
76func (ms *multiString) Set(s string) error { *ms = append(*ms, s); return nil }
77
78func main() {
79 flag.Parse()
80
81 // Must specify at least one root target.
82 if flag.NArg() == 0 {
83 flag.Usage()
84 os.Exit(2)
85 }
86
87 if len(*sources) == 0 {
88 flag.Usage()
89 fmt.Fprintf(os.Stderr, "\nMust specify at least 1 --rtrace source.\n")
90 os.Exit(2)
91 }
92
93 ctx := &context{
94 sources: *sources,
95 stripPrefix: *stripPrefix,
96 }
97 _, err := traceRestricted(ctx, os.Stdout, os.Stderr, flag.Args()...)
98 if err != nil {
99 if err == failNoneRequested {
100 flag.Usage()
101 }
102 fmt.Fprintf(os.Stderr, "%s\n", err.Error())
103 os.Exit(1)
104 }
105 os.Exit(0)
106}
107
108// traceRestricted implements the rtrace utility.
109func traceRestricted(ctx *context, stdout, stderr io.Writer, files ...string) (*compliance.LicenseGraph, error) {
110 if len(files) < 1 {
111 return nil, failNoneRequested
112 }
113
114 if len(ctx.sources) < 1 {
115 return nil, failNoSources
116 }
117
118 // Read the license graph from the license metadata files (*.meta_lic).
119 licenseGraph, err := compliance.ReadLicenseGraph(os.DirFS("."), stderr, files)
120 if err != nil {
121 return nil, fmt.Errorf("Unable to read license metadata file(s) %q: %v\n", files, err)
122 }
123 if licenseGraph == nil {
124 return nil, failNoLicenses
125 }
126
127 sourceMap := make(map[string]struct{})
128 for _, source := range ctx.sources {
129 sourceMap[source] = struct{}{}
130 }
131
132 compliance.TraceTopDownConditions(licenseGraph, func(tn *compliance.TargetNode) compliance.LicenseConditionSet {
133 if _, isPresent := sourceMap[tn.Name()]; isPresent {
134 return compliance.ImpliesRestricted
135 }
136 for _, project := range tn.Projects() {
137 if _, isPresent := sourceMap[project]; isPresent {
138 return compliance.ImpliesRestricted
139 }
140 }
141 return compliance.NewLicenseConditionSet()
142 })
143
144 // targetOut calculates the string to output for `target` adding `sep`-separated conditions as needed.
145 targetOut := func(target *compliance.TargetNode, sep string) string {
146 tOut := strings.TrimPrefix(target.Name(), ctx.stripPrefix)
147 return tOut
148 }
149
150 // outputResolution prints a resolution in the requested format to `stdout`, where one can read
151 // a resolution as `tname` resolves conditions named in `cnames`.
152 // `tname` is the name of the target the resolution traces back to.
153 // `cnames` is the list of conditions to resolve.
154 outputResolution := func(tname string, cnames []string) {
155 // ... one edge per line with names in a colon-separated tuple.
156 fmt.Fprintf(stdout, "%s %s\n", tname, strings.Join(cnames, ":"))
157 }
158
159 // Sort the resolutions by targetname for repeatability/stability.
160 actions := compliance.WalkResolutionsForCondition(licenseGraph, compliance.ImpliesShared).AllActions()
161 targets := make(compliance.TargetNodeList, 0, len(actions))
162 for tn := range actions {
163 if tn.LicenseConditions().MatchesAnySet(compliance.ImpliesRestricted) {
164 targets = append(targets, tn)
165 }
166 }
167 sort.Sort(targets)
168
169 // Output the sorted targets.
170 for _, target := range targets {
171 var tname string
172 tname = targetOut(target, ":")
173
174 // cnames accumulates the list of condition names originating at a single origin that apply to `target`.
175 cnames := target.LicenseConditions().Names()
176
177 // Output 1 line for each attachesTo+actsOn combination.
178 outputResolution(tname, cnames)
179 }
180 fmt.Fprintf(stdout, "restricted conditions trace to %d targets\n", len(targets))
181 if 0 == len(targets) {
182 fmt.Fprintln(stdout, " (check for typos in project names or metadata files)")
183 }
184 return licenseGraph, nil
185}