/* * 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.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.Sets; import com.google.common.hash.HashCode; import java.util.HashMap; 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 = new HashMap<>(); 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.AndroidInstrumentationApkDescriptionArg#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(HasJavaClassHashes::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(); } }