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

Java tutorial

Introduction

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

Source

/*
 * 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.android;

import com.facebook.buck.android.AndroidPackageableCollection.ResourceDetails;
import com.facebook.buck.cxx.NativeLinkable;
import com.facebook.buck.jvm.core.HasJavaClassHashes;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.HasBuildTarget;
import com.facebook.buck.rules.BuildRule;
import com.facebook.buck.rules.SourcePath;
import com.facebook.buck.rules.TargetGraph;
import com.facebook.buck.rules.coercer.BuildConfigFields;
import com.facebook.buck.util.HumanReadableException;
import com.facebook.buck.util.MoreCollectors;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Suppliers;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.hash.HashCode;

import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

public class AndroidPackageableCollector {

    private final AndroidPackageableCollection.Builder collectionBuilder = AndroidPackageableCollection.builder();

    private final ResourceDetails.Builder resourceDetailsBuilder = ResourceDetails.builder();

    private final ImmutableList.Builder<BuildTarget> resourcesWithNonEmptyResDir = ImmutableList.builder();
    private final ImmutableList.Builder<BuildTarget> resourcesWithAssets = ImmutableList.builder();
    private final ImmutableList.Builder<SourcePath> resourceDirectories = ImmutableList.builder();

    // Map is used instead of ImmutableMap.Builder for its containsKey() method.
    private final Map<String, BuildConfigFields> buildConfigs = Maps.newHashMap();

    private final ImmutableSet.Builder<HasJavaClassHashes> javaClassHashesProviders = ImmutableSet.builder();

    private final BuildTarget collectionRoot;
    private final ImmutableSet<BuildTarget> buildTargetsToExcludeFromDex;
    private final ImmutableSet<BuildTarget> resourcesToExclude;
    private final APKModuleGraph apkModuleGraph;

    @VisibleForTesting
    AndroidPackageableCollector(BuildTarget collectionRoot) {
        this(collectionRoot, ImmutableSet.of(), ImmutableSet.of(),
                new APKModuleGraph(TargetGraph.EMPTY, collectionRoot, Optional.empty()));
    }

    /**
     * @param resourcesToExclude Only relevant to {@link AndroidInstrumentationApk} which needs to
     *     remove resources that are already included in the
     *     {@link AndroidInstrumentationApkDescription.Arg#apk}
     */
    public AndroidPackageableCollector(BuildTarget collectionRoot,
            ImmutableSet<BuildTarget> buildTargetsToExcludeFromDex, ImmutableSet<BuildTarget> resourcesToExclude,
            APKModuleGraph apkModuleGraph) {
        this.collectionRoot = collectionRoot;
        this.buildTargetsToExcludeFromDex = buildTargetsToExcludeFromDex;
        this.resourcesToExclude = resourcesToExclude;
        this.apkModuleGraph = apkModuleGraph;
    }

    public void addPackageables(Iterable<AndroidPackageable> packageables) {
        Set<AndroidPackageable> explored = Sets.newHashSet();

        for (AndroidPackageable packageable : packageables) {
            postOrderTraverse(packageable, explored);
        }
    }

    private void postOrderTraverse(AndroidPackageable packageable, Set<AndroidPackageable> explored) {
        if (explored.contains(packageable)) {
            return;
        }
        explored.add(packageable);

        for (AndroidPackageable dep : packageable.getRequiredPackageables()) {
            postOrderTraverse(dep, explored);
        }
        packageable.addToCollector(this);
    }

    /**
     * Returns all {@link BuildRule}s of the given rules that are {@link AndroidPackageable}.
     * Helper for implementations of AndroidPackageable that just want to return all of their
     * packagable dependencies.
     */
    public static Iterable<AndroidPackageable> getPackageableRules(Iterable<BuildRule> rules) {
        return FluentIterable.from(rules).filter(AndroidPackageable.class).toList();
    }

    public AndroidPackageableCollector addStringWhitelistedResourceDirectory(BuildTarget owner,
            SourcePath resourceDir) {
        if (resourcesToExclude.contains(owner)) {
            return this;
        }

        resourceDetailsBuilder.addWhitelistedStringDirectories(resourceDir);
        doAddResourceDirectory(owner, resourceDir);
        return this;
    }

    public AndroidPackageableCollector addResourceDirectory(BuildTarget owner, SourcePath resourceDir) {
        if (resourcesToExclude.contains(owner)) {
            return this;
        }

        doAddResourceDirectory(owner, resourceDir);
        return this;
    }

    private void doAddResourceDirectory(BuildTarget owner, SourcePath resourceDir) {
        resourcesWithNonEmptyResDir.add(owner);
        resourceDirectories.add(resourceDir);
    }

    public AndroidPackageableCollector addNativeLibsDirectory(BuildTarget owner, SourcePath nativeLibDir) {
        APKModule module = apkModuleGraph.findModuleForTarget(owner);
        collectionBuilder.putNativeLibsTargets(module, owner);
        if (module.isRootModule()) {
            collectionBuilder.putNativeLibsDirectories(module, nativeLibDir);
        } else {
            collectionBuilder.putNativeLibAssetsDirectories(module, nativeLibDir);
        }
        return this;
    }

    public AndroidPackageableCollector addNativeLinkable(NativeLinkable nativeLinkable) {
        APKModule module = apkModuleGraph.findModuleForTarget(nativeLinkable.getBuildTarget());
        if (module.isRootModule()) {
            collectionBuilder.putNativeLinkables(module, nativeLinkable);
        } else {
            collectionBuilder.putNativeLinkablesAssets(module, nativeLinkable);
        }
        return this;
    }

    public AndroidPackageableCollector addNativeLinkableAsset(NativeLinkable nativeLinkable) {
        APKModule module = apkModuleGraph.findModuleForTarget(nativeLinkable.getBuildTarget());
        collectionBuilder.putNativeLinkablesAssets(module, nativeLinkable);
        return this;
    }

    public AndroidPackageableCollector addNativeLibAssetsDirectory(BuildTarget owner, SourcePath assetsDir) {
        // We need to build the native target in order to have the assets available still.
        APKModule module = apkModuleGraph.findModuleForTarget(owner);
        collectionBuilder.putNativeLibsTargets(module, owner);
        collectionBuilder.putNativeLibAssetsDirectories(module, assetsDir);
        return this;
    }

    public AndroidPackageableCollector addAssetsDirectory(BuildTarget owner, SourcePath assetsDirectory) {
        if (resourcesToExclude.contains(owner)) {
            return this;
        }

        resourcesWithAssets.add(owner);
        collectionBuilder.addAssetsDirectories(assetsDirectory);
        return this;
    }

    public AndroidPackageableCollector addProguardConfig(BuildTarget owner, SourcePath proguardConfig) {
        if (!buildTargetsToExcludeFromDex.contains(owner)) {
            collectionBuilder.addProguardConfigs(proguardConfig);
        }
        return this;
    }

    public AndroidPackageableCollector addClasspathEntry(HasJavaClassHashes hasJavaClassHashes,
            SourcePath classpathEntry) {
        BuildTarget target = hasJavaClassHashes.getBuildTarget();
        if (buildTargetsToExcludeFromDex.contains(target)) {
            collectionBuilder.addNoDxClasspathEntries(classpathEntry);
        } else {
            collectionBuilder.addClasspathEntriesToDex(classpathEntry);
            APKModule module = apkModuleGraph.findModuleForTarget(target);
            collectionBuilder.putModuleMappedClasspathEntriesToDex(module, classpathEntry);
            javaClassHashesProviders.add(hasJavaClassHashes);
        }
        return this;
    }

    public AndroidPackageableCollector addPathToThirdPartyJar(BuildTarget owner, SourcePath pathToThirdPartyJar) {
        if (buildTargetsToExcludeFromDex.contains(owner)) {
            collectionBuilder.addNoDxClasspathEntries(pathToThirdPartyJar);
        } else {
            collectionBuilder.addPathsToThirdPartyJars(pathToThirdPartyJar);
        }
        return this;
    }

    public void addBuildConfig(String javaPackage, BuildConfigFields constants) {
        if (buildConfigs.containsKey(javaPackage)) {
            throw new HumanReadableException(
                    "Multiple android_build_config() rules with the same package %s in the "
                            + "transitive deps of %s.",
                    javaPackage, collectionRoot);
        }
        buildConfigs.put(javaPackage, constants);
    }

    public AndroidPackageableCollection build() {
        collectionBuilder.setBuildConfigs(ImmutableMap.copyOf(buildConfigs));
        final ImmutableSet<HasJavaClassHashes> javaClassProviders = javaClassHashesProviders.build();
        collectionBuilder.addAllJavaLibrariesToDex(javaClassProviders.stream().map(HasBuildTarget::getBuildTarget)
                .collect(MoreCollectors.toImmutableSet()));
        collectionBuilder.setClassNamesToHashesSupplier(Suppliers.memoize(() -> {

            ImmutableMap.Builder<String, HashCode> builder = ImmutableMap.builder();
            for (HasJavaClassHashes hasJavaClassHashes : javaClassProviders) {
                builder.putAll(hasJavaClassHashes.getClassNamesToHashes());
            }
            return builder.build();
        }));

        ImmutableSet<BuildTarget> resources = ImmutableSet.copyOf(resourcesWithNonEmptyResDir.build());
        for (BuildTarget buildTarget : resourcesWithAssets.build()) {
            if (!resources.contains(buildTarget)) {
                resourceDetailsBuilder.addResourcesWithEmptyResButNonEmptyAssetsDir(buildTarget);
            }
        }

        // Reverse the resource directories/targets collections because we perform a post-order
        // traversal of the action graph, and we need to return these collections topologically
        // sorted.
        resourceDetailsBuilder.setResourceDirectories(
                resourceDirectories.build().reverse().stream().distinct().collect(Collectors.toList()));
        resourceDetailsBuilder.setResourcesWithNonEmptyResDir(resourcesWithNonEmptyResDir.build().reverse());

        collectionBuilder.setResourceDetails(resourceDetailsBuilder.build());
        return collectionBuilder.build();
    }
}