OSDN Git Service

ADT: Make release and debug builds really different.
authorXavier Ducrohet <xav@android.com>
Tue, 31 Aug 2010 17:21:29 +0000 (10:21 -0700)
committerXavier Ducrohet <xav@android.com>
Thu, 2 Sep 2010 02:43:08 +0000 (19:43 -0700)
Release export should not be debug builds that are
stripped of their signature and (optionnaly) resigned.
Instead they should actually build the apk in "release"
mode.

Refactor PostCompilerHelper to be easier to use for
export feature (moved all error handling into the
actual IncrementalBuilder since we don't want the
helper to put error/warning marker during release
export).

Update the API of ApkBuilder and PostCompilerHelper
to deal better with signing key:
- option to package with a "sign with debug" flag.
- new option to package/sign with given keys.

Debug build (through incremental builder) use the new
aapt option --debug-mode that automatically insert
debuggable=true in the manifest. This allows for the
same source code to generate debug and release builds.

Currently, only the "export unsigned release apk"
action use the new "release" build. Need to update
the export wizard. Also need to add support for this
in Ant.

New folder in sdk.git/testapps for test projects
to be used in upcoming automated build tests.
Simple "basicProject" to start with.

Change-Id: I3041312bc817153603656de2aa355f8fcaf00b5b

25 files changed:
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AndroidPrintStream.java [new file with mode: 0644]
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/MultiApkExportAction.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptParser.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/PostCompilerBuilder.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/PostCompilerHelper.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/OverviewExportPart.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ExportHelper.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/actions/ExportAction.java
sdkmanager/libs/sdklib/src/com/android/sdklib/build/ApkBuilder.java
testapps/.gitignore [new file with mode: 0644]
testapps/README.txt [new file with mode: 0644]
testapps/basicProject/.classpath [new file with mode: 0644]
testapps/basicProject/.project [new file with mode: 0644]
testapps/basicProject/AndroidManifest.xml [new file with mode: 0644]
testapps/basicProject/build.properties [new file with mode: 0644]
testapps/basicProject/build.xml [new file with mode: 0644]
testapps/basicProject/default.properties [new file with mode: 0644]
testapps/basicProject/res/drawable-hdpi/icon.png [new file with mode: 0644]
testapps/basicProject/res/drawable-ldpi/icon.png [new file with mode: 0644]
testapps/basicProject/res/drawable-mdpi/icon.png [new file with mode: 0644]
testapps/basicProject/res/layout/main.xml [new file with mode: 0644]
testapps/basicProject/res/values/strings.xml [new file with mode: 0644]
testapps/basicProject/src/com/android/tests/basicproject/Main.java [new file with mode: 0644]

index 41771f1..77ba50e 100644 (file)
@@ -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 (file)
index 0000000..5323742
--- /dev/null
@@ -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);
+    }
+
+}
index 6ff6d0b..70e886d 100644 (file)
@@ -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*/);
-
     }
 }
index 3b1c413..fd458de 100644 (file)
@@ -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;
         }
index 8c8a15d..cdea980 100644 (file)
@@ -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.
index 9bcc7d4..928ffac 100644 (file)
@@ -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);
+                                }
                             }
                         }
                     }
index 138f0b1..b0eb75a 100644 (file)
@@ -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();
                         }
                     }
                 }
index 235ba7c..2a10fb8 100644 (file)
 
 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 {
index db71d2c..b7de225 100644 (file)
@@ -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;
index 40edc5e..f9ef11b 100644 (file)
@@ -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);
                     }
                 }
             }
index 2bc65c4..8c18c3a 100644 (file)
@@ -153,11 +153,11 @@ public final class ApkBuilder {
         }
     }
 
-    private final File mApkFile;
-    private final File mResFile;
-    private final File mDexFile;
-    private final PrintStream mVerboseStream;
-    private final SignedJarBuilder mBuilder;
+    private File mApkFile;
+    private File mResFile;
+    private File mDexFile;
+    private PrintStream mVerboseStream;
+    private SignedJarBuilder mBuilder;
     private boolean mDebugMode = false;
     private boolean mIsSealed = false;
 
