Merge changes I5d48eaba,I4ff3f988
* changes: compliance package documentation compliance package: listshare and checkshare
This commit is contained in:
@@ -17,6 +17,20 @@ package {
|
||||
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 {
|
||||
name: "dumpgraph",
|
||||
srcs: ["cmd/dumpgraph.go"],
|
||||
@@ -37,6 +51,7 @@ bootstrap_go_package {
|
||||
"actionset.go",
|
||||
"condition.go",
|
||||
"conditionset.go",
|
||||
"doc.go",
|
||||
"graph.go",
|
||||
"policy/policy.go",
|
||||
"policy/resolve.go",
|
||||
|
114
tools/compliance/cmd/checkshare.go
Normal file
114
tools/compliance/cmd/checkshare.go
Normal 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
|
||||
}
|
299
tools/compliance/cmd/checkshare_test.go
Normal file
299
tools/compliance/cmd/checkshare_test.go
Normal 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])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
124
tools/compliance/cmd/listshare.go
Normal file
124
tools/compliance/cmd/listshare.go
Normal 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
|
||||
}
|
405
tools/compliance/cmd/listshare_test.go
Normal file
405
tools/compliance/cmd/listshare_test.go
Normal 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
77
tools/compliance/doc.go
Normal 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
|
Reference in New Issue
Block a user