// 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" ) // 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 attach both conditions to the binary to // act on the origin of each condition. By attaching 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. // // The action is defined by the context. In the above example, the action is // providing notice for the module acted on. In another context, the action // might be sharing the source-code or preserving the privacy of the module // acted on. type ResolutionSet map[*TargetNode]ActionSet // AttachesTo identifies the list of targets triggering action to resolve // conditions. (unordered) func (rs ResolutionSet) AttachesTo() TargetNodeList { result := make(TargetNodeList, 0, len(rs)) for attachesTo := range rs { result = append(result, attachesTo) } return result } // AttachesToTarget returns true if the set contains conditions that // are `attachedTo`. func (rs ResolutionSet) AttachesToTarget(target *TargetNode) bool { _, isPresent := rs[target] return isPresent } // IsPureAggregate returns true if `target`, which must be in // `AttachesTo()` resolves to a pure aggregate in the resolution. func (rs ResolutionSet) IsPureAggregate(target *TargetNode) bool { _, isPresent := rs[target] if !isPresent { panic(fmt.Errorf("ResolutionSet.IsPureAggregate(%s): not attached to %s", target.Name(), target.Name())) } return target.pure } // Resolutions returns the list of resolutions that `attachedTo` // target must resolve. Returns empty list if no conditions apply. func (rs ResolutionSet) Resolutions(attachesTo *TargetNode) ResolutionList { as, ok := rs[attachesTo] if !ok { return nil } result := make(ResolutionList, 0, len(as)) for actsOn, cs := range as { result = append(result, Resolution{attachesTo, actsOn, cs}) } return result } // AllActions returns the set of actions required to resolve the set omitting // the attachment. func (rs ResolutionSet) AllActions() ActionSet { result := make(ActionSet) for _, as := range rs { for actsOn, cs := range as { if _, ok := result[actsOn]; ok { result[actsOn] = cs.Union(result[actsOn]) } else { result[actsOn] = cs } } } return result } // String returns a human-readable string representation of the set. func (rs ResolutionSet) String() string { var sb strings.Builder fmt.Fprintf(&sb, "{") sep := "" for attachesTo, as := range rs { fmt.Fprintf(&sb, "%s%s -> %s", sep, attachesTo.Name(), as.String()) sep = ", " } fmt.Fprintf(&sb, "}") return sb.String() } // ActionSet identifies a set of targets to act on and the license conditions // the action will resolve. type ActionSet map[*TargetNode]LicenseConditionSet // String returns a human-readable string representation of the set. func (as ActionSet) String() string { var sb strings.Builder fmt.Fprintf(&sb, "{") sep := "" for actsOn, cs := range as { fmt.Fprintf(&sb, "%s%s%s", sep, actsOn.Name(), cs.String()) sep = ", " } fmt.Fprintf(&sb, "}") return sb.String() }