blob: 3e7e69bd0282879e8837b9fa70d8a652b24e8a39 [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 (
Bob Badour986a8392022-06-03 10:42:27 -070018 "bytes"
Bob Badourc8178452022-01-31 13:05:53 -080019 "flag"
20 "fmt"
21 "io"
Bob Badourc778e4c2022-03-22 13:05:19 -070022 "io/fs"
Bob Badourc8178452022-01-31 13:05:53 -080023 "os"
24 "path/filepath"
25 "sort"
26 "strings"
27
Bob Badour986a8392022-06-03 10:42:27 -070028 "android/soong/response"
Bob Badourc8178452022-01-31 13:05:53 -080029 "android/soong/tools/compliance"
30)
31
32var (
Bob Badourc8178452022-01-31 13:05:53 -080033 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 {
Bob Badour986a8392022-06-03 10:42:27 -070039 sources []string
40 stripPrefix []string
Bob Badour682e1ba2022-02-02 15:15:56 -080041}
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
Bob Badour986a8392022-06-03 10:42:27 -070056// newMultiString creates a flag that allows multiple values in an array.
57func newMultiString(flags *flag.FlagSet, name, usage string) *multiString {
58 var f multiString
59 flags.Var(&f, name, usage)
60 return &f
61}
62
63// multiString implements the flag `Value` interface for multiple strings.
64type multiString []string
65
66func (ms *multiString) String() string { return strings.Join(*ms, ", ") }
67func (ms *multiString) Set(s string) error { *ms = append(*ms, s); return nil }
68
69func main() {
70 var expandedArgs []string
71 for _, arg := range os.Args[1:] {
72 if strings.HasPrefix(arg, "@") {
73 f, err := os.Open(strings.TrimPrefix(arg, "@"))
74 if err != nil {
75 fmt.Fprintln(os.Stderr, err.Error())
76 os.Exit(1)
77 }
78
79 respArgs, err := response.ReadRspFile(f)
80 f.Close()
81 if err != nil {
82 fmt.Fprintln(os.Stderr, err.Error())
83 os.Exit(1)
84 }
85 expandedArgs = append(expandedArgs, respArgs...)
86 } else {
87 expandedArgs = append(expandedArgs, arg)
88 }
89 }
90
91 flags := flag.NewFlagSet("flags", flag.ExitOnError)
92
93 flags.Usage = func() {
Bob Badourc8178452022-01-31 13:05:53 -080094 fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...}
95
Bob Badour5a3e4a42023-06-13 21:02:41 -070096Calculates the source-sharing requirements in reverse starting at the
97-rtrace projects or metadata files that inherited source-sharing and
98working back to the targets where the source-sharing requirmements
99originate.
Bob Badourc8178452022-01-31 13:05:53 -0800100
Bob Badour5a3e4a42023-06-13 21:02:41 -0700101Outputs a space-separated pair where the first field is an originating
102target with one or more restricted conditions and where the second
103field is a colon-separated list of the restricted conditions.
Bob Badourc8178452022-01-31 13:05:53 -0800104
Bob Badour5a3e4a42023-06-13 21:02:41 -0700105Outputs a count of the originating targets, and if the count is zero,
106outputs a warning to check the -rtrace projects and/or filenames.
Bob Badourc8178452022-01-31 13:05:53 -0800107
108Options:
109`, filepath.Base(os.Args[0]))
Bob Badour986a8392022-06-03 10:42:27 -0700110 flags.PrintDefaults()
Bob Badourc8178452022-01-31 13:05:53 -0800111 }
Bob Badourc8178452022-01-31 13:05:53 -0800112
Bob Badour986a8392022-06-03 10:42:27 -0700113 outputFile := flags.String("o", "-", "Where to write the output. (default stdout)")
114 sources := newMultiString(flags, "rtrace", "Projects or metadata files to trace back from. (required; multiple allowed)")
115 stripPrefix := newMultiString(flags, "strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)")
Bob Badourc8178452022-01-31 13:05:53 -0800116
Bob Badour986a8392022-06-03 10:42:27 -0700117 flags.Parse(expandedArgs)
Bob Badourc8178452022-01-31 13:05:53 -0800118
119 // Must specify at least one root target.
Bob Badour986a8392022-06-03 10:42:27 -0700120 if flags.NArg() == 0 {
121 flags.Usage()
Bob Badourc8178452022-01-31 13:05:53 -0800122 os.Exit(2)
123 }
124
125 if len(*sources) == 0 {
Bob Badour986a8392022-06-03 10:42:27 -0700126 flags.Usage()
Bob Badourc8178452022-01-31 13:05:53 -0800127 fmt.Fprintf(os.Stderr, "\nMust specify at least 1 --rtrace source.\n")
128 os.Exit(2)
129 }
130
Bob Badour986a8392022-06-03 10:42:27 -0700131 if len(*outputFile) == 0 {
132 flags.Usage()
133 fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n")
134 os.Exit(2)
135 } else {
136 dir, err := filepath.Abs(filepath.Dir(*outputFile))
137 if err != nil {
138 fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err)
139 os.Exit(1)
140 }
141 fi, err := os.Stat(dir)
142 if err != nil {
143 fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err)
144 os.Exit(1)
145 }
146 if !fi.IsDir() {
147 fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile)
148 os.Exit(1)
149 }
Bob Badourc8178452022-01-31 13:05:53 -0800150 }
Bob Badour986a8392022-06-03 10:42:27 -0700151
152 var ofile io.Writer
153 ofile = os.Stdout
154 var obuf *bytes.Buffer
155 if *outputFile != "-" {
156 obuf = &bytes.Buffer{}
157 ofile = obuf
158 }
159
160 ctx := &context{
161 sources: *sources,
162 stripPrefix: *stripPrefix,
163 }
164 _, err := traceRestricted(ctx, ofile, os.Stderr, compliance.FS, flags.Args()...)
Bob Badourc8178452022-01-31 13:05:53 -0800165 if err != nil {
166 if err == failNoneRequested {
Bob Badour986a8392022-06-03 10:42:27 -0700167 flags.Usage()
Bob Badourc8178452022-01-31 13:05:53 -0800168 }
169 fmt.Fprintf(os.Stderr, "%s\n", err.Error())
170 os.Exit(1)
171 }
Bob Badour986a8392022-06-03 10:42:27 -0700172 if *outputFile != "-" {
173 err := os.WriteFile(*outputFile, obuf.Bytes(), 0666)
174 if err != nil {
175 fmt.Fprintf(os.Stderr, "could not write output to %q from %q: %s\n", *outputFile, os.Getenv("PWD"), err)
176 os.Exit(1)
177 }
178 }
Bob Badourc8178452022-01-31 13:05:53 -0800179 os.Exit(0)
180}
181
182// traceRestricted implements the rtrace utility.
Bob Badourc778e4c2022-03-22 13:05:19 -0700183func traceRestricted(ctx *context, stdout, stderr io.Writer, rootFS fs.FS, files ...string) (*compliance.LicenseGraph, error) {
Bob Badourc8178452022-01-31 13:05:53 -0800184 if len(files) < 1 {
185 return nil, failNoneRequested
186 }
187
188 if len(ctx.sources) < 1 {
189 return nil, failNoSources
190 }
191
192 // Read the license graph from the license metadata files (*.meta_lic).
Bob Badourc778e4c2022-03-22 13:05:19 -0700193 licenseGraph, err := compliance.ReadLicenseGraph(rootFS, stderr, files)
Bob Badourc8178452022-01-31 13:05:53 -0800194 if err != nil {
195 return nil, fmt.Errorf("Unable to read license metadata file(s) %q: %v\n", files, err)
196 }
197 if licenseGraph == nil {
198 return nil, failNoLicenses
199 }
200
201 sourceMap := make(map[string]struct{})
202 for _, source := range ctx.sources {
203 sourceMap[source] = struct{}{}
204 }
205
206 compliance.TraceTopDownConditions(licenseGraph, func(tn *compliance.TargetNode) compliance.LicenseConditionSet {
207 if _, isPresent := sourceMap[tn.Name()]; isPresent {
208 return compliance.ImpliesRestricted
209 }
210 for _, project := range tn.Projects() {
211 if _, isPresent := sourceMap[project]; isPresent {
212 return compliance.ImpliesRestricted
213 }
214 }
215 return compliance.NewLicenseConditionSet()
216 })
217
218 // targetOut calculates the string to output for `target` adding `sep`-separated conditions as needed.
219 targetOut := func(target *compliance.TargetNode, sep string) string {
Bob Badour682e1ba2022-02-02 15:15:56 -0800220 tOut := ctx.strip(target.Name())
Bob Badourc8178452022-01-31 13:05:53 -0800221 return tOut
222 }
223
224 // outputResolution prints a resolution in the requested format to `stdout`, where one can read
225 // a resolution as `tname` resolves conditions named in `cnames`.
226 // `tname` is the name of the target the resolution traces back to.
227 // `cnames` is the list of conditions to resolve.
228 outputResolution := func(tname string, cnames []string) {
229 // ... one edge per line with names in a colon-separated tuple.
230 fmt.Fprintf(stdout, "%s %s\n", tname, strings.Join(cnames, ":"))
231 }
232
233 // Sort the resolutions by targetname for repeatability/stability.
234 actions := compliance.WalkResolutionsForCondition(licenseGraph, compliance.ImpliesShared).AllActions()
235 targets := make(compliance.TargetNodeList, 0, len(actions))
236 for tn := range actions {
237 if tn.LicenseConditions().MatchesAnySet(compliance.ImpliesRestricted) {
238 targets = append(targets, tn)
239 }
240 }
241 sort.Sort(targets)
242
243 // Output the sorted targets.
244 for _, target := range targets {
245 var tname string
246 tname = targetOut(target, ":")
247
248 // cnames accumulates the list of condition names originating at a single origin that apply to `target`.
249 cnames := target.LicenseConditions().Names()
250
251 // Output 1 line for each attachesTo+actsOn combination.
252 outputResolution(tname, cnames)
253 }
254 fmt.Fprintf(stdout, "restricted conditions trace to %d targets\n", len(targets))
255 if 0 == len(targets) {
256 fmt.Fprintln(stdout, " (check for typos in project names or metadata files)")
257 }
258 return licenseGraph, nil
259}