1// Copyright 2021 Google LLC 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package compliance 16 17import ( 18 "fmt" 19 "strings" 20) 21 22// ResolutionSet describes an immutable set of targets and the license 23// conditions each target must satisfy or "resolve" in a specific context. 24// 25// Ultimately, the purpose of recording the license metadata and building a 26// license graph is to identify, describe, and verify the necessary actions or 27// operations for compliance policy. 28// 29// i.e. What is the source-sharing policy? Has it been met? Meet it. 30// 31// i.e. Are there incompatible policy requirements? Such as a source-sharing 32// policy applied to code that policy also says may not be shared? If so, stop 33// and remove the dependencies that create the situation. 34// 35// The ResolutionSet is the base unit for mapping license conditions to the 36// targets triggering some necessary action per policy. Different ResolutionSet 37// values may be calculated for different contexts. 38// 39// e.g. Suppose an unencumbered binary links in a notice .a library. 40// 41// An "unencumbered" condition would originate from the binary, and a "notice" 42// condition would originate from the .a library. A ResolutionSet for the 43// context of the Notice policy might attach both conditions to the binary to 44// act on the origin of each condition. By attaching the notice condition to 45// the binary, the ResolutionSet stipulates the policy that the release of the 46// unencumbered binary must provide suitable notice for the .a library. 47// 48// The resulting ResolutionSet could be used for building a notice file, for 49// validating that a suitable notice has been built into the distribution, or 50// for reporting what notices need to be given. 51// 52// The action is defined by the context. In the above example, the action is 53// providing notice for the module acted on. In another context, the action 54// might be sharing the source-code or preserving the privacy of the module 55// acted on. 56type ResolutionSet map[*TargetNode]ActionSet 57 58// AttachesTo identifies the list of targets triggering action to resolve 59// conditions. (unordered) 60func (rs ResolutionSet) AttachesTo() TargetNodeList { 61 result := make(TargetNodeList, 0, len(rs)) 62 for attachesTo := range rs { 63 result = append(result, attachesTo) 64 } 65 return result 66} 67 68// AttachesToTarget returns true if the set contains conditions that 69// are `attachedTo`. 70func (rs ResolutionSet) AttachesToTarget(target *TargetNode) bool { 71 _, isPresent := rs[target] 72 return isPresent 73} 74 75// IsPureAggregate returns true if `target`, which must be in 76// `AttachesTo()` resolves to a pure aggregate in the resolution. 77func (rs ResolutionSet) IsPureAggregate(target *TargetNode) bool { 78 _, isPresent := rs[target] 79 if !isPresent { 80 panic(fmt.Errorf("ResolutionSet.IsPureAggregate(%s): not attached to %s", target.Name(), target.Name())) 81 } 82 return target.pure 83} 84 85// Resolutions returns the list of resolutions that `attachedTo` 86// target must resolve. Returns empty list if no conditions apply. 87func (rs ResolutionSet) Resolutions(attachesTo *TargetNode) ResolutionList { 88 as, ok := rs[attachesTo] 89 if !ok { 90 return nil 91 } 92 result := make(ResolutionList, 0, len(as)) 93 for actsOn, cs := range as { 94 result = append(result, Resolution{attachesTo, actsOn, cs}) 95 } 96 return result 97} 98 99// AllActions returns the set of actions required to resolve the set omitting 100// the attachment. 101func (rs ResolutionSet) AllActions() ActionSet { 102 result := make(ActionSet) 103 for _, as := range rs { 104 for actsOn, cs := range as { 105 if _, ok := result[actsOn]; ok { 106 result[actsOn] = cs.Union(result[actsOn]) 107 } else { 108 result[actsOn] = cs 109 } 110 } 111 } 112 return result 113} 114 115// String returns a human-readable string representation of the set. 116func (rs ResolutionSet) String() string { 117 var sb strings.Builder 118 fmt.Fprintf(&sb, "{") 119 sep := "" 120 for attachesTo, as := range rs { 121 fmt.Fprintf(&sb, "%s%s -> %s", sep, attachesTo.Name(), as.String()) 122 sep = ", " 123 } 124 fmt.Fprintf(&sb, "}") 125 return sb.String() 126} 127 128// ActionSet identifies a set of targets to act on and the license conditions 129// the action will resolve. 130type ActionSet map[*TargetNode]LicenseConditionSet 131 132// String returns a human-readable string representation of the set. 133func (as ActionSet) String() string { 134 var sb strings.Builder 135 fmt.Fprintf(&sb, "{") 136 sep := "" 137 for actsOn, cs := range as { 138 fmt.Fprintf(&sb, "%s%s%s", sep, actsOn.Name(), cs.String()) 139 sep = ", " 140 } 141 fmt.Fprintf(&sb, "}") 142 return sb.String() 143} 144