aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTreehugger Robot <treehugger-gerrit@google.com>2021-12-07 03:48:59 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2021-12-07 03:48:59 +0000
commit195ace0fa6c4197f8585fc11624a8259428ac879 (patch)
tree802d2681ac99622ce8c978acb1b154758c2ba424
parenta1a1606184450b497cce459c9438dd62254a8d31 (diff)
parent203bbf3254c2dfcc7b1e4826c7422958238dc1d7 (diff)
downloadbuild-195ace0fa6c4197f8585fc11624a8259428ac879.tar.gz
Merge changes I5d48eaba,I4ff3f988 am: 203bbf3254
Original change: https://android-review.googlesource.com/c/platform/build/+/1870079 Change-Id: Idaf766bbdb25fa297f742764235a26bd7fe1bd65
-rw-r--r--tools/compliance/Android.bp15
-rw-r--r--tools/compliance/cmd/checkshare.go114
-rw-r--r--tools/compliance/cmd/checkshare_test.go299
-rw-r--r--tools/compliance/cmd/listshare.go124
-rw-r--r--tools/compliance/cmd/listshare_test.go405
-rw-r--r--tools/compliance/doc.go77
6 files changed, 1034 insertions, 0 deletions
diff --git a/tools/compliance/Android.bp b/tools/compliance/Android.bp
index d671f45f97..afb3080d3d 100644
--- a/tools/compliance/Android.bp
+++ b/tools/compliance/Android.bp
@@ -18,6 +18,20 @@ package {
}
blueprint_go_binary {
+ name: "checkshare",
+ srcs: ["cmd/checkshare.go"],
+ deps: ["compliance-module"],
+ testSrcs: ["cmd/checkshare_test.go"],
+}
+
+blueprint_go_binary {
+ name: "listshare",
+ srcs: ["cmd/listshare.go"],
+ deps: ["compliance-module"],
+ testSrcs: ["cmd/listshare_test.go"],
+}
+
+blueprint_go_binary {
name: "dumpgraph",
srcs: ["cmd/dumpgraph.go"],
deps: ["compliance-module"],
@@ -37,6 +51,7 @@ bootstrap_go_package {
"actionset.go",
"condition.go",
"conditionset.go",
+ "doc.go",
"graph.go",
"policy/policy.go",
"policy/resolve.go",
diff --git a/tools/compliance/cmd/checkshare.go b/tools/compliance/cmd/checkshare.go
new file mode 100644
index 0000000000..efac8dc661
--- /dev/null
+++ b/tools/compliance/cmd/checkshare.go
@@ -0,0 +1,114 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "compliance"
+ "flag"
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+ "sort"
+)
+
+func init() {
+ flag.Usage = func() {
+ fmt.Fprintf(os.Stderr, `Usage: %s file.meta_lic {file.meta_lic...}
+
+Reports on stderr any targets where policy says that the source both
+must and must not be shared. The error report indicates the target, the
+license condition with origin that has a source privacy policy, and the
+license condition with origin that has a source sharing policy.
+
+Any given target may appear multiple times with different combinations
+of conflicting license conditions.
+
+If all the source code that policy says must be shared may be shared,
+outputs "PASS" to stdout and exits with status 0.
+
+If policy says any source must both be shared and not be shared,
+outputs "FAIL" to stdout and exits with status 1.
+`, filepath.Base(os.Args[0]))
+ }
+}
+
+var (
+ failConflicts = fmt.Errorf("conflicts")
+ failNoneRequested = fmt.Errorf("\nNo metadata files requested")
+ failNoLicenses = fmt.Errorf("No licenses")
+)
+
+
+// byError orders conflicts by error string
+type byError []compliance.SourceSharePrivacyConflict
+
+func (l byError) Len() int { return len(l) }
+func (l byError) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
+func (l byError) Less(i, j int) bool { return l[i].Error() < l[j].Error() }
+
+func main() {
+ flag.Parse()
+
+ // Must specify at least one root target.
+ if flag.NArg() == 0 {
+ flag.Usage()
+ os.Exit(2)
+ }
+
+ err := checkShare(os.Stdout, os.Stderr, flag.Args()...)
+ if err != nil {
+ if err != failConflicts {
+ if err == failNoneRequested {
+ flag.Usage()
+ }
+ fmt.Fprintf(os.Stderr, "%s\n", err.Error())
+ }
+ os.Exit(1)
+ }
+ os.Exit(0)
+}
+
+// checkShare implements the checkshare utility.
+func checkShare(stdout, stderr io.Writer, files ...string) error {
+
+ if len(files) < 1 {
+ return failNoneRequested
+ }
+
+ // Read the license graph from the license metadata files (*.meta_lic).
+ licenseGraph, err := compliance.ReadLicenseGraph(os.DirFS("."), stderr, files)
+ if err != nil {
+ return fmt.Errorf("Unable to read license metadata file(s) %q: %w\n", files, err)
+ }
+ if licenseGraph == nil {
+ return failNoLicenses
+ }
+
+ // Apply policy to find conflicts and report them to stderr lexicographically ordered.
+ conflicts := compliance.ConflictingSharedPrivateSource(licenseGraph)
+ sort.Sort(byError(conflicts))
+ for _, conflict := range conflicts {
+ fmt.Fprintln(stderr, conflict.Error())
+ }
+
+ // Indicate pass or fail on stdout.
+ if len(conflicts) > 0 {
+ fmt.Fprintln(stdout, "FAIL")
+ return failConflicts
+ }
+ fmt.Fprintln(stdout, "PASS")
+ return nil
+}
diff --git a/tools/compliance/cmd/checkshare_test.go b/tools/compliance/cmd/checkshare_test.go
new file mode 100644
index 0000000000..8ea7748b90
--- /dev/null
+++ b/tools/compliance/cmd/checkshare_test.go
@@ -0,0 +1,299 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "bytes"
+ "fmt"
+ "strings"
+ "testing"
+)
+
+type outcome struct {
+ target string
+ privacyOrigin string
+ privacyCondition string
+ shareOrigin string
+ shareCondition string
+}
+
+func (o *outcome) String() string {
+ return fmt.Sprintf("%s %s from %s and must share from %s %s",
+ o.target, o.privacyCondition, o.privacyOrigin, o.shareCondition, o.shareOrigin)
+}
+
+type outcomeList []*outcome
+
+func (ol outcomeList) String() string {
+ result := ""
+ for _, o := range ol {
+ result = result + o.String() + "\n"
+ }
+ return result
+}
+
+func Test(t *testing.T) {
+ tests := []struct {
+ condition string
+ name string
+ roots []string
+ expectedStdout string
+ expectedOutcomes outcomeList
+ }{
+ {
+ condition: "firstparty",
+ name: "apex",
+ roots: []string{"highest.apex.meta_lic"},
+ expectedStdout: "PASS",
+ },
+ {
+ condition: "firstparty",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedStdout: "PASS",
+ },
+ {
+ condition: "firstparty",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedStdout: "PASS",
+ },
+ {
+ condition: "firstparty",
+ name: "binary",
+ roots: []string{"bin/bin2.meta_lic"},
+ expectedStdout: "PASS",
+ },
+ {
+ condition: "firstparty",
+ name: "library",
+ roots: []string{"lib/libd.so.meta_lic"},
+ expectedStdout: "PASS",
+ },
+ {
+ condition: "notice",
+ name: "apex",
+ roots: []string{"highest.apex.meta_lic"},
+ expectedStdout: "PASS",
+ },
+ {
+ condition: "notice",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedStdout: "PASS",
+ },
+ {
+ condition: "notice",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedStdout: "PASS",
+ },
+ {
+ condition: "notice",
+ name: "binary",
+ roots: []string{"bin/bin2.meta_lic"},
+ expectedStdout: "PASS",
+ },
+ {
+ condition: "notice",
+ name: "library",
+ roots: []string{"lib/libd.so.meta_lic"},
+ expectedStdout: "PASS",
+ },
+ {
+ condition: "reciprocal",
+ name: "apex",
+ roots: []string{"highest.apex.meta_lic"},
+ expectedStdout: "PASS",
+ },
+ {
+ condition: "reciprocal",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedStdout: "PASS",
+ },
+ {
+ condition: "reciprocal",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedStdout: "PASS",
+ },
+ {
+ condition: "reciprocal",
+ name: "binary",
+ roots: []string{"bin/bin2.meta_lic"},
+ expectedStdout: "PASS",
+ },
+ {
+ condition: "reciprocal",
+ name: "library",
+ roots: []string{"lib/libd.so.meta_lic"},
+ expectedStdout: "PASS",
+ },
+ {
+ condition: "restricted",
+ name: "apex",
+ roots: []string{"highest.apex.meta_lic"},
+ expectedStdout: "PASS",
+ },
+ {
+ condition: "restricted",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedStdout: "PASS",
+ },
+ {
+ condition: "restricted",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedStdout: "PASS",
+ },
+ {
+ condition: "restricted",
+ name: "binary",
+ roots: []string{"bin/bin2.meta_lic"},
+ expectedStdout: "PASS",
+ },
+ {
+ condition: "restricted",
+ name: "library",
+ roots: []string{"lib/libd.so.meta_lic"},
+ expectedStdout: "PASS",
+ },
+ {
+ condition: "proprietary",
+ name: "apex",
+ roots: []string{"highest.apex.meta_lic"},
+ expectedStdout: "FAIL",
+ expectedOutcomes: outcomeList{
+ &outcome{
+ target: "testdata/proprietary/bin/bin2.meta_lic",
+ privacyOrigin: "testdata/proprietary/bin/bin2.meta_lic",
+ privacyCondition: "proprietary",
+ shareOrigin: "testdata/proprietary/lib/libb.so.meta_lic",
+ shareCondition: "restricted",
+ },
+ },
+ },
+ {
+ condition: "proprietary",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedStdout: "FAIL",
+ expectedOutcomes: outcomeList{
+ &outcome{
+ target: "testdata/proprietary/bin/bin2.meta_lic",
+ privacyOrigin: "testdata/proprietary/bin/bin2.meta_lic",
+ privacyCondition: "proprietary",
+ shareOrigin: "testdata/proprietary/lib/libb.so.meta_lic",
+ shareCondition: "restricted",
+ },
+ },
+ },
+ {
+ condition: "proprietary",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedStdout: "FAIL",
+ expectedOutcomes: outcomeList{
+ &outcome{
+ target: "testdata/proprietary/lib/liba.so.meta_lic",
+ privacyOrigin: "testdata/proprietary/lib/liba.so.meta_lic",
+ privacyCondition: "proprietary",
+ shareOrigin: "testdata/proprietary/lib/libb.so.meta_lic",
+ shareCondition: "restricted",
+ },
+ },
+ },
+ {
+ condition: "proprietary",
+ name: "binary",
+ roots: []string{"bin/bin2.meta_lic", "lib/libb.so.meta_lic"},
+ expectedStdout: "FAIL",
+ expectedOutcomes: outcomeList{
+ &outcome{
+ target: "testdata/proprietary/bin/bin2.meta_lic",
+ privacyOrigin: "testdata/proprietary/bin/bin2.meta_lic",
+ privacyCondition: "proprietary",
+ shareOrigin: "testdata/proprietary/lib/libb.so.meta_lic",
+ shareCondition: "restricted",
+ },
+ },
+ },
+ {
+ condition: "proprietary",
+ name: "library",
+ roots: []string{"lib/libd.so.meta_lic"},
+ expectedStdout: "PASS",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.condition+" "+tt.name, func(t *testing.T) {
+ stdout := &bytes.Buffer{}
+ stderr := &bytes.Buffer{}
+
+ rootFiles := make([]string, 0, len(tt.roots))
+ for _, r := range tt.roots {
+ rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
+ }
+ err := checkShare(stdout, stderr, rootFiles...)
+ if err != nil && err != failConflicts {
+ t.Fatalf("checkshare: error = %v, stderr = %v", err, stderr)
+ return
+ }
+ var actualStdout string
+ for _, s := range strings.Split(stdout.String(), "\n") {
+ ts := strings.TrimLeft(s, " \t")
+ if len(ts) < 1 {
+ continue
+ }
+ if 0 < len(actualStdout) {
+ t.Errorf("checkshare: unexpected multiple output lines %q, want %q", actualStdout+"\n"+ts, tt.expectedStdout)
+ }
+ actualStdout = ts
+ }
+ if actualStdout != tt.expectedStdout {
+ t.Errorf("checkshare: unexpected stdout %q, want %q", actualStdout, tt.expectedStdout)
+ }
+ errList := strings.Split(stderr.String(), "\n")
+ actualOutcomes := make(outcomeList, 0, len(errList))
+ for _, cstring := range errList {
+ ts := strings.TrimLeft(cstring, " \t")
+ if len(ts) < 1 {
+ continue
+ }
+ cFields := strings.Split(ts, " ")
+ actualOutcomes = append(actualOutcomes, &outcome{
+ target: cFields[0],
+ privacyOrigin: cFields[3],
+ privacyCondition: cFields[1],
+ shareOrigin: cFields[9],
+ shareCondition: cFields[8],
+ })
+ }
+ if len(actualOutcomes) != len(tt.expectedOutcomes) {
+ t.Errorf("checkshare: unexpected got %d outcomes %s, want %d outcomes %s",
+ len(actualOutcomes), actualOutcomes, len(tt.expectedOutcomes), tt.expectedOutcomes)
+ return
+ }
+ for i := range actualOutcomes {
+ if actualOutcomes[i].String() != tt.expectedOutcomes[i].String() {
+ t.Errorf("checkshare: unexpected outcome #%d, got %q, want %q",
+ i+1, actualOutcomes[i], tt.expectedOutcomes[i])
+ }
+ }
+ })
+ }
+}
diff --git a/tools/compliance/cmd/listshare.go b/tools/compliance/cmd/listshare.go
new file mode 100644
index 0000000000..bba2308300
--- /dev/null
+++ b/tools/compliance/cmd/listshare.go
@@ -0,0 +1,124 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "compliance"
+ "flag"
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+ "sort"
+)
+
+func init() {
+ flag.Usage = func() {
+ fmt.Fprintf(os.Stderr, `Usage: %s file.meta_lic {file.meta_lic...}
+
+Outputs a csv file with 1 project per line in the first field followed
+by target:condition pairs describing why the project must be shared.
+
+Each target is the path to a generated license metadata file for a
+Soong module or Make target, and the license condition is either
+restricted (e.g. GPL) or reciprocal (e.g. MPL).
+`, filepath.Base(os.Args[0]))
+ }
+}
+
+var (
+ failNoneRequested = fmt.Errorf("\nNo license metadata files requested")
+ failNoLicenses = fmt.Errorf("No licenses found")
+)
+
+func main() {
+ flag.Parse()
+
+ // Must specify at least one root target.
+ if flag.NArg() == 0 {
+ flag.Usage()
+ os.Exit(2)
+ }
+
+ err := listShare(os.Stdout, os.Stderr, flag.Args()...)
+ if err != nil {
+ if err == failNoneRequested {
+ flag.Usage()
+ }
+ fmt.Fprintf(os.Stderr, "%s\n", err.Error())
+ os.Exit(1)
+ }
+ os.Exit(0)
+}
+
+// listShare implements the listshare utility.
+func listShare(stdout, stderr io.Writer, files ...string) error {
+ // Must be at least one root file.
+ if len(files) < 1 {
+ return failNoneRequested
+ }
+
+ // Read the license graph from the license metadata files (*.meta_lic).
+ licenseGraph, err := compliance.ReadLicenseGraph(os.DirFS("."), stderr, files)
+ if err != nil {
+ return fmt.Errorf("Unable to read license metadata file(s) %q: %v\n", files, err)
+ }
+ if licenseGraph == nil {
+ return failNoLicenses
+ }
+
+ // shareSource contains all source-sharing resolutions.
+ shareSource := compliance.ResolveSourceSharing(licenseGraph)
+
+ // Group the resolutions by project.
+ presolution := make(map[string]*compliance.LicenseConditionSet)
+ for _, target := range shareSource.AttachesTo() {
+ rl := shareSource.Resolutions(target)
+ sort.Sort(rl)
+ for _, r := range rl {
+ for _, p := range r.ActsOn().Projects() {
+ if _, ok := presolution[p]; !ok {
+ presolution[p] = r.Resolves().Copy()
+ continue
+ }
+ presolution[p].AddSet(r.Resolves())
+ }
+ }
+ }
+
+ // Sort the projects for repeatability/stability.
+ projects := make([]string, 0, len(presolution))
+ for p := range presolution {
+ projects = append(projects, p)
+ }
+ sort.Strings(projects)
+
+ // Output the sorted projects and the source-sharing license conditions that each project resolves.
+ for _, p := range projects {
+ fmt.Fprintf(stdout, "%s", p)
+
+ // Sort the conditions for repeatability/stability.
+ conditions := presolution[p].AsList()
+ sort.Sort(conditions)
+
+ // Output the sorted origin:condition pairs.
+ for _, lc := range conditions {
+ fmt.Fprintf(stdout, ",%s:%s", lc.Origin().Name(), lc.Name())
+ }
+ fmt.Fprintf(stdout, "\n")
+ }
+
+ return nil
+}
diff --git a/tools/compliance/cmd/listshare_test.go b/tools/compliance/cmd/listshare_test.go
new file mode 100644
index 0000000000..b4847e39c7
--- /dev/null
+++ b/tools/compliance/cmd/listshare_test.go
@@ -0,0 +1,405 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "bytes"
+ "strings"
+ "testing"
+)
+
+func Test(t *testing.T) {
+ type projectShare struct {
+ project string
+ conditions []string
+ }
+ tests := []struct {
+ condition string
+ name string
+ roots []string
+ expectedOut []projectShare
+ }{
+ {
+ condition: "firstparty",
+ name: "apex",
+ roots: []string{"highest.apex.meta_lic"},
+ expectedOut: []projectShare{},
+ },
+ {
+ condition: "firstparty",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []projectShare{},
+ },
+ {
+ condition: "firstparty",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []projectShare{},
+ },
+ {
+ condition: "firstparty",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ expectedOut: []projectShare{},
+ },
+ {
+ condition: "firstparty",
+ name: "library",
+ roots: []string{"lib/libd.so.meta_lic"},
+ expectedOut: []projectShare{},
+ },
+ {
+ condition: "notice",
+ name: "apex",
+ roots: []string{"highest.apex.meta_lic"},
+ expectedOut: []projectShare{},
+ },
+ {
+ condition: "notice",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []projectShare{},
+ },
+ {
+ condition: "notice",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []projectShare{},
+ },
+ {
+ condition: "notice",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ expectedOut: []projectShare{},
+ },
+ {
+ condition: "notice",
+ name: "library",
+ roots: []string{"lib/libd.so.meta_lic"},
+ expectedOut: []projectShare{},
+ },
+ {
+ condition: "reciprocal",
+ name: "apex",
+ roots: []string{"highest.apex.meta_lic"},
+ expectedOut: []projectShare{
+ {
+ project: "device/library",
+ conditions: []string{"lib/liba.so.meta_lic:reciprocal"},
+ },
+ {
+ project: "static/library",
+ conditions: []string{
+ "lib/libc.a.meta_lic:reciprocal",
+ },
+ },
+ },
+ },
+ {
+ condition: "reciprocal",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []projectShare{
+ {
+ project: "device/library",
+ conditions: []string{"lib/liba.so.meta_lic:reciprocal"},
+ },
+ {
+ project: "static/library",
+ conditions: []string{
+ "lib/libc.a.meta_lic:reciprocal",
+ },
+ },
+ },
+ },
+ {
+ condition: "reciprocal",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []projectShare{
+ {
+ project: "device/library",
+ conditions: []string{"lib/liba.so.meta_lic:reciprocal"},
+ },
+ },
+ },
+ {
+ condition: "reciprocal",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ expectedOut: []projectShare{
+ {
+ project: "device/library",
+ conditions: []string{
+ "lib/liba.so.meta_lic:reciprocal",
+ },
+ },
+ {
+ project: "static/library",
+ conditions: []string{
+ "lib/libc.a.meta_lic:reciprocal",
+ },
+ },
+ },
+ },
+ {
+ condition: "reciprocal",
+ name: "library",
+ roots: []string{"lib/libd.so.meta_lic"},
+ expectedOut: []projectShare{},
+ },
+ {
+ condition: "restricted",
+ name: "apex",
+ roots: []string{"highest.apex.meta_lic"},
+ expectedOut: []projectShare{
+ {
+ project: "base/library",
+ conditions: []string{"lib/libb.so.meta_lic:restricted"},
+ },
+ {
+ project: "device/library",
+ conditions: []string{"lib/liba.so.meta_lic:restricted"},
+ },
+ {
+ project: "dynamic/binary",
+ conditions: []string{"lib/libb.so.meta_lic:restricted"},
+ },
+ {
+ project: "highest/apex",
+ conditions: []string{
+ "lib/liba.so.meta_lic:restricted",
+ "lib/libb.so.meta_lic:restricted",
+ },
+ },
+ {
+ project: "static/binary",
+ conditions: []string{
+ "lib/liba.so.meta_lic:restricted",
+ },
+ },
+ {
+ project: "static/library",
+ conditions: []string{
+ "lib/liba.so.meta_lic:restricted",
+ "lib/libc.a.meta_lic:reciprocal",
+ },
+ },
+ },
+ },
+ {
+ condition: "restricted",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []projectShare{
+ {
+ project: "base/library",
+ conditions: []string{"lib/libb.so.meta_lic:restricted"},
+ },
+ {
+ project: "container/zip",
+ conditions: []string{
+ "lib/liba.so.meta_lic:restricted",
+ "lib/libb.so.meta_lic:restricted",
+ },
+ },
+ {
+ project: "device/library",
+ conditions: []string{"lib/liba.so.meta_lic:restricted"},
+ },
+ {
+ project: "dynamic/binary",
+ conditions: []string{"lib/libb.so.meta_lic:restricted"},
+ },
+ {
+ project: "static/binary",
+ conditions: []string{
+ "lib/liba.so.meta_lic:restricted",
+ },
+ },
+ {
+ project: "static/library",
+ conditions: []string{
+ "lib/liba.so.meta_lic:restricted",
+ "lib/libc.a.meta_lic:reciprocal",
+ },
+ },
+ },
+ },
+ {
+ condition: "restricted",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []projectShare{
+ {
+ project: "device/library",
+ conditions: []string{
+ "lib/liba.so.meta_lic:restricted",
+ "lib/libb.so.meta_lic:restricted",
+ },
+ },
+ {
+ project: "distributable/application",
+ conditions: []string{
+ "lib/liba.so.meta_lic:restricted",
+ "lib/libb.so.meta_lic:restricted",
+ },
+ },
+ },
+ },
+ {
+ condition: "restricted",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ expectedOut: []projectShare{
+ {
+ project: "device/library",
+ conditions: []string{
+ "lib/liba.so.meta_lic:restricted",
+ },
+ },
+ {
+ project: "static/binary",
+ conditions: []string{
+ "lib/liba.so.meta_lic:restricted",
+ },
+ },
+ {
+ project: "static/library",
+ conditions: []string{
+ "lib/liba.so.meta_lic:restricted",
+ "lib/libc.a.meta_lic:reciprocal",
+ },
+ },
+ },
+ },
+ {
+ condition: "restricted",
+ name: "library",
+ roots: []string{"lib/libd.so.meta_lic"},
+ expectedOut: []projectShare{},
+ },
+ {
+ condition: "proprietary",
+ name: "apex",
+ roots: []string{"highest.apex.meta_lic"},
+ expectedOut: []projectShare{
+ {
+ project: "base/library",
+ conditions: []string{"lib/libb.so.meta_lic:restricted"},
+ },
+ {
+ project: "dynamic/binary",
+ conditions: []string{"lib/libb.so.meta_lic:restricted"},
+ },
+ {
+ project: "highest/apex",
+ conditions: []string{"lib/libb.so.meta_lic:restricted"},
+ },
+ },
+ },
+ {
+ condition: "proprietary",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []projectShare{
+ {
+ project: "base/library",
+ conditions: []string{"lib/libb.so.meta_lic:restricted"},
+ },
+ {
+ project: "container/zip",
+ conditions: []string{"lib/libb.so.meta_lic:restricted"},
+ },
+ {
+ project: "dynamic/binary",
+ conditions: []string{"lib/libb.so.meta_lic:restricted"},
+ },
+ },
+ },
+ {
+ condition: "proprietary",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []projectShare{
+ {
+ project: "device/library",
+ conditions: []string{"lib/libb.so.meta_lic:restricted"},
+ },
+ {
+ project: "distributable/application",
+ conditions: []string{"lib/libb.so.meta_lic:restricted"},
+ },
+ },
+ },
+ {
+ condition: "proprietary",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ expectedOut: []projectShare{},
+ },
+ {
+ condition: "proprietary",
+ name: "library",
+ roots: []string{"lib/libd.so.meta_lic"},
+ expectedOut: []projectShare{},
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.condition+" "+tt.name, func(t *testing.T) {
+ expectedOut := &bytes.Buffer{}
+ for _, p := range tt.expectedOut {
+ expectedOut.WriteString(p.project)
+ for _, lc := range p.conditions {
+ expectedOut.WriteString(",")
+ expectedOut.WriteString("testdata/")
+ expectedOut.WriteString(tt.condition)
+ expectedOut.WriteString("/")
+ expectedOut.WriteString(lc)
+ }
+ expectedOut.WriteString("\n")
+ }
+
+ stdout := &bytes.Buffer{}
+ stderr := &bytes.Buffer{}
+
+ rootFiles := make([]string, 0, len(tt.roots))
+ for _, r := range tt.roots {
+ rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
+ }
+ err := listShare(stdout, stderr, rootFiles...)
+ if err != nil {
+ t.Fatalf("listshare: error = %v, stderr = %v", err, stderr)
+ return
+ }
+ if stderr.Len() > 0 {
+ t.Errorf("listshare: gotStderr = %v, want none", stderr)
+ }
+ out := stdout.String()
+ expected := expectedOut.String()
+ if out != expected {
+ outList := strings.Split(out, "\n")
+ expectedList := strings.Split(expected, "\n")
+ startLine := 0
+ for len(outList) > startLine && len(expectedList) > startLine && outList[startLine] == expectedList[startLine] {
+ startLine++
+ }
+ t.Errorf("listshare: gotStdout = %v, want %v, somewhere near line %d Stdout = %v, want %v",
+ out, expected, startLine+1, outList[startLine], expectedList[startLine])
+ }
+ })
+ }
+}
diff --git a/tools/compliance/doc.go b/tools/compliance/doc.go
new file mode 100644
index 0000000000..a47c1cfef0
--- /dev/null
+++ b/tools/compliance/doc.go
@@ -0,0 +1,77 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+/*
+
+Package compliance provides an approved means for reading, consuming, and
+analyzing license metadata graphs.
+
+Assuming the license metadata and dependencies are fully and accurately
+recorded in the build system, any discrepancy between the official policy for
+open source license compliance and this code is a bug in this code.
+
+A few principal types to understand are LicenseGraph, LicenseCondition, and
+ResolutionSet.
+
+LicenseGraph
+------------
+
+A LicenseGraph is an immutable graph of the targets and dependencies reachable
+from a specific set of root targets. In general, the root targets will be the
+artifacts in a release or distribution. While conceptually immutable, parts of
+the graph may be loaded or evaluated lazily.
+
+LicenseCondition
+----------------
+
+A LicenseCondition is an immutable tuple pairing a condition name with an
+originating target. e.g. Per current policy, a static library licensed under an
+MIT license would pair a "notice" condition with the static library target, and
+a dynamic license licensed under GPL would pair a "restricted" condition with
+the dynamic library target.
+
+ResolutionSet
+-------------
+
+A ResolutionSet is an immutable set of `AttachesTo`, `ActsOn`, `Resolves`
+tuples describing how license conditions apply to targets.
+
+`AttachesTo` is the trigger for acting. Distribution of the target invokes
+the policy.
+
+`ActsOn` is the target to share, give notice for, hide etc.
+
+`Resolves` is the license condition that the action resolves.
+
+Remember: Each license condition pairs a condition name with an originating
+target so each resolution in a ResolutionSet has two targets it applies to and
+one target from which it originates, all of which may be the same target.
+
+For most condition types, `ActsOn` and `Resolves.Origin` will be the same
+target. For example, a notice condition policy means attribution or notice must
+be given for the target where the condition originates. Likewise, a proprietary
+condition policy means the privacy of the target where the condition originates
+must be respected. i.e. The thing acted on is the origin.
+
+Restricted conditions are different. The infectious nature of restricted often
+means sharing code that is not the target where the restricted condition
+originates. Linking an MIT library to a GPL library implies a policy to share
+the MIT library despite the MIT license having no source sharing requirement.
+
+In this case, one or more resolution tuples will have the MIT license module in
+`ActsOn` and the restricted condition originating at the GPL library module in
+`Resolves`. These tuples will `AttachTo` every target that depends on the GPL
+library because shipping any of those targets trigger the policy to share the
+code.
+*/
+package compliance