diff options
author | Treehugger Robot <treehugger-gerrit@google.com> | 2021-12-07 03:48:59 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2021-12-07 03:48:59 +0000 |
commit | 195ace0fa6c4197f8585fc11624a8259428ac879 (patch) | |
tree | 802d2681ac99622ce8c978acb1b154758c2ba424 | |
parent | a1a1606184450b497cce459c9438dd62254a8d31 (diff) | |
parent | 203bbf3254c2dfcc7b1e4826c7422958238dc1d7 (diff) | |
download | build-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.bp | 15 | ||||
-rw-r--r-- | tools/compliance/cmd/checkshare.go | 114 | ||||
-rw-r--r-- | tools/compliance/cmd/checkshare_test.go | 299 | ||||
-rw-r--r-- | tools/compliance/cmd/listshare.go | 124 | ||||
-rw-r--r-- | tools/compliance/cmd/listshare_test.go | 405 | ||||
-rw-r--r-- | tools/compliance/doc.go | 77 |
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 |