platform_build/tools/compliance/resolutionset.go

305 lines
9.1 KiB
Go
Raw Normal View History

// 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]struct{})
for _, as := range rs.resolutions {
for actsOn := range as {
tset[actsOn] = struct{}{}
}
}
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]struct{})
for _, as := range rs.resolutions {
for _, cs := range as {
for _, origins := range cs.conditions {
for origin := range origins {
tset[origin] = struct{}{}
}
}
}
}
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]struct{})
for attachesTo, as := range rs.resolutions {
for _, cs := range as {
if cs.HasAnyByOrigin(origin) {
tset[attachesTo] = struct{}{}
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)
}
}