@@ -203,10 +203,121 @@ public final class ApkBuilder {
     }
 
     /**
+     * Signing information.
+     *
+     * Both the {@link PrivateKey} and the {@link X509Certificate} are guaranteed to be non-null.
+     *
+     */
+    public final static class SigningInfo {
+        public final PrivateKey key;
+        public final X509Certificate certificate;
+
+        private SigningInfo(PrivateKey key, X509Certificate certificate) {
+            if (key == null || certificate == null) {
+                throw new IllegalArgumentException("key and certificate cannot be null");
+            }
+            this.key = key;
+            this.certificate = certificate;
+        }
+    }
+
+    /**
+     * Returns the key and certificate from a given debug store.
+     *
+     * It is expected that the store password is 'android' and the key alias and password are
+     * 'androiddebugkey' and 'android' respectively.
+     *
+     * @param storeOsPath the OS path to the debug store.
+     * @param verboseStream an option {@link PrintStream} to display verbose information
+     * @return they key and certificate in a {@link SigningInfo} object or null.
+     * @throws ApkCreationException
+     */
+    public static SigningInfo getDebugKey(String storeOsPath, final PrintStream verboseStream)
+            throws ApkCreationException {
+        try {
+            if (storeOsPath != null) {
+                File storeFile = new File(storeOsPath);
+                try {
+                    checkInputFile(storeFile);
+                } catch (FileNotFoundException e) {
+                    // ignore these since the debug store can be created on the fly anyway.
+                }
+
+                // get the debug key
+                if (verboseStream != null) {
+                    verboseStream.println(String.format("Using keystore: %s", storeOsPath));
+                }
+
+                IKeyGenOutput keygenOutput = null;
+                if (verboseStream != null) {
+                    keygenOutput = new IKeyGenOutput() {
+                        public void out(String message) {
+                            verboseStream.println(message);
+                        }
+
+                        public void err(String message) {
+                            verboseStream.println(message);
+                        }
+                    };
+                }
+
+                DebugKeyProvider keyProvider = new DebugKeyProvider(
+                        storeOsPath, null /*store type*/, keygenOutput);
+
+                PrivateKey key = keyProvider.getDebugKey();
+                X509Certificate certificate = (X509Certificate)keyProvider.getCertificate();
+
+                if (key == null) {
+                    throw new ApkCreationException("Unable to get debug signature key");
+                }
+
+                // compare the certificate expiration date
+                if (certificate != null && certificate.getNotAfter().compareTo(new Date()) < 0) {
+                    // TODO, regenerate a new one.
+                    throw new ApkCreationException("Debug Certificate expired on " +
+                            DateFormat.getInstance().format(certificate.getNotAfter()));
+                }
+
+                return new SigningInfo(key, certificate);
+            } else {
+                return null;
+            }
+        } catch (KeytoolException e) {
+            if (e.getJavaHome() == null) {
+                throw new ApkCreationException(e.getMessage() +
+                        "\nJAVA_HOME seems undefined, setting it will help locating keytool automatically\n" +
+                        "You can also manually execute the following command\n:" +
+                        e.getCommandLine(), e);
+            } else {
+                throw new ApkCreationException(e.getMessage() +
+                        "\nJAVA_HOME is set to: " + e.getJavaHome() +
+                        "\nUpdate it if necessary, or manually execute the following command:\n" +
+                        e.getCommandLine(), e);
+            }
+        } catch (ApkCreationException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new ApkCreationException(e);
+        }
+    }
+
+    /**
      * Creates a new instance.
+     *
+     * This creates a new builder that will create the specified output file, using the two
+     * mandatory given input files.
+     *
+     * An optional debug keystore can be provided. If set, it is expected that the store password
+     * is 'android' and the key alias and password are 'androiddebugkey' and 'android'.
+     *
+     * An optional {@link PrintStream} can also be provided for verbose output. If null, there will
+     * be no output.
+     *
      * @param apkOsPath the OS path of the file to create.
      * @param resOsPath the OS path of the packaged resource file.
      * @param dexOsPath the OS path of the dex file. This can be null for apk with no code.
+     * @param verboseStream the stream to which verbose output should go. If null, verbose mode
+     *                      is not enabled.
      * @throws ApkCreationException
      */
     public ApkBuilder(String apkOsPath, String resOsPath, String dexOsPath, String storeOsPath,
@@ -224,6 +335,35 @@ public final class ApkBuilder {
      * This creates a new builder that will create the specified output file, using the two
      * mandatory given input files.
      *
+     * Optional {@link PrivateKey} and {@link X509Certificate} can be provided to sign the APK.
+     *
+     * An optional {@link PrintStream} can also be provided for verbose output. If null, there will
+     * be no output.
+     *
+     * @param apkOsPath the OS path of the file to create.
+     * @param resOsPath the OS path of the packaged resource file.
+     * @param dexOsPath the OS path of the dex file. This can be null for apk with no code.
+     * @param key the private key used to sign the package. Can be null.
+     * @param certificate the certificate used to sign the package. Can be null.
+     * @param verboseStream the stream to which verbose output should go. If null, verbose mode
+     *                      is not enabled.
+     * @throws ApkCreationException
+     */
+    public ApkBuilder(String apkOsPath, String resOsPath, String dexOsPath, PrivateKey key,
+            X509Certificate certificate, PrintStream verboseStream) throws ApkCreationException {
+        this(new File(apkOsPath),
+             new File(resOsPath),
+             dexOsPath != null ? new File(dexOsPath) : null,
+             key, certificate,
+             verboseStream);
+    }
+
+    /**
+     * Creates a new instance.
+     *
+     * This creates a new builder that will create the specified output file, using the two
+     * mandatory given input files.
+     *
      * An optional debug keystore can be provided. If set, it is expected that the store password
      * is 'android' and the key alias and password are 'androiddebugkey' and 'android'.
      *
@@ -239,7 +379,51 @@ public final class ApkBuilder {
      * @throws ApkCreationException
      */
     public ApkBuilder(File apkFile, File resFile, File dexFile, String debugStoreOsPath,
-            PrintStream verboseStream) throws ApkCreationException {
+            final PrintStream verboseStream) throws ApkCreationException {
+
+        SigningInfo info = getDebugKey(debugStoreOsPath, verboseStream);
+        if (info != null) {
+            init(apkFile, resFile, dexFile, info.key, info.certificate, verboseStream);
+        } else {
+            init(apkFile, resFile, dexFile, null /*key*/, null/*certificate*/, verboseStream);
+        }
+    }
+
+    /**
+     * Creates a new instance.
+     *
+     * This creates a new builder that will create the specified output file, using the two
+     * mandatory given input files.
+     *
+     * Optional {@link PrivateKey} and {@link X509Certificate} can be provided to sign the APK.
+     *
+     * An optional {@link PrintStream} can also be provided for verbose output. If null, there will
+     * be no output.
+     *
+     * @param apkFile the file to create
+     * @param resFile the file representing the packaged resource file.
+     * @param dexFile the file representing the dex file. This can be null for apk with no code.
+     * @param key the private key used to sign the package. Can be null.
+     * @param certificate the certificate used to sign the package. Can be null.
+     * @param verboseStream the stream to which verbose output should go. If null, verbose mode
+     *                      is not enabled.
+     * @throws ApkCreationException
+     */
+    public ApkBuilder(File apkFile, File resFile, File dexFile, PrivateKey key,
+            X509Certificate certificate, PrintStream verboseStream) throws ApkCreationException {
+        init(apkFile, resFile, dexFile, key, certificate, verboseStream);
+    }
+
+
+    /**
+     * Constructor init method.
+     *
+     * @see #ApkBuilder(File, File, File, String, PrintStream)
+     * @see #ApkBuilder(String, String, String, String, PrintStream)
+     * @see #ApkBuilder(File, File, File, PrivateKey, X509Certificate, PrintStream)
+     */
+    private void init(File apkFile, File resFile, File dexFile, PrivateKey key,
+            X509Certificate certificate, PrintStream verboseStream) throws ApkCreationException {
 
         try {
             checkOutputFile(mApkFile = apkFile);
@@ -251,56 +435,9 @@ public final class ApkBuilder {
             }
             mVerboseStream = verboseStream;
 
-            if (debugStoreOsPath != null) {
-                File storeFile = new File(debugStoreOsPath);
-                try {
-                    checkInputFile(storeFile);
-                } catch (FileNotFoundException e) {
-                    // ignore these since the debug store can be created on the fly anyway.
-                }
-
-                // get the debug key
-                verbosePrintln("Using keystore: %s", debugStoreOsPath);
-
-                IKeyGenOutput keygenOutput = null;
-                if (mVerboseStream != null) {
-                    keygenOutput = new IKeyGenOutput() {
-                        public void out(String message) {
-                            mVerboseStream.println(message);
-                        }
-
-                        public void err(String message) {
-                            mVerboseStream.println(message);
-                        }
-                    };
-                }
-
-                DebugKeyProvider keyProvider = new DebugKeyProvider(
-                        debugStoreOsPath, null /*store type*/, keygenOutput);
-
-                PrivateKey key = keyProvider.getDebugKey();
-                X509Certificate certificate = (X509Certificate)keyProvider.getCertificate();
-
-                if (key == null) {
-                    throw new ApkCreationException("Unable to get debug signature key");
-                }
-
-                // compare the certificate expiration date
-                if (certificate != null && certificate.getNotAfter().compareTo(new Date()) < 0) {
-                    // TODO, regenerate a new one.
-                    throw new ApkCreationException("Debug Certificate expired on " +
-                            DateFormat.getInstance().format(certificate.getNotAfter()));
-                }
-
-                mBuilder = new SignedJarBuilder(
-                        new FileOutputStream(mApkFile, false /* append */), key,
-                        certificate);
-            } else {
-                // no debug keystore? build without signing.
-                mBuilder = new SignedJarBuilder(
-                        new FileOutputStream(mApkFile, false /* append */),
-                        null /* key */, null /* certificate */);
-            }
+            mBuilder = new SignedJarBuilder(
+                    new FileOutputStream(mApkFile, false /* append */), key,
+                    certificate);
 
             verbosePrintln("Packaging %s", mApkFile.getName());
 
@@ -312,18 +449,6 @@ public final class ApkBuilder {
                 addFile(mDexFile, SdkConstants.FN_APK_CLASSES_DEX);
             }
 
-        } catch (KeytoolException e) {
-            if (e.getJavaHome() == null) {
-                throw new ApkCreationException(e.getMessage() +
-                        "\nJAVA_HOME seems undefined, setting it will help locating keytool automatically\n" +
-                        "You can also manually execute the following command\n:" +
-                        e.getCommandLine());
-            } else {
-                throw new ApkCreationException(e.getMessage() +
-                        "\nJAVA_HOME is set to: " + e.getJavaHome() +
-                        "\nUpdate it if necessary, or manually execute the following command:\n" +
-                        e.getCommandLine());
-            }
         } catch (ApkCreationException e) {
             throw e;
         } catch (Exception e) {
@@ -690,7 +815,7 @@ public final class ApkBuilder {
      * @throws FileNotFoundException if the file is not here.
      * @throws ApkCreationException If the file is a folder or a file that cannot be read.
      */
-    private void checkInputFile(File file) throws FileNotFoundException, ApkCreationException {
+    private static void checkInputFile(File file) throws FileNotFoundException, ApkCreationException {
         if (file.isDirectory()) {
             throw new ApkCreationException("%s is a directory!", file);
         }
diff --git a/testapps/.gitignore b/testapps/.gitignore
new file mode 100644 (file)
index 0000000..0c75cfd
--- /dev/null
@@ -0,0 +1,3 @@
+*/bin
+*/local.properties
+*/gen
diff --git a/testapps/README.txt b/testapps/README.txt
new file mode 100644 (file)
index 0000000..e76bc4d
--- /dev/null
@@ -0,0 +1,6 @@
+This repository contains test applications used by the SDK automated tests.
+
+These are not meant to be packaged with the SDK.
+
+Each project represents a different test case. For more information, read
+the readme file located in the project directory.
\ No newline at end of file
diff --git a/testapps/basicProject/.classpath b/testapps/basicProject/.classpath
new file mode 100644 (file)
index 0000000..609aa00
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+       <classpathentry kind="src" path="src"/>
+       <classpathentry kind="src" path="gen"/>
+       <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
+       <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/testapps/basicProject/.project b/testapps/basicProject/.project
new file mode 100644 (file)
index 0000000..2bf35ce
--- /dev/null
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>BasicProject</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>com.android.ide.eclipse.adt.ApkBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>com.android.ide.eclipse.adt.AndroidNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/testapps/basicProject/AndroidManifest.xml b/testapps/basicProject/AndroidManifest.xml
new file mode 100644 (file)
index 0000000..978c71e
--- /dev/null
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+      package="com.android.tests.basicproject"
+      android:versionCode="1"
+      android:versionName="1.0">
+    <application android:label="@string/app_name" android:icon="@drawable/icon">
+        <activity android:name="Main"
+                  android:label="@string/app_name">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+    <uses-sdk android:minSdkVersion="AOSP" />
+</manifest> 
diff --git a/testapps/basicProject/build.properties b/testapps/basicProject/build.properties
new file mode 100644 (file)
index 0000000..edc7f23
--- /dev/null
@@ -0,0 +1,17 @@
+# This file is used to override default values used by the Ant build system.
+# 
+# This file must be checked in Version Control Systems, as it is
+# integral to the build system of your project.
+
+# This file is only used by the Ant script.
+
+# You can use this to override default values such as
+#  'source.dir' for the location of your java source folder and
+#  'out.dir' for the location of your output folder.
+
+# You can also use it define how the release builds are signed by declaring
+# the following properties:
+#  'key.store' for the location of your keystore and
+#  'key.alias' for the name of the key to use.
+# The password will be asked during the build when you use the 'release' target.
+
diff --git a/testapps/basicProject/build.xml b/testapps/basicProject/build.xml
new file mode 100644 (file)
index 0000000..9ac802c
--- /dev/null
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="BasicProject" default="help">
+
+<!-- The local.properties file is created and updated by the 'android'
+     tool.
+     It contains the path to the SDK. It should *NOT* be checked into
+     Version Control Systems. -->
+    <property file="local.properties" />
+
+    <!-- The build.properties file can be created by you and is never touched
+         by the 'android' tool. This is the place to change some of the
+         default property values used by the Ant rules.
+         Here are some properties you may want to change/update:
+
+         source.dir
+             The name of the source directory. Default is 'src'.
+         out.dir
+             The name of the output directory. Default is 'bin'.
+
+         Properties related to the SDK location or the project target should
+         be updated using the 'android' tool with the 'update' action.
+
+         This file is an integral part of the build system for your
+         application and should be checked into Version Control Systems.
+
+         -->
+    <property file="build.properties" />
+
+    <!-- The default.properties file is created and updated by the 'android'
+         tool, as well as ADT.
+         This file is an integral part of the build system for your
+         application and should be checked into Version Control Systems. -->
+    <property file="default.properties" />
+
+    <!-- Custom Android task to deal with the project target, and import the
+         proper rules.
+         This requires ant 1.6.0 or above. -->
+    <path id="android.antlibs">
+        <pathelement path="${sdk.dir}/tools/lib/anttasks.jar" />
+        <pathelement path="${sdk.dir}/tools/lib/sdklib.jar" />
+        <pathelement path="${sdk.dir}/tools/lib/androidprefs.jar" />
+    </path>
+
+    <taskdef name="setup"
+        classname="com.android.ant.SetupTask"
+        classpathref="android.antlibs" />
+
+<!-- extension targets. Uncomment the ones where you want to do custom work
+     in between standard targets -->
+<!--
+    <target name="-pre-build">
+    </target>
+    <target name="-pre-compile">
+    </target>
+
+    [This is typically used for code obfuscation.
+     Compiled code location: ${out.classes.absolute.dir}
+     If this is not done in place, override ${out.dex.input.absolute.dir}]
+    <target name="-post-compile">
+    </target>
+-->
+
+
+    <!-- Execute the Android Setup task that will setup some properties
+         specific to the target, and import the build rules files.
+
+         The rules file is imported from
+            <SDK>/platforms/<target_platform>/ant/ant_rules_r#.xml
+
+         To customize existing targets, there are two options:
+         - Customize only one target:
+             - copy/paste the target into this file, *before* the
+               <setup> task.
+             - customize it to your needs.
+         - Customize the whole script.
+             - copy/paste the content of the rules files (minus the top node)
+               into this file, *after* the <setup> task
+             - disable the import of the rules by changing the setup task
+               below to <setup import="false" />. 
+             - customize to your needs.
+    -->
+    <setup />
+
+</project>
diff --git a/testapps/basicProject/default.properties b/testapps/basicProject/default.properties
new file mode 100644 (file)
index 0000000..640d5d4
--- /dev/null
@@ -0,0 +1,11 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+# 
+# This file must be checked in Version Control Systems.
+# 
+# To customize properties used by the Ant build system use,
+# "build.properties", and override values to adapt the script to your
+# project structure.
+
+# Project target.
+target=android-AOSP
diff --git a/testapps/basicProject/res/drawable-hdpi/icon.png b/testapps/basicProject/res/drawable-hdpi/icon.png
new file mode 100644 (file)
index 0000000..8074c4c
Binary files /dev/null and b/testapps/basicProject/res/drawable-hdpi/icon.png differ
diff --git a/testapps/basicProject/res/drawable-ldpi/icon.png b/testapps/basicProject/res/drawable-ldpi/icon.png
new file mode 100644 (file)
index 0000000..1095584
Binary files /dev/null and b/testapps/basicProject/res/drawable-ldpi/icon.png differ
diff --git a/testapps/basicProject/res/drawable-mdpi/icon.png b/testapps/basicProject/res/drawable-mdpi/icon.png
new file mode 100644 (file)
index 0000000..a07c69f
Binary files /dev/null and b/testapps/basicProject/res/drawable-mdpi/icon.png differ
diff --git a/testapps/basicProject/res/layout/main.xml b/testapps/basicProject/res/layout/main.xml
new file mode 100644 (file)
index 0000000..829994c
--- /dev/null
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    >
+<TextView  
+    android:layout_width="fill_parent" 
+    android:layout_height="wrap_content" 
+    android:text="Hello World, Main"
+    />
+</LinearLayout>
+
diff --git a/testapps/basicProject/res/values/strings.xml b/testapps/basicProject/res/values/strings.xml
new file mode 100644 (file)
index 0000000..549e4ea
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="app_name">Main</string>
+</resources>
diff --git a/testapps/basicProject/src/com/android/tests/basicproject/Main.java b/testapps/basicProject/src/com/android/tests/basicproject/Main.java
new file mode 100644 (file)
index 0000000..1240556
--- /dev/null
@@ -0,0 +1,15 @@
+package com.android.tests.basicproject;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class Main extends Activity
+{
+    /** Called when the activity is first created. */
+    @Override
+    public void onCreate(Bundle savedInstanceState)
+    {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+    }
+}