/*
* 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.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AndroidConstants;
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.project.ProjectHelper;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.prefs.AndroidLocation.AndroidLocationException;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.IAndroidTarget.IOptionalLibrary;
import com.android.sdklib.SdkConstants;
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.SealedApkException;
import com.android.sdklib.internal.build.DebugKeyProvider;
import com.android.sdklib.internal.build.DebugKeyProvider.KeytoolException;
import com.android.sdklib.internal.build.SignedJarBuilder;
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.IResourceProxy;
import org.eclipse.core.resources.IResourceProxyVisitor;
import org.eclipse.core.resources.IWorkspace;
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.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.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
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 final IProject mProject;
private final AndroidPrintStream mOutStream;
private final AndroidPrintStream mErrStream;
private final boolean mVerbose;
private final boolean mDebugMode;
/**
* 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
*/
public BuildHelper(IProject project, AndroidPrintStream outStream,
AndroidPrintStream errStream, boolean debugMode, boolean verbose) {
mProject = project;
mOutStream = outStream;
mErrStream = errStream;
mDebugMode = debugMode;
mVerbose = verbose;
}
/**
* 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 {
// need to figure out some path before we can execute aapt;
// get the resource folder
IFolder resFolder = mProject.getFolder(AndroidConstants.WS_RESOURCES);
// and the assets folder
IFolder assetsFolder = mProject.getFolder(AndroidConstants.WS_ASSETS);
// we need to make sure this one exists.
if (assetsFolder.exists() == false) {
assetsFolder = null;
}
IPath resLocation = resFolder.getLocation();
IPath manifestLocation = manifestFile.getLocation();
if (resLocation != null && manifestLocation != null) {
// list of res folder (main project + maybe libraries)
ArrayList<String> osResPaths = new ArrayList<String>();
osResPaths.add(resLocation.toOSString()); //main project
// libraries?
if (libProjects != null) {
for (IProject lib : libProjects) {
IFolder libResFolder = lib.getFolder(SdkConstants.FD_RES);
if (libResFolder.exists()) {
osResPaths.add(libResFolder.getLocation().toOSString());
}
}
}
String osManifestPath = manifestLocation.toOSString();
String osAssetsPath = null;
if (assetsFolder != null) {
osAssetsPath = assetsFolder.getLocation().toOSString();
}
// build the default resource package
executeAapt(osManifestPath, osResPaths, osAssetsPath,
outputFolder + File.separator + outputFilename, resFilter,
versionCode);
}
}
/**
* 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 javaProject the java project being compiled
* @param libProjects an optional list of library projects (can be null)
* @param referencedJavaProjects referenced projects.
* @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,
final IJavaProject javaProject, List<IProject> libProjects,
List<IJavaProject> referencedJavaProjects, 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, javaProject, libProjects,
referencedJavaProjects, null /*abiFilter*/,
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 javaProject the java project being compiled
* @param libProjects an optional list of library projects (can be null)
* @param referencedJavaProjects referenced projects.
* @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,
final IJavaProject javaProject, List<IProject> libProjects,
List<IJavaProject> referencedJavaProjects, String abiFilter, 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);
// Now we write the standard resources from the project and the referenced projects.
writeStandardResources(apkBuilder, javaProject, referencedJavaProjects);
// Now we write the standard resources from the external jars
for (String libraryOsPath : getExternalDependencies(resMarker)) {
File libFile = new File(libraryOsPath);
if (libFile.isFile()) {
JarStatus jarStatus = apkBuilder.addResourcesFromJar(new File(libraryOsPath));
// 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 = new File(libraryOsPath).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 (libFile.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(libFile);
}
}
// 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(), abiFilter);
}
// 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(), abiFilter);
}
}
}
// seal the APK.
apkBuilder.sealApk();
} catch (SealedApkException e) {
// this won't happen as we control when the apk is sealed.
}
}
public String[] getProjectOutputs() throws CoreException {
IFolder outputFolder = BaseProjectHelper.getOutputFolder(mProject);
// get the list of referenced projects output to add
List<IProject> javaProjects = ProjectHelper.getReferencedProjects(mProject);
List<IJavaProject> referencedJavaProjects = BuildHelper.getJavaProjects(javaProjects);
// get the project output, and since it's a new list object, just add the outputFolder
// of the project directly to it.
List<String> projectOutputs = getProjectOutputs(referencedJavaProjects);
projectOutputs.add(0, outputFolder.getLocation().toOSString());
return projectOutputs.toArray(new String[projectOutputs.size()]);
}
/**
* Returns an array for all the compiled code for the project. This can include the
* code compiled by Eclipse for the main project and dependencies (Java only projects), as well
* as external jars used by the project or its library.
*
* This array of paths is compatible with the input for dx and can be passed as is to
* {@link #executeDx(IJavaProject, String[], String)}.
*
* @param resMarker
* @return a array (never empty) containing paths to compiled code.
* @throws CoreException
*/
public String[] getCompiledCodePaths(boolean includeProjectOutputs, ResourceMarker resMarker)
throws CoreException {
// get the list of libraries to include with the source code
String[] libraries = getExternalDependencies(resMarker);
int startIndex = 0;
String[] compiledPaths;
if (includeProjectOutputs) {
String[] projectOutputs = getProjectOutputs();
compiledPaths = new String[libraries.length + projectOutputs.length];
System.arraycopy(projectOutputs, 0, compiledPaths, 0, projectOutputs.length);
startIndex = projectOutputs.length;
} else {
compiledPaths = new String[libraries.length];
}
System.arraycopy(libraries, 0, compiledPaths, startIndex, libraries.length);
return compiledPaths;
}
public void runProguard(File proguardConfig, File inputJar, 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());
command.add("@" + proguardConfig.getAbsolutePath()); //$NON-NLS-1$
command.add("-injars"); //$NON-NLS-1$
StringBuilder sb = new StringBuilder(inputJar.getAbsolutePath());
for (String jarFile : jarFiles) {
sb.append(File.pathSeparatorChar);
sb.append(jarFile);
}
command.add(sb.toString());
command.add("-outjars"); //$NON-NLS-1$
command.add(obfuscatedJar.getAbsolutePath());
command.add("-libraryjars"); //$NON-NLS-1$
sb = new StringBuilder(target.getPath(IAndroidTarget.ANDROID_JAR));
IOptionalLibrary[] libraries = target.getOptionalLibraries();
if (libraries != null) {
for (IOptionalLibrary lib : libraries) {
sb.append(File.pathSeparatorChar);
sb.append(lib.getJarPath());
}
}
command.add(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[];
if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS) {
// 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.
commandArray = new String[3];
// Arg 0 is the proguard.bat path and arg 1 is the user config file
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$
} else {
// 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().getSdkLocation() + //$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());
}
}
// 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);
}
}
/**
* Execute the Dx tool for dalvik code conversion.
* @param javaProject The java project
* @param inputPath the path to the main input of dex
* @param osOutFilePath the path of the dex file to create.
*
* @throws CoreException
* @throws DexException
*/
public void executeDx(IJavaProject javaProject, String[] inputPaths, String osOutFilePath)
throws CoreException, DexException {
// get the dex wrapper
Sdk sdk = Sdk.getCurrent();
DexWrapper wrapper = sdk.getDexWrapper();
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);
int res = wrapper.run(osOutFilePath,
inputPaths,
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);
}
}
/**
* Executes aapt. If any error happen, files or the project will be marked.
* @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.
* @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 osManifestPath,
List<String> osResPaths, String osAssetsPath, String osOutFilePath,
String configFilter, int versionCode) throws AaptExecException, AaptResultException {
IAndroidTarget target = Sdk.getCurrent().getTarget(mProject);
// Create the command line.
ArrayList<String> commandArray = new ArrayList<String>();
commandArray.add(target.getPath(IAndroidTarget.AAPT));
commandArray.add("package"); //$NON-NLS-1$
commandArray.add("-f");//$NON-NLS-1$
if (AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE) {
commandArray.add("-v"); //$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);
}
commandArray.add("-M"); //$NON-NLS-1$
commandArray.add(osManifestPath);
for (String path : osResPaths) {
commandArray.add("-S"); //$NON-NLS-1$
commandArray.add(path);
}
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);
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());
}
// launch
int execError = 1;
try {
// launch the command line process
Process process = Runtime.getRuntime().exec(command);
// 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 AaptResultException(execError,
results.toArray(new String[results.size()]));
}
} catch (IOException e) {
String msg = String.format(Messages.AAPT_Exec_Error, command[0]);
throw new AaptExecException(msg, e);
} catch (InterruptedException e) {
String msg = String.format(Messages.AAPT_Exec_Error, command[0]);
throw new AaptExecException(msg, e);
}
}
/**
* Writes the standard resources of a project and its referenced projects
* into a {@link SignedJarBuilder}.
* Standard resources are non java/aidl files placed in the java package folders.
* @param apkBuilder the {@link ApkBuilder}.
* @param javaProject the javaProject object.
* @param referencedJavaProjects the java projects that this project references.
* @throws ApkCreationException if an error occurred
* @throws SealedApkException if the APK is already sealed.
* @throws DuplicateFileException if a file conflicts with another already added to the APK
* at the same location inside the APK archive.
* @throws CoreException
*/
private void writeStandardResources(ApkBuilder apkBuilder, IJavaProject javaProject,
List<IJavaProject> referencedJavaProjects)
throws DuplicateFileException, ApkCreationException, SealedApkException,
CoreException {
IWorkspace ws = ResourcesPlugin.getWorkspace();
IWorkspaceRoot wsRoot = ws.getRoot();
// create a list of path already put into the archive, in order to detect conflict
ArrayList<String> list = new ArrayList<String>();
writeStandardProjectResources(apkBuilder, javaProject, wsRoot, list);
for (IJavaProject referencedJavaProject : referencedJavaProjects) {
// only include output from non android referenced project
// (This is to handle the case of reference Android projects in the context of
// instrumentation projects that need to reference the projects to be tested).
if (referencedJavaProject.getProject().hasNature(
AndroidConstants.NATURE_DEFAULT) == false) {
writeStandardProjectResources(apkBuilder, referencedJavaProject, wsRoot, list);
}
}
}
/**
* Writes the standard resources of a {@link IJavaProject} into a {@link SignedJarBuilder}.
* Standard resources are non java/aidl files placed in the java package folders.
* @param jarBuilder the {@link ApkBuilder}.
* @param javaProject the javaProject object.
* @param wsRoot the {@link IWorkspaceRoot}.
* @param list a list of files already added to the archive, to detect conflicts.
* @throws ApkCreationException if an error occurred
* @throws SealedApkException if the APK is already sealed.
* @throws DuplicateFileException if a file conflicts with another already added to the APK
* at the same location inside the APK archive.
* @throws CoreException
*/
private void writeStandardProjectResources(ApkBuilder apkBuilder,
IJavaProject javaProject, IWorkspaceRoot wsRoot, ArrayList<String> list)
throws DuplicateFileException, ApkCreationException, SealedApkException, CoreException {
// get the source pathes
List<IPath> sourceFolders = BaseProjectHelper.getSourceClasspaths(javaProject);
// loop on them and then recursively go through the content looking for matching files.
for (IPath sourcePath : sourceFolders) {
IResource sourceResource = wsRoot.findMember(sourcePath);
if (sourceResource != null && sourceResource.getType() == IResource.FOLDER) {
writeFolderResources(apkBuilder, javaProject, (IFolder) sourceResource);
}
}
}
private void writeFolderResources(ApkBuilder apkBuilder, final IJavaProject javaProject,
IFolder root) throws CoreException, ApkCreationException,
SealedApkException, DuplicateFileException {
final List<IPath> pathsToPackage = new ArrayList<IPath>();
root.accept(new IResourceProxyVisitor() {
public boolean visit(IResourceProxy proxy) throws CoreException {
if (proxy.getType() == IResource.FOLDER) {
// If this folder isn't wanted, don't traverse into it.
return ApkBuilder.checkFolderForPackaging(proxy.getName());
}
// If it's not a folder, it must be a file. We won't see any other resource type.
if (!ApkBuilder.checkFileForPackaging(proxy.getName())) {
return true;
}
IResource res = proxy.requestResource();
if (!javaProject.isOnClasspath(res)) {
return true;
}
// Just record that we need to package this. Packaging here throws
// inappropriate checked exceptions.
IPath location = res.getLocation();
pathsToPackage.add(location);
return true;
}
}, 0);
IPath rootLocation = root.getLocation();
for (IPath path : pathsToPackage) {
IPath archivePath = path.makeRelativeTo(rootLocation);
apkBuilder.addFile(path.toFile(), archivePath.toString());
}
}
/**
* Returns an array of external dependencies used the project. This can be paths to jar files
* or to source folders.
*
* @param resMarker if non null, used to put Resource marker on problem files.
* @return an array of OS-specific absolute file paths
*/
private final String[] getExternalDependencies(ResourceMarker resMarker) {
// get a java project from it
IJavaProject javaProject = JavaCore.create(mProject);
IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot();
ArrayList<String> oslibraryList = new ArrayList<String>();
IClasspathEntry[] classpaths = javaProject.readRawClasspath();
if (classpaths != null) {
for (IClasspathEntry e : classpaths) {
if (e.getEntryKind() == IClasspathEntry.CPE_LIBRARY ||
e.getEntryKind() == IClasspathEntry.CPE_VARIABLE) {
// if this is a classpath variable reference, we resolve it.
if (e.getEntryKind() == IClasspathEntry.CPE_VARIABLE) {
e = JavaCore.getResolvedClasspathEntry(e);
}
// get the IPath
IPath path = e.getPath();
IResource resource = wsRoot.findMember(path);
// case of a jar file (which could be relative to the workspace or a full path)
if (AndroidConstants.EXT_JAR.equalsIgnoreCase(path.getFileExtension())) {
if (resource != null && resource.exists() &&
resource.getType() == IResource.FILE) {
oslibraryList.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()) {
oslibraryList.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) {
oslibraryList.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()) {
oslibraryList.add(osFullPath);
}
}
}
}
}
}
return oslibraryList.toArray(new String[oslibraryList.size()]);
}
/**
* Returns the list of the output folders for the specified {@link IJavaProject} objects, if
* they are Android projects.
*
* @param referencedJavaProjects the java projects.
* @return a new list object containing the output folder paths.
* @throws CoreException
*/
private List<String> getProjectOutputs(List<IJavaProject> referencedJavaProjects)
throws CoreException {
ArrayList<String> list = new ArrayList<String>();
IWorkspace ws = ResourcesPlugin.getWorkspace();
IWorkspaceRoot wsRoot = ws.getRoot();
for (IJavaProject javaProject : referencedJavaProjects) {
// only include output from non android referenced project
// (This is to handle the case of reference Android projects in the context of
// instrumentation projects that need to reference the projects to be tested).
if (javaProject.getProject().hasNature(AndroidConstants.NATURE_DEFAULT) == false) {
// get the output folder
IPath path = null;
try {
path = javaProject.getOutputLocation();
} catch (JavaModelException e) {
continue;
}
IResource outputResource = wsRoot.findMember(path);
if (outputResource != null && outputResource.getType() == IResource.FOLDER) {
String outputOsPath = outputResource.getLocation().toOSString();
list.add(outputOsPath);
}
}
}
return list;
}
/**
* 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 ouput from
* @param results 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> results)
throws InterruptedException {
// Due to the limited buffer size on windows for the standard io (stderr, stdout), we
// *need* to read both stdout and stderr all the time. If we don't and a process output
// a large amount, this could deadlock the process.
// read the lines as they come. if null is returned, it's
// because the process finished
new Thread("") { //$NON-NLS-1$
@Override
public void run() {
// create a buffer to read the stderr output
InputStreamReader is = new InputStreamReader(process.getErrorStream());
BufferedReader errReader = new BufferedReader(is);
try {
while (true) {
String line = errReader.readLine();
if (line != null) {
results.add(line);
} else {
break;
}
}
} catch (IOException e) {
// do nothing.
}
}
}.start();
new Thread("") { //$NON-NLS-1$
@Override
public void run() {
InputStreamReader is = new InputStreamReader(process.getInputStream());
BufferedReader outReader = new BufferedReader(is);
try {
while (true) {
String line = outReader.readLine();
if (line != null) {
AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE,
project, line);
} else {
break;
}
}
} catch (IOException e) {
// do nothing.
}
}
}.start();
// get the return code from the process
return process.waitFor();
}
}