// 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 // ResolveBottomUpConditions performs a bottom-up walk of the LicenseGraph // propagating conditions up the graph as necessary according to the properties // of each edge and according to each license condition in question. // // Subsequent top-down walks of the graph will filter some resolutions and may // introduce new resolutions. // // e.g. if a "restricted" condition applies to a binary, it also applies to all // of the statically-linked libraries and the transitive closure of their static // dependencies; even if neither they nor the transitive closure of their // dependencies originate any "restricted" conditions. The bottom-up walk will // not resolve the library and its transitive closure, but the later top-down // walk will. func ResolveBottomUpConditions(lg *LicenseGraph) *ResolutionSet { // short-cut if already walked and cached lg.mu.Lock() rs := lg.rsBU lg.mu.Unlock() if rs != nil { return rs } // must be indexed for fast lookup lg.indexForward() rs = newResolutionSet() // cmap contains an entry for every target that was previously walked as a pure aggregate only. cmap := make(map[string]bool) var walk func(f string, treatAsAggregate bool) actionSet walk = func(f string, treatAsAggregate bool) actionSet { target := lg.targets[f] result := make(actionSet) result[target] = newLicenseConditionSet() result[target].add(target, target.proto.LicenseConditions...) if preresolved, ok := rs.resolutions[target]; ok { if treatAsAggregate { result.addSet(preresolved) return result } if _, asAggregate := cmap[f]; !asAggregate { result.addSet(preresolved) return result } // previously walked in a pure aggregate context, // needs to walk again in non-aggregate context delete(cmap, f) } if treatAsAggregate { cmap[f] = true } // add all the conditions from all the dependencies for _, edge := range lg.index[f] { // walk dependency to get its conditions as := walk(edge.dependency, treatAsAggregate && lg.targets[edge.dependency].IsContainer()) // turn those into the conditions that apply to the target as = depActionsApplicableToTarget(TargetEdge{lg, edge}, as, treatAsAggregate) // add them to the result result.addSet(as) } // record these conditions as applicable to the target rs.addConditions(target, result) rs.addSelf(target, result.byName(ImpliesRestricted)) // return this up the tree return result } // walk each of the roots for _, r := range lg.rootFiles { _ = walk(r, lg.targets[r].IsContainer()) } // if not yet cached, save the result lg.mu.Lock() if lg.rsBU == nil { lg.rsBU = rs } else { // if we end up with 2, release the later for garbage collection rs = lg.rsBU } lg.mu.Unlock() return rs } // ResolveTopDownCondtions performs a top-down walk of the LicenseGraph // resolving all reachable nodes for `condition`. Policy establishes the rules // for transforming and propagating resolutions down the graph. // // e.g. For current policy, none of the conditions propagate from target to // dependency except restricted. For restricted, the policy is to share the // source of any libraries linked to restricted code and to provide notice. func ResolveTopDownConditions(lg *LicenseGraph) *ResolutionSet { // short-cut if already walked and cached lg.mu.Lock() rs := lg.rsTD lg.mu.Unlock() if rs != nil { return rs } // start with the conditions propagated up the graph rs = ResolveBottomUpConditions(lg) // rmap maps 'appliesTo' targets to their applicable conditions // // rmap is the resulting ResolutionSet rmap := make(map[*TargetNode]actionSet) for attachesTo, as := range rs.resolutions { rmap[attachesTo] = as.copy() } path := make([]*dependencyEdge, 0, 32) var walk func(f string, cs *LicenseConditionSet, treatAsAggregate bool) walk = func(f string, cs *LicenseConditionSet, treatAsAggregate bool) { fnode := lg.targets[f] if !cs.IsEmpty() { parentsAllAggregate := true for _, e := range path { target := lg.targets[e.target] if _, ok := rmap[target]; !ok { rmap[target] = make(actionSet) } rmap[target].add(fnode, cs) if !target.IsContainer() { parentsAllAggregate = false break } } if parentsAllAggregate { if _, ok := rmap[fnode]; !ok { rmap[fnode] = make(actionSet) } rmap[fnode].add(fnode, cs) } } // add conditions attached to `f` cs = cs.Copy() for _, fcs := range rs.resolutions[fnode] { cs.AddSet(fcs) } // for each dependency for _, edge := range lg.index[f] { e := TargetEdge{lg, edge} // dcs holds the dpendency conditions inherited from the target dcs := targetConditionsApplicableToDep(e, cs, treatAsAggregate) if dcs.IsEmpty() { if !treatAsAggregate || (!edgeIsDerivation(e) && !edgeIsDynamicLink(e)) { continue } } path = append(path, edge) // add the conditions to the dependency walk(edge.dependency, dcs, treatAsAggregate && lg.targets[edge.dependency].IsContainer()) path = path[:len(path)-1] } } // walk each of the roots for _, r := range lg.rootFiles { as, ok := rs.resolutions[lg.targets[r]] if !ok { // no conditions in root or transitive closure of dependencies continue } if as.isEmpty() { continue } path = path[:0] // add the conditions to the root and its transitive closure walk(r, newLicenseConditionSet(), lg.targets[r].IsContainer()) } rs = &ResolutionSet{rmap} // if not yet cached, save the result lg.mu.Lock() if lg.rsTD == nil { lg.rsTD = rs } else { // if we end up with 2, release the later for garbage collection rs = lg.rsTD } lg.mu.Unlock() return rs }