aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/BuildHelper.java
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/BuildHelper.java')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/BuildHelper.java1225
1 files changed, 1225 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/BuildHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/BuildHelper.java
new file mode 100644
index 000000000..78d9d94e4
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/BuildHelper.java
@@ -0,0 +1,1225 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.android.ide.eclipse.adt.internal.build;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AndroidPrintStream;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity;
+import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
+import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.prefs.AndroidLocation.AndroidLocationException;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.IAndroidTarget.IOptionalLibrary;
+import com.android.sdklib.build.ApkBuilder;
+import com.android.sdklib.build.ApkBuilder.JarStatus;
+import com.android.sdklib.build.ApkBuilder.SigningInfo;
+import com.android.sdklib.build.ApkCreationException;
+import com.android.sdklib.build.DuplicateFileException;
+import com.android.sdklib.build.RenderScriptProcessor;
+import com.android.sdklib.build.SealedApkException;
+import com.android.sdklib.internal.build.DebugKeyProvider;
+import com.android.sdklib.internal.build.DebugKeyProvider.KeytoolException;
+import com.android.utils.GrabProcessOutput;
+import com.android.utils.GrabProcessOutput.IProcessOutput;
+import com.android.utils.GrabProcessOutput.Wait;
+import com.google.common.base.Charsets;
+import com.google.common.hash.HashCode;
+import com.google.common.hash.HashFunction;
+import com.google.common.hash.Hashing;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jdt.core.IClasspathContainer;
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jface.preference.IPreferenceStore;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+/**
+ * Helper with methods for the last 3 steps of the generation of an APK.
+ *
+ * {@link #packageResources(IFile, IProject[], String, int, String, String)} packages the
+ * application resources using aapt into a zip file that is ready to be integrated into the apk.
+ *
+ * {@link #executeDx(IJavaProject, String, String, IJavaProject[])} will convert the Java byte
+ * code into the Dalvik bytecode.
+ *
+ * {@link #finalPackage(String, String, String, boolean, IJavaProject, IProject[], IJavaProject[], String, boolean)}
+ * will make the apk from all the previous components.
+ *
+ * This class only executes the 3 above actions. It does not handle the errors, and simply sends
+ * them back as custom exceptions.
+ *
+ * Warnings are handled by the {@link ResourceMarker} interface.
+ *
+ * Console output (verbose and non verbose) is handled through the {@link AndroidPrintStream} passed
+ * to the constructor.
+ *
+ */
+public class BuildHelper {
+
+ private static final String CONSOLE_PREFIX_DX = "Dx"; //$NON-NLS-1$
+ private final static String TEMP_PREFIX = "android_"; //$NON-NLS-1$
+
+ private static final String COMMAND_CRUNCH = "crunch"; //$NON-NLS-1$
+ private static final String COMMAND_PACKAGE = "package"; //$NON-NLS-1$
+
+ @NonNull
+ private final ProjectState mProjectState;
+ @NonNull
+ private final IProject mProject;
+ @NonNull
+ private final BuildToolInfo mBuildToolInfo;
+ @NonNull
+ private final AndroidPrintStream mOutStream;
+ @NonNull
+ private final AndroidPrintStream mErrStream;
+ private final boolean mForceJumbo;
+ private final boolean mDisableDexMerger;
+ private final boolean mVerbose;
+ private final boolean mDebugMode;
+
+ private final Set<String> mCompiledCodePaths = new HashSet<String>();
+
+ public static final boolean BENCHMARK_FLAG = false;
+ public static long sStartOverallTime = 0;
+ public static long sStartJavaCTime = 0;
+
+ private final static int MILLION = 1000000;
+ private String mProguardFile;
+
+ /**
+ * An object able to put a marker on a resource.
+ */
+ public interface ResourceMarker {
+ void setWarning(IResource resource, String message);
+ }
+
+ /**
+ * Creates a new post-compiler helper
+ * @param project
+ * @param outStream
+ * @param errStream
+ * @param debugMode whether this is a debug build
+ * @param verbose
+ * @throws CoreException
+ */
+ public BuildHelper(@NonNull ProjectState projectState,
+ @NonNull BuildToolInfo buildToolInfo,
+ @NonNull AndroidPrintStream outStream,
+ @NonNull AndroidPrintStream errStream,
+ boolean forceJumbo, boolean disableDexMerger, boolean debugMode,
+ boolean verbose, ResourceMarker resMarker) throws CoreException {
+ mProjectState = projectState;
+ mProject = projectState.getProject();
+ mBuildToolInfo = buildToolInfo;
+ mOutStream = outStream;
+ mErrStream = errStream;
+ mDebugMode = debugMode;
+ mVerbose = verbose;
+ mForceJumbo = forceJumbo;
+ mDisableDexMerger = disableDexMerger;
+
+ gatherPaths(resMarker);
+ }
+
+ public void updateCrunchCache() throws AaptExecException, AaptResultException {
+ // Benchmarking start
+ long startCrunchTime = 0;
+ if (BENCHMARK_FLAG) {
+ String msg = "BENCHMARK ADT: Starting Initial Packaging (.ap_)"; //$NON-NLS-1$
+ AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, mProject, msg);
+ startCrunchTime = System.nanoTime();
+ }
+
+ // Get the resources folder to crunch from
+ IFolder resFolder = mProject.getFolder(AdtConstants.WS_RESOURCES);
+ List<String> resPaths = new ArrayList<String>();
+ resPaths.add(resFolder.getLocation().toOSString());
+
+ // Get the output folder where the cache is stored.
+ IFolder binFolder = BaseProjectHelper.getAndroidOutputFolder(mProject);
+ IFolder cacheFolder = binFolder.getFolder(AdtConstants.WS_BIN_RELATIVE_CRUNCHCACHE);
+ String cachePath = cacheFolder.getLocation().toOSString();
+
+ /* For crunching, we don't need the osManifestPath, osAssetsPath, or the configFilter
+ * parameters for executeAapt
+ */
+ executeAapt(COMMAND_CRUNCH, "", resPaths, "", cachePath, "", 0);
+
+ // Benchmarking end
+ if (BENCHMARK_FLAG) {
+ String msg = "BENCHMARK ADT: Ending Initial Package (.ap_). \nTime Elapsed: " //$NON-NLS-1$
+ + ((System.nanoTime() - startCrunchTime)/MILLION) + "ms"; //$NON-NLS-1$
+ AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, mProject, msg);
+ }
+ }
+
+ /**
+ * Packages the resources of the projet into a .ap_ file.
+ * @param manifestFile the manifest of the project.
+ * @param libProjects the list of library projects that this project depends on.
+ * @param resFilter an optional resource filter to be used with the -c option of aapt. If null
+ * no filters are used.
+ * @param versionCode an optional versionCode to be inserted in the manifest during packaging.
+ * If the value is <=0, no values are inserted.
+ * @param outputFolder where to write the resource ap_ file.
+ * @param outputFilename the name of the resource ap_ file.
+ * @throws AaptExecException
+ * @throws AaptResultException
+ */
+ public void packageResources(IFile manifestFile, List<IProject> libProjects, String resFilter,
+ int versionCode, String outputFolder, String outputFilename)
+ throws AaptExecException, AaptResultException {
+
+ // Benchmarking start
+ long startPackageTime = 0;
+ if (BENCHMARK_FLAG) {
+ String msg = "BENCHMARK ADT: Starting Initial Packaging (.ap_)"; //$NON-NLS-1$
+ AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, mProject, msg);
+ startPackageTime = System.nanoTime();
+ }
+
+ // need to figure out some path before we can execute aapt;
+ IFolder binFolder = BaseProjectHelper.getAndroidOutputFolder(mProject);
+
+ // get the cache folder
+ IFolder cacheFolder = binFolder.getFolder(AdtConstants.WS_BIN_RELATIVE_CRUNCHCACHE);
+
+ // get the BC folder
+ IFolder bcFolder = binFolder.getFolder(AdtConstants.WS_BIN_RELATIVE_BC);
+
+ // get the resource folder
+ IFolder resFolder = mProject.getFolder(AdtConstants.WS_RESOURCES);
+
+ // and the assets folder
+ IFolder assetsFolder = mProject.getFolder(AdtConstants.WS_ASSETS);
+
+ // we need to make sure this one exists.
+ if (assetsFolder.exists() == false) {
+ assetsFolder = null;
+ }
+
+ // list of res folder (main project + maybe libraries)
+ ArrayList<String> osResPaths = new ArrayList<String>();
+
+ IPath resLocation = resFolder.getLocation();
+ IPath manifestLocation = manifestFile.getLocation();
+
+ if (resLocation != null && manifestLocation != null) {
+
+ // png cache folder first.
+ addFolderToList(osResPaths, cacheFolder);
+ addFolderToList(osResPaths, bcFolder);
+
+ // regular res folder next.
+ osResPaths.add(resLocation.toOSString());
+
+ // then libraries
+ if (libProjects != null) {
+ for (IProject lib : libProjects) {
+ // png cache folder first
+ IFolder libBinFolder = BaseProjectHelper.getAndroidOutputFolder(lib);
+
+ IFolder libCacheFolder = libBinFolder.getFolder(AdtConstants.WS_BIN_RELATIVE_CRUNCHCACHE);
+ addFolderToList(osResPaths, libCacheFolder);
+
+ IFolder libBcFolder = libBinFolder.getFolder(AdtConstants.WS_BIN_RELATIVE_BC);
+ addFolderToList(osResPaths, libBcFolder);
+
+ // regular res folder next.
+ IFolder libResFolder = lib.getFolder(AdtConstants.WS_RESOURCES);
+ addFolderToList(osResPaths, libResFolder);
+ }
+ }
+
+ String osManifestPath = manifestLocation.toOSString();
+
+ String osAssetsPath = null;
+ if (assetsFolder != null) {
+ osAssetsPath = assetsFolder.getLocation().toOSString();
+ }
+
+ // build the default resource package
+ executeAapt(COMMAND_PACKAGE, osManifestPath, osResPaths, osAssetsPath,
+ outputFolder + File.separator + outputFilename, resFilter,
+ versionCode);
+ }
+
+ // Benchmarking end
+ if (BENCHMARK_FLAG) {
+ String msg = "BENCHMARK ADT: Ending Initial Package (.ap_). \nTime Elapsed: " //$NON-NLS-1$
+ + ((System.nanoTime() - startPackageTime)/MILLION) + "ms"; //$NON-NLS-1$
+ AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, mProject, msg);
+ }
+ }
+
+ /**
+ * Adds os path of a folder to a list only if the folder actually exists.
+ * @param pathList
+ * @param folder
+ */
+ private void addFolderToList(List<String> pathList, IFolder folder) {
+ // use a File instead of the IFolder API to ignore workspace refresh issue.
+ File testFile = new File(folder.getLocation().toOSString());
+ if (testFile.isDirectory()) {
+ pathList.add(testFile.getAbsolutePath());
+ }
+ }
+
+ /**
+ * Makes a final package signed with the debug key.
+ *
+ * Packages the dex files, the temporary resource file into the final package file.
+ *
+ * Whether the package is a debug package is controlled with the <var>debugMode</var> parameter
+ * in {@link #PostCompilerHelper(IProject, PrintStream, PrintStream, boolean, boolean)}
+ *
+ * @param intermediateApk The path to the temporary resource file.
+ * @param dex The path to the dex file.
+ * @param output The path to the final package file to create.
+ * @param libProjects an optional list of library projects (can be null)
+ * @return true if success, false otherwise.
+ * @throws ApkCreationException
+ * @throws AndroidLocationException
+ * @throws KeytoolException
+ * @throws NativeLibInJarException
+ * @throws CoreException
+ * @throws DuplicateFileException
+ */
+ public void finalDebugPackage(String intermediateApk, String dex, String output,
+ List<IProject> libProjects, ResourceMarker resMarker)
+ throws ApkCreationException, KeytoolException, AndroidLocationException,
+ NativeLibInJarException, DuplicateFileException, CoreException {
+
+ AdtPlugin adt = AdtPlugin.getDefault();
+ if (adt == null) {
+ return;
+ }
+
+ // get the debug keystore to use.
+ IPreferenceStore store = adt.getPreferenceStore();
+ String keystoreOsPath = store.getString(AdtPrefs.PREFS_CUSTOM_DEBUG_KEYSTORE);
+ if (keystoreOsPath == null || new File(keystoreOsPath).isFile() == false) {
+ keystoreOsPath = DebugKeyProvider.getDefaultKeyStoreOsPath();
+ AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, mProject,
+ Messages.ApkBuilder_Using_Default_Key);
+ } else {
+ AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, mProject,
+ String.format(Messages.ApkBuilder_Using_s_To_Sign, keystoreOsPath));
+ }
+
+ // from the keystore, get the signing info
+ SigningInfo info = ApkBuilder.getDebugKey(keystoreOsPath, mVerbose ? mOutStream : null);
+
+ finalPackage(intermediateApk, dex, output, libProjects,
+ info != null ? info.key : null, info != null ? info.certificate : null, resMarker);
+ }
+
+ /**
+ * Makes the final package.
+ *
+ * Packages the dex files, the temporary resource file into the final package file.
+ *
+ * Whether the package is a debug package is controlled with the <var>debugMode</var> parameter
+ * in {@link #PostCompilerHelper(IProject, PrintStream, PrintStream, boolean, boolean)}
+ *
+ * @param intermediateApk The path to the temporary resource file.
+ * @param dex The path to the dex file.
+ * @param output The path to the final package file to create.
+ * @param debugSign whether the apk must be signed with the debug key.
+ * @param libProjects an optional list of library projects (can be null)
+ * @param abiFilter an optional filter. If not null, then only the matching ABI is included in
+ * the final archive
+ * @return true if success, false otherwise.
+ * @throws NativeLibInJarException
+ * @throws ApkCreationException
+ * @throws CoreException
+ * @throws DuplicateFileException
+ */
+ public void finalPackage(String intermediateApk, String dex, String output,
+ List<IProject> libProjects,
+ PrivateKey key, X509Certificate certificate, ResourceMarker resMarker)
+ throws NativeLibInJarException, ApkCreationException, DuplicateFileException,
+ CoreException {
+
+ try {
+ ApkBuilder apkBuilder = new ApkBuilder(output, intermediateApk, dex,
+ key, certificate,
+ mVerbose ? mOutStream: null);
+ apkBuilder.setDebugMode(mDebugMode);
+
+ // either use the full compiled code paths or just the proguard file
+ // if present
+ Collection<String> pathsCollection = mCompiledCodePaths;
+ if (mProguardFile != null) {
+ pathsCollection = Collections.singletonList(mProguardFile);
+ mProguardFile = null;
+ }
+
+ // Now we write the standard resources from all the output paths.
+ for (String path : pathsCollection) {
+ File file = new File(path);
+ if (file.isFile()) {
+ JarStatus jarStatus = apkBuilder.addResourcesFromJar(file);
+
+ // check if we found native libraries in the external library. This
+ // constitutes an error or warning depending on if they are in lib/
+ if (jarStatus.getNativeLibs().size() > 0) {
+ String libName = file.getName();
+
+ String msg = String.format(
+ "Native libraries detected in '%1$s'. See console for more information.",
+ libName);
+
+ ArrayList<String> consoleMsgs = new ArrayList<String>();
+
+ consoleMsgs.add(String.format(
+ "The library '%1$s' contains native libraries that will not run on the device.",
+ libName));
+
+ if (jarStatus.hasNativeLibsConflicts()) {
+ consoleMsgs.add("Additionally some of those libraries will interfer with the installation of the application because of their location in lib/");
+ consoleMsgs.add("lib/ is reserved for NDK libraries.");
+ }
+
+ consoleMsgs.add("The following libraries were found:");
+
+ for (String lib : jarStatus.getNativeLibs()) {
+ consoleMsgs.add(" - " + lib);
+ }
+
+ String[] consoleStrings = consoleMsgs.toArray(new String[consoleMsgs.size()]);
+
+ // if there's a conflict or if the prefs force error on any native code in jar
+ // files, throw an exception
+ if (jarStatus.hasNativeLibsConflicts() ||
+ AdtPrefs.getPrefs().getBuildForceErrorOnNativeLibInJar()) {
+ throw new NativeLibInJarException(jarStatus, msg, libName, consoleStrings);
+ } else {
+ // otherwise, put a warning, and output to the console also.
+ if (resMarker != null) {
+ resMarker.setWarning(mProject, msg);
+ }
+
+ for (String string : consoleStrings) {
+ mOutStream.println(string);
+ }
+ }
+ }
+ } else if (file.isDirectory()) {
+ // this is technically not a source folder (class folder instead) but since we
+ // only care about Java resources (ie non class/java files) this will do the
+ // same
+ apkBuilder.addSourceFolder(file);
+ }
+ }
+
+ // now write the native libraries.
+ // First look if the lib folder is there.
+ IResource libFolder = mProject.findMember(SdkConstants.FD_NATIVE_LIBS);
+ if (libFolder != null && libFolder.exists() &&
+ libFolder.getType() == IResource.FOLDER) {
+ // get a File for the folder.
+ apkBuilder.addNativeLibraries(libFolder.getLocation().toFile());
+ }
+
+ // next the native libraries for the renderscript support mode.
+ if (mProjectState.getRenderScriptSupportMode()) {
+ IFolder androidOutputFolder = BaseProjectHelper.getAndroidOutputFolder(mProject);
+ IResource rsLibFolder = androidOutputFolder.getFolder(
+ AdtConstants.WS_BIN_RELATIVE_RS_LIBS);
+ File rsLibFolderFile = rsLibFolder.getLocation().toFile();
+ if (rsLibFolderFile.isDirectory()) {
+ apkBuilder.addNativeLibraries(rsLibFolderFile);
+ }
+
+ File rsLibs = RenderScriptProcessor.getSupportNativeLibFolder(
+ mBuildToolInfo.getLocation().getAbsolutePath());
+ if (rsLibs.isDirectory()) {
+ apkBuilder.addNativeLibraries(rsLibs);
+ }
+ }
+
+ // write the native libraries for the library projects.
+ if (libProjects != null) {
+ for (IProject lib : libProjects) {
+ libFolder = lib.findMember(SdkConstants.FD_NATIVE_LIBS);
+ if (libFolder != null && libFolder.exists() &&
+ libFolder.getType() == IResource.FOLDER) {
+ apkBuilder.addNativeLibraries(libFolder.getLocation().toFile());
+ }
+ }
+ }
+
+ // seal the APK.
+ apkBuilder.sealApk();
+ } catch (SealedApkException e) {
+ // this won't happen as we control when the apk is sealed.
+ }
+ }
+
+ public void setProguardOutput(String proguardFile) {
+ mProguardFile = proguardFile;
+ }
+
+ public Collection<String> getCompiledCodePaths() {
+ return mCompiledCodePaths;
+ }
+
+ public void runProguard(List<File> proguardConfigs, File inputJar, Collection<String> jarFiles,
+ File obfuscatedJar, File logOutput)
+ throws ProguardResultException, ProguardExecException, IOException {
+ IAndroidTarget target = Sdk.getCurrent().getTarget(mProject);
+
+ // prepare the command line for proguard
+ List<String> command = new ArrayList<String>();
+ command.add(AdtPlugin.getOsAbsoluteProguard());
+
+ for (File configFile : proguardConfigs) {
+ command.add("-include"); //$NON-NLS-1$
+ command.add(quotePath(configFile.getAbsolutePath()));
+ }
+
+ command.add("-injars"); //$NON-NLS-1$
+ StringBuilder sb = new StringBuilder(quotePath(inputJar.getAbsolutePath()));
+ for (String jarFile : jarFiles) {
+ sb.append(File.pathSeparatorChar);
+ sb.append(quotePath(jarFile));
+ }
+ command.add(quoteWinArg(sb.toString()));
+
+ command.add("-outjars"); //$NON-NLS-1$
+ command.add(quotePath(obfuscatedJar.getAbsolutePath()));
+
+ command.add("-libraryjars"); //$NON-NLS-1$
+ sb = new StringBuilder(quotePath(target.getPath(IAndroidTarget.ANDROID_JAR)));
+ IOptionalLibrary[] libraries = target.getOptionalLibraries();
+ if (libraries != null) {
+ for (IOptionalLibrary lib : libraries) {
+ sb.append(File.pathSeparatorChar);
+ sb.append(quotePath(lib.getJarPath()));
+ }
+ }
+ command.add(quoteWinArg(sb.toString()));
+
+ if (logOutput != null) {
+ if (logOutput.isDirectory() == false) {
+ logOutput.mkdirs();
+ }
+
+ command.add("-dump"); //$NON-NLS-1$
+ command.add(new File(logOutput, "dump.txt").getAbsolutePath()); //$NON-NLS-1$
+
+ command.add("-printseeds"); //$NON-NLS-1$
+ command.add(new File(logOutput, "seeds.txt").getAbsolutePath()); //$NON-NLS-1$
+
+ command.add("-printusage"); //$NON-NLS-1$
+ command.add(new File(logOutput, "usage.txt").getAbsolutePath()); //$NON-NLS-1$
+
+ command.add("-printmapping"); //$NON-NLS-1$
+ command.add(new File(logOutput, "mapping.txt").getAbsolutePath()); //$NON-NLS-1$
+ }
+
+ String commandArray[] = null;
+
+ if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS) {
+ commandArray = createWindowsProguardConfig(command);
+ }
+
+ if (commandArray == null) {
+ // For Mac & Linux, use a regular command string array.
+ commandArray = command.toArray(new String[command.size()]);
+ }
+
+ // Define PROGUARD_HOME to point to $SDK/tools/proguard if it's not yet defined.
+ // The Mac/Linux proguard.sh can infer it correctly but not the proguard.bat one.
+ String[] envp = null;
+ Map<String, String> envMap = new TreeMap<String, String>(System.getenv());
+ if (!envMap.containsKey("PROGUARD_HOME")) { //$NON-NLS-1$
+ envMap.put("PROGUARD_HOME", Sdk.getCurrent().getSdkOsLocation() + //$NON-NLS-1$
+ SdkConstants.FD_TOOLS + File.separator +
+ SdkConstants.FD_PROGUARD);
+ envp = new String[envMap.size()];
+ int i = 0;
+ for (Map.Entry<String, String> entry : envMap.entrySet()) {
+ envp[i++] = String.format("%1$s=%2$s", //$NON-NLS-1$
+ entry.getKey(),
+ entry.getValue());
+ }
+ }
+
+ if (AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE) {
+ sb = new StringBuilder();
+ for (String c : commandArray) {
+ sb.append(c).append(' ');
+ }
+ AdtPlugin.printToConsole(mProject, sb.toString());
+ }
+
+ // launch
+ int execError = 1;
+ try {
+ // launch the command line process
+ Process process = Runtime.getRuntime().exec(commandArray, envp);
+
+ // list to store each line of stderr
+ ArrayList<String> results = new ArrayList<String>();
+
+ // get the output and return code from the process
+ execError = grabProcessOutput(mProject, process, results);
+
+ if (mVerbose) {
+ for (String resultString : results) {
+ mOutStream.println(resultString);
+ }
+ }
+
+ if (execError != 0) {
+ throw new ProguardResultException(execError,
+ results.toArray(new String[results.size()]));
+ }
+
+ } catch (IOException e) {
+ String msg = String.format(Messages.Proguard_Exec_Error, commandArray[0]);
+ throw new ProguardExecException(msg, e);
+ } catch (InterruptedException e) {
+ String msg = String.format(Messages.Proguard_Exec_Error, commandArray[0]);
+ throw new ProguardExecException(msg, e);
+ }
+ }
+
+ /**
+ * For tools R8 up to R11, the proguard.bat launcher on Windows only accepts
+ * arguments %1..%9. Since we generally have about 15 arguments, we were working
+ * around this by generating a temporary config file for proguard and then using
+ * that.
+ * Starting with tools R12, the proguard.bat launcher has been fixed to take
+ * all arguments using %* so we no longer need this hack.
+ *
+ * @param command
+ * @return
+ * @throws IOException
+ */
+ private String[] createWindowsProguardConfig(List<String> command) throws IOException {
+
+ // Arg 0 is the proguard.bat path and arg 1 is the user config file
+ String launcher = AdtPlugin.readFile(new File(command.get(0)));
+ if (launcher.contains("%*")) { //$NON-NLS-1$
+ // This is the launcher from Tools R12. Don't work around it.
+ return null;
+ }
+
+ // On Windows, proguard.bat can only pass %1...%9 to the java -jar proguard.jar
+ // call, but we have at least 15 arguments here so some get dropped silently
+ // and quoting is a big issue. So instead we'll work around that by writing
+ // all the arguments to a temporary config file.
+
+ String[] commandArray = new String[3];
+
+ commandArray[0] = command.get(0);
+ commandArray[1] = command.get(1);
+
+ // Write all the other arguments to a config file
+ File argsFile = File.createTempFile(TEMP_PREFIX, ".pro"); //$NON-NLS-1$
+ // TODO FIXME this may leave a lot of temp files around on a long session.
+ // Should have a better way to clean up e.g. before each build.
+ argsFile.deleteOnExit();
+
+ FileWriter fw = new FileWriter(argsFile);
+
+ for (int i = 2; i < command.size(); i++) {
+ String s = command.get(i);
+ fw.write(s);
+ fw.write(s.startsWith("-") ? ' ' : '\n'); //$NON-NLS-1$
+ }
+
+ fw.close();
+
+ commandArray[2] = "@" + argsFile.getAbsolutePath(); //$NON-NLS-1$
+ return commandArray;
+ }
+
+ /**
+ * Quotes a single path for proguard to deal with spaces.
+ *
+ * @param path The path to quote.
+ * @return The original path if it doesn't contain a space.
+ * Or the original path surrounded by single quotes if it contains spaces.
+ */
+ private String quotePath(String path) {
+ if (path.indexOf(' ') != -1) {
+ path = '\'' + path + '\'';
+ }
+ return path;
+ }
+
+ /**
+ * Quotes a compound proguard argument to deal with spaces.
+ * <p/>
+ * Proguard takes multi-path arguments such as "path1;path2" for some options.
+ * When the {@link #quotePath} methods adds quotes for such a path if it contains spaces,
+ * the proguard shell wrapper will absorb the quotes, so we need to quote around the
+ * quotes.
+ *
+ * @param path The path to quote.
+ * @return The original path if it doesn't contain a single quote.
+ * Or on Windows the original path surrounded by double quotes if it contains a quote.
+ */
+ private String quoteWinArg(String path) {
+ if (path.indexOf('\'') != -1 &&
+ SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS) {
+ path = '"' + path + '"';
+ }
+ return path;
+ }
+
+
+ /**
+ * Execute the Dx tool for dalvik code conversion.
+ * @param javaProject The java project
+ * @param inputPaths the input paths for DX
+ * @param osOutFilePath the path of the dex file to create.
+ *
+ * @throws CoreException
+ * @throws DexException
+ */
+ public void executeDx(IJavaProject javaProject, Collection<String> inputPaths,
+ String osOutFilePath)
+ throws CoreException, DexException {
+
+ // get the dex wrapper
+ Sdk sdk = Sdk.getCurrent();
+ DexWrapper wrapper = sdk.getDexWrapper(mBuildToolInfo);
+
+ if (wrapper == null) {
+ throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ Messages.ApkBuilder_UnableBuild_Dex_Not_loaded));
+ }
+
+ try {
+ // set a temporary prefix on the print streams.
+ mOutStream.setPrefix(CONSOLE_PREFIX_DX);
+ mErrStream.setPrefix(CONSOLE_PREFIX_DX);
+
+ IFolder binFolder = BaseProjectHelper.getAndroidOutputFolder(javaProject.getProject());
+ File binFile = binFolder.getLocation().toFile();
+ File dexedLibs = new File(binFile, "dexedLibs");
+ if (dexedLibs.exists() == false) {
+ dexedLibs.mkdir();
+ }
+
+ // replace the libs by their dexed versions (dexing them if needed.)
+ List<String> finalInputPaths = new ArrayList<String>(inputPaths.size());
+ if (mDisableDexMerger || inputPaths.size() == 1) {
+ // only one input, no need to put a pre-dexed version, even if this path is
+ // just a jar file (case for proguard'ed builds)
+ finalInputPaths.addAll(inputPaths);
+ } else {
+
+ for (String input : inputPaths) {
+ File inputFile = new File(input);
+ if (inputFile.isDirectory()) {
+ finalInputPaths.add(input);
+ } else if (inputFile.isFile()) {
+ String fileName = getDexFileName(inputFile);
+
+ File dexedLib = new File(dexedLibs, fileName);
+ String dexedLibPath = dexedLib.getAbsolutePath();
+
+ if (dexedLib.isFile() == false ||
+ dexedLib.lastModified() < inputFile.lastModified()) {
+
+ if (mVerbose) {
+ mOutStream.println(
+ String.format("Pre-Dexing %1$s -> %2$s", input, fileName));
+ }
+
+ if (dexedLib.isFile()) {
+ dexedLib.delete();
+ }
+
+ int res = wrapper.run(dexedLibPath, Collections.singleton(input),
+ mForceJumbo, mVerbose, mOutStream, mErrStream);
+
+ if (res != 0) {
+ // output error message and mark the project.
+ String message = String.format(Messages.Dalvik_Error_d, res);
+ throw new DexException(message);
+ }
+ } else {
+ if (mVerbose) {
+ mOutStream.println(
+ String.format("Using Pre-Dexed %1$s <- %2$s",
+ fileName, input));
+ }
+ }
+
+ finalInputPaths.add(dexedLibPath);
+ }
+ }
+ }
+
+ if (mVerbose) {
+ for (String input : finalInputPaths) {
+ mOutStream.println("Input: " + input);
+ }
+ }
+
+ int res = wrapper.run(osOutFilePath,
+ finalInputPaths,
+ mForceJumbo,
+ mVerbose,
+ mOutStream, mErrStream);
+
+ mOutStream.setPrefix(null);
+ mErrStream.setPrefix(null);
+
+ if (res != 0) {
+ // output error message and marker the project.
+ String message = String.format(Messages.Dalvik_Error_d, res);
+ throw new DexException(message);
+ }
+ } catch (DexException e) {
+ throw e;
+ } catch (Throwable t) {
+ String message = t.getMessage();
+ if (message == null) {
+ message = t.getClass().getCanonicalName();
+ }
+ message = String.format(Messages.Dalvik_Error_s, message);
+
+ throw new DexException(message, t);
+ }
+ }
+
+ private String getDexFileName(File inputFile) {
+ // get the filename
+ String name = inputFile.getName();
+ // remove the extension
+ int pos = name.lastIndexOf('.');
+ if (pos != -1) {
+ name = name.substring(0, pos);
+ }
+
+ // add a hash of the original file path
+ HashFunction hashFunction = Hashing.md5();
+ HashCode hashCode = hashFunction.hashString(inputFile.getAbsolutePath(), Charsets.UTF_8);
+
+ return name + "-" + hashCode.toString() + ".jar";
+ }
+
+ /**
+ * Executes aapt. If any error happen, files or the project will be marked.
+ * @param command The command for aapt to execute. Currently supported: package and crunch
+ * @param osManifestPath The path to the manifest file
+ * @param osResPath The path to the res folder
+ * @param osAssetsPath The path to the assets folder. This can be null.
+ * @param osOutFilePath The path to the temporary resource file to create,
+ * or in the case of crunching the path to the cache to create/update.
+ * @param configFilter The configuration filter for the resources to include
+ * (used with -c option, for example "port,en,fr" to include portrait, English and French
+ * resources.)
+ * @param versionCode optional version code to insert in the manifest during packaging. If <=0
+ * then no value is inserted
+ * @throws AaptExecException
+ * @throws AaptResultException
+ */
+ private void executeAapt(String aaptCommand, String osManifestPath,
+ List<String> osResPaths, String osAssetsPath, String osOutFilePath,
+ String configFilter, int versionCode) throws AaptExecException, AaptResultException {
+ IAndroidTarget target = Sdk.getCurrent().getTarget(mProject);
+
+ String aapt = mBuildToolInfo.getPath(BuildToolInfo.PathId.AAPT);
+
+ // Create the command line.
+ ArrayList<String> commandArray = new ArrayList<String>();
+ commandArray.add(aapt);
+ commandArray.add(aaptCommand);
+ if (AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE) {
+ commandArray.add("-v"); //$NON-NLS-1$
+ }
+
+ // Common to all commands
+ for (String path : osResPaths) {
+ commandArray.add("-S"); //$NON-NLS-1$
+ commandArray.add(path);
+ }
+
+ if (aaptCommand.equals(COMMAND_PACKAGE)) {
+ commandArray.add("-f"); //$NON-NLS-1$
+ commandArray.add("--no-crunch"); //$NON-NLS-1$
+
+ // if more than one res, this means there's a library (or more) and we need
+ // to activate the auto-add-overlay
+ if (osResPaths.size() > 1) {
+ commandArray.add("--auto-add-overlay"); //$NON-NLS-1$
+ }
+
+ if (mDebugMode) {
+ commandArray.add("--debug-mode"); //$NON-NLS-1$
+ }
+
+ if (versionCode > 0) {
+ commandArray.add("--version-code"); //$NON-NLS-1$
+ commandArray.add(Integer.toString(versionCode));
+ }
+
+ if (configFilter != null) {
+ commandArray.add("-c"); //$NON-NLS-1$
+ commandArray.add(configFilter);
+ }
+
+ // never compress apks.
+ commandArray.add("-0");
+ commandArray.add("apk");
+
+ commandArray.add("-M"); //$NON-NLS-1$
+ commandArray.add(osManifestPath);
+
+ if (osAssetsPath != null) {
+ commandArray.add("-A"); //$NON-NLS-1$
+ commandArray.add(osAssetsPath);
+ }
+
+ commandArray.add("-I"); //$NON-NLS-1$
+ commandArray.add(target.getPath(IAndroidTarget.ANDROID_JAR));
+
+ commandArray.add("-F"); //$NON-NLS-1$
+ commandArray.add(osOutFilePath);
+ } else if (aaptCommand.equals(COMMAND_CRUNCH)) {
+ commandArray.add("-C"); //$NON-NLS-1$
+ commandArray.add(osOutFilePath);
+ }
+
+ String command[] = commandArray.toArray(
+ new String[commandArray.size()]);
+
+ if (AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE) {
+ StringBuilder sb = new StringBuilder();
+ for (String c : command) {
+ sb.append(c);
+ sb.append(' ');
+ }
+ AdtPlugin.printToConsole(mProject, sb.toString());
+ }
+
+ // Benchmarking start
+ long startAaptTime = 0;
+ if (BENCHMARK_FLAG) {
+ String msg = "BENCHMARK ADT: Starting " + aaptCommand //$NON-NLS-1$
+ + " call to Aapt"; //$NON-NLS-1$
+ AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, mProject, msg);
+ startAaptTime = System.nanoTime();
+ }
+
+ // launch
+ try {
+ // launch the command line process
+ Process process = Runtime.getRuntime().exec(command);
+
+ // list to store each line of stderr
+ ArrayList<String> stdErr = new ArrayList<String>();
+
+ // get the output and return code from the process
+ int returnCode = grabProcessOutput(mProject, process, stdErr);
+
+ if (mVerbose) {
+ for (String stdErrString : stdErr) {
+ mOutStream.println(stdErrString);
+ }
+ }
+ if (returnCode != 0) {
+ throw new AaptResultException(returnCode,
+ stdErr.toArray(new String[stdErr.size()]));
+ }
+ } catch (IOException e) {
+ String msg = String.format(Messages.AAPT_Exec_Error_s, command[0]);
+ throw new AaptExecException(msg, e);
+ } catch (InterruptedException e) {
+ String msg = String.format(Messages.AAPT_Exec_Error_s, command[0]);
+ throw new AaptExecException(msg, e);
+ }
+
+ // Benchmarking end
+ if (BENCHMARK_FLAG) {
+ String msg = "BENCHMARK ADT: Ending " + aaptCommand //$NON-NLS-1$
+ + " call to Aapt.\nBENCHMARK ADT: Time Elapsed: " //$NON-NLS-1$
+ + ((System.nanoTime() - startAaptTime)/MILLION) + "ms"; //$NON-NLS-1$
+ AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, mProject, msg);
+ }
+ }
+
+ /**
+ * Computes all the project output and dependencies that must go into building the apk.
+ *
+ * @param resMarker
+ * @throws CoreException
+ */
+ private void gatherPaths(ResourceMarker resMarker)
+ throws CoreException {
+ IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot();
+
+ // get a java project for the project.
+ IJavaProject javaProject = JavaCore.create(mProject);
+
+
+ // get the output of the main project
+ IPath path = javaProject.getOutputLocation();
+ IResource outputResource = wsRoot.findMember(path);
+ if (outputResource != null && outputResource.getType() == IResource.FOLDER) {
+ mCompiledCodePaths.add(outputResource.getLocation().toOSString());
+ }
+
+ // we could use IJavaProject.getResolvedClasspath directly, but we actually
+ // want to see the containers themselves.
+ IClasspathEntry[] classpaths = javaProject.readRawClasspath();
+ if (classpaths != null) {
+ for (IClasspathEntry e : classpaths) {
+ // ignore non exported entries, unless they're in the DEPEDENCIES container,
+ // in which case we always want it (there may be some older projects that
+ // have it as non exported).
+ if (e.isExported() ||
+ (e.getEntryKind() == IClasspathEntry.CPE_CONTAINER &&
+ e.getPath().toString().equals(AdtConstants.CONTAINER_DEPENDENCIES))) {
+ handleCPE(e, javaProject, wsRoot, resMarker);
+ }
+ }
+ }
+ }
+
+ private void handleCPE(IClasspathEntry entry, IJavaProject javaProject,
+ IWorkspaceRoot wsRoot, ResourceMarker resMarker) {
+
+ // if this is a classpath variable reference, we resolve it.
+ if (entry.getEntryKind() == IClasspathEntry.CPE_VARIABLE) {
+ entry = JavaCore.getResolvedClasspathEntry(entry);
+ }
+
+ if (entry.getEntryKind() == IClasspathEntry.CPE_PROJECT) {
+ IProject refProject = wsRoot.getProject(entry.getPath().lastSegment());
+ try {
+ // ignore if it's an Android project, or if it's not a Java Project
+ if (refProject.hasNature(JavaCore.NATURE_ID) &&
+ refProject.hasNature(AdtConstants.NATURE_DEFAULT) == false) {
+ IJavaProject refJavaProject = JavaCore.create(refProject);
+
+ // get the output folder
+ IPath path = refJavaProject.getOutputLocation();
+ IResource outputResource = wsRoot.findMember(path);
+ if (outputResource != null && outputResource.getType() == IResource.FOLDER) {
+ mCompiledCodePaths.add(outputResource.getLocation().toOSString());
+ }
+ }
+ } catch (CoreException exception) {
+ // can't query the project nature? ignore
+ }
+
+ } else if (entry.getEntryKind() == IClasspathEntry.CPE_LIBRARY) {
+ handleClasspathLibrary(entry, wsRoot, resMarker);
+ } else if (entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER) {
+ // get the container
+ try {
+ IClasspathContainer container = JavaCore.getClasspathContainer(
+ entry.getPath(), javaProject);
+ // ignore the system and default_system types as they represent
+ // libraries that are part of the runtime.
+ if (container != null && container.getKind() == IClasspathContainer.K_APPLICATION) {
+ IClasspathEntry[] entries = container.getClasspathEntries();
+ for (IClasspathEntry cpe : entries) {
+ handleCPE(cpe, javaProject, wsRoot, resMarker);
+ }
+ }
+ } catch (JavaModelException jme) {
+ // can't resolve the container? ignore it.
+ AdtPlugin.log(jme, "Failed to resolve ClasspathContainer: %s", entry.getPath());
+ }
+ }
+ }
+
+ private void handleClasspathLibrary(IClasspathEntry e, IWorkspaceRoot wsRoot,
+ ResourceMarker resMarker) {
+ // get the IPath
+ IPath path = e.getPath();
+
+ IResource resource = wsRoot.findMember(path);
+
+ if (resource != null && resource.getType() == IResource.PROJECT) {
+ // if it's a project we should just ignore it because it's going to be added
+ // later when we add all the referenced projects.
+
+ } else if (SdkConstants.EXT_JAR.equalsIgnoreCase(path.getFileExtension())) {
+ // case of a jar file (which could be relative to the workspace or a full path)
+ if (resource != null && resource.exists() &&
+ resource.getType() == IResource.FILE) {
+ mCompiledCodePaths.add(resource.getLocation().toOSString());
+ } else {
+ // if the jar path doesn't match a workspace resource,
+ // then we get an OSString and check if this links to a valid file.
+ String osFullPath = path.toOSString();
+
+ File f = new File(osFullPath);
+ if (f.isFile()) {
+ mCompiledCodePaths.add(osFullPath);
+ } else {
+ String message = String.format( Messages.Couldnt_Locate_s_Error,
+ path);
+ // always output to the console
+ mOutStream.println(message);
+
+ // put a marker
+ if (resMarker != null) {
+ resMarker.setWarning(mProject, message);
+ }
+ }
+ }
+ } else {
+ // this can be the case for a class folder.
+ if (resource != null && resource.exists() &&
+ resource.getType() == IResource.FOLDER) {
+ mCompiledCodePaths.add(resource.getLocation().toOSString());
+ } else {
+ // if the path doesn't match a workspace resource,
+ // then we get an OSString and check if this links to a valid folder.
+ String osFullPath = path.toOSString();
+
+ File f = new File(osFullPath);
+ if (f.isDirectory()) {
+ mCompiledCodePaths.add(osFullPath);
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks a {@link IFile} to make sure it should be packaged as standard resources.
+ * @param file the IFile representing the file.
+ * @return true if the file should be packaged as standard java resources.
+ */
+ public static boolean checkFileForPackaging(IFile file) {
+ String name = file.getName();
+
+ String ext = file.getFileExtension();
+ return ApkBuilder.checkFileForPackaging(name, ext);
+ }
+
+ /**
+ * Checks whether an {@link IFolder} and its content is valid for packaging into the .apk as
+ * standard Java resource.
+ * @param folder the {@link IFolder} to check.
+ */
+ public static boolean checkFolderForPackaging(IFolder folder) {
+ String name = folder.getName();
+ return ApkBuilder.checkFolderForPackaging(name);
+ }
+
+ /**
+ * Returns a list of {@link IJavaProject} matching the provided {@link IProject} objects.
+ * @param projects the IProject objects.
+ * @return a new list object containing the IJavaProject object for the given IProject objects.
+ * @throws CoreException
+ */
+ public static List<IJavaProject> getJavaProjects(List<IProject> projects) throws CoreException {
+ ArrayList<IJavaProject> list = new ArrayList<IJavaProject>();
+
+ for (IProject p : projects) {
+ if (p.isOpen() && p.hasNature(JavaCore.NATURE_ID)) {
+
+ list.add(JavaCore.create(p));
+ }
+ }
+
+ return list;
+ }
+
+ /**
+ * Get the stderr output of a process and return when the process is done.
+ * @param process The process to get the output from
+ * @param stderr The array to store the stderr output
+ * @return the process return code.
+ * @throws InterruptedException
+ */
+ public final static int grabProcessOutput(
+ final IProject project,
+ final Process process,
+ final ArrayList<String> stderr)
+ throws InterruptedException {
+
+ return GrabProcessOutput.grabProcessOutput(
+ process,
+ Wait.WAIT_FOR_READERS, // we really want to make sure we get all the output!
+ new IProcessOutput() {
+
+ @SuppressWarnings("unused")
+ @Override
+ public void out(@Nullable String line) {
+ if (line != null) {
+ // If benchmarking always print the lines that
+ // correspond to benchmarking info returned by ADT
+ if (BENCHMARK_FLAG && line.startsWith("BENCHMARK:")) { //$NON-NLS-1$
+ AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS,
+ project, line);
+ } else {
+ AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE,
+ project, line);
+ }
+ }
+ }
+
+ @Override
+ public void err(@Nullable String line) {
+ if (line != null) {
+ stderr.add(line);
+ if (BuildVerbosity.VERBOSE == AdtPrefs.getPrefs().getBuildVerbosity()) {
+ AdtPlugin.printErrorToConsole(project, line);
+ }
+ }
+ }
+ });
+ }
+}