xref: /aosp_15_r20/build/make/tools/compliance/cmd/listshare/listshare.go (revision 9e94795a3d4ef5c1d47486f9a02bb378756cea8a)
1// 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	"bytes"
19	"flag"
20	"fmt"
21	"io"
22	"io/fs"
23	"os"
24	"path/filepath"
25	"sort"
26	"strings"
27
28	"android/soong/response"
29	"android/soong/tools/compliance"
30)
31
32var (
33	failNoneRequested = fmt.Errorf("\nNo license metadata files requested")
34	failNoLicenses    = fmt.Errorf("No licenses found")
35)
36
37func main() {
38	var expandedArgs []string
39	for _, arg := range os.Args[1:] {
40		if strings.HasPrefix(arg, "@") {
41			f, err := os.Open(strings.TrimPrefix(arg, "@"))
42			if err != nil {
43				fmt.Fprintln(os.Stderr, err.Error())
44				os.Exit(1)
45			}
46
47			respArgs, err := response.ReadRspFile(f)
48			f.Close()
49			if err != nil {
50				fmt.Fprintln(os.Stderr, err.Error())
51				os.Exit(1)
52			}
53			expandedArgs = append(expandedArgs, respArgs...)
54		} else {
55			expandedArgs = append(expandedArgs, arg)
56		}
57	}
58
59	flags := flag.NewFlagSet("flags", flag.ExitOnError)
60
61	flags.Usage = func() {
62		fmt.Fprintf(os.Stderr, `Usage: %s {-o outfile} file.meta_lic {file.meta_lic...}
63
64Outputs a csv file with 1 project per line in the first field followed
65by target:condition pairs describing why the project must be shared.
66
67Each target is the path to a generated license metadata file for a
68Soong module or Make target, and the license condition is either
69restricted (e.g. GPL) or reciprocal (e.g. MPL).
70`, filepath.Base(os.Args[0]))
71	}
72
73	outputFile := flags.String("o", "-", "Where to write the list of projects to share. (default stdout)")
74
75	flags.Parse(expandedArgs)
76
77	// Must specify at least one root target.
78	if flags.NArg() == 0 {
79		flags.Usage()
80		os.Exit(2)
81	}
82
83	if len(*outputFile) == 0 {
84		flags.Usage()
85		fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n")
86		os.Exit(2)
87	} else {
88		dir, err := filepath.Abs(filepath.Dir(*outputFile))
89		if err != nil {
90			fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err)
91			os.Exit(1)
92		}
93		fi, err := os.Stat(dir)
94		if err != nil {
95			fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err)
96			os.Exit(1)
97		}
98		if !fi.IsDir() {
99			fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile)
100			os.Exit(1)
101		}
102	}
103
104	var ofile io.Writer
105	ofile = os.Stdout
106	var obuf *bytes.Buffer
107	if *outputFile != "-" {
108		obuf = &bytes.Buffer{}
109		ofile = obuf
110	}
111
112	err := listShare(ofile, os.Stderr, compliance.FS, flags.Args()...)
113	if err != nil {
114		if err == failNoneRequested {
115			flags.Usage()
116		}
117		fmt.Fprintf(os.Stderr, "%s\n", err.Error())
118		os.Exit(1)
119	}
120	if *outputFile != "-" {
121		err := os.WriteFile(*outputFile, obuf.Bytes(), 0666)
122		if err != nil {
123			fmt.Fprintf(os.Stderr, "could not write output to %q from %q: %s\n", *outputFile, os.Getenv("PWD"), err)
124			os.Exit(1)
125		}
126	}
127	os.Exit(0)
128}
129
130// listShare implements the listshare utility.
131func listShare(stdout, stderr io.Writer, rootFS fs.FS, files ...string) error {
132	// Must be at least one root file.
133	if len(files) < 1 {
134		return failNoneRequested
135	}
136
137	// Read the license graph from the license metadata files (*.meta_lic).
138	licenseGraph, err := compliance.ReadLicenseGraph(rootFS, stderr, files)
139	if err != nil {
140		return fmt.Errorf("Unable to read license metadata file(s) %q from %q: %v\n", files, os.Getenv("PWD"), err)
141	}
142	if licenseGraph == nil {
143		return failNoLicenses
144	}
145
146	// shareSource contains all source-sharing resolutions.
147	shareSource := compliance.ResolveSourceSharing(licenseGraph)
148
149	// Group the resolutions by project.
150	presolution := make(map[string]compliance.LicenseConditionSet)
151	for _, target := range shareSource.AttachesTo() {
152		if shareSource.IsPureAggregate(target) && !target.LicenseConditions().MatchesAnySet(compliance.ImpliesShared) {
153			continue
154		}
155		rl := shareSource.Resolutions(target)
156		sort.Sort(rl)
157		for _, r := range rl {
158			for _, p := range r.ActsOn().Projects() {
159				if _, ok := presolution[p]; !ok {
160					presolution[p] = r.Resolves()
161					continue
162				}
163				presolution[p] = presolution[p].Union(r.Resolves())
164			}
165		}
166	}
167
168	// Sort the projects for repeatability/stability.
169	projects := make([]string, 0, len(presolution))
170	for p := range presolution {
171		projects = append(projects, p)
172	}
173	sort.Strings(projects)
174
175	// Output the sorted projects and the source-sharing license conditions that each project resolves.
176	for _, p := range projects {
177		if presolution[p].IsEmpty() {
178			fmt.Fprintf(stdout, "%s\n", p)
179		} else {
180			fmt.Fprintf(stdout, "%s,%s\n", p, strings.Join(presolution[p].Names(), ","))
181		}
182	}
183
184	return nil
185}
186