| /* |
| * 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.ExecutionContext; |
| import com.facebook.buck.util.AndroidPlatformTarget; |
| import com.facebook.buck.util.Functions; |
| import com.facebook.buck.util.HumanReadableException; |
| import com.facebook.buck.zip.CustomZipOutputStream; |
| import com.facebook.buck.zip.ZipOutputStreams; |
| import com.google.common.annotations.VisibleForTesting; |
| 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.util.Map; |
| import java.util.Set; |
| import java.util.zip.ZipEntry; |
| |
| public final class ProGuardObfuscateStep extends ShellStep { |
| |
| 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; |
| |
| /** |
| * @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. |
| */ |
| public ProGuardObfuscateStep( |
| String generatedProGuardConfig, |
| Set<String> customProguardConfigs, |
| boolean useProguardOptimizations, |
| Map<String, String> inputAndOutputEntries, |
| Set<String> additionalLibraryJarsForProguard, |
| String proguardDirectory) { |
| 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); |
| } |
| |
| @Override |
| public String getShortName() { |
| return "proguard_obfuscation"; |
| } |
| |
| @Override |
| protected ImmutableList<String> getShellCommandInternal(ExecutionContext context) { |
| ImmutableList.Builder<String> args = ImmutableList.builder(); |
| AndroidPlatformTarget androidPlatformTarget = context.getAndroidPlatformTarget(); |
| Joiner pathJoiner = Joiner.on(':'); |
| |
| // Run ProGuard as a standalone executable JAR file. |
| String proguardJar = androidPlatformTarget.getProguardJar().getAbsolutePath(); |
| args.add("java").add("-Xmx1024M").add("-jar").add(proguardJar); |
| |
| // -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.FILE_TO_ABSOLUTE_PATH); |
| 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 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) { |
| ensureAllOutputsExist(); |
| } |
| |
| return exitCode; |
| } |
| |
| private void ensureAllOutputsExist() { |
| for (String outputJar : inputAndOutputEntries.values()) { |
| File outputJarFile = new File(outputJar); |
| if (!outputJarFile.exists()) { |
| try { |
| createEmptyZip(outputJarFile); |
| } catch (IOException e) { |
| throw new HumanReadableException("Failed to create empty jar file: %s", outputJar); |
| } |
| } |
| } |
| } |
| |
| @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 (obj == null || !(obj instanceof ProGuardObfuscateStep)) { |
| return false; |
| } |
| ProGuardObfuscateStep that = (ProGuardObfuscateStep) 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); |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hashCode(useAndroidProguardConfigWithOptimizations, |
| additionalLibraryJarsForProguard, |
| customProguardConfigs, |
| generatedProGuardConfig, |
| inputAndOutputEntries, |
| proguardDirectory); |
| } |
| } |