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