| /* |
| * 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.apple; |
| |
| import com.facebook.buck.log.Logger; |
| import com.facebook.buck.graph.AbstractAcyclicDepthFirstPostOrderTraversal; |
| import com.facebook.buck.rules.BuildRule; |
| import com.facebook.buck.rules.BuildRuleType; |
| import com.facebook.buck.rules.TargetGraph; |
| import com.facebook.buck.rules.TargetNode; |
| import com.google.common.base.Optional; |
| import com.google.common.base.Predicate; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.ImmutableSortedSet; |
| import com.google.common.collect.Iterables; |
| |
| import java.io.IOException; |
| import java.util.Iterator; |
| |
| import javax.annotation.Nullable; |
| |
| /** |
| * Helpers for reading properties of Apple target build rules. |
| */ |
| public final class AppleBuildRules { |
| |
| private static final Logger LOG = Logger.get(AppleBuildRules.class); |
| |
| // Utility class not to be instantiated. |
| private AppleBuildRules() { } |
| |
| public static final ImmutableSet<BuildRuleType> XCODE_TARGET_BUILD_RULE_TYPES = |
| ImmutableSet.of( |
| AppleLibraryDescription.TYPE, |
| AppleBinaryDescription.TYPE, |
| AppleBundleDescription.TYPE, |
| AppleTestDescription.TYPE); |
| |
| private static final ImmutableSet<BuildRuleType> XCODE_TARGET_BUILD_RULE_TEST_TYPES = |
| ImmutableSet.of(AppleTestDescription.TYPE); |
| |
| private static final ImmutableSet<AppleBundleExtension> XCODE_TARGET_TEST_BUNDLE_EXTENSIONS = |
| ImmutableSet.of(AppleBundleExtension.OCTEST, AppleBundleExtension.XCTEST); |
| |
| /** |
| * Whether the build rule type is equivalent to some kind of Xcode target. |
| */ |
| public static boolean isXcodeTargetBuildRuleType(@Nullable BuildRuleType type) { |
| return XCODE_TARGET_BUILD_RULE_TYPES.contains(type); |
| } |
| |
| /** |
| * Whether the build rule type is a test target. |
| */ |
| public static boolean isXcodeTargetTestBuildRule(BuildRule rule) { |
| return XCODE_TARGET_BUILD_RULE_TEST_TYPES.contains(rule.getType()); |
| } |
| |
| /** |
| * Whether the bundle extension is a test bundle extension. |
| */ |
| public static boolean isXcodeTargetTestBundleExtension(AppleBundleExtension extension) { |
| return XCODE_TARGET_TEST_BUNDLE_EXTENSIONS.contains(extension); |
| } |
| |
| public static String getOutputFileNameFormatForLibrary(boolean isSharedLibrary) { |
| if (isSharedLibrary) { |
| return "lib%s.dylib"; |
| } else { |
| return "lib%s.a"; |
| } |
| } |
| |
| public enum RecursiveDependenciesMode { |
| /** |
| * Will traverse all rules that are built. |
| */ |
| BUILDING, |
| /** |
| * Will also not traverse the dependencies of bundles, as those are copied inside the bundle. |
| */ |
| COPYING, |
| /** |
| * Will also not traverse the dependencies of shared libraries, as those are linked already. |
| */ |
| LINKING, |
| } |
| |
| public static Iterable<TargetNode<?>> getRecursiveTargetNodeDependenciesOfTypes( |
| final TargetGraph targetGraph, |
| final RecursiveDependenciesMode mode, |
| final TargetNode<?> targetNode, |
| final Optional<ImmutableSet<BuildRuleType>> types) { |
| LOG.verbose( |
| "Getting recursive dependencies of node %s, mode %s, including only types %s\n", |
| targetNode, |
| mode, |
| types); |
| final ImmutableList.Builder<TargetNode<?>> filteredRules = ImmutableList.builder(); |
| AbstractAcyclicDepthFirstPostOrderTraversal<TargetNode<?>> traversal = |
| new AbstractAcyclicDepthFirstPostOrderTraversal<TargetNode<?>>() { |
| @Override |
| protected Iterator<TargetNode<?>> findChildren(TargetNode<?> node) throws IOException { |
| LOG.verbose("Finding children of node: %s", node); |
| ImmutableSortedSet.Builder<TargetNode<?>> defaultDepsBuilder = |
| ImmutableSortedSet.naturalOrder(); |
| ImmutableSortedSet.Builder<TargetNode<?>> exportedDepsBuilder = |
| ImmutableSortedSet.naturalOrder(); |
| addDirectAndExportedDeps(targetGraph, node, defaultDepsBuilder, exportedDepsBuilder); |
| ImmutableSortedSet<TargetNode<?>> defaultDeps = defaultDepsBuilder.build(); |
| ImmutableSortedSet<TargetNode<?>> exportedDeps = exportedDepsBuilder.build(); |
| |
| if (node.getType().equals(AppleBundleDescription.TYPE)) { |
| AppleBundleDescription.Arg arg = |
| (AppleBundleDescription.Arg) node.getConstructorArg(); |
| |
| ImmutableSortedSet.Builder<TargetNode<?>> editedDeps = |
| ImmutableSortedSet.naturalOrder(); |
| ImmutableSortedSet.Builder<TargetNode<?>> editedExportedDeps = |
| ImmutableSortedSet.naturalOrder(); |
| for (TargetNode<?> rule : defaultDeps) { |
| if (!rule.getBuildTarget().equals(arg.binary)) { |
| editedDeps.add(rule); |
| } else { |
| addDirectAndExportedDeps( |
| targetGraph, |
| targetGraph.get(arg.binary), |
| editedDeps, |
| editedExportedDeps); |
| } |
| } |
| |
| ImmutableSortedSet<TargetNode<?>> newDefaultDeps = editedDeps.build(); |
| ImmutableSortedSet<TargetNode<?>> newExportedDeps = editedExportedDeps.build(); |
| LOG.verbose( |
| "Transformed deps for bundle %s: %s -> %s, exported deps %s -> %s", |
| node, |
| defaultDeps, |
| newDefaultDeps, |
| exportedDeps, |
| newExportedDeps); |
| defaultDeps = newDefaultDeps; |
| exportedDeps = newExportedDeps; |
| } |
| |
| LOG.verbose("Default deps for node %s mode %s: %s", node, mode, defaultDeps); |
| if (!exportedDeps.isEmpty()) { |
| LOG.verbose("Exported deps for node %s mode %s: %s", node, mode, exportedDeps); |
| } |
| |
| ImmutableSortedSet<TargetNode<?>> deps = ImmutableSortedSet.of(); |
| |
| if (node != targetNode) { |
| switch (mode) { |
| case LINKING: |
| if (node.getType().equals(AppleLibraryDescription.TYPE)) { |
| if (AppleLibraryDescription.isSharedLibraryTarget(node.getBuildTarget())) { |
| deps = exportedDeps; |
| } else { |
| deps = defaultDeps; |
| } |
| } else if (node.getType().equals(AppleBundleDescription.TYPE)) { |
| deps = exportedDeps; |
| } else { |
| deps = defaultDeps; |
| } |
| break; |
| case COPYING: |
| if (node.getType().equals(AppleBundleDescription.TYPE)) { |
| deps = exportedDeps; |
| } else { |
| deps = defaultDeps; |
| } |
| break; |
| case BUILDING: |
| deps = defaultDeps; |
| break; |
| } |
| } else { |
| deps = defaultDeps; |
| } |
| |
| LOG.verbose("Walking children of node %s: %s", node, deps); |
| return deps.iterator(); |
| } |
| |
| @Override |
| protected void onNodeExplored(TargetNode<?> node) { |
| if (node != targetNode && |
| (!types.isPresent() || types.get().contains(node.getType()))) { |
| filteredRules.add(node); |
| } |
| } |
| |
| @Override |
| protected void onTraversalComplete(Iterable<TargetNode<?>> nodesInExplorationOrder) { |
| } |
| }; |
| try { |
| traversal.traverse(ImmutableList.of(targetNode)); |
| } catch (AbstractAcyclicDepthFirstPostOrderTraversal.CycleException | IOException | |
| InterruptedException e) { |
| // actual load failures and cycle exceptions should have been caught at an earlier stage |
| throw new RuntimeException(e); |
| } |
| ImmutableList<TargetNode<?>> result = filteredRules.build(); |
| LOG.verbose( |
| "Got recursive dependencies of node %s mode %s types %s: %s\n", |
| targetNode, |
| mode, |
| types, |
| result); |
| |
| return result; |
| } |
| |
| private static void addDirectAndExportedDeps( |
| TargetGraph targetGraph, |
| TargetNode<?> targetNode, |
| ImmutableSortedSet.Builder<TargetNode<?>> directDepsBuilder, |
| ImmutableSortedSet.Builder<TargetNode<?>> exportedDepsBuilder |
| ) { |
| directDepsBuilder.addAll(targetGraph.getAll(targetNode.getDeps())); |
| if (targetNode.getType() == AppleLibraryDescription.TYPE) { |
| AppleNativeTargetDescriptionArg arg = |
| (AppleNativeTargetDescriptionArg) targetNode.getConstructorArg(); |
| LOG.verbose("Exported deps of node %s: %s", targetNode, arg.exportedDeps.get()); |
| Iterable<TargetNode<?>> exportedNodes = targetGraph.getAll(arg.exportedDeps.get()); |
| directDepsBuilder.addAll(exportedNodes); |
| exportedDepsBuilder.addAll(exportedNodes); |
| } |
| } |
| |
| public static ImmutableSet<TargetNode<?>> getSchemeBuildableTargetNodes( |
| TargetGraph targetGraph, |
| TargetNode<?> targetNode) { |
| Iterable<TargetNode<?>> targetNodes = Iterables.concat( |
| getRecursiveTargetNodeDependenciesOfTypes( |
| targetGraph, |
| RecursiveDependenciesMode.BUILDING, |
| targetNode, |
| Optional.<ImmutableSet<BuildRuleType>>absent()), |
| ImmutableSet.of(targetNode)); |
| |
| return ImmutableSet.copyOf( |
| Iterables.filter( |
| targetNodes, |
| new Predicate<TargetNode<?>>() { |
| @Override |
| public boolean apply(TargetNode<?> input) { |
| return isXcodeTargetBuildRuleType(input.getType()); |
| } |
| })); |
| } |
| |
| /** |
| * Given a list of nodes, return AppleTest nodes that can be grouped with other tests. |
| */ |
| public static ImmutableSet<TargetNode<AppleTestDescription.Arg>> filterGroupableTests( |
| Iterable<TargetNode<?>> tests) { |
| ImmutableSet.Builder<TargetNode<AppleTestDescription.Arg>> builder = ImmutableSet.builder(); |
| for (TargetNode<?> node : tests) { |
| Optional<TargetNode<AppleTestDescription.Arg>> testNode = |
| node.castArg(AppleTestDescription.Arg.class); |
| if (testNode.isPresent() && testNode.get().getConstructorArg().canGroup()) { |
| builder.add(testNode.get()); |
| } |
| } |
| return builder.build(); |
| } |
| } |