Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 414d9186 authored by Treehugger Robot's avatar Treehugger Robot Committed by Automerger Merge Worker
Browse files

Merge changes I5d48eaba,I4ff3f988 am: 203bbf32 am: 195ace0f

Original change: https://android-review.googlesource.com/c/platform/build/+/1870079

Change-Id: I08a95f347198e2e7cd671448346c5c8c470d0129
parents 63f16645 195ace0f
Loading
Loading
Loading
Loading
+15 −0
Original line number Diff line number Diff line
@@ -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 −0
Original line number Diff line number Diff line
// 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 −0
Original line number Diff line number Diff line
// 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 −0
Original line number Diff line number Diff line
// 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 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading