/*
* Copyright 2014-present Facebook, Inc.
*
* 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 com.facebook.buck.cxx;
import com.facebook.buck.graph.AbstractBreadthFirstTraversal;
import com.facebook.buck.graph.MutableDirectedGraph;
import com.facebook.buck.graph.TopologicalSort;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.parser.NoSuchBuildTargetException;
import com.facebook.buck.rules.BuildRule;
import com.facebook.buck.rules.SourcePath;
import com.facebook.buck.util.HumanReadableException;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Iterables;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
public class NativeLinkables {
private NativeLinkables() {}
/**
* Find all {@link NativeLinkable} transitive roots reachable from the given {@link BuildRule}s.
*
* @param from the starting set of {@link BuildRule}s to begin the search from.
* @param traverse a {@link Predicate} determining acceptable dependencies to traverse when
* searching for {@link NativeLinkable}s.
* @param skip Skip this {@link BuildRule} even if it is an instance of {@link NativeLinkable}
* @return all the roots found as a map from {@link BuildTarget} to {@link NativeLinkable}.
*/
static ImmutableMap<BuildTarget, NativeLinkable> getNativeLinkableRoots(
Iterable<? extends BuildRule> from,
final Predicate<Object> traverse,
final Predicate<Object> skip) {
final ImmutableMap.Builder<BuildTarget, NativeLinkable> nativeLinkables =
ImmutableMap.builder();
AbstractBreadthFirstTraversal<BuildRule> visitor =
new AbstractBreadthFirstTraversal<BuildRule>(from) {
@Override
public ImmutableSet<BuildRule> visit(BuildRule rule) {
// If this is `NativeLinkable`, we've found a root so record the rule and terminate
// the search.
if (rule instanceof NativeLinkable && !skip.apply(rule)) {
NativeLinkable nativeLinkable = (NativeLinkable) rule;
nativeLinkables.put(nativeLinkable.getBuildTarget(), nativeLinkable);
return ImmutableSet.of();
}
// Otherwise, make sure this rule is marked as traversable before following it's deps.
if (traverse.apply(rule)) {
return rule.getBuildDeps();
}
return ImmutableSet.of();
}
};
visitor.start();
return nativeLinkables.build();
}
/**
* Find all {@link NativeLinkable} transitive roots reachable from the given {@link BuildRule}s.
*
* @param from the starting set of {@link BuildRule}s to begin the search from.
* @param traverse a {@link Predicate} determining acceptable dependencies to traverse when
* searching for {@link NativeLinkable}s.
* @return all the roots found as a map from {@link BuildTarget} to {@link NativeLinkable}.
*/
public static ImmutableMap<BuildTarget, NativeLinkable> getNativeLinkableRoots(
Iterable<? extends BuildRule> from, final Predicate<Object> traverse) {
return getNativeLinkableRoots(from, traverse, x -> false);
}
/**
* Extract from the dependency graph all the libraries which must be considered for linking.
*
* <p>Traversal proceeds depending on whether each dependency is to be statically or dynamically
* linked.
*
* @param linkStyle how dependencies should be linked, if their preferred_linkage is {@code
* NativeLinkable.Linkage.ANY}.
*/
public static ImmutableMap<BuildTarget, NativeLinkable> getNativeLinkables(
final CxxPlatform cxxPlatform,
Iterable<? extends NativeLinkable> inputs,
final Linker.LinkableDepType linkStyle,
final Predicate<? super NativeLinkable> traverse) {
final Map<BuildTarget, NativeLinkable> nativeLinkables = new HashMap<>();
for (NativeLinkable nativeLinkable : inputs) {
nativeLinkables.put(nativeLinkable.getBuildTarget(), nativeLinkable);
}
final MutableDirectedGraph<BuildTarget> graph = new MutableDirectedGraph<>();
AbstractBreadthFirstTraversal<BuildTarget> visitor =
new AbstractBreadthFirstTraversal<BuildTarget>(nativeLinkables.keySet()) {
@Override
public ImmutableSet<BuildTarget> visit(BuildTarget target) {
NativeLinkable nativeLinkable = Preconditions.checkNotNull(nativeLinkables.get(target));
graph.addNode(target);
// We always traverse a rule's exported native linkables.
Iterable<? extends NativeLinkable> nativeLinkableDeps =
nativeLinkable.getNativeLinkableExportedDepsForPlatform(cxxPlatform);
boolean shouldTraverse = true;
switch (nativeLinkable.getPreferredLinkage(cxxPlatform)) {
case ANY:
shouldTraverse = linkStyle != Linker.LinkableDepType.SHARED;
break;
case SHARED:
shouldTraverse = false;
break;
case STATIC:
shouldTraverse = true;
break;
}
// If we're linking this dependency statically, we also need to traverse its deps.
if (shouldTraverse) {
nativeLinkableDeps =
Iterables.concat(
nativeLinkableDeps,
nativeLinkable.getNativeLinkableDepsForPlatform(cxxPlatform));
}
// Process all the traversable deps.
ImmutableSet.Builder<BuildTarget> deps = ImmutableSet.builder();
for (NativeLinkable dep : nativeLinkableDeps) {
if (traverse.apply(dep)) {
BuildTarget depTarget = dep.getBuildTarget();
graph.addEdge(target, depTarget);
deps.add(depTarget);
nativeLinkables.put(depTarget, dep);
}
}
return deps.build();
}
};
visitor.start();
// Topologically sort the rules.
Iterable<BuildTarget> ordered = TopologicalSort.sort(graph).reverse();
// Return a map of of the results.
ImmutableMap.Builder<BuildTarget, NativeLinkable> result = ImmutableMap.builder();
for (BuildTarget target : ordered) {
result.put(target, nativeLinkables.get(target));
}
return result.build();
}
public static ImmutableMap<BuildTarget, NativeLinkable> getNativeLinkables(
final CxxPlatform cxxPlatform,
Iterable<? extends NativeLinkable> inputs,
final Linker.LinkableDepType linkStyle) {
return getNativeLinkables(cxxPlatform, inputs, linkStyle, x -> true);
}
public static Linker.LinkableDepType getLinkStyle(
NativeLinkable.Linkage preferredLinkage, Linker.LinkableDepType requestedLinkStyle) {
Linker.LinkableDepType linkStyle;
switch (preferredLinkage) {
case SHARED:
linkStyle = Linker.LinkableDepType.SHARED;
break;
case STATIC:
linkStyle =
requestedLinkStyle == Linker.LinkableDepType.STATIC
? Linker.LinkableDepType.STATIC
: Linker.LinkableDepType.STATIC_PIC;
break;
case ANY:
linkStyle = requestedLinkStyle;
break;
default:
throw new IllegalStateException();
}
return linkStyle;
}
public static NativeLinkableInput getNativeLinkableInput(
CxxPlatform cxxPlatform, Linker.LinkableDepType linkStyle, NativeLinkable nativeLinkable)
throws NoSuchBuildTargetException {
NativeLinkable.Linkage link = nativeLinkable.getPreferredLinkage(cxxPlatform);
return nativeLinkable.getNativeLinkableInput(cxxPlatform, getLinkStyle(link, linkStyle));
}
/**
* Collect up and merge all {@link com.facebook.buck.cxx.NativeLinkableInput} objects from
* transitively traversing all unbroken dependency chains of {@link
* com.facebook.buck.cxx.NativeLinkable} objects found via the passed in {@link
* com.facebook.buck.rules.BuildRule} roots.
*/
public static NativeLinkableInput getTransitiveNativeLinkableInput(
CxxPlatform cxxPlatform,
Iterable<? extends BuildRule> inputs,
Linker.LinkableDepType depType,
Predicate<Object> traverse,
Predicate<Object> skip)
throws NoSuchBuildTargetException {
// Get the topologically sorted native linkables.
ImmutableMap<BuildTarget, NativeLinkable> roots =
getNativeLinkableRoots(inputs, traverse, skip);
ImmutableMap<BuildTarget, NativeLinkable> nativeLinkables =
getNativeLinkables(cxxPlatform, roots.values(), depType);
ImmutableList.Builder<NativeLinkableInput> nativeLinkableInputs = ImmutableList.builder();
for (NativeLinkable nativeLinkable : nativeLinkables.values()) {
nativeLinkableInputs.add(getNativeLinkableInput(cxxPlatform, depType, nativeLinkable));
}
return NativeLinkableInput.concat(nativeLinkableInputs.build());
}
public static ImmutableMap<BuildTarget, NativeLinkable> getTransitiveNativeLinkables(
final CxxPlatform cxxPlatform, Iterable<? extends NativeLinkable> inputs) {
final Map<BuildTarget, NativeLinkable> nativeLinkables = new HashMap<>();
for (NativeLinkable nativeLinkable : inputs) {
nativeLinkables.put(nativeLinkable.getBuildTarget(), nativeLinkable);
}
final MutableDirectedGraph<BuildTarget> graph = new MutableDirectedGraph<>();
AbstractBreadthFirstTraversal<BuildTarget> visitor =
new AbstractBreadthFirstTraversal<BuildTarget>(nativeLinkables.keySet()) {
@Override
public ImmutableSet<BuildTarget> visit(BuildTarget target) {
NativeLinkable nativeLinkable = Preconditions.checkNotNull(nativeLinkables.get(target));
graph.addNode(target);
ImmutableSet.Builder<BuildTarget> deps = ImmutableSet.builder();
for (NativeLinkable dep :
Iterables.concat(
nativeLinkable.getNativeLinkableDepsForPlatform(cxxPlatform),
nativeLinkable.getNativeLinkableExportedDepsForPlatform(cxxPlatform))) {
BuildTarget depTarget = dep.getBuildTarget();
graph.addEdge(target, depTarget);
deps.add(depTarget);
nativeLinkables.put(depTarget, dep);
}
return deps.build();
}
};
visitor.start();
return ImmutableMap.copyOf(nativeLinkables);
}
/**
* Collect up and merge all {@link com.facebook.buck.cxx.NativeLinkableInput} objects from
* transitively traversing all unbroken dependency chains of {@link
* com.facebook.buck.cxx.NativeLinkable} objects found via the passed in {@link
* com.facebook.buck.rules.BuildRule} roots.
*/
public static NativeLinkableInput getTransitiveNativeLinkableInput(
CxxPlatform cxxPlatform,
Iterable<? extends BuildRule> inputs,
Linker.LinkableDepType depType,
Predicate<Object> traverse)
throws NoSuchBuildTargetException {
return getTransitiveNativeLinkableInput(cxxPlatform, inputs, depType, traverse, x -> false);
}
/**
* Collect all the shared libraries generated by {@link NativeLinkable}s found by transitively
* traversing all unbroken dependency chains of {@link com.facebook.buck.cxx.NativeLinkable}
* objects found via the passed in {@link com.facebook.buck.rules.BuildRule} roots.
*
* @return a mapping of library name to the library {@link SourcePath}.
*/
public static ImmutableSortedMap<String, SourcePath> getTransitiveSharedLibraries(
CxxPlatform cxxPlatform,
Iterable<? extends BuildRule> inputs,
Predicate<Object> traverse,
Predicate<Object> skip)
throws NoSuchBuildTargetException {
ImmutableMap<BuildTarget, NativeLinkable> roots =
getNativeLinkableRoots(inputs, traverse, skip);
ImmutableMap<BuildTarget, NativeLinkable> nativeLinkables =
getTransitiveNativeLinkables(cxxPlatform, roots.values());
Map<String, SourcePath> libraries = new LinkedHashMap<>();
for (NativeLinkable nativeLinkable : nativeLinkables.values()) {
NativeLinkable.Linkage linkage = nativeLinkable.getPreferredLinkage(cxxPlatform);
if (linkage != NativeLinkable.Linkage.STATIC) {
ImmutableMap<String, SourcePath> libs = nativeLinkable.getSharedLibraries(cxxPlatform);
for (Map.Entry<String, SourcePath> lib : libs.entrySet()) {
SourcePath prev = libraries.put(lib.getKey(), lib.getValue());
if (prev != null && !prev.equals(lib.getValue())) {
throw new HumanReadableException(
"conflicting libraries for key %s: %s != %s", lib.getKey(), lib.getValue(), prev);
}
}
}
}
return ImmutableSortedMap.copyOf(libraries);
}
/**
* Collect all the shared libraries generated by {@link NativeLinkable}s found by transitively
* traversing all unbroken dependency chains of {@link com.facebook.buck.cxx.NativeLinkable}
* objects found via the passed in {@link com.facebook.buck.rules.BuildRule} roots.
*
* @return a mapping of library name to the library {@link SourcePath}.
*/
public static ImmutableSortedMap<String, SourcePath> getTransitiveSharedLibraries(
CxxPlatform cxxPlatform, Iterable<? extends BuildRule> inputs, Predicate<Object> traverse)
throws NoSuchBuildTargetException {
return getTransitiveSharedLibraries(cxxPlatform, inputs, traverse, x -> false);
}
/** @return the {@link NativeLinkTarget} that can be extracted from {@code object}, if any. */
public static Optional<NativeLinkTarget> getNativeLinkTarget(
Object object, CxxPlatform cxxPlatform) {
if (object instanceof NativeLinkTarget) {
return Optional.of((NativeLinkTarget) object);
}
if (object instanceof CanProvideNativeLinkTarget) {
return ((CanProvideNativeLinkTarget) object).getNativeLinkTarget(cxxPlatform);
}
return Optional.empty();
}
}