blob: 3f3a0e79d38dac6ffc5afb1b18f7843f6d52e19a [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.shell.ShellStep;
import com.facebook.buck.step.AbstractExecutionStep;
import com.facebook.buck.step.CompositeStep;
import com.facebook.buck.step.ExecutionContext;
import com.facebook.buck.step.Step;
import com.facebook.buck.util.AndroidPlatformTarget;
import com.facebook.buck.zip.CustomZipOutputStream;
import com.facebook.buck.zip.ZipOutputStreams;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Functions;
import com.google.common.base.Joiner;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.io.Files;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
public final class ProGuardObfuscateStep extends ShellStep {
private final Map<String, String> inputAndOutputEntries;
private final String pathToProGuardCommandLineArgsFile;
/**
* @return step that writes out ProGuard's command line arguments to a text file and then runs
* ProGuard using those arguments. We write the arguments to a file to avoid blowing out
* exec()'s ARG_MAX limit.
*/
public static Step create(
String generatedProGuardConfig,
Set<String> customProguardConfigs,
boolean useProguardOptimizations,
Map<String, String> inputAndOutputEntries,
Set<String> additionalLibraryJarsForProguard,
String proguardDirectory) {
String pathToProGuardCommandLineArgsFile = proguardDirectory + "/command-line.txt";
CommandLineHelperStep commandLineHelperStep = new CommandLineHelperStep(
generatedProGuardConfig,
customProguardConfigs,
useProguardOptimizations,
inputAndOutputEntries,
additionalLibraryJarsForProguard,
proguardDirectory,
pathToProGuardCommandLineArgsFile);
ProGuardObfuscateStep proGuardStep = new ProGuardObfuscateStep(
inputAndOutputEntries, pathToProGuardCommandLineArgsFile);
return new CompositeStep(ImmutableList.of(commandLineHelperStep, proGuardStep));
}
/**
* @param inputAndOutputEntries Map of input/output pairs to proguard. The key represents an
* input jar (-injars); the value an output jar (-outjars).
* @param pathToProGuardCommandLineArgsFile Path to file containing arguments to ProGuard.
*/
private ProGuardObfuscateStep(
Map<String, String> inputAndOutputEntries,
String pathToProGuardCommandLineArgsFile) {
this.inputAndOutputEntries = ImmutableMap.copyOf(inputAndOutputEntries);
this.pathToProGuardCommandLineArgsFile = Preconditions.checkNotNull(
pathToProGuardCommandLineArgsFile);
}
@Override
public String getShortName() {
return "proguard_obfuscation";
}
@Override
protected ImmutableList<String> getShellCommandInternal(ExecutionContext context) {
AndroidPlatformTarget androidPlatformTarget = context.getAndroidPlatformTarget();
// Run ProGuard as a standalone executable JAR file.
String proguardJar = androidPlatformTarget.getProguardJar().getAbsolutePath();
ImmutableList.Builder<String> args = ImmutableList.builder();
args.add("java")
.add("-Xmx1024M")
.add("-jar").add(proguardJar)
.add("@" + pathToProGuardCommandLineArgsFile);
return args.build();
}
@Override
public int execute(ExecutionContext context) {
int exitCode = super.execute(context);
// proguard has a peculiar behaviour when multiple -injars/outjars pairs are specified in which
// any -injars that would have been fully stripped away will not produce their matching -outjars
// as requested (so the file won't exist). Our build steps are not sophisticated enough to
// account for this and remove those entries from the classes to dex so we hack things here to
// ensure that the files exist but are empty.
if (exitCode == 0) {
exitCode = ensureAllOutputsExist(context);
}
return exitCode;
}
private int ensureAllOutputsExist(ExecutionContext context) {
for (String outputJar : inputAndOutputEntries.values()) {
File outputJarFile = new File(outputJar);
if (!outputJarFile.exists()) {
try {
createEmptyZip(outputJarFile);
} catch (IOException e) {
context.logError(e, "Error creating empty zip file at: %s.", outputJarFile);
return 1;
}
}
}
return 0;
}
@VisibleForTesting
static void createEmptyZip(File file) throws IOException {
Files.createParentDirs(file);
CustomZipOutputStream out = ZipOutputStreams.newOutputStream(file);
// Sun's java 6 runtime doesn't allow us to create a truly empty zip, but this should be enough
// to pass through dx/split-zip without any issue.
// ...and Sun's java 7 runtime doesn't let us use an empty string for the zip entry name.
out.putNextEntry(new ZipEntry("proguard_no_result"));
out.close();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
} else if (!(obj instanceof ProGuardObfuscateStep)) {
return false;
}
ProGuardObfuscateStep that = (ProGuardObfuscateStep) obj;
return Objects.equal(this.inputAndOutputEntries, that.inputAndOutputEntries) &&
Objects.equal(this.pathToProGuardCommandLineArgsFile,
that.pathToProGuardCommandLineArgsFile);
}
@Override
public int hashCode() {
return Objects.hashCode(inputAndOutputEntries, pathToProGuardCommandLineArgsFile);
}
/**
* Helper class to run as a step before ProGuardObfuscateStep to write out the
* command-line parameters to a file. The ProGuardObfuscateStep references
* this file when it runs using ProGuard's '@' syntax. This allows for longer
* command-lines than would otherwise be supported.
*/
private static class CommandLineHelperStep extends AbstractExecutionStep {
private final String generatedProGuardConfig;
private final Set<String> customProguardConfigs;
private final Map<String, String> inputAndOutputEntries;
private final Set<String> additionalLibraryJarsForProguard;
private final boolean useAndroidProguardConfigWithOptimizations;
private final String proguardDirectory;
private final String pathToProGuardCommandLineArgsFile;
/**
* @param generatedProGuardConfig Proguard configuration as produced by aapt.
* @param customProguardConfigs Main rule and its dependencies proguard configurations.
* @param useProguardOptimizations Whether to include the Android SDK proguard defaults.
* @param inputAndOutputEntries Map of input/output pairs to proguard. The key represents an
* input jar (-injars); the value an output jar (-outjars).
* @param additionalLibraryJarsForProguard Libraries that are not operated upon by proguard but
* needed to resolve symbols.
* @param proguardDirectory Output directory for various proguard-generated meta artifacts.
* @param pathToProGuardCommandLineArgsFile Path to file containing arguments to ProGuard.
*/
private CommandLineHelperStep(
String generatedProGuardConfig,
Set<String> customProguardConfigs,
boolean useProguardOptimizations,
Map<String, String> inputAndOutputEntries,
Set<String> additionalLibraryJarsForProguard,
String proguardDirectory,
String pathToProGuardCommandLineArgsFile) {
super("write_proguard_command_line_parameters");
this.generatedProGuardConfig = Preconditions.checkNotNull(generatedProGuardConfig);
this.customProguardConfigs = ImmutableSet.copyOf(customProguardConfigs);
this.useAndroidProguardConfigWithOptimizations = useProguardOptimizations;
this.inputAndOutputEntries = ImmutableMap.copyOf(inputAndOutputEntries);
this.additionalLibraryJarsForProguard = ImmutableSet.copyOf(additionalLibraryJarsForProguard);
this.proguardDirectory = Preconditions.checkNotNull(proguardDirectory);
this.pathToProGuardCommandLineArgsFile = pathToProGuardCommandLineArgsFile;
}
@Override
public int execute(ExecutionContext context) {
String proGuardArguments = Joiner.on('\n').join(getParameters(context));
try {
context.getProjectFilesystem().writeContentsToPath(
proGuardArguments,
Paths.get(pathToProGuardCommandLineArgsFile));
} catch (IOException e) {
context.logError(e,
"Error writing ProGuard arguments to file: %s.",
pathToProGuardCommandLineArgsFile);
return 1;
}
return 0;
}
/** @return the list of arguments to pass to ProGuard. */
private ImmutableList<String> getParameters(ExecutionContext context) {
ImmutableList.Builder<String> args = ImmutableList.builder();
AndroidPlatformTarget androidPlatformTarget = context.getAndroidPlatformTarget();
Joiner pathJoiner = Joiner.on(':');
// Relative paths should be interpreted relative to project directory root, not the
// written parameters file.
args.add("-basedirectory")
.add(context.getProjectDirectoryRoot().getAbsolutePath());
// -include
if (useAndroidProguardConfigWithOptimizations) {
args.add("-include")
.add(androidPlatformTarget.getOptimizedProguardConfig().getAbsolutePath());
} else {
args.add("-include").add(androidPlatformTarget.getProguardConfig().getAbsolutePath());
}
for (String proguardConfig : customProguardConfigs) {
args.add("-include").add(proguardConfig);
}
args.add("-include").add(generatedProGuardConfig);
// -injars and -outjars paired together for each input.
for (Map.Entry<String, String> inputOutputEntry : inputAndOutputEntries.entrySet()) {
args.add("-injars").add(inputOutputEntry.getKey());
args.add("-outjars").add(inputOutputEntry.getValue());
}
// -libraryjars
Iterable<String> bootclasspathPaths = Iterables.transform(
androidPlatformTarget.getBootclasspathEntries(),
Functions.toStringFunction());
Iterable<String> libraryJars = Iterables.concat(bootclasspathPaths,
additionalLibraryJarsForProguard);
args.add("-libraryjars").add(pathJoiner.join(libraryJars));
// -dump
args.add("-dump").add(proguardDirectory + "/dump.txt");
args.add("-printseeds").add(proguardDirectory + "/seeds.txt");
args.add("-printusage").add(proguardDirectory + "/usage.txt");
args.add("-printmapping").add(proguardDirectory + "/mapping.txt");
args.add("-printconfiguration").add(proguardDirectory + "/configuration.txt");
return args.build();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof CommandLineHelperStep)) {
return false;
}
CommandLineHelperStep that = (CommandLineHelperStep) obj;
return
Objects.equal(useAndroidProguardConfigWithOptimizations,
that.useAndroidProguardConfigWithOptimizations) &&
Objects.equal(additionalLibraryJarsForProguard,
that.additionalLibraryJarsForProguard) &&
Objects.equal(customProguardConfigs, that.customProguardConfigs) &&
Objects.equal(generatedProGuardConfig, that.generatedProGuardConfig) &&
Objects.equal(inputAndOutputEntries, that.inputAndOutputEntries) &&
Objects.equal(proguardDirectory, that.proguardDirectory) &&
Objects.equal(pathToProGuardCommandLineArgsFile, that.pathToProGuardCommandLineArgsFile);
}
@Override
public int hashCode() {
return Objects.hashCode(useAndroidProguardConfigWithOptimizations,
additionalLibraryJarsForProguard,
customProguardConfigs,
generatedProGuardConfig,
inputAndOutputEntries,
proguardDirectory,
pathToProGuardCommandLineArgsFile);
}
}
}