Java tutorial
/* * 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.ANDROID; import static com.facebook.buck.rules.BuildableProperties.Kind.PACKAGING; import com.android.common.SdkConstants; import com.facebook.buck.android.FilterResourcesStep.ResourceFilter; import com.facebook.buck.dalvik.ZipSplitter; import com.facebook.buck.java.Classpaths; import com.facebook.buck.java.HasClasspathEntries; import com.facebook.buck.java.JavaLibraryRule; import com.facebook.buck.java.Keystore; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.BuildTargetPattern; import com.facebook.buck.rules.AbstractBuildRuleBuilder; import com.facebook.buck.rules.AbstractBuildRuleBuilderParams; import com.facebook.buck.rules.BuildContext; import com.facebook.buck.rules.BuildRule; import com.facebook.buck.rules.BuildRuleParams; import com.facebook.buck.rules.BuildRuleResolver; import com.facebook.buck.rules.BuildRuleType; import com.facebook.buck.rules.Buildable; import com.facebook.buck.rules.BuildableContext; import com.facebook.buck.rules.BuildableProperties; import com.facebook.buck.rules.DoNotUseAbstractBuildable; import com.facebook.buck.rules.InstallableBuildRule; import com.facebook.buck.rules.RuleKey; import com.facebook.buck.rules.SourcePath; import com.facebook.buck.rules.SourcePaths; import com.facebook.buck.shell.AbstractGenruleStep; import com.facebook.buck.shell.EchoStep; import com.facebook.buck.shell.SymlinkFilesIntoDirectoryStep; import com.facebook.buck.step.ExecutionContext; import com.facebook.buck.step.Step; import com.facebook.buck.step.fs.CopyStep; import com.facebook.buck.step.fs.MakeCleanDirectoryStep; import com.facebook.buck.step.fs.MkdirAndSymlinkFileStep; import com.facebook.buck.step.fs.MkdirStep; import com.facebook.buck.util.BuckConstant; import com.facebook.buck.util.DefaultDirectoryTraverser; import com.facebook.buck.util.DirectoryTraversal; import com.facebook.buck.util.DirectoryTraverser; import com.facebook.buck.util.HumanReadableException; import com.facebook.buck.util.MorePaths; import com.facebook.buck.util.Optionals; import com.facebook.buck.zip.RepackZipEntriesStep; import com.facebook.buck.zip.ZipDirectoryWithMaxDeflateStep; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Predicates; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableBiMap; 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.Iterables; import com.google.common.io.Files; import java.io.File; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collections; import java.util.EnumSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; /** * <pre> * android_binary( * name = 'messenger', * manifest = 'AndroidManifest.xml', * target = 'Google Inc.:Google APIs:16', * deps = [ * '//src/com/facebook/messenger:messenger_library', * ], * ) * </pre> */ public class AndroidBinaryRule extends DoNotUseAbstractBuildable implements HasAndroidPlatformTarget, HasClasspathEntries, InstallableBuildRule { private final static BuildableProperties PROPERTIES = new BuildableProperties(ANDROID, PACKAGING); /** * The largest file size Froyo will deflate. */ private final long FROYO_DEFLATE_LIMIT_BYTES = 1 << 20; /** * This list of package types is taken from the set of targets that the default build.xml provides * for Android projects. * <p> * Note: not all package types are supported. If unsupported, will be treated as "DEBUG". */ static enum PackageType { DEBUG, INSTRUMENTED, RELEASE, TEST,; /** * @return true if ProGuard should be used to obfuscate the output */ private final boolean isBuildWithObfuscation() { return this == RELEASE; } private final boolean isCrunchPngFiles() { return this == RELEASE; } } static enum TargetCpuType { ARM, ARMV7, X86, MIPS, } static enum ResourceCompressionMode { DISABLED(/* isCompressResources */ false, /* isStoreStringsAsAssets */ false), ENABLED( /* isCompressResources */ true, /* isStoreStringsAsAssets */ false), ENABLED_WITH_STRINGS_AS_ASSETS( /* isCompressResources */ true, /* isStoreStringsAsAssets */ true),; private final boolean isCompressResources; private final boolean isStoreStringsAsAssets; private ResourceCompressionMode(boolean isCompressResources, boolean isStoreStringsAsAssets) { this.isCompressResources = isCompressResources; this.isStoreStringsAsAssets = isStoreStringsAsAssets; } public boolean isCompressResources() { return isCompressResources; } public boolean isStoreStringsAsAssets() { return isStoreStringsAsAssets; } } private final SourcePath manifest; private final String target; private final ImmutableSortedSet<BuildRule> classpathDeps; private final Keystore keystore; private final PackageType packageType; private final ImmutableSortedSet<BuildRule> buildRulesToExcludeFromDex; private DexSplitMode dexSplitMode; private final boolean useAndroidProguardConfigWithOptimizations; private final Optional<SourcePath> proguardConfig; private final ResourceCompressionMode resourceCompressionMode; private final ImmutableSet<String> primaryDexSubstrings; private final long linearAllocHardLimit; /** * File that whitelists the class files that should be in the primary dex. * <p> * Values in this file must match JAR entries exactly, so they should contain path separators. * For example: * <pre> * com/google/common/collect/ImmutableSet.class * </pre> */ private final Optional<SourcePath> primaryDexClassesFile; private final FilterResourcesStep.ResourceFilter resourceFilter; private final ImmutableSet<TargetCpuType> cpuFilters; private final ImmutableSet<IntermediateDexRule> preDexDeps; private final ImmutableSortedSet<BuildRule> preprocessJavaClassesDeps; private final Optional<String> preprocessJavaClassesBash; private final AndroidTransitiveDependencyGraph transitiveDependencyGraph; /** This path is guaranteed to end with a slash. */ private final String outputGenDirectory; /** * @param target the Android platform version to target, e.g., "Google Inc.:Google APIs:16". You * can find the list of valid values on your system by running * {@code android list targets --compact}. */ protected AndroidBinaryRule(BuildRuleParams buildRuleParams, SourcePath manifest, String target, ImmutableSortedSet<BuildRule> classpathDeps, Keystore keystore, PackageType packageType, Set<BuildRule> buildRulesToExcludeFromDex, DexSplitMode dexSplitMode, boolean useAndroidProguardConfigWithOptimizations, Optional<SourcePath> proguardConfig, ResourceCompressionMode resourceCompressionMode, Set<String> primaryDexSubstrings, long linearAllocHardLimit, Optional<SourcePath> primaryDexClassesFile, FilterResourcesStep.ResourceFilter resourceFilter, Set<TargetCpuType> cpuFilters, Set<IntermediateDexRule> preDexDeps, Set<BuildRule> preprocessJavaClassesDeps, Optional<String> preprocessJavaClassesBash) { super(buildRuleParams); this.manifest = Preconditions.checkNotNull(manifest); this.target = Preconditions.checkNotNull(target); this.classpathDeps = ImmutableSortedSet.copyOf(classpathDeps); this.keystore = Preconditions.checkNotNull(keystore); this.packageType = Preconditions.checkNotNull(packageType); this.buildRulesToExcludeFromDex = ImmutableSortedSet.copyOf(buildRulesToExcludeFromDex); this.dexSplitMode = Preconditions.checkNotNull(dexSplitMode); this.useAndroidProguardConfigWithOptimizations = useAndroidProguardConfigWithOptimizations; this.proguardConfig = Preconditions.checkNotNull(proguardConfig); this.resourceCompressionMode = Preconditions.checkNotNull(resourceCompressionMode); this.primaryDexSubstrings = ImmutableSet.copyOf(primaryDexSubstrings); this.linearAllocHardLimit = linearAllocHardLimit; this.primaryDexClassesFile = Preconditions.checkNotNull(primaryDexClassesFile); this.outputGenDirectory = String.format("%s/%s", BuckConstant.GEN_DIR, getBuildTarget().getBasePathWithSlash()); this.resourceFilter = Preconditions.checkNotNull(resourceFilter); this.cpuFilters = ImmutableSet.copyOf(cpuFilters); this.preDexDeps = ImmutableSet.copyOf(preDexDeps); this.preprocessJavaClassesDeps = ImmutableSortedSet.copyOf(preprocessJavaClassesDeps); this.preprocessJavaClassesBash = Preconditions.checkNotNull(preprocessJavaClassesBash); this.transitiveDependencyGraph = new AndroidTransitiveDependencyGraph(this); } @Override public BuildRuleType getType() { return BuildRuleType.ANDROID_BINARY; } @Override public BuildableProperties getProperties() { return PROPERTIES; } @Override public String getAndroidPlatformTarget() { return target; } @Override public RuleKey.Builder appendToRuleKey(RuleKey.Builder builder) throws IOException { super.appendToRuleKey(builder).set("manifest", manifest.asReference()).set("target", target) .set("keystore", keystore.getBuildTarget().getFullyQualifiedName()) .setRuleNames("classpathDeps", classpathDeps).set("packageType", packageType.toString()) .set("buildRulesToExcludeFromDex", buildRulesToExcludeFromDex) .set("useAndroidProguardConfigWithOptimizations", useAndroidProguardConfigWithOptimizations) .set("proguardConfig", proguardConfig.transform(SourcePath.TO_REFERENCE)) .set("resourceCompressionMode", resourceCompressionMode.toString()) .set("primaryDexSubstrings", primaryDexSubstrings).set("linearAllocHardLimit", linearAllocHardLimit) .set("primaryDexClassesFile", primaryDexClassesFile.transform(SourcePath.TO_REFERENCE)) .set("resourceFilter", resourceFilter.getDescription()) .set("cpuFilters", ImmutableSortedSet.copyOf(cpuFilters).toString()) .set("preprocessJavaClassesBash", preprocessJavaClassesBash) .set("preprocessJavaClassesDeps", preprocessJavaClassesDeps); return dexSplitMode.appendToRuleKey("dexSplitMode", builder); } public ImmutableSortedSet<BuildRule> getBuildRulesToExcludeFromDex() { return buildRulesToExcludeFromDex; } public AndroidTransitiveDependencyGraph getTransitiveDependencyGraph() { return transitiveDependencyGraph; } public Optional<SourcePath> getProguardConfig() { return proguardConfig; } public boolean isRelease() { return packageType == PackageType.RELEASE; } private boolean isCompressResources() { return resourceCompressionMode.isCompressResources(); } private boolean isStoreStringsAsAssets() { return resourceCompressionMode.isStoreStringsAsAssets(); } public ResourceCompressionMode getResourceCompressionMode() { return resourceCompressionMode; } public boolean requiresResourceFilter() { return resourceFilter.isEnabled() || isStoreStringsAsAssets(); } public FilterResourcesStep.ResourceFilter getResourceFilter() { return this.resourceFilter; } public ImmutableSet<TargetCpuType> getCpuFilters() { return this.cpuFilters; } public ImmutableSet<IntermediateDexRule> getPreDexDeps() { return preDexDeps; } public ImmutableSortedSet<BuildRule> getPreprocessJavaClassesDeps() { return preprocessJavaClassesDeps; } public Optional<String> getPreprocessJavaClassesBash() { return preprocessJavaClassesBash; } /** * Native libraries compiled for different CPU architectures are placed in the * respective ABI subdirectories, such as 'armeabi', 'armeabi-v7a', 'x86' and 'mips'. * This looks at the cpu filter and returns the correct subdirectory. If cpu filter is * not present or not supported, returns Optional.absent(); */ private static Optional<String> getAbiDirectoryComponent(TargetCpuType cpuType) { String component = null; if (cpuType.equals(TargetCpuType.ARM)) { component = SdkConstants.ABI_ARMEABI; } else if (cpuType.equals(TargetCpuType.ARMV7)) { component = SdkConstants.ABI_ARMEABI_V7A; } else if (cpuType.equals(TargetCpuType.X86)) { component = SdkConstants.ABI_INTEL_ATOM; } else if (cpuType.equals(TargetCpuType.MIPS)) { component = SdkConstants.ABI_MIPS; } return Optional.fromNullable(component); } @VisibleForTesting void copyNativeLibrary(String sourceDir, String destinationDir, ImmutableList.Builder<Step> steps) { Path sourceDirPath = Paths.get(sourceDir); Path destinationDirPath = Paths.get(destinationDir); if (getCpuFilters().isEmpty()) { steps.add(new CopyStep(sourceDirPath, destinationDirPath, true)); } else { for (TargetCpuType cpuType : getCpuFilters()) { Optional<String> abiDirectoryComponent = getAbiDirectoryComponent(cpuType); Preconditions.checkState(abiDirectoryComponent.isPresent()); final Path libSourceDir = sourceDirPath.resolve(abiDirectoryComponent.get()); Path libDestinationDir = destinationDirPath.resolve(abiDirectoryComponent.get()); final MkdirStep mkDirStep = new MkdirStep(libDestinationDir); final CopyStep copyStep = new CopyStep(libSourceDir, libDestinationDir, true); steps.add(new Step() { @Override public int execute(ExecutionContext context) { if (!context.getProjectFilesystem().exists(libSourceDir.toString())) { return 0; } if (mkDirStep.execute(context) == 0 && copyStep.execute(context) == 0) { return 0; } return 1; } @Override public String getShortName() { return "copy_native_libraries"; } @Override public String getDescription(ExecutionContext context) { ImmutableList.Builder<String> stringBuilder = ImmutableList.builder(); stringBuilder.add(String.format("[ -d %s ]", libSourceDir.toString())); stringBuilder.add(mkDirStep.getDescription(context)); stringBuilder.add(copyStep.getDescription(context)); return Joiner.on(" && ").join(stringBuilder.build()); } }); } } } /** The APK at this path is the final one that points to an APK that a user should install. */ @Override public String getApkPath() { return getUnsignedApkPath().replaceAll("\\.unsigned\\.apk$", ".apk"); } @Override public String getPathToOutputFile() { return getApkPath(); } @Override public List<String> getInputsToCompareToOutput() { ImmutableList.Builder<SourcePath> sourcePaths = ImmutableList.builder(); sourcePaths.add(manifest); Optionals.addIfPresent(proguardConfig, sourcePaths); ImmutableList.Builder<String> inputs = ImmutableList.builder(); inputs.addAll(SourcePaths.filterInputsToCompareToOutput(sourcePaths.build())); return inputs.build(); } /** * Sets up filtering of resources, images/drawables and strings in particular, based on build * rule parameters {@link #resourceFilter} and {@link #isStoreStringsAsAssets}. * * {@link com.facebook.buck.android.FilterResourcesStep.ResourceFilter} {@code resourceFilter} * determines which drawables end up in the APK (based on density - mdpi, hdpi etc), and also * whether higher density drawables get scaled down to the specified density (if not present). * * {@code isStoreStringsAsAssets} determines whether non-english string resources are packaged * separately as assets (and not bundled together into the {@code resources.arsc} file). */ @VisibleForTesting FilterResourcesStep createFilterResourcesStep(Set<String> resourceDirectories) { ImmutableBiMap.Builder<String, String> filteredResourcesDirMapBuilder = ImmutableBiMap.builder(); String resDestinationBasePath = getBinPath("__filtered__%s__"); int count = 0; for (String resDir : resourceDirectories) { filteredResourcesDirMapBuilder.put(resDir, Paths.get(resDestinationBasePath, String.valueOf(count++)).toString()); } ImmutableBiMap<String, String> resSourceToDestDirMap = filteredResourcesDirMapBuilder.build(); FilterResourcesStep.Builder filterResourcesStepBuilder = FilterResourcesStep.builder() .setInResToOutResDirMap(resSourceToDestDirMap).setResourceFilter(resourceFilter); if (isStoreStringsAsAssets()) { filterResourcesStepBuilder.enableStringsFilter(); } return filterResourcesStepBuilder.build(); } @Override public List<Step> getBuildSteps(BuildContext context, BuildableContext buildableContext) { ImmutableList.Builder<Step> steps = ImmutableList.builder(); // Symlink the manifest to a path named AndroidManifest.xml. Do this before running any other // commands to ensure that it is available at the desired path. steps.add(new MkdirAndSymlinkFileStep(getManifest().resolve(context).toString(), getAndroidManifestXml())); final AndroidTransitiveDependencies transitiveDependencies = findTransitiveDependencies(); final AndroidDexTransitiveDependencies dexTransitiveDependencies = findDexTransitiveDependencies(); // Add the steps for the aapt_package command. This method returns data that the ApkBuilder // needs to create a final, unsigned APK. ResourceDirectoriesFromAapt resourceDirectoriesFromAapt = addAaptPackageSteps(steps, transitiveDependencies, dexTransitiveDependencies); // Create the .dex files and create the unsigned APK using ApkBuilder. addDxAndApkBuilderSteps(context, steps, transitiveDependencies, dexTransitiveDependencies, resourceDirectoriesFromAapt.resDirectories, resourceDirectoriesFromAapt.nativeLibraryDirectories, getResourceApkPath(), getUnsignedApkPath()); // Sign the APK. String signedApkPath = getSignedApkPath(); SignApkStep signApkStep = new SignApkStep(keystore.getPathToStore(), keystore.getPathToPropertiesFile(), getUnsignedApkPath(), signedApkPath); steps.add(signApkStep); String apkToAlign; // Optionally, compress the resources file in the .apk. if (this.isCompressResources()) { String compressedApkPath = getCompressedResourcesApkPath(); apkToAlign = compressedApkPath; RepackZipEntriesStep arscComp = new RepackZipEntriesStep(signedApkPath, compressedApkPath, ImmutableSet.of("resources.arsc")); steps.add(arscComp); } else { apkToAlign = signedApkPath; } String apkPath = getApkPath(); ZipalignStep zipalign = new ZipalignStep(apkToAlign, apkPath); steps.add(zipalign); // Inform the user where the APK can be found. EchoStep success = new EchoStep(String.format("built APK for %s at %s", getFullyQualifiedName(), apkPath)); steps.add(success); return steps.build(); } private ResourceDirectoriesFromAapt addAaptPackageSteps(ImmutableList.Builder<Step> steps, final AndroidTransitiveDependencies transitiveDependencies, final AndroidDexTransitiveDependencies dexTransitiveDependencies) { final Set<String> rDotJavaPackages = transitiveDependencies.rDotJavaPackages; final FilterResourcesStep filterResourcesStep; final ImmutableSet<String> resDirectories; if (requiresResourceFilter()) { filterResourcesStep = createFilterResourcesStep(transitiveDependencies.resDirectories); resDirectories = filterResourcesStep.getOutputResourceDirs(); steps.add(filterResourcesStep); } else { filterResourcesStep = null; resDirectories = transitiveDependencies.resDirectories; } // Extract the resources from third-party jars. // TODO(mbolin): The results of this should be cached between runs. String extractedResourcesDir = getBinPath("__resources__%s__"); steps.add(new MakeCleanDirectoryStep(extractedResourcesDir)); steps.add(new ExtractResourcesStep(dexTransitiveDependencies.pathsToThirdPartyJars, extractedResourcesDir)); // Create the R.java files. Their compiled versions must be included in classes.dex. // TODO(mbolin): Skip this step if the transitive set of AndroidResourceRules is cached. if (!resDirectories.isEmpty()) { UberRDotJavaUtil.generateRDotJavaFiles(resDirectories, rDotJavaPackages, getBuildTarget(), steps); if (isStoreStringsAsAssets()) { Path tmpStringsDirPath = getPathForTmpStringAssetsDirectory(); steps.add(new MakeCleanDirectoryStep(tmpStringsDirPath)); steps.add(new CompileStringsStep(filterResourcesStep, Paths.get(UberRDotJavaUtil.getPathToGeneratedRDotJavaSrcFiles(getBuildTarget())), tmpStringsDirPath)); } } // Copy the transitive closure of files in assets to a single directory, if any. Step collectAssets = new Step() { @Override public int execute(ExecutionContext context) { // This must be done in a Command because the files and directories that are specified may // not exist at the time this Command is created because the previous Commands have not run // yet. ImmutableList.Builder<Step> commands = ImmutableList.builder(); try { createAllAssetsDirectory(transitiveDependencies.assetsDirectories, commands, new DefaultDirectoryTraverser()); } catch (IOException e) { context.logError(e, "Error creating all assets directory in %s.", getBuildTarget()); return 1; } for (Step command : commands.build()) { int exitCode = command.execute(context); if (exitCode != 0) { throw new HumanReadableException("Error running " + command.getDescription(context)); } } return 0; } @Override public String getShortName() { return "symlink_assets"; } @Override public String getDescription(ExecutionContext context) { return getShortName(); } }; steps.add(collectAssets); // Copy the transitive closure of files in native_libs to a single directory, if any. ImmutableSet<String> nativeLibraryDirectories; if (!transitiveDependencies.nativeLibsDirectories.isEmpty()) { ImmutableSet.Builder<String> nativeLibraryDirectoriesBuilder = ImmutableSet.builder(); String pathForNativeLibs = getPathForNativeLibs(); String libSubdirectory = pathForNativeLibs + "/lib"; nativeLibraryDirectoriesBuilder.add(libSubdirectory); steps.add(new MakeCleanDirectoryStep(libSubdirectory)); for (String nativeLibDir : transitiveDependencies.nativeLibsDirectories) { copyNativeLibrary(nativeLibDir, libSubdirectory, steps); } nativeLibraryDirectories = nativeLibraryDirectoriesBuilder.build(); } else { nativeLibraryDirectories = ImmutableSet.of(); } Optional<String> assetsDirectory; if (transitiveDependencies.assetsDirectories.isEmpty() && transitiveDependencies.nativeLibAssetsDirectories.isEmpty() && !isStoreStringsAsAssets()) { assetsDirectory = Optional.absent(); } else { assetsDirectory = Optional.of(getPathToAllAssetsDirectory()); } if (!transitiveDependencies.nativeLibAssetsDirectories.isEmpty()) { String nativeLibAssetsDir = assetsDirectory.get() + "/lib"; steps.add(new MakeCleanDirectoryStep(nativeLibAssetsDir)); for (String nativeLibDir : transitiveDependencies.nativeLibAssetsDirectories) { copyNativeLibrary(nativeLibDir, nativeLibAssetsDir, steps); } } if (isStoreStringsAsAssets()) { Path stringAssetsDir = Paths.get(assetsDirectory.get()).resolve("strings"); steps.add(new MakeCleanDirectoryStep(stringAssetsDir)); steps.add( new CopyStep(getPathForTmpStringAssetsDirectory(), stringAssetsDir, /* shouldRecurse */ true)); } steps.add(new MkdirStep(outputGenDirectory)); if (!canSkipAaptResourcePackaging()) { AaptStep aaptCommand = new AaptStep(getAndroidManifestXml(), resDirectories, assetsDirectory, getResourceApkPath(), ImmutableSet.of(extractedResourcesDir), packageType.isCrunchPngFiles()); steps.add(aaptCommand); } ResourceDirectoriesFromAapt resourceDirectoriesFromAapt = new ResourceDirectoriesFromAapt(resDirectories, nativeLibraryDirectories); return resourceDirectoriesFromAapt; } private static class ResourceDirectoriesFromAapt { final ImmutableSet<String> resDirectories; final ImmutableSet<String> nativeLibraryDirectories; public ResourceDirectoriesFromAapt(ImmutableSet<String> resDirectories, ImmutableSet<String> nativeLibraryDirectories) { this.resDirectories = resDirectories; this.nativeLibraryDirectories = nativeLibraryDirectories; } } private void addDxAndApkBuilderSteps(BuildContext context, ImmutableList.Builder<Step> steps, final AndroidTransitiveDependencies transitiveDependencies, final AndroidDexTransitiveDependencies dexTransitiveDependencies, ImmutableSet<String> resDirectories, ImmutableSet<String> nativeLibraryDirectories, String resourceApkPath, String unsignedApkPath) { // Execute preprocess_java_classes_binary, if appropriate. ImmutableSet<String> classpathEntriesToDex; if (preprocessJavaClassesBash.isPresent()) { // Symlink everything in dexTransitiveDependencies.classpathEntriesToDex to the input // directory. Expect parallel outputs in the output directory and update classpathEntriesToDex // to reflect that. final String preprocessJavaClassesInDir = getBinPath("java_classes_preprocess_in_%s"); final String preprocessJavaClassesOutDir = getBinPath("java_classes_preprocess_out_%s"); steps.add(new MakeCleanDirectoryStep(preprocessJavaClassesInDir)); steps.add(new MakeCleanDirectoryStep(preprocessJavaClassesOutDir)); steps.add(new SymlinkFilesIntoDirectoryStep(Paths.get("."), dexTransitiveDependencies.classpathEntriesToDex, Paths.get(preprocessJavaClassesInDir))); classpathEntriesToDex = FluentIterable.from(dexTransitiveDependencies.classpathEntriesToDex) .transform(new Function<String, String>() { @Override public String apply(String classpathEntry) { return Paths.get(preprocessJavaClassesOutDir, classpathEntry).toString(); } }).toSet(); AbstractGenruleStep.CommandString commandString = new AbstractGenruleStep.CommandString( /* cmd */ Optional.<String>absent(), /* bash */ preprocessJavaClassesBash, /* cmdExe */ Optional.<String>absent()); steps.add(new AbstractGenruleStep(this, commandString, preprocessJavaClassesDeps) { @Override protected void addEnvironmentVariables(ExecutionContext context, ImmutableMap.Builder<String, String> environmentVariablesBuilder) { environmentVariablesBuilder.put("IN_JARS_DIR", preprocessJavaClassesInDir); environmentVariablesBuilder.put("OUT_JARS_DIR", preprocessJavaClassesOutDir); } }); } else { classpathEntriesToDex = dexTransitiveDependencies.classpathEntriesToDex; } // Execute proguard if desired (transforms input classpaths). if (packageType.isBuildWithObfuscation()) { classpathEntriesToDex = addProguardCommands(context, classpathEntriesToDex, transitiveDependencies.proguardConfigs, steps, resDirectories); } // Create the final DEX (or set of DEX files in the case of split dex). // The APK building command needs to take a directory of raw files, so we create a directory // that can only contain .dex files from this build rule. String dexDir = getBinPath(".dex/%s"); steps.add(new MkdirStep(dexDir)); String dexFile = String.format("%s/classes.dex", dexDir); // Create dex artifacts. ImmutableSet.Builder<String> secondaryDexDirectoriesBuilder = ImmutableSet.builder(); if (preDexDeps.isEmpty()) { addDexingSteps(classpathEntriesToDex, secondaryDexDirectoriesBuilder, steps, dexFile, context.getSourcePathResolver()); } else { Iterable<Path> filesToDex = FluentIterable.from(preDexDeps) .transform(new Function<IntermediateDexRule, Path>() { @Override @Nullable public Path apply(IntermediateDexRule preDexDep) { DexProducedFromJavaLibraryThatContainsClassFiles preDex = preDexDep.getBuildable(); if (preDex.hasOutput()) { return preDex.getPathToDex(); } else { return null; } } }).filter(Predicates.notNull()); // If this APK has Android resources, then the generated R.class files also need to be dexed. if (dexTransitiveDependencies.pathToCompiledRDotJavaFiles.isPresent()) { Path pathToCompiledRDotJavaFilesDirectory = dexTransitiveDependencies.pathToCompiledRDotJavaFiles .get(); filesToDex = Iterables.concat(filesToDex, Collections.singleton(pathToCompiledRDotJavaFilesDirectory)); } // This will combine the pre-dexed files and the R.class files into a single classes.dex file. steps.add(new DxStep(dexFile, filesToDex, /* options */ EnumSet.of(DxStep.Option.USE_CUSTOM_DX_IF_AVAILABLE))); } ImmutableSet<String> secondaryDexDirectories = secondaryDexDirectoriesBuilder.build(); // Due to limitations of Froyo, we need to ensure that all secondary zip files are STORED in // the final APK, not DEFLATED. The only way to ensure this with ApkBuilder is to zip up the // the files properly and then add the zip files to the apk. ImmutableSet.Builder<String> secondaryDexZips = ImmutableSet.builder(); for (String secondaryDexDirectory : secondaryDexDirectories) { // String the trailing slash from the directory name and add the zip extension. String zipFile = secondaryDexDirectory.replaceAll("/$", "") + ".zip"; secondaryDexZips.add(zipFile); steps.add( new ZipDirectoryWithMaxDeflateStep(secondaryDexDirectory, zipFile, FROYO_DEFLATE_LIMIT_BYTES)); } ApkBuilderStep apkBuilderCommand = new ApkBuilderStep(resourceApkPath, unsignedApkPath, dexFile, ImmutableSet.<String>of(), nativeLibraryDirectories, secondaryDexZips.build(), false); steps.add(apkBuilderCommand); } /** * Given a set of assets directories to include in the APK (which may be empty), return the path * to the directory that contains the union of all the assets. If any work needs to be done to * create such a directory, the appropriate commands should be added to the {@code commands} * list builder. * <p> * If there are no assets (i.e., {@code assetsDirectories} is empty), then the return value will * be an empty {@link Optional}. */ @VisibleForTesting Optional<String> createAllAssetsDirectory(Set<String> assetsDirectories, ImmutableList.Builder<Step> steps, DirectoryTraverser traverser) throws IOException { if (assetsDirectories.isEmpty()) { return Optional.absent(); } // Due to a limitation of aapt, only one assets directory can be specified, so if multiple are // specified in Buck, then all of the contents must be symlinked to a single directory. String destination = getPathToAllAssetsDirectory(); steps.add(new MakeCleanDirectoryStep(destination)); final ImmutableMap.Builder<String, File> allAssets = ImmutableMap.builder(); File destinationDirectory = new File(destination); for (String assetsDirectory : assetsDirectories) { traverser.traverse(new DirectoryTraversal(new File(assetsDirectory)) { @Override public void visit(File file, String relativePath) { allAssets.put(relativePath, file); } }); } for (Map.Entry<String, File> entry : allAssets.build().entrySet()) { steps.add(new MkdirAndSymlinkFileStep(MorePaths.newPathInstance(entry.getValue()).toString(), MorePaths.newPathInstance(destinationDirectory + "/" + entry.getKey()).toString())); } return Optional.of(destination); } public AndroidTransitiveDependencies findTransitiveDependencies() { return getTransitiveDependencyGraph().findDependencies(getAndroidResourceDepsInternal()); } public AndroidDexTransitiveDependencies findDexTransitiveDependencies() { return getTransitiveDependencyGraph().findDexDependencies(getAndroidResourceDepsInternal(), buildRulesToExcludeFromDex); } /** * @return a list of {@link HasAndroidResourceDeps}s that should be passed, in order, to {@code aapt} * when generating the {@code R.java} files for this APK. */ protected ImmutableList<HasAndroidResourceDeps> getAndroidResourceDepsInternal() { return UberRDotJavaUtil.getAndroidResourceDeps(this); } private boolean canSkipAaptResourcePackaging() { // TODO(mbolin): Create a RuleKey for resources only and use it to determine the value of this // boolean. Whether the resources have not changed since the last build run is irrelevant // because this AndroidBinary may not have been written as part of the last build run. return false; } /** * This is the path to the directory for generated files related to ProGuard. Ultimately, it * should include: * <ul> * <li>proguard.txt * <li>dump.txt * <li>seeds.txt * <li>usage.txt * <li>mapping.txt * <li>obfuscated.jar * </ul> * @return path to directory (will not include trailing slash) */ @VisibleForTesting Path getPathForProGuardDirectory() { return MorePaths.newPathInstance(String.format("%s/%s.proguard/%s", BuckConstant.GEN_DIR, getBuildTarget().getBasePathWithSlash(), getBuildTarget().getShortName())); } @VisibleForTesting String getPathToAllAssetsDirectory() { return getBinPath("__assets_%s__"); } private Path getPathForTmpStringAssetsDirectory() { return Paths.get(getBinPath("__strings_%s__")); } /** * All native libs are copied to this directory before running aapt. */ private String getPathForNativeLibs() { return getBinPath("__native_libs_%s__"); } public Keystore getKeystore() { return keystore; } public String getResourceApkPath() { return String.format("%s%s.unsigned.ap_", outputGenDirectory, getBuildTarget().getShortName()); } public String getUnsignedApkPath() { return String.format("%s%s.unsigned.apk", outputGenDirectory, getBuildTarget().getShortName()); } /** The APK at this path will be signed, but not zipaligned. */ private String getSignedApkPath() { return getUnsignedApkPath().replaceAll("\\.unsigned\\.apk$", ".signed.apk"); } /** The APK at this path will have compressed resources, but will not be zipaligned. */ private String getCompressedResourcesApkPath() { return getUnsignedApkPath().replaceAll("\\.unsigned\\.apk$", ".compressed.apk"); } /** * Buck does not require the manifest to be named AndroidManifest.xml, but commands such as aapt * do. For this reason, we symlink the path to {@link #getManifest()} to the path returned by * this method, whose name is always "AndroidManifest.xml". * <p> * Therefore, commands created by this method should use this method instead of * {@link #getManifest()}. */ private String getAndroidManifestXml() { return getBinPath("__manifest_%s__/AndroidManifest.xml"); } /** * Return a path to a file in the buck-out/bin/ directory. {@code format} will be prepended with * the {@link BuckConstant#BIN_DIR} and the target base path, then formatted with the target * short name. * {@code format} should not start with a slash. */ private String getBinPath(String format) { return String.format("%s/%s" + format, BuckConstant.BIN_DIR, getBuildTarget().getBasePathWithSlash(), getBuildTarget().getShortName()); } @VisibleForTesting Path getProguardOutputFromInputClasspath(String classpathEntry) { // Hehe, this is so ridiculously fragile. Preconditions.checkArgument(classpathEntry.charAt(0) != '/', "Classpath entries should be relative rather than absolute paths: %s", classpathEntry); String obfuscatedName = Files.getNameWithoutExtension(classpathEntry) + "-obfuscated.jar"; Path dirName = MorePaths.newPathInstance(new File(classpathEntry).getParent()); Path outputJar = getPathForProGuardDirectory().resolve(dirName).resolve(obfuscatedName); return outputJar; } /** * @return the resulting set of ProGuarded classpath entries to dex. */ @VisibleForTesting ImmutableSet<String> addProguardCommands(BuildContext context, Set<String> classpathEntriesToDex, Set<String> depsProguardConfigs, ImmutableList.Builder<Step> steps, Set<String> resDirectories) { final ImmutableSetMultimap<JavaLibraryRule, String> classpathEntriesMap = getTransitiveClasspathEntries(); ImmutableSet.Builder<String> additionalLibraryJarsForProguardBuilder = ImmutableSet.builder(); for (BuildRule buildRule : buildRulesToExcludeFromDex) { if (buildRule instanceof JavaLibraryRule) { additionalLibraryJarsForProguardBuilder .addAll(classpathEntriesMap.get((JavaLibraryRule) buildRule)); } } // Clean out the directory for generated ProGuard files. Path proguardDirectory = getPathForProGuardDirectory(); steps.add(new MakeCleanDirectoryStep(proguardDirectory)); // Generate a file of ProGuard config options using aapt. String generatedProGuardConfig = proguardDirectory + "/proguard.txt"; GenProGuardConfigStep genProGuardConfig = new GenProGuardConfigStep(getAndroidManifestXml(), resDirectories, generatedProGuardConfig); steps.add(genProGuardConfig); // Create list of proguard Configs for the app project and its dependencies ImmutableSet.Builder<String> proguardConfigsBuilder = ImmutableSet.builder(); proguardConfigsBuilder.addAll(depsProguardConfigs); if (proguardConfig.isPresent()) { proguardConfigsBuilder.add(proguardConfig.get().resolve(context).toString()); } // Transform our input classpath to a set of output locations for each input classpath. // TODO(devjasta): the output path we choose is the result of a slicing function against // input classpath. This is fragile and should be replaced with knowledge of the BuildTarget. final ImmutableMap<String, String> inputOutputEntries = FluentIterable.from(classpathEntriesToDex) .toMap(new Function<String, String>() { @Override public String apply(String classpathEntry) { return getProguardOutputFromInputClasspath(classpathEntry).toString(); } }); // Run ProGuard on the classpath entries. // TODO: ProGuardObfuscateStep's final argument should be a Path Step obfuscateCommand = ProGuardObfuscateStep.create(generatedProGuardConfig, proguardConfigsBuilder.build(), useAndroidProguardConfigWithOptimizations, inputOutputEntries, additionalLibraryJarsForProguardBuilder.build(), proguardDirectory.toString()); steps.add(obfuscateCommand); // Apply the transformed inputs to the classpath (this will modify deps.classpathEntriesToDex // so that we're now dexing the proguarded artifacts). return ImmutableSet.copyOf(inputOutputEntries.values()); } /** * Create dex artifacts for all of the individual directories of compiled .class files (or * the obfuscated jar files if proguard is used). If split dex is used, multiple dex artifacts * will be produced. * * @param classpathEntriesToDex Full set of classpath entries that must make * their way into the final APK structure (but not necessarily into the * primary dex). * @param steps List of steps to add to. * @param primaryDexPath Output path for the primary dex file. */ @VisibleForTesting void addDexingSteps(Set<String> classpathEntriesToDex, ImmutableSet.Builder<String> secondaryDexDirectories, ImmutableList.Builder<Step> steps, String primaryDexPath, Function<SourcePath, Path> sourcePathResolver) { final Set<String> primaryInputsToDex; final Optional<String> secondaryDexDir; final Optional<String> secondaryInputsDir; if (shouldSplitDex()) { Optional<Path> proguardMappingFile = Optional.absent(); if (packageType.isBuildWithObfuscation()) { proguardMappingFile = Optional.of(getPathForProGuardDirectory().resolve("mapping.txt")); } // DexLibLoader expects that metadata.txt and secondary jar files are under this dir // in assets. String magicSecondaryDexSubdir = "assets/secondary-program-dex-jars"; // Intermediate directory holding the primary split-zip jar. String splitZipDir = getBinPath("__%s_split_zip__"); steps.add(new MakeCleanDirectoryStep(splitZipDir)); String primaryJarPath = splitZipDir + "/primary.jar"; String secondaryJarMetaDirParent = splitZipDir + "/secondary_meta/"; String secondaryJarMetaDir = secondaryJarMetaDirParent + magicSecondaryDexSubdir; steps.add(new MakeCleanDirectoryStep(secondaryJarMetaDir)); String secondaryJarMeta = secondaryJarMetaDir + "/metadata.txt"; // Intermediate directory holding _ONLY_ the secondary split-zip jar files. This is // important because SmartDexingCommand will try to dx every entry in this directory. It // does this because it's impossible to know what outputs split-zip will generate until it // runs. String secondaryZipDir = getBinPath("__%s_secondary_zip__"); steps.add(new MakeCleanDirectoryStep(secondaryZipDir)); // Run the split-zip command which is responsible for dividing the large set of input // classpaths into a more compact set of jar files such that no one jar file when dexed will // yield a dex artifact too large for dexopt or the dx method limit to handle. String zipSplitReportDir = getBinPath("__%s_split_zip_report__"); steps.add(new MakeCleanDirectoryStep(zipSplitReportDir)); SplitZipStep splitZipCommand = new SplitZipStep(classpathEntriesToDex, secondaryJarMeta, primaryJarPath, secondaryZipDir, "secondary-%d.jar", proguardMappingFile, primaryDexSubstrings, primaryDexClassesFile.transform(sourcePathResolver), dexSplitMode.getDexSplitStrategy(), dexSplitMode.getDexStore(), zipSplitReportDir, dexSplitMode.useLinearAllocSplitDex(), linearAllocHardLimit); steps.add(splitZipCommand); // Add the secondary dex directory that has yet to be created, but will be by the // smart dexing command. Smart dex will handle "cleaning" this directory properly. String secondaryDexParentDir = getBinPath("__%s_secondary_dex__/"); secondaryDexDir = Optional.of(secondaryDexParentDir + magicSecondaryDexSubdir); steps.add(new MkdirStep(secondaryDexDir.get())); secondaryDexDirectories.add(secondaryJarMetaDirParent); secondaryDexDirectories.add(secondaryDexParentDir); // Adjust smart-dex inputs for the split-zip case. primaryInputsToDex = ImmutableSet.of(primaryJarPath); secondaryInputsDir = Optional.of(secondaryZipDir); } else { // Simple case where our inputs are the natural classpath directories and we don't have // to worry about secondary jar/dex files. primaryInputsToDex = classpathEntriesToDex; secondaryDexDir = Optional.absent(); secondaryInputsDir = Optional.absent(); } // Stores checksum information from each invocation to intelligently decide when dx needs // to be re-run. Path successDir = Paths.get(getBinPath("__%s_smart_dex__/.success")); steps.add(new MkdirStep(successDir)); // Add the smart dexing tool that is capable of avoiding the external dx invocation(s) if // it can be shown that the inputs have not changed. It also parallelizes dx invocations // where applicable. // // Note that by not specifying the number of threads this command will use it will select an // optimal default regardless of the value of --num-threads. This decision was made with the // assumption that --num-threads specifies the threading of build rule execution and does not // directly apply to the internal threading/parallelization details of various build commands // being executed. For example, aapt is internally threaded by default when preprocessing // images. SmartDexingStep smartDexingCommand = new SmartDexingStep(primaryDexPath, primaryInputsToDex, secondaryDexDir, secondaryInputsDir, successDir, Optional.<Integer>absent(), dexSplitMode.getDexStore(), /* optimize */ PackageType.RELEASE.equals(packageType)); steps.add(smartDexingCommand); } /** * @return the path to the AndroidManifest.xml. Note that this file is not guaranteed to be named * AndroidManifest.xml. */ @Override public SourcePath getManifest() { return manifest; } String getTarget() { return target; } boolean shouldSplitDex() { return dexSplitMode.isShouldSplitDex(); } boolean isUseAndroidProguardConfigWithOptimizations() { return useAndroidProguardConfigWithOptimizations; } ImmutableSet<String> getPrimaryDexSubstrings() { return primaryDexSubstrings; } long getLinearAllocHardLimit() { return linearAllocHardLimit; } Optional<SourcePath> getPrimaryDexClassesFile() { return primaryDexClassesFile; } public ImmutableSortedSet<BuildRule> getClasspathDeps() { return classpathDeps; } @Override public ImmutableSetMultimap<JavaLibraryRule, String> getTransitiveClasspathEntries() { // This is used primarily for buck audit classpath. return Classpaths.getClasspathEntries(classpathDeps); } public static Builder newAndroidBinaryRuleBuilder(AbstractBuildRuleBuilderParams params) { return new Builder(params); } public static class Builder extends AbstractBuildRuleBuilder<AndroidBinaryRule> { private static final PackageType DEFAULT_PACKAGE_TYPE = PackageType.DEBUG; private SourcePath manifest; private String target; /** This should always be a subset of {@link #getDeps()}. */ private ImmutableSet.Builder<BuildTarget> classpathDeps = ImmutableSet.builder(); private BuildTarget keystoreTarget; private PackageType packageType = DEFAULT_PACKAGE_TYPE; private ImmutableSet.Builder<BuildTarget> buildRulesToExcludeFromDexBuilder = ImmutableSet.builder(); private boolean disablePreDex = false; private DexSplitMode dexSplitMode = new DexSplitMode(/* shouldSplitDex */ false, ZipSplitter.DexSplitStrategy.MAXIMIZE_PRIMARY_DEX_SIZE, DexStore.JAR, /* useLinearAllocSplitDex */ false); private boolean useAndroidProguardConfigWithOptimizations = false; private Optional<SourcePath> proguardConfig = Optional.absent(); private ResourceCompressionMode resourceCompressionMode = ResourceCompressionMode.DISABLED; private ImmutableSet.Builder<String> primaryDexSubstrings = ImmutableSet.builder(); private long linearAllocHardLimit = 0; private Optional<SourcePath> primaryDexClassesFile = Optional.absent(); private FilterResourcesStep.ResourceFilter resourceFilter = new FilterResourcesStep.ResourceFilter( ImmutableList.<String>of()); private ImmutableSet.Builder<TargetCpuType> cpuFilters = ImmutableSet.builder(); private ImmutableSet.Builder<BuildTarget> preprocessJavaClassesDeps = ImmutableSet.builder(); private Optional<String> preprocessJavaClassesBash = Optional.absent(); private Builder(AbstractBuildRuleBuilderParams params) { super(params); } @Override public AndroidBinaryRule build(BuildRuleResolver ruleResolver) { // Make sure the "keystore" argument refers to a KeystoreRule. BuildRule rule = ruleResolver.get(keystoreTarget); Buildable keystore = rule.getBuildable(); if (!(keystore instanceof Keystore)) { throw new HumanReadableException("In %s, keystore='%s' must be a keystore() but was %s().", getBuildTarget(), rule.getFullyQualifiedName(), rule.getType().getName()); } BuildRuleParams originalParams = createBuildRuleParams(ruleResolver); ImmutableSortedSet<BuildRule> originalDeps = originalParams.getDeps(); ImmutableSet<IntermediateDexRule> preDexDeps; ImmutableSet<BuildTarget> buildRulesToExcludeFromDex = buildRulesToExcludeFromDexBuilder.build(); if (!disablePreDex && PackageType.DEBUG.equals(packageType) && !dexSplitMode.isShouldSplitDex() // TODO(mbolin): Support predex for split dex. && !preprocessJavaClassesBash.isPresent() // TODO(mbolin): Support predex post-preprocess. ) { AndroidBinaryGraphEnhancer graphEnhancer = new AndroidBinaryGraphEnhancer(originalDeps, buildRulesToExcludeFromDex, originalParams.getPathRelativizer(), originalParams.getRuleKeyBuilderFactory()); preDexDeps = graphEnhancer.createDepsForPreDexing(ruleResolver); } else { preDexDeps = ImmutableSet.of(); } boolean allowNonExistentRule = false; // Must create a new BuildRuleParams to supersede the one built by // createBuildRuleParams(ruleResolver). ImmutableSortedSet<BuildRule> totalDeps = ImmutableSortedSet.<BuildRule>naturalOrder() .addAll(originalDeps).addAll(preDexDeps).build(); BuildRuleParams finalParams = new BuildRuleParams(getBuildTarget(), totalDeps, originalParams.getVisibilityPatterns(), originalParams.getPathRelativizer(), originalParams.getRuleKeyBuilderFactory()); return new AndroidBinaryRule(finalParams, manifest, target, getBuildTargetsAsBuildRules(ruleResolver, classpathDeps.build()), (Keystore) keystore, packageType, /* buildRulesToExcludeFromDex */ getBuildTargetsAsBuildRules(ruleResolver, buildRulesToExcludeFromDex, allowNonExistentRule), dexSplitMode, useAndroidProguardConfigWithOptimizations, proguardConfig, resourceCompressionMode, primaryDexSubstrings.build(), linearAllocHardLimit, primaryDexClassesFile, resourceFilter, cpuFilters.build(), preDexDeps, getBuildTargetsAsBuildRules(ruleResolver, preprocessJavaClassesDeps.build()), preprocessJavaClassesBash); } @Override public Builder setBuildTarget(BuildTarget buildTarget) { super.setBuildTarget(buildTarget); return this; } @Override public Builder addDep(BuildTarget dep) { super.addDep(dep); return this; } @Override public Builder addVisibilityPattern(BuildTargetPattern visibilityPattern) { super.addVisibilityPattern(visibilityPattern); return this; } public Builder setManifest(SourcePath manifest) { this.manifest = manifest; return this; } public Builder setTarget(String target) { this.target = target; return this; } public Builder addClasspathDep(BuildTarget classpathDep) { this.classpathDeps.add(classpathDep); addDep(classpathDep); return this; } public Builder setKeystore(BuildTarget keystoreTarget) { this.keystoreTarget = keystoreTarget; addDep(keystoreTarget); return this; } public Builder setPackageType(String packageType) { if (packageType == null) { this.packageType = DEFAULT_PACKAGE_TYPE; } else { this.packageType = PackageType.valueOf(packageType.toUpperCase()); } return this; } public Builder addBuildRuleToExcludeFromDex(BuildTarget entry) { this.buildRulesToExcludeFromDexBuilder.add(entry); return this; } public Builder setDisablePreDex(boolean disablePreDex) { this.disablePreDex = disablePreDex; return this; } public Builder setDexSplitMode(DexSplitMode dexSplitMode) { this.dexSplitMode = dexSplitMode; return this; } public Builder setUseAndroidProguardConfigWithOptimizations( boolean useAndroidProguardConfigWithOptimizations) { this.useAndroidProguardConfigWithOptimizations = useAndroidProguardConfigWithOptimizations; return this; } public Builder setProguardConfig(Optional<SourcePath> proguardConfig) { this.proguardConfig = Preconditions.checkNotNull(proguardConfig); return this; } public Builder addPrimaryDexSubstrings(Iterable<String> primaryDexSubstrings) { this.primaryDexSubstrings.addAll(primaryDexSubstrings); return this; } public Builder setLinearAllocHardLimit(long linearAllocHardLimit) { this.linearAllocHardLimit = linearAllocHardLimit; return this; } public Builder setPrimaryDexClassesFile(Optional<SourcePath> primaryDexClassesFile) { this.primaryDexClassesFile = Preconditions.checkNotNull(primaryDexClassesFile); return this; } public Builder setResourceFilter(ResourceFilter resourceFilter) { this.resourceFilter = Preconditions.checkNotNull(resourceFilter); return this; } public Builder setResourceCompressionMode(String resourceCompressionMode) { Preconditions.checkNotNull(resourceCompressionMode); try { this.resourceCompressionMode = ResourceCompressionMode .valueOf(resourceCompressionMode.toUpperCase()); } catch (IllegalArgumentException e) { throw new HumanReadableException( String.format("In %s, android_binary() was passed an invalid resource compression mode: %s", buildTarget.getFullyQualifiedName(), resourceCompressionMode)); } return this; } public Builder addCpuFilter(String cpuFilter) { if (cpuFilter != null) { try { this.cpuFilters.add(TargetCpuType.valueOf(cpuFilter.toUpperCase())); } catch (IllegalArgumentException e) { throw new HumanReadableException( "android_binary() was passed an invalid cpu filter: " + cpuFilter); } } return this; } public Builder addPreprocessJavaClassesDep(BuildTarget preprocessJavaClassesDep) { this.preprocessJavaClassesDeps.add(preprocessJavaClassesDep); this.addDep(preprocessJavaClassesDep); return this; } public Builder setPreprocessJavaClassesBash(Optional<String> preprocessJavaClassesBash) { this.preprocessJavaClassesBash = Preconditions.checkNotNull(preprocessJavaClassesBash); return this; } } }