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