com.facebook.buck.android.AndroidTransitiveDependencyGraph.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.buck.android.AndroidTransitiveDependencyGraph.java

Source

/*
 * 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.DefaultJavaLibrary;
import com.facebook.buck.java.JavaLibrary;
import com.facebook.buck.java.PrebuiltJar;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.rules.AbstractDependencyVisitor;
import com.facebook.buck.rules.BuildRule;
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.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Sets;
import com.google.common.hash.HashCode;

import java.nio.file.Path;
import java.util.Map;
import java.util.Set;

public class AndroidTransitiveDependencyGraph {

    private final ImmutableSortedSet<BuildRule> rulesToTraverseForTransitiveDeps;

    /**
     * @param deps A set of dependencies for a {@link BuildRule}, presumably one that is in the
     *     process of being constructed via its builder.
     */
    AndroidTransitiveDependencyGraph(ImmutableSortedSet<BuildRule> deps) {
        this.rulesToTraverseForTransitiveDeps = Preconditions.checkNotNull(deps);
    }

    /**
     * @param uberRDotJava that may have produced {@code R.class} files that need to be dexed.
     */
    public AndroidDexTransitiveDependencies findDexDependencies(
            ImmutableList<HasAndroidResourceDeps> androidResourceDeps,
            final ImmutableSet<JavaLibrary> buildRulesToExcludeFromDex, final UberRDotJava uberRDotJava) {
        // 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<Path> pathsToDexBuilder = ImmutableSet.builder();

        // Paths to the classfiles to not dex.
        final ImmutableSet.Builder<Path> 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<Path> pathsToThirdPartyJarsBuilder = ImmutableSet.builder();

        AndroidResourceDetails details = findAndroidResourceDetails(androidResourceDeps);

        // Update pathsToDex.
        final ImmutableSetMultimap<JavaLibrary, Path> classpath = Classpaths
                .getClasspathEntries(rulesToTraverseForTransitiveDeps);
        for (Map.Entry<JavaLibrary, Path> entry : classpath.entries()) {
            if (!buildRulesToExcludeFromDex.contains(entry.getKey())) {
                pathsToDexBuilder.add(entry.getValue());
            } else {
                noDxPathsBuilder.add(entry.getValue());
            }
        }
        // Include the directory of compiled R.java files on the classpath.
        final ImmutableSet<String> rDotJavaPackages = details.rDotJavaPackages;
        if (!rDotJavaPackages.isEmpty()) {
            Path pathToCompiledRDotJavaFiles = uberRDotJava.getPathToCompiledRDotJavaFiles();
            pathsToDexBuilder.add(pathToCompiledRDotJavaFiles);
        }

        ImmutableSet<Path> noDxPaths = noDxPathsBuilder.build();

        // Filter out the classpath entries to exclude from dex'ing, if appropriate
        Set<Path> classpathEntries = Sets.difference(pathsToDexBuilder.build(), noDxPaths);

        Supplier<Map<String, HashCode>> classNamesToHashesSupplier = Suppliers
                .memoize(new Supplier<Map<String, HashCode>>() {
                    @Override
                    public Map<String, HashCode> get() {
                        ImmutableMap.Builder<String, HashCode> builder = ImmutableMap.builder();
                        for (JavaLibrary javaLibrary : classpath.keySet()) {
                            if (!buildRulesToExcludeFromDex.contains(javaLibrary)) {
                                builder.putAll(javaLibrary.getClassNamesToHashes());
                            }
                        }
                        if (!rDotJavaPackages.isEmpty()) {
                            builder.putAll(uberRDotJava.getClassNamesToHashes());
                        }
                        return builder.build();
                    }
                });

        // Visit all of the transitive dependencies to populate the above collections.
        new AbstractDependencyVisitor(rulesToTraverseForTransitiveDeps) {
            @Override
            public ImmutableSet<BuildRule> 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 PrebuiltJar) {
                    PrebuiltJar prebuiltJar = (PrebuiltJar) rule.getBuildable();
                    pathsToThirdPartyJarsBuilder.add(prebuiltJar.getBinaryJar());
                }
                return maybeVisitAllDeps(rule, rule.getProperties().is(LIBRARY));
            }
        }.start();

        // 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<Path> pathsToThirdPartyJars = Sets.difference(pathsToThirdPartyJarsBuilder.build(), noDxPaths);

        return new AndroidDexTransitiveDependencies(classpathEntries, pathsToThirdPartyJars, noDxPaths,
                classNamesToHashesSupplier);
    }

    public AndroidResourceDetails findAndroidResourceDetails(
            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 AndroidResourceDetails(androidResourceDeps);
    }

    public AndroidTransitiveDependencies findDependencies() {

        // Paths to assets/ directories that should be included in the final APK.
        final ImmutableSet.Builder<Path> 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<Path> 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<Path> nativeLibAssetsDirectories = ImmutableSet.builder();

        final ImmutableSet.Builder<BuildTarget> nativeTargetsWithAssets = ImmutableSet.builder();

        // Path to the module's manifest file
        final ImmutableSet.Builder<Path> manifestFiles = ImmutableSet.builder();

        // Path to the module's proguard_config
        final ImmutableSet.Builder<Path> proguardConfigs = ImmutableSet.builder();

        // Visit all of the transitive dependencies to populate the above collections.
        new AbstractDependencyVisitor(rulesToTraverseForTransitiveDeps) {
            @Override
            public ImmutableSet<BuildRule> 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());
                        nativeTargetsWithAssets.add(rule.getBuildTarget());
                    } 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 rule.getDeps();
                    }
                } else if (rule.getBuildable() instanceof AndroidResource) {
                    AndroidResource androidRule = (AndroidResource) rule.getBuildable();
                    Path assetsDirectory = androidRule.getAssets();
                    if (assetsDirectory != null) {
                        assetsDirectories.add(assetsDirectory);
                    }
                    Path manifestFile = androidRule.getManifestFile();
                    if (manifestFile != null) {
                        manifestFiles.add(manifestFile);
                    }
                } else if (rule.getBuildable() instanceof DefaultJavaLibrary) {
                    DefaultJavaLibrary defaultJavaLibrary = (DefaultJavaLibrary) rule.getBuildable();
                    Optionals.addIfPresent(defaultJavaLibrary.getProguardConfig(), proguardConfigs);

                    if (rule.getBuildable() instanceof AndroidLibrary) {
                        AndroidLibrary androidLibraryRule = (AndroidLibrary) rule.getBuildable();
                        Optionals.addIfPresent(androidLibraryRule.getManifestFile(), manifestFiles);
                    }
                }
                return maybeVisitAllDeps(rule, rule.getProperties().is(LIBRARY));
            }
        }.start();

        return new AndroidTransitiveDependencies(nativeLibsDirectories.build(), nativeLibAssetsDirectories.build(),
                assetsDirectories.build(), nativeTargetsWithAssets.build(), manifestFiles.build(),
                proguardConfigs.build());
    }
}