blob: 4ca46c305c21694964eaea322cbef8d799becec3 [file] [log] [blame]
/*
* Copyright 2012-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.android;
import static com.facebook.buck.rules.BuildableProperties.Kind.LIBRARY;
import com.facebook.buck.java.Classpaths;
import com.facebook.buck.java.DefaultJavaLibraryRule;
import com.facebook.buck.java.JavaLibraryRule;
import com.facebook.buck.java.PrebuiltJarRule;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.rules.AbstractDependencyVisitor;
import com.facebook.buck.rules.BuildRule;
import com.facebook.buck.rules.Buildable;
import com.facebook.buck.rules.DependencyGraph;
import com.facebook.buck.util.Optionals;
import com.google.common.base.Preconditions;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Sets;
import java.util.Map;
import java.util.Set;
public class AndroidTransitiveDependencyGraph {
private final Supplier<ImmutableSortedSet<BuildRule>> rulesToTraverseForTransitiveDeps;
private final BuildTarget buildTargetForTheRootOfTheTraversal;
public AndroidTransitiveDependencyGraph(final AndroidBinaryRule buildRule) {
// Because AndroidBinaryRule#getDeps() includes items such as its keystore, we use
// AndroidBinaryRule#getClasspathDeps() instead to ensure we only traverse the rules that
// the user specified as the "deps" argument in the build file.
this(buildRule.getClasspathDeps(), buildRule.getBuildTarget());
}
/**
* As we move towards a world designed around {@link Buildable}s rather than {@link BuildRule}s,
* we will likely need to specify the {@link BuildTarget} independently.
*/
private AndroidTransitiveDependencyGraph(ImmutableSortedSet<BuildRule> deps,
BuildTarget buildTarget) {
this.rulesToTraverseForTransitiveDeps = Suppliers.ofInstance(Preconditions.checkNotNull(deps));
this.buildTargetForTheRootOfTheTraversal = Preconditions.checkNotNull(buildTarget);
}
public static AndroidTransitiveDependencyGraph createForAndroidManifest(
AndroidManifest androidManifest, DependencyGraph graph) {
BuildTarget buildTarget = androidManifest.getBuildTarget();
BuildRule rule = graph.findBuildRuleByTarget(buildTarget);
return new AndroidTransitiveDependencyGraph(rule.getDeps(), buildTarget);
}
public AndroidDexTransitiveDependencies findDexDependencies(
ImmutableList<HasAndroidResourceDeps> androidResourceDeps,
ImmutableSet<BuildRule> buildRulesToExcludeFromDex) {
// These are paths that will be dex'ed. They may be either directories of compiled .class files,
// or paths to compiled JAR files.
final ImmutableSet.Builder<String> pathsToDexBuilder = ImmutableSet.builder();
// Paths to the classfiles to not dex.
final ImmutableSet.Builder<String> noDxPathsBuilder = ImmutableSet.builder();
// These are paths to third-party jars that may contain resources that must be included in the
// final APK.
final ImmutableSet.Builder<String> pathsToThirdPartyJarsBuilder = ImmutableSet.builder();
UberRDotJavaUtil.AndroidResourceDetails details =
createAndroidResourceDetails(androidResourceDeps);
// Update pathsToDex.
ImmutableSet<Map.Entry<JavaLibraryRule, String>> classpath =
Classpaths.getClasspathEntries(rulesToTraverseForTransitiveDeps.get()).entries();
for (Map.Entry<JavaLibraryRule, String> entry : classpath) {
if (!buildRulesToExcludeFromDex.contains(entry.getKey())) {
pathsToDexBuilder.add(entry.getValue());
} else {
noDxPathsBuilder.add(entry.getValue());
}
}
// Visit all of the transitive dependencies to populate the above collections.
new AbstractDependencyVisitor(rulesToTraverseForTransitiveDeps.get()) {
@Override
public boolean visit(BuildRule rule) {
// We need to include the transitive closure of the compiled .class files when dex'ing, as
// well as the third-party jars that they depend on.
// Update pathsToThirdPartyJars.
if (rule instanceof PrebuiltJarRule) {
PrebuiltJarRule prebuiltJarRule = (PrebuiltJarRule) rule;
pathsToThirdPartyJarsBuilder.add(prebuiltJarRule.getBinaryJar());
}
return rule.getProperties().is(LIBRARY);
}
}.start();
// Include the directory of compiled R.java files on the classpath.
ImmutableSet<String> rDotJavaPackages = details.rDotJavaPackages;
if (!rDotJavaPackages.isEmpty()) {
pathsToDexBuilder.add(UberRDotJavaUtil.getPathToCompiledRDotJavaFiles(buildTargetForTheRootOfTheTraversal));
}
ImmutableSet<String> noDxPaths = noDxPathsBuilder.build();
// Filter out the classpath entries to exclude from dex'ing, if appropriate
Set<String> classpathEntries = Sets.difference(pathsToDexBuilder.build(), noDxPaths);
// Classpath entries that should be excluded from dexing should also be excluded from
// pathsToThirdPartyJars because their resources should not end up in main APK. If they do,
// the pre-dexed library may try to load a resource from the main APK rather than from within
// the pre-dexed library (even though the resource is available in both locations). This
// causes a significant performance regression, as the resource may take more than one second
// longer to load.
Set<String> pathsToThirdPartyJars =
Sets.difference(pathsToThirdPartyJarsBuilder.build(), noDxPaths);
return new AndroidDexTransitiveDependencies(classpathEntries,
pathsToThirdPartyJars,
noDxPaths);
}
public AndroidTransitiveDependencies findDependencies(
ImmutableList<HasAndroidResourceDeps> androidResourceDeps) {
// Paths to assets/ directories that should be included in the final APK.
final ImmutableSet.Builder<String> assetsDirectories = ImmutableSet.builder();
// Paths to native libs directories (often named libs/) that should be included as raw files
// directories in the final APK.
final ImmutableSet.Builder<String> nativeLibsDirectories = ImmutableSet.builder();
// Paths to native libs directories that are to be treated as assets and so should be included
// as raw files under /assets/lib/ directory in the APK.
final ImmutableSet.Builder<String> nativeLibAssetsDirectories = ImmutableSet.builder();
// Path to the module's manifest file
final ImmutableSet.Builder<String> manifestFiles = ImmutableSet.builder();
// Path to the module's proguard_config
final ImmutableSet.Builder<String> proguardConfigs = ImmutableSet.builder();
UberRDotJavaUtil.AndroidResourceDetails details =
createAndroidResourceDetails(androidResourceDeps);
// Visit all of the transitive dependencies to populate the above collections.
new AbstractDependencyVisitor(rulesToTraverseForTransitiveDeps.get()) {
@Override
public boolean visit(BuildRule rule) {
// We need to include the transitive closure of the compiled .class files when dex'ing, as
// well as the third-party jars that they depend on.
// Update pathsToThirdPartyJars.
if (rule.getBuildable() instanceof NativeLibraryBuildable) {
NativeLibraryBuildable nativeLibraryRule = (NativeLibraryBuildable) rule.getBuildable();
if (nativeLibraryRule.isAsset()) {
nativeLibAssetsDirectories.add(nativeLibraryRule.getLibraryPath());
} else {
nativeLibsDirectories.add(nativeLibraryRule.getLibraryPath());
}
// In the rare event that a PrebuiltNativeLibraryBuildRule has deps, it is likely another
// NativeLibraryRule that will need to be included in the final APK, so traverse the deps.
if (rule.getBuildable() instanceof PrebuiltNativeLibrary) {
return true;
}
} else if (rule instanceof AndroidResourceRule) {
AndroidResourceRule androidRule = (AndroidResourceRule) rule;
String assetsDirectory = androidRule.getAssets();
if (assetsDirectory != null) {
assetsDirectories.add(assetsDirectory);
}
String manifestFile = androidRule.getManifestFile();
if (manifestFile != null) {
manifestFiles.add(manifestFile);
}
} else if (rule instanceof DefaultJavaLibraryRule) {
DefaultJavaLibraryRule defaultJavaLibraryRule = (DefaultJavaLibraryRule)rule;
Optionals.addIfPresent(defaultJavaLibraryRule.getProguardConfig(), proguardConfigs);
if (rule instanceof AndroidLibraryRule) {
AndroidLibraryRule androidLibraryRule = (AndroidLibraryRule)rule;
Optionals.addIfPresent(androidLibraryRule.getManifestFile(), manifestFiles);
}
}
return rule.getProperties().is(LIBRARY);
}
}.start();
return new AndroidTransitiveDependencies(assetsDirectories.build(),
nativeLibsDirectories.build(),
nativeLibAssetsDirectories.build(),
manifestFiles.build(),
details.resDirectories,
details.rDotJavaPackages,
proguardConfigs.build());
}
private UberRDotJavaUtil.AndroidResourceDetails createAndroidResourceDetails(
ImmutableList<HasAndroidResourceDeps> androidResourceDeps) {
// This is not part of the AbstractDependencyVisitor traversal because
// AndroidResourceRule.getAndroidResourceDeps() does a topological sort whereas
// AbstractDependencyVisitor does only a breadth-first search.
return new UberRDotJavaUtil.AndroidResourceDetails(androidResourceDeps);
}
}