// 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 = []safePathPrefixesType{ {"external/", true}, {"art/", false}, {"build/", false}, {"cts/", false}, {"dalvik/", false}, {"developers/", false}, {"development/", false}, {"frameworks/", false}, {"packages/", true}, {"prebuilts/module_sdk/", 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 []safePrebuiltPrefixesType // 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 | 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 | 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 | WeaklyRestrictedCondition) ) type safePathPrefixesType struct { prefix string strip bool } type safePrebuiltPrefixesType struct { safePathPrefixesType re *regexp.Regexp } 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 _, safePathPrefix := range safePathPrefixes { if strings.HasPrefix(safePathPrefix.prefix, "prebuilts/") { continue } r := regexp.MustCompile("^prebuilts/(?:runtime/mainline/)?" + safePathPrefix.prefix) safePrebuiltPrefixes = append(safePrebuiltPrefixes, safePrebuiltPrefixesType{safePathPrefix, r}) } } // LicenseConditionSetFromNames returns a set containing the recognized `names` and // silently ignoring or discarding the unrecognized `names`. func LicenseConditionSetFromNames(names ...string) LicenseConditionSet { cs := NewLicenseConditionSet() for _, name := range names { 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) 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) 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) 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 }