// 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 ( // RecognizedAnnotations identifies the set of annotations that have // meaning for compliance policy. RecognizedAnnotations = map[string]string{ // used in readgraph.go to avoid creating 1000's of copies of the below 3 strings. "static": "static", "dynamic": "dynamic", "toolchain": "toolchain", } // SafePathPrefixes maps the path prefixes presumed not to contain any // proprietary or confidential pathnames to whether to strip the prefix // from the path when used as the library name for notices. SafePathPrefixes = map[string]bool{ "external/": true, "art/": false, "build/": false, "cts/": false, "dalvik/": false, "developers/": false, "development/": false, "frameworks/": false, "packages/": true, "prebuilts/": false, "sdk/": false, "system/": false, "test/": false, "toolchain/": false, "tools/": false, } // SafePrebuiltPrefixes maps the regular expression to match a prebuilt // containing the path of a safe prefix to the safe prefix. SafePrebuiltPrefixes = make(map[*regexp.Regexp]string) // ImpliesUnencumbered lists the condition names representing an author attempt to disclaim copyright. ImpliesUnencumbered = LicenseConditionSet(UnencumberedCondition) // ImpliesPermissive lists the condition names representing copyrighted but "licensed without policy requirements". ImpliesPermissive = LicenseConditionSet(PermissiveCondition) // ImpliesNotice lists the condition names implying a notice or attribution policy. ImpliesNotice = LicenseConditionSet(UnencumberedCondition | PermissiveCondition | NoticeCondition | ReciprocalCondition | RestrictedCondition | RestrictedClasspathExceptionCondition | WeaklyRestrictedCondition | ProprietaryCondition | ByExceptionOnlyCondition) // ImpliesReciprocal lists the condition names implying a local source-sharing policy. ImpliesReciprocal = LicenseConditionSet(ReciprocalCondition) // Restricted lists the condition names implying an infectious source-sharing policy. ImpliesRestricted = LicenseConditionSet(RestrictedCondition | RestrictedClasspathExceptionCondition | WeaklyRestrictedCondition) // ImpliesProprietary lists the condition names implying a confidentiality policy. ImpliesProprietary = LicenseConditionSet(ProprietaryCondition) // ImpliesByExceptionOnly lists the condition names implying a policy for "license review and approval before use". ImpliesByExceptionOnly = LicenseConditionSet(ProprietaryCondition | ByExceptionOnlyCondition) // ImpliesPrivate lists the condition names implying a source-code privacy policy. ImpliesPrivate = LicenseConditionSet(ProprietaryCondition) // ImpliesShared lists the condition names implying a source-code sharing policy. ImpliesShared = LicenseConditionSet(ReciprocalCondition | RestrictedCondition | RestrictedClasspathExceptionCondition | WeaklyRestrictedCondition) ) 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.*`) ) func init() { for prefix := range SafePathPrefixes { if prefix == "prebuilts/" { continue } r := regexp.MustCompile("^prebuilts/[^ ]*/" + prefix) SafePrebuiltPrefixes[r] = prefix } } // LicenseConditionSetFromNames returns a set containing the recognized `names` and // silently ignoring or discarding the unrecognized `names`. func LicenseConditionSetFromNames(tn *TargetNode, names ...string) LicenseConditionSet { cs := NewLicenseConditionSet() for _, name := range names { if name == "restricted" { if 0 == len(tn.LicenseKinds()) { cs = cs.Plus(RestrictedCondition) continue } hasLgpl := false hasClasspath := false hasGeneric := false for _, kind := range tn.LicenseKinds() { if strings.HasSuffix(kind, "-with-classpath-exception") { cs = cs.Plus(RestrictedClasspathExceptionCondition) hasClasspath = true } else if anyLgpl.MatchString(kind) { cs = cs.Plus(WeaklyRestrictedCondition) hasLgpl = true } else if versionedGpl.MatchString(kind) { cs = cs.Plus(RestrictedCondition) } else if genericGpl.MatchString(kind) { hasGeneric = true } else if kind == "legacy_restricted" || ccBySa.MatchString(kind) { cs = cs.Plus(RestrictedCondition) } else { cs = cs.Plus(RestrictedCondition) } } if hasGeneric && !hasLgpl && !hasClasspath { cs = cs.Plus(RestrictedCondition) } continue } if lc, ok := RecognizedConditionNames[name]; ok { cs |= LicenseConditionSet(lc) } } return cs } // Resolution happens in three phases: // // 1. A bottom-up traversal propagates (restricted) license conditions up to // targets from dendencies as needed. // // 2. For each condition of interest, a top-down traversal propagates // (restricted) conditions down from targets into linked dependencies. // // 3. Finally, a walk of the shipped target nodes attaches resolutions to the // ancestor nodes from the root down to and including the first non-container. // // e.g. If a disk image contains a binary bin1 that links a library liba, the // notice requirement for liba gets attached to the disk image and to bin1. // Because liba doesn't actually get shipped as a separate artifact, but only // as bits in bin1, it has no actions 'attached' to it. The actions attached // to the image and to bin1 'act on' liba by providing notice. // // The behavior of the 3 phases gets controlled by the 3 functions below. // // The first function controls what happens during the bottom-up propagation. // Restricted conditions propagate up all non-toolchain dependencies; except, // some do not propagate up dynamic links, which may depend on whether the // modules are independent. // // The second function controls what happens during the top-down propagation. // Restricted conditions propagate down as above with the added caveat that // inherited restricted conditions do not propagate from pure aggregates to // their dependencies. // // The final function controls which conditions apply/get attached to ancestors // depending on the types of dependencies involved. All conditions apply across // normal derivation dependencies. No conditions apply across toolchain // dependencies. Some restricted conditions apply across dynamic link // dependencies. // // Not all restricted licenses are create equal. Some have special rules or // exceptions. e.g. LGPL or "with classpath excption". // depConditionsPropagatingToTarget returns the conditions which propagate up an // edge from dependency to target. // // This function sets the policy for the bottom-up propagation 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 depConditionsPropagatingToTarget(lg *LicenseGraph, e *TargetEdge, depConditions LicenseConditionSet, treatAsAggregate bool) LicenseConditionSet { result := LicenseConditionSet(0x0000) if edgeIsDerivation(e) { result |= depConditions & ImpliesRestricted return result } if !edgeIsDynamicLink(e) { return result } result |= depConditions & LicenseConditionSet(RestrictedCondition) if 0 != (depConditions&LicenseConditionSet(RestrictedClasspathExceptionCondition)) && !edgeNodesAreIndependentModules(e) { result |= LicenseConditionSet(RestrictedClasspathExceptionCondition) } return result } // targetConditionsPropagatingToDep 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 targetConditionsPropagatingToDep(lg *LicenseGraph, e *TargetEdge, targetConditions LicenseConditionSet, treatAsAggregate bool, conditionsFn TraceConditions) LicenseConditionSet { result := targetConditions // reverse direction -- none of these apply to things depended-on, only to targets depending-on. result = result.Minus(UnencumberedCondition, PermissiveCondition, NoticeCondition, ReciprocalCondition, ProprietaryCondition, ByExceptionOnlyCondition) if !edgeIsDerivation(e) && !edgeIsDynamicLink(e) { // target is not a derivative work of dependency and is not linked to dependency result = result.Difference(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. if !conditionsFn(e.target).MatchesAnySet(ImpliesRestricted) { result = result.Difference(ImpliesRestricted) } return result } if edgeIsDerivation(e) { return result } result = result.Minus(WeaklyRestrictedCondition) if edgeNodesAreIndependentModules(e) { result = result.Minus(RestrictedClasspathExceptionCondition) } return result } // conditionsAttachingAcrossEdge returns the subset of conditions in `universe` // that apply across edge `e`. // // This function sets the policy for attaching actions to ancestor nodes in the // final resolution walk. func conditionsAttachingAcrossEdge(lg *LicenseGraph, e *TargetEdge, universe LicenseConditionSet) LicenseConditionSet { result := universe if edgeIsDerivation(e) { return result } if !edgeIsDynamicLink(e) { return NewLicenseConditionSet() } result &= LicenseConditionSet(RestrictedCondition | RestrictedClasspathExceptionCondition) if 0 != (result&LicenseConditionSet(RestrictedClasspathExceptionCondition)) && edgeNodesAreIndependentModules(e) { result &= LicenseConditionSet(RestrictedCondition) } return result } // edgeIsDynamicLink returns true for edges representing shared libraries // linked dynamically at runtime. func edgeIsDynamicLink(e *TargetEdge) bool { return 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.annotations.HasAnnotation("dynamic") isToolchain := 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() }