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"],
|
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",
|
||||||
|
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