Merge changes I5d48eaba,I4ff3f988

* changes:
  compliance package documentation
  compliance package: listshare and checkshare
This commit is contained in:
Treehugger Robot
2021-12-07 03:24:23 +00:00
committed by Gerrit Code Review
6 changed files with 1034 additions and 0 deletions

View File

@@ -17,6 +17,20 @@ package {
default_applicable_licenses: ["Android-Apache-2.0"], default_applicable_licenses: ["Android-Apache-2.0"],
} }
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 { blueprint_go_binary {
name: "dumpgraph", name: "dumpgraph",
srcs: ["cmd/dumpgraph.go"], srcs: ["cmd/dumpgraph.go"],
@@ -37,6 +51,7 @@ bootstrap_go_package {
"actionset.go", "actionset.go",
"condition.go", "condition.go",
"conditionset.go", "conditionset.go",
"doc.go",
"graph.go", "graph.go",
"policy/policy.go", "policy/policy.go",
"policy/resolve.go", "policy/resolve.go",

View File

@@ -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
}

View File

@@ -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])
}
}
})
}
}

View File

@@ -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
}

View File

@@ -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])
}
})
}
}

77
tools/compliance/doc.go Normal file
View File

@@ -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