Files
build/tools/compliance/policy/policy.go
Bob Badour 9ee7d03e1c compliance package policy and resolves
package to read, consume, and analyze license metadata and dependency
graph.

Bug: 68860345
Bug: 151177513
Bug: 151953481

Change-Id: Ic08406fa2250a08ad26f2167d934f841c95d9148
2021-12-03 15:52:48 -08:00

239 lines
8.5 KiB
Go

// 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 (
"regexp"
"strings"
)
var (
// ImpliesUnencumbered lists the condition names representing an author attempt to disclaim copyright.
ImpliesUnencumbered = ConditionNames{"unencumbered"}
// ImpliesPermissive lists the condition names representing copyrighted but "licensed without policy requirements".
ImpliesPermissive = ConditionNames{"permissive"}
// ImpliesNotice lists the condition names implying a notice or attribution policy.
ImpliesNotice = ConditionNames{"unencumbered", "permissive", "notice", "reciprocal", "restricted", "proprietary", "by_exception_only"}
// ImpliesReciprocal lists the condition names implying a local source-sharing policy.
ImpliesReciprocal = ConditionNames{"reciprocal"}
// Restricted lists the condition names implying an infectious source-sharing policy.
ImpliesRestricted = ConditionNames{"restricted"}
// ImpliesProprietary lists the condition names implying a confidentiality policy.
ImpliesProprietary = ConditionNames{"proprietary"}
// ImpliesByExceptionOnly lists the condition names implying a policy for "license review and approval before use".
ImpliesByExceptionOnly = ConditionNames{"proprietary", "by_exception_only"}
// ImpliesPrivate lists the condition names implying a source-code privacy policy.
ImpliesPrivate = ConditionNames{"proprietary"}
// ImpliesShared lists the condition names implying a source-code sharing policy.
ImpliesShared = ConditionNames{"reciprocal", "restricted"}
)
var (
anyLgpl = regexp.MustCompile(`^SPDX-license-identifier-LGPL.*`)
versionedGpl = regexp.MustCompile(`^SPDX-license-identifier-GPL-\p{N}.*`)
genericGpl = regexp.MustCompile(`^SPDX-license-identifier-GPL$`)
ccBySa = regexp.MustCompile(`^SPDX-license-identifier-CC-BY.*-SA.*`)
)
// Resolution happens in two passes:
//
// 1. A bottom-up traversal propagates license conditions up to targets from
// dendencies as needed.
//
// 2. For each condition of interest, a top-down traversal adjusts the attached
// conditions pushing restricted down from targets into linked dependencies.
//
// The behavior of the 2 passes gets controlled by the 2 functions below.
//
// The first function controls what happens during the bottom-up traversal. In
// general conditions flow up through static links but not other dependencies;
// except, restricted sometimes flows up through dynamic links.
//
// In general, too, the originating target gets acted on to resolve the
// condition (e.g. providing notice), but again restricted is special in that
// it requires acting on (i.e. sharing source of) both the originating module
// and the target using the module.
//
// The latter function controls what happens during the top-down traversal. In
// general, only restricted conditions flow down at all, and only through
// static links.
//
// Not all restricted licenses are create equal. Some have special rules or
// exceptions. e.g. LGPL or "with classpath excption".
// depActionsApplicableToTarget returns the actions which propagate up an
// edge from dependency to target.
//
// This function sets the policy for the bottom-up traversal and how conditions
// flow up the graph from dependencies to targets.
//
// If a pure aggregation is built into a derivative work that is not a pure
// aggregation, per policy it ceases to be a pure aggregation in the context of
// that derivative work. The `treatAsAggregate` parameter will be false for
// non-aggregates and for aggregates in non-aggregate contexts.
func depActionsApplicableToTarget(e TargetEdge, depActions actionSet, treatAsAggregate bool) actionSet {
result := make(actionSet)
if edgeIsDerivation(e) {
result.addSet(depActions)
for _, cs := range depActions.byName(ImpliesRestricted) {
result.add(e.Target(), cs)
}
return result
}
if !edgeIsDynamicLink(e) {
return result
}
restricted := depActions.byName(ImpliesRestricted)
for actsOn, cs := range restricted {
for _, lc := range cs.AsList() {
hasGpl := false
hasLgpl := false
hasClasspath := false
hasGeneric := false
hasOther := false
for _, kind := range lc.origin.LicenseKinds() {
if strings.HasSuffix(kind, "-with-classpath-exception") {
hasClasspath = true
} else if anyLgpl.MatchString(kind) {
hasLgpl = true
} else if versionedGpl.MatchString(kind) {
hasGpl = true
} else if genericGpl.MatchString(kind) {
hasGeneric = true
} else if kind == "legacy_restricted" || ccBySa.MatchString(kind) {
hasOther = true
}
}
if hasOther || hasGpl {
result.addCondition(actsOn, lc)
result.addCondition(e.Target(), lc)
continue
}
if hasClasspath && !edgeNodesAreIndependentModules(e) {
result.addCondition(actsOn, lc)
result.addCondition(e.Target(), lc)
continue
}
if hasLgpl || hasClasspath {
continue
}
if !hasGeneric {
continue
}
result.addCondition(actsOn, lc)
result.addCondition(e.Target(), lc)
}
}
return result
}
// targetConditionsApplicableToDep returns the conditions which propagate down
// an edge from target to dependency.
//
// This function sets the policy for the top-down traversal and how conditions
// flow down the graph from targets to dependencies.
//
// If a pure aggregation is built into a derivative work that is not a pure
// aggregation, per policy it ceases to be a pure aggregation in the context of
// that derivative work. The `treatAsAggregate` parameter will be false for
// non-aggregates and for aggregates in non-aggregate contexts.
func targetConditionsApplicableToDep(e TargetEdge, targetConditions *LicenseConditionSet, treatAsAggregate bool) *LicenseConditionSet {
result := targetConditions.Copy()
// reverse direction -- none of these apply to things depended-on, only to targets depending-on.
result.RemoveAllByName(ConditionNames{"unencumbered", "permissive", "notice", "reciprocal", "proprietary", "by_exception_only"})
if !edgeIsDerivation(e) && !edgeIsDynamicLink(e) {
// target is not a derivative work of dependency and is not linked to dependency
result.RemoveAllByName(ImpliesRestricted)
return result
}
if treatAsAggregate {
// If the author of a pure aggregate licenses it restricted, apply restricted to immediate dependencies.
// Otherwise, restricted does not propagate back down to dependencies.
restricted := result.ByName(ImpliesRestricted).AsList()
for _, lc := range restricted {
if lc.origin.name != e.e.target {
result.Remove(lc)
}
}
return result
}
if edgeIsDerivation(e) {
return result
}
restricted := result.ByName(ImpliesRestricted).AsList()
for _, lc := range restricted {
hasGpl := false
hasLgpl := false
hasClasspath := false
hasGeneric := false
hasOther := false
for _, kind := range lc.origin.LicenseKinds() {
if strings.HasSuffix(kind, "-with-classpath-exception") {
hasClasspath = true
} else if anyLgpl.MatchString(kind) {
hasLgpl = true
} else if versionedGpl.MatchString(kind) {
hasGpl = true
} else if genericGpl.MatchString(kind) {
hasGeneric = true
} else if kind == "legacy_restricted" || ccBySa.MatchString(kind) {
hasOther = true
}
}
if hasOther || hasGpl {
continue
}
if hasClasspath && !edgeNodesAreIndependentModules(e) {
continue
}
if hasGeneric && !hasLgpl && !hasClasspath {
continue
}
result.Remove(lc)
}
return result
}
// edgeIsDynamicLink returns true for edges representing shared libraries
// linked dynamically at runtime.
func edgeIsDynamicLink(e TargetEdge) bool {
return e.e.annotations.HasAnnotation("dynamic")
}
// edgeIsDerivation returns true for edges where the target is a derivative
// work of dependency.
func edgeIsDerivation(e TargetEdge) bool {
isDynamic := e.e.annotations.HasAnnotation("dynamic")
isToolchain := e.e.annotations.HasAnnotation("toolchain")
return !isDynamic && !isToolchain
}
// edgeNodesAreIndependentModules returns true for edges where the target and
// dependency are independent modules.
func edgeNodesAreIndependentModules(e TargetEdge) bool {
return e.Target().PackageName() != e.Dependency().PackageName()
}