compliance package structures for license metadata
package to read, consume, and analyze license metadata and dependency
graph.
Bug: 68860345
Bug: 151177513
Bug: 151953481
Change-Id: I3ebf44e4d5195b9851fd076161049bf82ed76dd2
diff --git a/tools/compliance/resolutionset.go b/tools/compliance/resolutionset.go
new file mode 100644
index 0000000..ea49db9
--- /dev/null
+++ b/tools/compliance/resolutionset.go
@@ -0,0 +1,304 @@
+// 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
+
+import (
+ "fmt"
+ "strings"
+)
+
+// JoinResolutionSets returns a new ResolutionSet combining the resolutions from
+// multiple resolution sets. All sets must be derived from the same license
+// graph.
+//
+// e.g. combine "restricted", "reciprocal", and "proprietary" resolutions.
+func JoinResolutionSets(resolutions ...*ResolutionSet) *ResolutionSet {
+ if len(resolutions) < 1 {
+ panic(fmt.Errorf("attempt to join 0 resolution sets"))
+ }
+ rmap := make(map[*TargetNode]actionSet)
+ for _, r := range resolutions {
+ if len(r.resolutions) < 1 {
+ continue
+ }
+ for attachesTo, as := range r.resolutions {
+ if as.isEmpty() {
+ continue
+ }
+ if _, ok := rmap[attachesTo]; !ok {
+ rmap[attachesTo] = as.copy()
+ continue
+ }
+ rmap[attachesTo].addSet(as)
+ }
+ }
+ return &ResolutionSet{rmap}
+}
+
+// ResolutionSet describes an immutable set of targets and the license
+// conditions each target must satisfy or "resolve" in a specific context.
+//
+// Ultimately, the purpose of recording the license metadata and building a
+// license graph is to identify, describe, and verify the necessary actions or
+// operations for compliance policy.
+//
+// i.e. What is the source-sharing policy? Has it been met? Meet it.
+//
+// i.e. Are there incompatible policy requirements? Such as a source-sharing
+// policy applied to code that policy also says may not be shared? If so, stop
+// and remove the dependencies that create the situation.
+//
+// The ResolutionSet is the base unit for mapping license conditions to the
+// targets triggering some necessary action per policy. Different ResolutionSet
+// values may be calculated for different contexts.
+//
+// e.g. Suppose an unencumbered binary links in a notice .a library.
+//
+// An "unencumbered" condition would originate from the binary, and a "notice"
+// condition would originate from the .a library. A ResolutionSet for the
+// context of the Notice policy might apply both conditions to the binary while
+// preserving the origin of each condition. By applying the notice condition to
+// the binary, the ResolutionSet stipulates the policy that the release of the
+// unencumbered binary must provide suitable notice for the .a library.
+//
+// The resulting ResolutionSet could be used for building a notice file, for
+// validating that a suitable notice has been built into the distribution, or
+// for reporting what notices need to be given.
+//
+// Resolutions for different contexts may be combined in a new ResolutionSet
+// using JoinResolutions(...).
+//
+// See: resolve.go for:
+// * ResolveBottomUpConditions(...)
+// * ResolveTopDownForCondition(...)
+// See also: policy.go for:
+// * ResolveSourceSharing(...)
+// * ResolveSourcePrivacy(...)
+type ResolutionSet struct {
+ // resolutions maps names of target with applicable conditions to the set of conditions that apply.
+ resolutions map[*TargetNode]actionSet
+}
+
+// String returns a string representation of the set.
+func (rs *ResolutionSet) String() string {
+ var sb strings.Builder
+ fmt.Fprintf(&sb, "{")
+ sep := ""
+ for attachesTo, as := range rs.resolutions {
+ fmt.Fprintf(&sb, "%s%s -> %s", sep, attachesTo.name, as.String())
+ sep = ", "
+ }
+ fmt.Fprintf(&sb, "}")
+ return sb.String()
+}
+
+// AttachesTo identifies the list of targets triggering action to resolve
+// conditions. (unordered)
+func (rs *ResolutionSet) AttachesTo() TargetNodeList {
+ targets := make(TargetNodeList, 0, len(rs.resolutions))
+ for attachesTo := range rs.resolutions {
+ targets = append(targets, attachesTo)
+ }
+ return targets
+}
+
+// ActsOn identifies the list of targets to act on (share, give notice etc.)
+// to resolve conditions. (unordered)
+func (rs *ResolutionSet) ActsOn() TargetNodeList {
+ tset := make(map[*TargetNode]bool)
+ for _, as := range rs.resolutions {
+ for actsOn := range as {
+ tset[actsOn] = true
+ }
+ }
+ targets := make(TargetNodeList, 0, len(tset))
+ for target := range tset {
+ targets = append(targets, target)
+ }
+ return targets
+}
+
+// Origins identifies the list of targets originating conditions to resolve.
+// (unordered)
+func (rs *ResolutionSet) Origins() TargetNodeList {
+ tset := make(map[*TargetNode]bool)
+ for _, as := range rs.resolutions {
+ for _, cs := range as {
+ for _, origins := range cs.conditions {
+ for origin := range origins {
+ tset[origin] = true
+ }
+ }
+ }
+ }
+ targets := make(TargetNodeList, 0, len(tset))
+ for target := range tset {
+ targets = append(targets, target)
+ }
+ return targets
+}
+
+// Resolutions returns the list of resolutions that `attachedTo`
+// target must resolve. Returns empty list if no conditions apply.
+//
+// Panics if `attachedTo` does not appear in the set.
+func (rs *ResolutionSet) Resolutions(attachedTo *TargetNode) ResolutionList {
+ as, ok := rs.resolutions[attachedTo]
+ if !ok {
+ return ResolutionList{}
+ }
+ result := make(ResolutionList, 0, len(as))
+ for actsOn, cs := range as {
+ result = append(result, Resolution{attachedTo, actsOn, cs.Copy()})
+ }
+ return result
+}
+
+// ResolutionsByActsOn returns the list of resolutions that must `actOn` to
+// resolvee. Returns empty list if no conditions apply.
+//
+// Panics if `actOn` does not appear in the set.
+func (rs *ResolutionSet) ResolutionsByActsOn(actOn *TargetNode) ResolutionList {
+ c := 0
+ for _, as := range rs.resolutions {
+ if _, ok := as[actOn]; ok {
+ c++
+ }
+ }
+ result := make(ResolutionList, 0, c)
+ for attachedTo, as := range rs.resolutions {
+ if cs, ok := as[actOn]; ok {
+ result = append(result, Resolution{attachedTo, actOn, cs.Copy()})
+ }
+ }
+ return result
+}
+
+// AttachesToByOrigin identifies the list of targets requiring action to
+// resolve conditions originating at `origin`. (unordered)
+func (rs *ResolutionSet) AttachesToByOrigin(origin *TargetNode) TargetNodeList {
+ tset := make(map[*TargetNode]bool)
+ for attachesTo, as := range rs.resolutions {
+ for _, cs := range as {
+ if cs.HasAnyByOrigin(origin) {
+ tset[attachesTo] = true
+ break
+ }
+ }
+ }
+ targets := make(TargetNodeList, 0, len(tset))
+ for target := range tset {
+ targets = append(targets, target)
+ }
+ return targets
+}
+
+// AttachesToTarget returns true if the set contains conditions that
+// are `attachedTo`.
+func (rs *ResolutionSet) AttachesToTarget(attachedTo *TargetNode) bool {
+ _, isPresent := rs.resolutions[attachedTo]
+ return isPresent
+}
+
+// AnyByNameAttachToTarget returns true if the set contains conditions matching
+// `names` that attach to `attachedTo`.
+func (rs *ResolutionSet) AnyByNameAttachToTarget(attachedTo *TargetNode, names ...ConditionNames) bool {
+ as, isPresent := rs.resolutions[attachedTo]
+ if !isPresent {
+ return false
+ }
+ for _, cs := range as {
+ for _, cn := range names {
+ for _, name := range cn {
+ _, isPresent = cs.conditions[name]
+ if isPresent {
+ return true
+ }
+ }
+ }
+ }
+ return false
+}
+
+// AllByNameAttachTo returns true if the set contains at least one condition
+// matching each element of `names` for `attachedTo`.
+func (rs *ResolutionSet) AllByNameAttachToTarget(attachedTo *TargetNode, names ...ConditionNames) bool {
+ as, isPresent := rs.resolutions[attachedTo]
+ if !isPresent {
+ return false
+ }
+ for _, cn := range names {
+ found := false
+ asloop:
+ for _, cs := range as {
+ for _, name := range cn {
+ _, isPresent = cs.conditions[name]
+ if isPresent {
+ found = true
+ break asloop
+ }
+ }
+ }
+ if !found {
+ return false
+ }
+ }
+ return true
+}
+
+// IsEmpty returns true if the set contains no conditions to resolve.
+func (rs *ResolutionSet) IsEmpty() bool {
+ for _, as := range rs.resolutions {
+ if !as.isEmpty() {
+ return false
+ }
+ }
+ return true
+}
+
+// compliance-only ResolutionSet methods
+
+// newResolutionSet constructs a new, empty instance of resolutionSetImp for graph `lg`.
+func newResolutionSet() *ResolutionSet {
+ return &ResolutionSet{make(map[*TargetNode]actionSet)}
+}
+
+// addConditions attaches all of the license conditions in `as` to `attachTo` to act on the originating node if not already applied.
+func (rs *ResolutionSet) addConditions(attachTo *TargetNode, as actionSet) {
+ _, ok := rs.resolutions[attachTo]
+ if !ok {
+ rs.resolutions[attachTo] = as.copy()
+ return
+ }
+ rs.resolutions[attachTo].addSet(as)
+}
+
+// add attaches all of the license conditions in `as` to `attachTo` to act on `attachTo` if not already applied.
+func (rs *ResolutionSet) addSelf(attachTo *TargetNode, as actionSet) {
+ for _, cs := range as {
+ if cs.IsEmpty() {
+ return
+ }
+ _, ok := rs.resolutions[attachTo]
+ if !ok {
+ rs.resolutions[attachTo] = make(actionSet)
+ }
+ _, ok = rs.resolutions[attachTo][attachTo]
+ if !ok {
+ rs.resolutions[attachTo][attachTo] = newLicenseConditionSet()
+ }
+ rs.resolutions[attachTo][attachTo].AddSet(cs)
+ }
+}