diff options
author | Xavier Ducrohet <xav@android.com> | 2010-09-02 10:52:41 -0700 |
---|---|---|
committer | Android Code Review <code-review@android.com> | 2010-09-02 10:52:41 -0700 |
commit | 68ae08034da99bef9a5f5f486db417ef04f992fa (patch) | |
tree | 56bd695ac88bf6042d2acff2c5a776b5e0f9ef20 /eclipse/plugins | |
parent | c6613461499d0a812ce1bcf7dd72df6f02010417 (diff) | |
parent | 3d3c3c3a3e4e05f7ae7a0dff440fe500f90b785c (diff) | |
download | sdk-68ae08034da99bef9a5f5f486db417ef04f992fa.zip sdk-68ae08034da99bef9a5f5f486db417ef04f992fa.tar.gz sdk-68ae08034da99bef9a5f5f486db417ef04f992fa.tar.bz2 |
Merge "ADT: Make release and debug builds really different."
Diffstat (limited to 'eclipse/plugins')
10 files changed, 716 insertions, 525 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java index 41771f1..77ba50e 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java @@ -31,9 +31,7 @@ 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.AndroidClasspathContainerInitializer; import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; -import com.android.ide.eclipse.adt.internal.project.ExportHelper; import com.android.ide.eclipse.adt.internal.project.ProjectHelper; -import com.android.ide.eclipse.adt.internal.project.ExportHelper.IExportCallback; import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor; import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources; import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolder; @@ -44,7 +42,6 @@ import com.android.ide.eclipse.adt.internal.sdk.LoadStatus; import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener; import com.android.ide.eclipse.adt.internal.ui.EclipseUiHelper; -import com.android.ide.eclipse.adt.internal.wizards.export.ExportWizard; import com.android.ide.eclipse.ddms.DdmsPlugin; import com.android.ide.eclipse.hierarchyviewer.HierarchyViewerPlugin; import com.android.sdklib.IAndroidTarget; @@ -73,8 +70,6 @@ import org.eclipse.core.runtime.jobs.JobChangeAdapter; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.resource.ImageDescriptor; -import org.eclipse.jface.viewers.StructuredSelection; -import org.eclipse.jface.wizard.WizardDialog; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Display; @@ -105,12 +100,10 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; -import java.io.PrintStream; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; -import java.util.Calendar; import java.util.List; /** @@ -156,44 +149,6 @@ public class AdtPlugin extends AbstractUIPlugin { protected boolean mSdkIsLoading; /** - * Custom PrintStream for Dx output. This class overrides the method - * <code>println()</code> and adds the standard output tag with the - * date and the project name in front of every messages. - */ - private static final class AndroidPrintStream extends PrintStream { - private IProject mProject; - private String mPrefix; - - /** - * Default constructor with project and output stream. - * The project is used to get the project name for the output tag. - * - * @param project The Project - * @param prefix A prefix to be printed before the actual message. Can be null - * @param stream The Stream - */ - public AndroidPrintStream(IProject project, String prefix, OutputStream stream) { - super(stream); - mProject = project; - } - - @Override - public void println(String message) { - // write the date/project tag first. - String tag = getMessageTag(mProject != null ? mProject.getName() : null); - - print(tag); - print(' '); - if (mPrefix != null) { - print(mPrefix); - } - - // then write the regular message - super.println(message); - } - } - - /** * An error handler for checkSdkLocationAndId() that will handle the generated error * or warning message. Each method must return a boolean that will in turn be returned by * checkSdkLocationAndId. @@ -346,19 +301,6 @@ public class AdtPlugin extends AbstractUIPlugin { } }); - // setup export callback for editors - ExportHelper.setCallback(new IExportCallback() { - public void startExportWizard(IProject project) { - StructuredSelection selection = new StructuredSelection(project); - - ExportWizard wizard = new ExportWizard(); - wizard.init(PlatformUI.getWorkbench(), selection); - WizardDialog dialog = new WizardDialog(getDisplay().getActiveShell(), - wizard); - dialog.open(); - } - }); - // initialize editors startEditors(); @@ -805,40 +747,6 @@ public class AdtPlugin extends AbstractUIPlugin { } /** - * Returns an standard PrintStream object for a specific project.<br> - * This PrintStream will add a date/project at the beginning of every - * <code>println()</code> output. - * - * @param project The project object - * @param prefix The prefix to be added to the message. Can be null. - * @return a new PrintStream - */ - public static synchronized PrintStream getOutPrintStream(IProject project, String prefix) { - if (sPlugin != null) { - return new AndroidPrintStream(project, prefix, sPlugin.mAndroidConsoleStream); - } - - return null; - } - - /** - * Returns an error PrintStream object for a specific project.<br> - * This PrintStream will add a date/project at the beginning of every - * <code>println()</code> output. - * - * @param project The project object - * @param prefix The prefix to be added to the message. Can be null. - * @return a new PrintStream - */ - public static synchronized PrintStream getErrPrintStream(IProject project, String prefix) { - if (sPlugin != null) { - return new AndroidPrintStream(project, prefix, sPlugin.mAndroidConsoleErrorStream); - } - - return null; - } - - /** * Returns whether the {@link IAndroidTarget}s have been loaded from the SDK. */ public final LoadStatus getSdkLoadStatus() { @@ -1365,6 +1273,10 @@ public class AdtPlugin extends AbstractUIPlugin { }); } + public static synchronized OutputStream getOutStream() { + return sPlugin.mAndroidConsoleStream; + } + public static synchronized OutputStream getErrorStream() { return sPlugin.mAndroidConsoleErrorStream; } @@ -1409,7 +1321,7 @@ public class AdtPlugin extends AbstractUIPlugin { */ public static synchronized void printToStream(MessageConsoleStream stream, String tag, Object... objects) { - String dateTag = getMessageTag(tag); + String dateTag = AndroidPrintStream.getMessageTag(tag); for (Object obj : objects) { stream.print(dateTag); @@ -1423,21 +1335,4 @@ public class AdtPlugin extends AbstractUIPlugin { } } } - - /** - * Creates a string containing the current date/time, and the tag. - * The tag does not end with a whitespace. - * @param tag The tag associated to the message. Can be null - * @return The dateTag - */ - public static String getMessageTag(String tag) { - Calendar c = Calendar.getInstance(); - - if (tag == null) { - return String.format(Messages.Console_Date_Tag, c); - } - - return String.format(Messages.Console_Data_Project_Tag, c, tag); - } - } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AndroidPrintStream.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AndroidPrintStream.java new file mode 100644 index 0000000..5323742 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AndroidPrintStream.java @@ -0,0 +1,88 @@ +/* + * 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; + +import org.eclipse.core.resources.IProject; + +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.Calendar; + +/** + * Custom PrintStream allowing to precede the message with a tag containing data/project info. + * + * Additionally, a prefix can be set (and removed) at runtime. + * + * Only {@link #println()} is supported. + */ +public class AndroidPrintStream extends PrintStream { + private IProject mProject; + private String mPrefix; + + /** + * Default constructor with project and output stream. + * The project is used to get the project name for the output tag. + * + * @param project The Project + * @param prefix A prefix to be printed before the actual message. Can be null + * @param stream The Stream + */ + public AndroidPrintStream(IProject project, String prefix, OutputStream stream) { + super(stream); + mProject = project; + } + + /** + * Updates the value of the prefix. + * @param prefix + */ + public void setPrefix(String prefix) { + mPrefix = prefix; + } + + @Override + public void println(String message) { + // write the date/project tag first. + String tag = getMessageTag(mProject != null ? mProject.getName() : null); + + print(tag); + print(": "); + if (mPrefix != null) { + print(mPrefix); + } + + // then write the regular message + super.println(message); + } + + /** + * Creates a string containing the current date/time, and the tag. + * The tag does not end with a whitespace. + * @param tag The tag associated to the message. Can be null + * @return The dateTag + */ + public static String getMessageTag(String tag) { + Calendar c = Calendar.getInstance(); + + if (tag == null) { + return String.format(Messages.Console_Date_Tag, c); + } + + return String.format(Messages.Console_Data_Project_Tag, c, tag); + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/MultiApkExportAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/MultiApkExportAction.java index 6ff6d0b..70e886d 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/MultiApkExportAction.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/MultiApkExportAction.java @@ -17,6 +17,7 @@ package com.android.ide.eclipse.adt.internal.actions; import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.AndroidPrintStream; import com.android.ide.eclipse.adt.internal.build.PostCompilerHelper; import com.android.ide.eclipse.adt.internal.project.ProjectHelper; import com.android.ide.eclipse.adt.internal.sdk.ProjectState; @@ -43,6 +44,8 @@ import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jface.action.IAction; @@ -175,6 +178,11 @@ public class MultiApkExportAction implements IObjectActionDelegate { binFolder.create(true, true, monitor); } + AndroidPrintStream stdout = new AndroidPrintStream(exportProject, null /*prefix*/, + System.out); + AndroidPrintStream stderr = new AndroidPrintStream(exportProject, null /*prefix*/, + System.err); + for (ApkData apk : apks) { // find the IProject object for this apk. ProjectConfig projectConfig = apk.getProjectConfig(); @@ -218,11 +226,11 @@ public class MultiApkExportAction implements IObjectActionDelegate { // if there are soft variants, only export those. for (Entry<String, String> entry : variantMap.entrySet()) { buildVariant(wsRoot, projectState, appPackage, versionCode, apk, entry, - binFolder); + binFolder, stdout, stderr); } } else { buildVariant(wsRoot, projectState, appPackage, versionCode, apk, - null /*soft variant*/, binFolder); + null /*soft variant*/, binFolder, stdout, stderr); } } @@ -241,63 +249,71 @@ public class MultiApkExportAction implements IObjectActionDelegate { * @throws CoreException */ private void buildVariant(IWorkspaceRoot wsRoot, ProjectState projectState, String appPackage, - int versionCode, ApkData apk, Entry<String, String> softVariant, IFolder binFolder) + int versionCode, ApkData apk, Entry<String, String> softVariant, IFolder binFolder, + AndroidPrintStream stdout, AndroidPrintStream stderr) throws CoreException { - // get the libraries for this project - IProject[] libProjects = projectState.getFullLibraryProjects(); - - IProject project = projectState.getProject(); - IJavaProject javaProject = JavaCore.create(project); - - int compositeVersionCode = apk.getCompositeVersionCode(versionCode); - - // figure out the file names - String pkgName = project.getName() + "-" + apk.getBuildInfo(); - String finalNameRoot = appPackage + "-" + compositeVersionCode; - if (softVariant != null) { - String tmp = "-" + softVariant.getKey(); - pkgName += tmp; - finalNameRoot += tmp; - } + try { + // get the libraries for this project + IProject[] libProjects = projectState.getFullLibraryProjects(); - pkgName += ".ap_"; - String outputName = finalNameRoot + "-unsigned.apk"; + IProject project = projectState.getProject(); + IJavaProject javaProject = JavaCore.create(project); - PostCompilerHelper helper = new PostCompilerHelper(project, System.out, System.err); + int compositeVersionCode = apk.getCompositeVersionCode(versionCode); - // get the manifest file - IFile manifestFile = project.getFile(SdkConstants.FN_ANDROID_MANIFEST_XML); - // get the project bin folder - IFolder projectBinFolder = wsRoot.getFolder(javaProject.getOutputLocation()); - String projectBinFolderPath = projectBinFolder.getLocation().toOSString(); + // figure out the file names + String pkgName = project.getName() + "-" + apk.getBuildInfo(); + String finalNameRoot = appPackage + "-" + compositeVersionCode; + if (softVariant != null) { + String tmp = "-" + softVariant.getKey(); + pkgName += tmp; + finalNameRoot += tmp; + } - // package the resources - if (helper.packageResources(manifestFile, libProjects, - softVariant != null ? softVariant.getValue() : null, compositeVersionCode, - projectBinFolderPath, pkgName) == false) { - return; + pkgName += ".ap_"; + String outputName = finalNameRoot + "-unsigned.apk"; + + PostCompilerHelper helper = new PostCompilerHelper(project, stdout, stderr, + false /*debugMode*/, false/*verbose*/); + + // get the manifest file + IFile manifestFile = project.getFile(SdkConstants.FN_ANDROID_MANIFEST_XML); + // get the project bin folder + IFolder projectBinFolder = wsRoot.getFolder(javaProject.getOutputLocation()); + String projectBinFolderPath = projectBinFolder.getLocation().toOSString(); + + // package the resources + helper.packageResources(manifestFile, libProjects, + softVariant != null ? softVariant.getValue() : null, + compositeVersionCode, projectBinFolderPath, pkgName); + + apk.setOutputName(softVariant != null ? softVariant.getKey() : null, outputName); + + // do the final export. + IFile dexFile = projectBinFolder.getFile(SdkConstants.FN_APK_CLASSES_DEX); + String outputFile = binFolder.getFile(outputName).getLocation().toOSString(); + + // get the list of referenced projects. + IProject[] javaRefs = ProjectHelper.getReferencedProjects(project); + IJavaProject[] referencedJavaProjects = PostCompilerHelper.getJavaProjects(javaRefs); + + helper.finalPackage( + new File(projectBinFolderPath, pkgName).getAbsolutePath(), + dexFile.getLocation().toOSString(), + outputFile, + javaProject, + libProjects, + referencedJavaProjects, + apk.getAbi(), + null, //key + null, //certificate + null); //ResourceMarker + } catch (CoreException e) { + throw e; + } catch (Exception e) { + throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, + e.getMessage(), e)); } - apk.setOutputName(softVariant != null ? softVariant.getKey() : null, outputName); - - // do the final export. - IFile dexFile = projectBinFolder.getFile(SdkConstants.FN_APK_CLASSES_DEX); - String outputFile = binFolder.getFile(outputName).getLocation().toOSString(); - - // get the list of referenced projects. - IProject[] javaRefs = ProjectHelper.getReferencedProjects(project); - IJavaProject[] referencedJavaProjects = PostCompilerHelper.getJavaProjects(javaRefs); - - helper.finalPackage( - new File(projectBinFolderPath, pkgName).getAbsolutePath(), - dexFile.getLocation().toOSString(), - outputFile, - false /*debugSign */, - javaProject, - libProjects, - referencedJavaProjects, - apk.getAbi(), - false /*debuggable*/); - } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptParser.java index 3b1c413..fd458de 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptParser.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptParser.java @@ -25,7 +25,7 @@ import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import java.io.File; -import java.util.ArrayList; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -126,6 +126,16 @@ public class AaptParser { private final static Pattern sPattern9Line1 = Pattern.compile( "^Invalid configuration: (.+)$"); //$NON-NLS-1$ + /** + * Parse the output of aapt and mark the incorrect file with error markers + * + * @param results the output of aapt + * @param project the project containing the file to mark + * @return true if the parsing failed, false if success. + */ + static final boolean parseOutput(List<String> results, IProject project) { + return parseOutput(results.toArray(new String[results.size()]), project); + } /** * Parse the output of aapt and mark the incorrect file with error markers @@ -134,10 +144,9 @@ public class AaptParser { * @param project the project containing the file to mark * @return true if the parsing failed, false if success. */ - static final boolean parseOutput(ArrayList<String> results, - IProject project) { + static final boolean parseOutput(String[] results, IProject project) { // nothing to parse? just return false; - if (results.size() == 0) { + if (results.length == 0) { return false; } @@ -147,8 +156,8 @@ public class AaptParser { Matcher m; - for (int i = 0; i < results.size(); i++) { - String p = results.get(i); + for (int i = 0; i < results.length ; i++) { + String p = results[i]; m = sPattern0Line1.matcher(p); if (m.matches()) { @@ -183,10 +192,10 @@ public class AaptParser { String location = m.group(1); String msg = p; // default msg is the line in case we don't find anything else - if (++i < results.size()) { - msg = results.get(i).trim(); - if (++i < results.size()) { - msg = msg + " - " + results.get(i).trim(); //$NON-NLS-1$ + if (++i < results.length) { + msg = results[i].trim(); + if (++i < results.length) { + msg = msg + " - " + results[i].trim(); //$NON-NLS-1$ // skip the next line i++; @@ -429,16 +438,16 @@ public class AaptParser { * @param pattern The pattern to match * @return null if error or no match, the matcher otherwise. */ - private static final Matcher getNextLineMatcher(ArrayList<String> lines, + private static final Matcher getNextLineMatcher(String[] lines, int nextIndex, Pattern pattern) { // unless we can't, because we reached the last line - if (nextIndex == lines.size()) { + if (nextIndex == lines.length) { // we expected a 2nd line, so we flag as error // and we bail return null; } - Matcher m = pattern.matcher(lines.get(nextIndex)); + Matcher m = pattern.matcher(lines[nextIndex]); if (m.matches()) { return m; } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/PostCompilerBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/PostCompilerBuilder.java index 8c8a15d..cdea980 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/PostCompilerBuilder.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/PostCompilerBuilder.java @@ -18,15 +18,24 @@ 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.build.PostCompilerHelper.AaptExecException; +import com.android.ide.eclipse.adt.internal.build.PostCompilerHelper.AaptResultException; +import com.android.ide.eclipse.adt.internal.build.PostCompilerHelper.DexException; +import com.android.ide.eclipse.adt.internal.build.PostCompilerHelper.NativeLibInJarException; +import com.android.ide.eclipse.adt.internal.build.PostCompilerHelper.ResourceMarker; +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.ApkInstallManager; 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.ProjectState; import com.android.ide.eclipse.adt.internal.sdk.Sdk; +import com.android.prefs.AndroidLocation.AndroidLocationException; import com.android.sdklib.SdkConstants; -import com.android.sdklib.xml.AndroidManifest; -import com.android.sdklib.xml.AndroidXPathFactory; +import com.android.sdklib.build.ApkCreationException; +import com.android.sdklib.build.DuplicateFileException; +import com.android.sdklib.internal.build.DebugKeyProvider.KeytoolException; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; @@ -43,19 +52,13 @@ import org.eclipse.core.runtime.IStatus; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; -import org.xml.sax.InputSource; import java.io.File; -import java.io.PrintStream; import java.util.ArrayList; import java.util.Map; -import javax.xml.xpath.XPath; - public class PostCompilerBuilder extends BaseBuilder { - private static final String CONSOLE_PREFIX_DX = "Dx"; //$NON-NLS-1$ - /** This ID is used in plugin.xml and in each project's .project file. * It cannot be changed even if the class is renamed/moved */ public static final String ID = "com.android.ide.eclipse.adt.ApkBuilder"; //$NON-NLS-1$ @@ -83,8 +86,8 @@ public class PostCompilerBuilder extends BaseBuilder { */ private boolean mBuildFinalPackage = false; - private PrintStream mDxOutStream = null; - private PrintStream mDxErrStream = null; + private AndroidPrintStream mOutStream = null; + private AndroidPrintStream mErrStream = null; /** * Basic Resource Delta Visitor class to check if a referenced project had a change in its @@ -168,6 +171,14 @@ public class PostCompilerBuilder extends BaseBuilder { } } + private ResourceMarker mResourceMarker = new ResourceMarker() { + public void setWarning(IResource resource, String message) { + BaseProjectHelper.markResource(resource, AndroidConstants.MARKER_PACKAGING, + message, IMarker.SEVERITY_WARNING); + } + }; + + public PostCompilerBuilder() { super(); } @@ -215,7 +226,8 @@ public class PostCompilerBuilder extends BaseBuilder { // get the list of referenced projects. javaProjects = ProjectHelper.getReferencedProjects(project); - IJavaProject[] referencedJavaProjects = PostCompilerHelper.getJavaProjects(javaProjects); + IJavaProject[] referencedJavaProjects = PostCompilerHelper.getJavaProjects( + javaProjects); // mix the java project and the library projects final int libCount = libProjects.length; @@ -292,8 +304,9 @@ public class PostCompilerBuilder extends BaseBuilder { IJavaProject referencedJavaProject = referencedJavaProjects[i]; delta = getDelta(referencedJavaProject.getProject()); if (delta != null) { - ReferencedProjectDeltaVisitor refProjectDv = new ReferencedProjectDeltaVisitor( - referencedJavaProject); + ReferencedProjectDeltaVisitor refProjectDv = + new ReferencedProjectDeltaVisitor(referencedJavaProject); + delta.accept(refProjectDv); // save the state @@ -383,15 +396,20 @@ public class PostCompilerBuilder extends BaseBuilder { // Get the DX output stream. Since the builder is created for the life of the // project, they can be kept around. - if (mDxOutStream == null) { - mDxOutStream = AdtPlugin.getOutPrintStream(project, CONSOLE_PREFIX_DX); - mDxErrStream = AdtPlugin.getErrPrintStream(project, CONSOLE_PREFIX_DX); + if (mOutStream == null) { + mOutStream = new AndroidPrintStream(project, null /*prefix*/, + AdtPlugin.getOutStream()); + mErrStream = new AndroidPrintStream(project, null /*prefix*/, + AdtPlugin.getOutStream()); } // we need to test all three, as we may need to make the final package // but not the intermediary ones. if (mPackageResources || mConvertToDex || mBuildFinalPackage) { - PostCompilerHelper helper = new PostCompilerHelper(project, mDxOutStream, mDxErrStream); + PostCompilerHelper helper = new PostCompilerHelper(project, + mOutStream, mErrStream, + true /*debugMode*/, + AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE); // resource to the AndroidManifest.xml file IFile manifestFile = project.getFile(SdkConstants.FN_ANDROID_MANIFEST_XML); @@ -429,12 +447,31 @@ public class PostCompilerBuilder extends BaseBuilder { removeMarkersFromContainer(project, AndroidConstants.MARKER_AAPT_PACKAGE); // need to figure out some path before we can execute aapt; - if (helper.packageResources( manifestFile, libProjects, null /*resfilter*/, - 0 /*versionCode */, osBinPath, - AndroidConstants.FN_RESOURCES_AP_) == false) { - // aapt failed. Whatever files that needed to be marked - // have already been marked. We just return. + try { + helper.packageResources(manifestFile, libProjects, null /*resfilter*/, + 0 /*versionCode */, osBinPath, + AndroidConstants.FN_RESOURCES_AP_); + } catch (AaptExecException e) { + BaseProjectHelper.markResource(project, AndroidConstants.MARKER_PACKAGING, + e.getMessage(), IMarker.SEVERITY_ERROR); return allRefProjects; + } catch (AaptResultException e) { + // attempt to parse the error output + String[] aaptOutput = e.getOutput(); + boolean parsingError = AaptParser.parseOutput(aaptOutput, project); + + // if we couldn't parse the output we display it in the console. + if (parsingError) { + AdtPlugin.printErrorToConsole(project, (Object[]) aaptOutput); + + // if the exec failed, and we couldn't parse the error output (and + // therefore not all files that should have been marked, were marked), + // we put a generic marker on the project and abort. + BaseProjectHelper.markResource(project, + AndroidConstants.MARKER_PACKAGING, + Messages.Unparsed_AAPT_Errors, + IMarker.SEVERITY_ERROR); + } } // build has been done. reset the state of the builder @@ -446,8 +483,25 @@ public class PostCompilerBuilder extends BaseBuilder { // then we check if we need to package the .class into classes.dex if (mConvertToDex) { - if (helper.executeDx(javaProject, osBinPath, osBinPath + File.separator + - SdkConstants.FN_APK_CLASSES_DEX, referencedJavaProjects) == false) { + try { + helper.executeDx(javaProject, osBinPath, osBinPath + File.separator + + SdkConstants.FN_APK_CLASSES_DEX, referencedJavaProjects, + mResourceMarker); + } catch (DexException e) { + String message = e.getMessage(); + + AdtPlugin.printErrorToConsole(project, message); + BaseProjectHelper.markResource(project, AndroidConstants.MARKER_PACKAGING, + message, IMarker.SEVERITY_ERROR); + + Throwable cause = e.getCause(); + + if (cause instanceof NoClassDefFoundError + || cause instanceof NoSuchMethodError) { + AdtPlugin.printErrorToConsole(project, Messages.Incompatible_VM_Warning, + Messages.Requires_1_5_Error); + } + // dx failed, we return return allRefProjects; } @@ -459,33 +513,68 @@ public class PostCompilerBuilder extends BaseBuilder { saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex); } - // figure out whether the application is debuggable. - // It is considered debuggable if the attribute debuggable is set to true - // in the manifest - boolean debuggable = false; - XPath xpath = AndroidXPathFactory.newXPath(); - String result = xpath.evaluate( - "/" + AndroidManifest.NODE_MANIFEST + //$NON-NLS-1$ - "/" + AndroidManifest.NODE_APPLICATION + //$NON-NLS-1$ - "/@" + AndroidXPathFactory.DEFAULT_NS_PREFIX + //$NON-NLS-1$ - ":" + AndroidManifest.ATTRIBUTE_DEBUGGABLE, //$NON-NLS-1$ - new InputSource(manifestFile.getContents())); - if (result.length() > 0) { - debuggable = Boolean.valueOf(result); - } - // now we need to make the final package from the intermediary apk // and classes.dex. // This is the default package with all the resources. String classesDexPath = osBinPath + File.separator + SdkConstants.FN_APK_CLASSES_DEX; - if (helper.finalPackage( + try { + helper.finalDebugPackage( osBinPath + File.separator + AndroidConstants.FN_RESOURCES_AP_, - classesDexPath, osFinalPackagePath, true /*debugSign*/, - javaProject, libProjects, - referencedJavaProjects, null /*abiFilter*/, debuggable) == false) { + classesDexPath, osFinalPackagePath, + javaProject, libProjects, referencedJavaProjects, mResourceMarker); + } catch (KeytoolException e) { + String eMessage = e.getMessage(); + + // mark the project with the standard message + String msg = String.format(Messages.Final_Archive_Error_s, eMessage); + BaseProjectHelper.markResource(project, AndroidConstants.MARKER_PACKAGING, msg, + IMarker.SEVERITY_ERROR); + + // output more info in the console + AdtPlugin.printErrorToConsole(project, + msg, + String.format(Messages.ApkBuilder_JAVA_HOME_is_s, e.getJavaHome()), + Messages.ApkBuilder_Update_or_Execute_manually_s, + e.getCommandLine()); + return allRefProjects; + } catch (ApkCreationException e) { + String eMessage = e.getMessage(); + + // mark the project with the standard message + String msg = String.format(Messages.Final_Archive_Error_s, eMessage); + BaseProjectHelper.markResource(project, AndroidConstants.MARKER_PACKAGING, msg, + IMarker.SEVERITY_ERROR); + } catch (AndroidLocationException e) { + String eMessage = e.getMessage(); + + // mark the project with the standard message + String msg = String.format(Messages.Final_Archive_Error_s, eMessage); + BaseProjectHelper.markResource(project, AndroidConstants.MARKER_PACKAGING, msg, + IMarker.SEVERITY_ERROR); + } catch (NativeLibInJarException e) { + String msg = e.getMessage(); + + BaseProjectHelper.markResource(project, AndroidConstants.MARKER_PACKAGING, + msg, IMarker.SEVERITY_ERROR); + + AdtPlugin.printErrorToConsole(project, (Object[]) e.getConsoleMsgs()); + } catch (CoreException e) { + // mark project and return + String msg = String.format(Messages.Final_Archive_Error_s, e.getMessage()); + AdtPlugin.printErrorToConsole(project, msg); + BaseProjectHelper.markResource(project, AndroidConstants.MARKER_PACKAGING, msg, + IMarker.SEVERITY_ERROR); + } catch (DuplicateFileException e) { + String msg1 = String.format( + "Found duplicate file for APK: %1$s\nOrigin 1: %2$s\nOrigin 2: %3$s", + e.getArchivePath(), e.getFile1(), e.getFile2()); + String msg2 = String.format(Messages.Final_Archive_Error_s, msg1); + AdtPlugin.printErrorToConsole(project, msg2); + BaseProjectHelper.markResource(project, AndroidConstants.MARKER_PACKAGING, msg2, + IMarker.SEVERITY_ERROR); } // we are done. diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/PostCompilerHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/PostCompilerHelper.java index 9bcc7d4..928ffac 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/PostCompilerHelper.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/PostCompilerHelper.java @@ -18,6 +18,7 @@ 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; @@ -32,13 +33,13 @@ import com.android.sdklib.build.ApkCreationException; import com.android.sdklib.build.DuplicateFileException; import com.android.sdklib.build.SealedApkException; import com.android.sdklib.build.ApkBuilder.JarStatus; +import com.android.sdklib.build.ApkBuilder.SigningInfo; import com.android.sdklib.internal.build.DebugKeyProvider; import com.android.sdklib.internal.build.SignedJarBuilder; import com.android.sdklib.internal.build.DebugKeyProvider.KeytoolException; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; -import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspace; @@ -57,6 +58,8 @@ import org.eclipse.jface.preference.IPreferenceStore; import java.io.File; import java.io.IOException; import java.io.PrintStream; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; @@ -72,17 +75,115 @@ import java.util.List; * {@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 PostCompilerHelper { + private static final String CONSOLE_PREFIX_DX = "Dx"; //$NON-NLS-1$ + private final IProject mProject; - private final PrintStream mOutStream; - private final PrintStream mErrStream; + private final AndroidPrintStream mOutStream; + private final AndroidPrintStream mErrStream; + private final boolean mVerbose; + private final boolean mDebugMode; + + public static final class AaptExecException extends Exception { + private static final long serialVersionUID = 1L; + + AaptExecException(String message, Throwable cause) { + super(message, cause); + } + } + + public static final class AaptResultException extends Exception { + private static final long serialVersionUID = 1L; + + private final int mErrorCode; + private final String[] mOutput; + + AaptResultException(int errorCode, String[] output) { + mErrorCode = errorCode; + mOutput = output; + } + + public String[] getOutput() { + return mOutput; + } + + public int getErrorCode() { + return mErrorCode; + } + } + + public static final class DexException extends Exception { + private static final long serialVersionUID = 1L; + + DexException(String message) { + super(message); + } + + DexException(String message, Throwable cause) { + super(message, cause); + } + } + + public static final class NativeLibInJarException extends Exception { + private static final long serialVersionUID = 1L; + + private final JarStatus mStatus; + private final String mLibName; + private final String[] mConsoleMsgs; - public PostCompilerHelper(IProject project, PrintStream outStream, PrintStream errStream) { + NativeLibInJarException(JarStatus status, String message, String libName, + String[] consoleMsgs) { + super(message); + mStatus = status; + mLibName = libName; + mConsoleMsgs = consoleMsgs; + } + + public JarStatus getStatus() { + return mStatus; + } + + public String getLibName() { + return mLibName; + } + + public String[] getConsoleMsgs() { + return mConsoleMsgs; + } + } + + /** + * 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 PostCompilerHelper(IProject project, AndroidPrintStream outStream, + AndroidPrintStream errStream, boolean debugMode, boolean verbose) { mProject = project; mOutStream = outStream; mErrStream = errStream; + mDebugMode = debugMode; + mVerbose = verbose; } /** @@ -95,10 +196,12 @@ public class PostCompilerHelper { * 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. - * @return true if success. + * @throws AaptExecException + * @throws AaptResultException */ - public boolean packageResources(IFile manifestFile, IProject[] libProjects, String resFilter, - int versionCode, String outputFolder, String outputFilename) { + public void packageResources(IFile manifestFile, 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 @@ -138,21 +241,68 @@ public class PostCompilerHelper { } // build the default resource package - if (executeAapt(osManifestPath, osResPaths, osAssetsPath, + executeAapt(osManifestPath, osResPaths, osAssetsPath, outputFolder + File.separator + outputFilename, resFilter, - versionCode) == false) { - // aapt failed. Whatever files that needed to be marked - // have already been marked. We just return. - return false; - } + 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, IProject[] libProjects, + IJavaProject[] referencedJavaProjects, ResourceMarker resMarker) + throws ApkCreationException, KeytoolException, AndroidLocationException, + NativeLibInJarException, DuplicateFileException, CoreException { + + // get the debug keystore to use. + IPreferenceStore store = AdtPlugin.getDefault().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)); } - return true; + // 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. Package the dex files, the temporary resource file into the final - * package file. + * 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. @@ -162,101 +312,75 @@ public class PostCompilerHelper { * @param referencedJavaProjects referenced projects. * @param abiFilter an optional filter. If not null, then only the matching ABI is included in * the final archive - * @param debuggable whether the project manifest has debuggable==true. If true, any gdbserver - * executables will be packaged with the native libraries. * @return true if success, false otherwise. + * @throws NativeLibInJarException + * @throws ApkCreationException + * @throws CoreException + * @throws DuplicateFileException */ - public boolean finalPackage(String intermediateApk, String dex, String output, - boolean debugSign, final IJavaProject javaProject, IProject[] libProjects, - IJavaProject[] referencedJavaProjects, String abiFilter, boolean debuggable) { - - IProject project = javaProject.getProject(); - - String keystoreOsPath = null; - if (debugSign) { - IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore(); - keystoreOsPath = store.getString(AdtPrefs.PREFS_CUSTOM_DEBUG_KEYSTORE); - if (keystoreOsPath == null || new File(keystoreOsPath).isFile() == false) { - try { - keystoreOsPath = DebugKeyProvider.getDefaultKeyStoreOsPath(); - AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, mProject, - Messages.ApkBuilder_Using_Default_Key); - } catch (KeytoolException e) { - String eMessage = e.getMessage(); - - // mark the project with the standard message - String msg = String.format(Messages.Final_Archive_Error_s, eMessage); - BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, msg, - IMarker.SEVERITY_ERROR); - - // output more info in the console - AdtPlugin.printErrorToConsole(mProject, - msg, - String.format(Messages.ApkBuilder_JAVA_HOME_is_s, e.getJavaHome()), - Messages.ApkBuilder_Update_or_Execute_manually_s, - e.getCommandLine()); - - return false; - } catch (AndroidLocationException e) { - String eMessage = e.getMessage(); - - // mark the project with the standard message - String msg = String.format(Messages.Final_Archive_Error_s, eMessage); - BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, msg, - IMarker.SEVERITY_ERROR); - - return false; - } - } else { - AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, mProject, - String.format(Messages.ApkBuilder_Using_s_To_Sign, keystoreOsPath)); - } - } - + public void finalPackage(String intermediateApk, String dex, String output, + final IJavaProject javaProject, IProject[] libProjects, + IJavaProject[] referencedJavaProjects, String abiFilter, PrivateKey key, + X509Certificate certificate, ResourceMarker resMarker) + throws NativeLibInJarException, ApkCreationException, DuplicateFileException, + CoreException { try { - ApkBuilder apkBuilder = new ApkBuilder(output, intermediateApk, dex, keystoreOsPath, - AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE ? - AdtPlugin.getOutPrintStream(project, null): null); - apkBuilder.setDebugMode(debuggable); + 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 : getExternalJars()) { - JarStatus status = apkBuilder.addResourcesFromJar(new File(libraryOsPath)); + for (String libraryOsPath : getExternalJars(resMarker)) { + 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 (status.getNativeLibs().size() > 0) { + 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); - BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, - msg, - status.hasNativeLibsConflicts() || - AdtPrefs.getPrefs().getBuildForceErrorOnNativeLibInJar() ? - IMarker.SEVERITY_ERROR : IMarker.SEVERITY_WARNING); - 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 (status.hasNativeLibsConflicts()) { + + 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 : status.getNativeLibs()) { + + for (String lib : jarStatus.getNativeLibs()) { consoleMsgs.add(" - " + lib); } - AdtPlugin.printErrorToConsole(mProject, - consoleMsgs.toArray()); - return false; + 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); + } + } } } @@ -282,46 +406,9 @@ public class PostCompilerHelper { // seal the APK. apkBuilder.sealApk(); - return true; - } catch (CoreException e) { - // mark project and return - String msg = String.format(Messages.Final_Archive_Error_s, e.getMessage()); - AdtPlugin.printErrorToConsole(mProject, msg); - BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, msg, - IMarker.SEVERITY_ERROR); - } catch (ApkCreationException e) { - // mark project and return - String msg = String.format(Messages.Final_Archive_Error_s, e.getMessage()); - AdtPlugin.printErrorToConsole(mProject, msg); - BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, msg, - IMarker.SEVERITY_ERROR); - } catch (DuplicateFileException e) { - String msg1 = String.format( - "Found duplicate file for APK: %1$s\nOrigin 1: %2$s\nOrigin 2: %3$s", - e.getArchivePath(), e.getFile1(), e.getFile2()); - String msg2 = String.format(Messages.Final_Archive_Error_s, msg1); - AdtPlugin.printErrorToConsole(mProject, msg2); - BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, msg2, - IMarker.SEVERITY_ERROR); } catch (SealedApkException e) { // this won't happen as we control when the apk is sealed. - } catch (Exception e) { - // try to catch other exception to actually display an error. This will be useful - // if we get an NPE or something so that we can at least notify the user that something - // went wrong (otherwise the build appears to succeed but the zip archive is not closed - // and therefore invalid. - String msg = e.getMessage(); - if (msg == null) { - msg = e.getClass().getCanonicalName(); - } - - msg = String.format("Unknown error: %1$s", msg); - AdtPlugin.printErrorToConsole(mProject, msg); - BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, msg, - IMarker.SEVERITY_ERROR); } - - return false; } /** @@ -332,9 +419,11 @@ public class PostCompilerHelper { * @param referencedJavaProjects the list of referenced projects for this project. * * @throws CoreException + * @throws DexException */ - boolean executeDx(IJavaProject javaProject, String osBinPath, String osOutFilePath, - IJavaProject[] referencedJavaProjects) throws CoreException { + public void executeDx(IJavaProject javaProject, String osBinPath, String osOutFilePath, + IJavaProject[] referencedJavaProjects, ResourceMarker resMarker) throws CoreException, + DexException { IAndroidTarget target = Sdk.getCurrent().getTarget(mProject); AndroidTargetData targetData = Sdk.getCurrent().getTargetData(target); @@ -353,7 +442,7 @@ public class PostCompilerHelper { try { // get the list of libraries to include with the source code - String[] libraries = getExternalJars(); + String[] libraries = getExternalJars(resMarker); // get the list of referenced projects output to add String[] projectOutputs = getProjectOutputs(referencedJavaProjects); @@ -369,40 +458,35 @@ public class PostCompilerHelper { // then external jars. System.arraycopy(libraries, 0, fileNames, 1 + projectOutputs.length, libraries.length); + // set a temporary prefix on the print streams. + mOutStream.setPrefix(CONSOLE_PREFIX_DX); + mErrStream.setPrefix(CONSOLE_PREFIX_DX); + int res = wrapper.run(osOutFilePath, fileNames, - AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE, + 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); - AdtPlugin.printErrorToConsole(mProject, message); - BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, - message, IMarker.SEVERITY_ERROR); - return false; + String message = String.format(Messages.Dalvik_Error_d, res); + throw new DexException(message); } - } catch (Throwable ex) { - String message = ex.getMessage(); + } catch (DexException e) { + throw e; + } catch (Throwable t) { + String message = t.getMessage(); if (message == null) { - message = ex.getClass().getCanonicalName(); + message = t.getClass().getCanonicalName(); } message = String.format(Messages.Dalvik_Error_s, message); - AdtPlugin.printErrorToConsole(mProject, message); - BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, - message, IMarker.SEVERITY_ERROR); - if ((ex instanceof NoClassDefFoundError) - || (ex instanceof NoSuchMethodError)) { - AdtPlugin.printErrorToConsole(mProject, Messages.Incompatible_VM_Warning, - Messages.Requires_1_5_Error); - } - return false; - } - return true; + 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 @@ -414,11 +498,12 @@ public class PostCompilerHelper { * resources.) * @param versionCode optional version code to insert in the manifest during packaging. If <=0 * then no value is inserted - * @return true if success, false otherwise. + * @throws AaptExecException + * @throws AaptResultException */ - private boolean executeAapt(String osManifestPath, + private void executeAapt(String osManifestPath, List<String> osResPaths, String osAssetsPath, String osOutFilePath, - String configFilter, int versionCode) { + String configFilter, int versionCode) throws AaptExecException, AaptResultException { IAndroidTarget target = Sdk.getCurrent().getTarget(mProject); // Create the command line. @@ -436,6 +521,10 @@ public class PostCompilerHelper { 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)); @@ -489,49 +578,25 @@ public class PostCompilerHelper { // get the output and return code from the process execError = BaseBuilder.grabProcessOutput(mProject, process, results); - // attempt to parse the error output - boolean parsingError = AaptParser.parseOutput(results, mProject); - - // if we couldn't parse the output we display it in the console. - if (parsingError) { - if (execError != 0) { - AdtPlugin.printErrorToConsole(mProject, results.toArray()); - } else { - AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, mProject, - results.toArray()); + if (execError != 0) { + throw new AaptResultException(execError, + results.toArray(new String[results.size()])); + } else if (mVerbose) { + for (String resultString : results) { + mOutStream.println(resultString); } } - // We need to abort if the exec failed. - if (execError != 0) { - // if the exec failed, and we couldn't parse the error output (and therefore - // not all files that should have been marked, were marked), we put a generic - // marker on the project and abort. - if (parsingError) { - BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, - Messages.Unparsed_AAPT_Errors, - IMarker.SEVERITY_ERROR); - } - // abort if exec failed. - return false; - } - } catch (IOException e1) { + } catch (IOException e) { String msg = String.format(Messages.AAPT_Exec_Error, command[0]); - BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, msg, - IMarker.SEVERITY_ERROR); - return false; + throw new AaptExecException(msg, e); } catch (InterruptedException e) { String msg = String.format(Messages.AAPT_Exec_Error, command[0]); - BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, msg, - IMarker.SEVERITY_ERROR); - return false; + throw new AaptExecException(msg, e); } - - return true; } - /** * Writes the standard resources of a project and its referenced projects * into a {@link SignedJarBuilder}. @@ -600,7 +665,7 @@ public class PostCompilerHelper { * Returns an array of external jar files used by the project. * @return an array of OS-specific absolute file paths */ - private final String[] getExternalJars() { + private final String[] getExternalJars(ResourceMarker resMarker) { // get a java project from it IJavaProject javaProject = JavaCore.create(mProject); @@ -637,13 +702,13 @@ public class PostCompilerHelper { } else { String message = String.format( Messages.Couldnt_Locate_s_Error, path); - AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, - mProject, message); + // always output to the console + mOutStream.println(message); - // Also put a warning marker on the project - BaseProjectHelper.markResource(mProject, - AndroidConstants.MARKER_PACKAGING, message, - IMarker.SEVERITY_WARNING); + // put a marker + if (resMarker != null) { + resMarker.setWarning(mProject, message); + } } } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/OverviewExportPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/OverviewExportPart.java index 138f0b1..b0eb75a 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/OverviewExportPart.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/OverviewExportPart.java @@ -21,11 +21,15 @@ import com.android.ide.eclipse.adt.internal.editors.ui.SectionHelper.ManifestSec import com.android.ide.eclipse.adt.internal.project.ExportHelper; import com.android.ide.eclipse.adt.internal.sdk.ProjectState; import com.android.ide.eclipse.adt.internal.sdk.Sdk; +import com.android.ide.eclipse.adt.internal.wizards.export.ExportWizard; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.wizard.WizardDialog; import org.eclipse.swt.widgets.Composite; import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.PlatformUI; import org.eclipse.ui.forms.events.HyperlinkAdapter; import org.eclipse.ui.forms.events.HyperlinkEvent; import org.eclipse.ui.forms.widgets.FormText; @@ -40,7 +44,8 @@ final class OverviewExportPart extends ManifestSectionPart { private final OverviewPage mOverviewPage; - public OverviewExportPart(OverviewPage overviewPage, Composite body, FormToolkit toolkit, ManifestEditor editor) { + public OverviewExportPart(OverviewPage overviewPage, final Composite body, FormToolkit toolkit, + ManifestEditor editor) { super(body, toolkit, Section.TWISTIE | Section.EXPANDED, true /* description */); mOverviewPage = overviewPage; Section section = getSection(); @@ -84,10 +89,15 @@ final class OverviewExportPart extends ManifestSectionPart { if (project != null) { if ("manual".equals(e.data)) { //$NON-NLS-1$ // now we can export an unsigned apk for the project. - ExportHelper.exportProject(project); + ExportHelper.exportUnsignedReleaseApk(project); } else { // call the export wizard - ExportHelper.startExportWizard(project); + StructuredSelection selection = new StructuredSelection(project); + + ExportWizard wizard = new ExportWizard(); + wizard.init(PlatformUI.getWorkbench(), selection); + WizardDialog dialog = new WizardDialog(body.getShell(), wizard); + dialog.open(); } } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ExportHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ExportHelper.java index 235ba7c0..2a10fb8 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ExportHelper.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ExportHelper.java @@ -16,11 +16,25 @@ package com.android.ide.eclipse.adt.internal.project; +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.build.PostCompilerHelper; +import com.android.ide.eclipse.adt.internal.sdk.ProjectState; +import com.android.ide.eclipse.adt.internal.sdk.Sdk; +import com.android.sdklib.SdkConstants; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IncrementalProjectBuilder; +import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Display; @@ -28,41 +42,116 @@ import org.eclipse.swt.widgets.FileDialog; import org.eclipse.swt.widgets.Shell; import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.IOException; -import java.util.jar.JarEntry; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; -import java.util.zip.ZipOutputStream; +import java.io.OutputStream; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; /** * Export helper for project. */ public final class ExportHelper { - private static IExportCallback sCallback; - - public interface IExportCallback { - void startExportWizard(IProject project); - } - - public static void setCallback(IExportCallback callback) { - sCallback = callback; - } + /** + * Exports a release version of the application created by the given project. + * @param project the project to export + * @param outputFile the file to write + * @param key the key to used for signing. Can be null. + * @param certificate the certificate used for signing. Can be null. + * @param monitor + */ + public static void export(IProject project, File outputFile, PrivateKey key, + X509Certificate certificate, IProgressMonitor monitor) throws CoreException { + + // the export, takes the output of the precompiler & Java builders so it's + // important to call build in case the auto-build option of the workspace is disabled. + project.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, monitor); + + // if either key or certificate is null, ensure the other is null. + if (key == null) { + certificate = null; + } else if (certificate == null) { + key = null; + } - public static void startExportWizard(IProject project) { - if (sCallback != null) { - sCallback.startExportWizard(project); + try { + AndroidPrintStream fakeStream = new AndroidPrintStream(null, null, new OutputStream() { + @Override + public void write(int b) throws IOException { + // do nothing + } + }); + + PostCompilerHelper helper = new PostCompilerHelper(project, + fakeStream, fakeStream, + false /*debugMode*/, false /*verbose*/); + + // get the list of library projects + ProjectState projectState = Sdk.getProjectState(project); + IProject[] libProjects = projectState.getFullLibraryProjects(); + + // Step 1. Package the resources. + + // tmp file for the packaged resource file. To not disturb the incremental builders + // output, all intermediary files are created in tmp files. + File resourceFile = File.createTempFile("android_", ".ap_"); + + // package the resources. + helper.packageResources( + project.getFile(SdkConstants.FN_ANDROID_MANIFEST_XML), + libProjects, + null, // res filter + 0, // versionCode + resourceFile.getParent(), + resourceFile.getName()); + + // Step 2. Convert the byte code to Dalvik bytecode + + // tmp file for the packaged resource file. + File dexFile = File.createTempFile("android_", ".dex"); + + IFolder outputFolder = BaseProjectHelper.getOutputFolder(project); + + IJavaProject javaProject = JavaCore.create(project); + IProject[] javaProjects = ProjectHelper.getReferencedProjects(project); + IJavaProject[] referencedJavaProjects = PostCompilerHelper.getJavaProjects( + javaProjects); + + helper.executeDx( + javaProject, + outputFolder.getLocation().toOSString(), + dexFile.getAbsolutePath(), + referencedJavaProjects, + null /*resourceMarker*/); + + // Step 3. Final package + + helper.finalPackage( + resourceFile.getAbsolutePath(), + dexFile.getAbsolutePath(), + outputFile.getAbsolutePath(), + javaProject, + libProjects, + referencedJavaProjects, + null /*abiFilter*/, + key, + certificate, + null); //resourceMarker + + // success! + } catch (Exception e) { + //? } } /** - * Exports an <b>unsigned</b> version of the application created by the given project. + * Exports an unsigned release APK after prompting the user for a location. + * + * <strong>Must be called from the UI thread.</strong> + * * @param project the project to export */ - public static void exportProject(IProject project) { + public static void exportUnsignedReleaseApk(final IProject project) { Shell shell = Display.getCurrent().getActiveShell(); // get the java project to get the output directory @@ -89,103 +178,34 @@ public final class ExportHelper { fileDialog.setText("Export Project"); fileDialog.setFileName(fileName); - String saveLocation = fileDialog.open(); + final String saveLocation = fileDialog.open(); if (saveLocation != null) { - // get the stream from the original file - - ZipInputStream zis = null; - ZipOutputStream zos = null; - FileInputStream input = null; - FileOutputStream output = null; - - try { - input = new FileInputStream(file); - zis = new ZipInputStream(input); - - // get an output stream into the new file - File saveFile = new File(saveLocation); - output = new FileOutputStream(saveFile); - zos = new ZipOutputStream(output); - } catch (FileNotFoundException e) { - // only the input/output stream are throwing this exception. - // so we only have to close zis if output is the one that threw. - if (zis != null) { + new Job("Android Release Export") { + @Override + protected IStatus run(IProgressMonitor monitor) { try { - zis.close(); - } catch (IOException e1) { - // pass - } - } - - MessageDialog.openError(shell, "Android IDE Plug-in", - String.format("Failed to export %1$s: %2$s doesn't exist!", - project.getName(), file.getPath())); - return; - } - - try { - ZipEntry entry; - - byte[] buffer = new byte[4096]; - - while ((entry = zis.getNextEntry()) != null) { - String name = entry.getName(); - - // do not take directories or anything inside the META-INF folder since - // we want to strip the signature. - if (entry.isDirectory() || name.startsWith("META-INF/")) { //$NON-NL1$ - continue; - } - - ZipEntry newEntry; - - // Preserve the STORED method of the input entry. - if (entry.getMethod() == JarEntry.STORED) { - newEntry = new JarEntry(entry); - } else { - // Create a new entry so that the compressed len is recomputed. - newEntry = new JarEntry(name); - } - - // add the entry to the jar archive - zos.putNextEntry(newEntry); - - // read the content of the entry from the input stream, and write it into the archive. - int count; - while ((count = zis.read(buffer)) != -1) { - zos.write(buffer, 0, count); + export(project, + new File(saveLocation), + null, //key + null, //certificate + monitor); + + // this is unsigned export. Let's tell the developers to run zip align + AdtPlugin.displayWarning("Android IDE Plug-in", String.format( + "An unsigned package of the application was saved at\n%1$s\n\n" + + "Before publishing the application you will need to:\n" + + "- Sign the application with your release key,\n" + + "- run zipalign on the signed package. ZipAlign is located in <SDK>/tools/\n\n" + + "Aligning applications allows Android to use application resources\n" + + "more efficiently.", saveLocation)); + + return Status.OK_STATUS; + } catch (CoreException e) { + return e.getStatus(); } - - // close the entry for this file - zos.closeEntry(); - zis.closeEntry(); } + }.schedule(); - } catch (IOException e) { - MessageDialog.openError(shell, "Android IDE Plug-in", - String.format("Failed to export %1$s: %2$s", - project.getName(), e.getMessage())); - } finally { - try { - zos.close(); - } catch (IOException e) { - // pass - } - try { - zis.close(); - } catch (IOException e) { - // pass - } - } - - // this is unsigned export. Let's tell the developers to run zip align - MessageDialog.openWarning(shell, "Android IDE Plug-in", String.format( - "An unsigned package of the application was saved at\n%1$s\n\n" + - "Before publishing the application you will need to:\n" + - "- Sign the application with your release key,\n" + - "- run zipalign on the signed package. ZipAlign is located in <SDK>/tools/\n\n" + - "Aligning applications allows Android to use application resources\n" + - "more efficiently.", saveLocation)); } } else { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java index db71d2c..b7de225 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java @@ -20,7 +20,6 @@ import com.android.ddmlib.IDevice; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.AndroidConstants; import com.android.ide.eclipse.adt.internal.project.AndroidClasspathContainerInitializer; -import com.android.ide.eclipse.adt.internal.project.AndroidNature; 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.resources.manager.GlobalProjectMonitor; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/actions/ExportAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/actions/ExportAction.java index 40edc5e..f9ef11b 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/actions/ExportAction.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/actions/ExportAction.java @@ -64,7 +64,7 @@ public class ExportAction implements IObjectActionDelegate { MessageDialog.openError(mShell, "Android Export", "Android library projects cannot be exported."); } else { - ExportHelper.exportProject(project); + ExportHelper.exportUnsignedReleaseApk(project); } } } |