blob: c84663a9835ea31958ee5e26760d1a436f56dacc [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 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.rules.AbstractDependencyVisitor;
import com.facebook.buck.rules.BuildRule;
import com.facebook.buck.rules.NativeLibraryRule;
import com.facebook.buck.util.Optionals;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.util.Map;
import java.util.Set;
public class AndroidTransitiveDependencyGraph {
private BuildRule buildRule;
public AndroidTransitiveDependencyGraph(BuildRule buildRule) {
this.buildRule = Preconditions.checkNotNull(buildRule);
}
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(buildRule.getDeps()).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(buildRule) {
@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());
}
// AbstractDependencyVisitor will start from this (AndroidBinaryRule) so make sure it
// descends to its dependencies even though it is not a library rule.
return rule.isLibrary() || rule == buildRule;
}
}.start();
// Include the directory of compiled R.java files on the classpath.
ImmutableSet<String> rDotJavaPackages = details.rDotJavaPackages;
if (!rDotJavaPackages.isEmpty()) {
pathsToDexBuilder.add(UberRDotJavaUtil.getPathToCompiledRDotJavaFiles(
buildRule.getBuildTarget()));
}
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(buildRule) {
@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 NativeLibraryRule) {
NativeLibraryRule nativeLibraryRule = (NativeLibraryRule)rule;
if (nativeLibraryRule.isAsset()) {
nativeLibAssetsDirectories.add(nativeLibraryRule.getLibraryPath());
} else {
nativeLibsDirectories.add(nativeLibraryRule.getLibraryPath());
}
} 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);
}
}
// AbstractDependencyVisitor will start from this (AndroidBinaryRule) so make sure it
// descends to its dependencies even though it is not a library rule.
return rule.isLibrary() || rule == buildRule;
}
}.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);
}
}