com.android.ide.eclipse.adt.internal.build.builders.PostCompilerBuilder.java Source code

Java tutorial

Introduction

Here is the source code for com.android.ide.eclipse.adt.internal.build.builders.PostCompilerBuilder.java

Source

/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Eclipse Public License, Version 1.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.eclipse.org/org/documents/epl-v10.php
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.ide.eclipse.adt.internal.build.builders;

import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AndroidPrintStream;
import com.android.ide.eclipse.adt.internal.build.AaptExecException;
import com.android.ide.eclipse.adt.internal.build.AaptParser;
import com.android.ide.eclipse.adt.internal.build.AaptResultException;
import com.android.ide.eclipse.adt.internal.build.BuildHelper;
import com.android.ide.eclipse.adt.internal.build.BuildHelper.ResourceMarker;
import com.android.ide.eclipse.adt.internal.build.DexException;
import com.android.ide.eclipse.adt.internal.build.Messages;
import com.android.ide.eclipse.adt.internal.build.NativeLibInJarException;
import com.android.ide.eclipse.adt.internal.lint.LintDeltaProcessor;
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.LibraryClasspathContainerInitializer;
import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.ide.eclipse.adt.io.IFileWrapper;
import com.android.prefs.AndroidLocation.AndroidLocationException;
import com.android.sdklib.build.ApkBuilder;
import com.android.sdklib.build.ApkCreationException;
import com.android.sdklib.build.DuplicateFileException;
import com.android.sdklib.build.IArchiveBuilder;
import com.android.sdklib.build.SealedApkException;
import com.android.sdklib.internal.build.DebugKeyProvider.KeytoolException;
import com.android.xml.AndroidManifest;

import org.eclipse.core.resources.IContainer;
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.IResourceDelta;
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.jdt.core.IJavaModelMarker;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.regex.Pattern;

public class PostCompilerBuilder extends BaseBuilder {

    /** 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$

    private static final String PROPERTY_CONVERT_TO_DEX = "convertToDex"; //$NON-NLS-1$
    private static final String PROPERTY_PACKAGE_RESOURCES = "packageResources"; //$NON-NLS-1$
    private static final String PROPERTY_BUILD_APK = "buildApk"; //$NON-NLS-1$

    /** Flag to pass to PostCompiler builder that sets if it runs or not.
     *  Set this flag whenever calling build if PostCompiler is to run
     */
    public final static String POST_C_REQUESTED = "RunPostCompiler"; //$NON-NLS-1$

    /**
     * Dex conversion flag. This is set to true if one of the changed/added/removed
     * file is a .class file. Upon visiting all the delta resource, if this
     * flag is true, then we know we'll have to make the "classes.dex" file.
     */
    private boolean mConvertToDex = false;

    /**
     * Package resources flag. This is set to true if one of the changed/added/removed
     * file is a resource file. Upon visiting all the delta resource, if
     * this flag is true, then we know we'll have to repackage the resources.
     */
    private boolean mPackageResources = false;

    /**
     * Final package build flag.
     */
    private boolean mBuildFinalPackage = false;

    private AndroidPrintStream mOutStream = null;
    private AndroidPrintStream mErrStream = null;

    private ResourceMarker mResourceMarker = new ResourceMarker() {
        @Override
        public void setWarning(IResource resource, String message) {
            BaseProjectHelper.markResource(resource, AdtConstants.MARKER_PACKAGING, message,
                    IMarker.SEVERITY_WARNING);
        }
    };

    public PostCompilerBuilder() {
        super();
    }

    @Override
    protected void clean(IProgressMonitor monitor) throws CoreException {
        super.clean(monitor);

        // Get the project.
        IProject project = getProject();

        if (DEBUG_LOG) {
            AdtPlugin.log(IStatus.INFO, "%s CLEAN(POST)", project.getName());
        }

        // Clear the project of the generic markers
        removeMarkersFromContainer(project, AdtConstants.MARKER_AAPT_PACKAGE);
        removeMarkersFromContainer(project, AdtConstants.MARKER_PACKAGING);

        // also remove the files in the output folder (but not the Eclipse output folder).
        IFolder javaOutput = BaseProjectHelper.getJavaOutputFolder(project);
        IFolder androidOutput = BaseProjectHelper.getAndroidOutputFolder(project);

        if (javaOutput.equals(androidOutput) == false) {
            // get the content
            IResource[] members = androidOutput.members();
            for (IResource member : members) {
                if (member.equals(javaOutput) == false) {
                    member.delete(true /*force*/, monitor);
                }
            }
        }
    }

    // build() returns a list of project from which this project depends for future compilation.
    @Override
    protected IProject[] build(int kind, @SuppressWarnings("rawtypes") Map args, IProgressMonitor monitor)
            throws CoreException {
        // get a project object
        IProject project = getProject();

        if (DEBUG_LOG) {
            AdtPlugin.log(IStatus.INFO, "%s BUILD(POST)", project.getName());
        }

        // Benchmarking start
        long startBuildTime = 0;
        if (BuildHelper.BENCHMARK_FLAG) {
            // End JavaC Timer
            String msg = "BENCHMARK ADT: Ending Compilation \n BENCHMARK ADT: Time Elapsed: " + //$NON-NLS-1$
                    (System.nanoTime() - BuildHelper.sStartJavaCTime) / Math.pow(10, 6) + "ms"; //$NON-NLS-1$
            AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, project, msg);
            msg = "BENCHMARK ADT: Starting PostCompilation"; //$NON-NLS-1$
            AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, project, msg);
            startBuildTime = System.nanoTime();
        }

        // list of referenced projects. This is a mix of java projects and library projects
        // and is computed below.
        IProject[] allRefProjects = null;

        try {
            // get the project info
            ProjectState projectState = Sdk.getProjectState(project);

            // this can happen if the project has no project.properties.
            if (projectState == null) {
                return null;
            }

            boolean isLibrary = projectState.isLibrary();

            // get the libraries
            List<IProject> libProjects = projectState.getFullLibraryProjects();

            IJavaProject javaProject = JavaCore.create(project);

            // get the list of referenced projects.
            List<IProject> javaProjects = ProjectHelper.getReferencedProjects(project);
            List<IJavaProject> referencedJavaProjects = BuildHelper.getJavaProjects(javaProjects);

            // mix the java project and the library projects
            final int size = libProjects.size() + javaProjects.size();
            ArrayList<IProject> refList = new ArrayList<IProject>(size);
            refList.addAll(libProjects);
            refList.addAll(javaProjects);
            allRefProjects = refList.toArray(new IProject[size]);

            // get the android output folder
            IFolder androidOutputFolder = BaseProjectHelper.getAndroidOutputFolder(project);
            IFolder resOutputFolder = androidOutputFolder.getFolder(SdkConstants.FD_RES);

            // First thing we do is go through the resource delta to not
            // lose it if we have to abort the build for any reason.
            if (args.containsKey(POST_C_REQUESTED) && AdtPrefs.getPrefs().getBuildSkipPostCompileOnFileSave()) {
                // Skip over flag setting
            } else if (kind == FULL_BUILD) {
                AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, Messages.Start_Full_Apk_Build);

                if (DEBUG_LOG) {
                    AdtPlugin.log(IStatus.INFO, "%s full build!", project.getName());
                }

                // Full build: we do all the steps.
                mPackageResources = true;
                mConvertToDex = true;
                mBuildFinalPackage = true;
            } else {
                AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, Messages.Start_Inc_Apk_Build);

                // go through the resources and see if something changed.
                IResourceDelta delta = getDelta(project);
                if (delta == null) {
                    // no delta? Same as full build: we do all the steps.
                    mPackageResources = true;
                    mConvertToDex = true;
                    mBuildFinalPackage = true;
                } else {

                    if (ResourceManager.isAutoBuilding() && AdtPrefs.getPrefs().isLintOnSave()) {
                        // Check for errors on save/build, if enabled
                        LintDeltaProcessor.create().process(delta);
                    }

                    PatternBasedDeltaVisitor dv = new PatternBasedDeltaVisitor(project, project, "POST:Main");

                    ChangedFileSet manifestCfs = ChangedFileSetHelper.getMergedManifestCfs(project);
                    dv.addSet(manifestCfs);

                    ChangedFileSet resCfs = ChangedFileSetHelper.getResCfs(project);
                    dv.addSet(resCfs);

                    ChangedFileSet androidCodeCfs = ChangedFileSetHelper.getCodeCfs(project);
                    dv.addSet(androidCodeCfs);

                    ChangedFileSet javaResCfs = ChangedFileSetHelper.getJavaResCfs(project);
                    dv.addSet(javaResCfs);
                    dv.addSet(ChangedFileSetHelper.NATIVE_LIBS);

                    delta.accept(dv);

                    // save the state
                    mPackageResources |= dv.checkSet(manifestCfs) || dv.checkSet(resCfs);

                    mConvertToDex |= dv.checkSet(androidCodeCfs);

                    mBuildFinalPackage |= dv.checkSet(javaResCfs) || dv.checkSet(ChangedFileSetHelper.NATIVE_LIBS);
                }

                // check the libraries
                if (libProjects.size() > 0) {
                    for (IProject libProject : libProjects) {
                        delta = getDelta(libProject);
                        if (delta != null) {
                            PatternBasedDeltaVisitor visitor = new PatternBasedDeltaVisitor(project, libProject,
                                    "POST:Lib");

                            ChangedFileSet libResCfs = ChangedFileSetHelper.getFullResCfs(libProject);
                            visitor.addSet(libResCfs);
                            visitor.addSet(ChangedFileSetHelper.NATIVE_LIBS);
                            // FIXME: add check on the library.jar?

                            delta.accept(visitor);

                            mPackageResources |= visitor.checkSet(libResCfs);
                            mBuildFinalPackage |= visitor.checkSet(ChangedFileSetHelper.NATIVE_LIBS);
                        }
                    }
                }

                // also go through the delta for all the referenced projects
                final int referencedCount = referencedJavaProjects.size();
                for (int i = 0; i < referencedCount; i++) {
                    IJavaProject referencedJavaProject = referencedJavaProjects.get(i);
                    delta = getDelta(referencedJavaProject.getProject());
                    if (delta != null) {
                        IProject referencedProject = referencedJavaProject.getProject();
                        PatternBasedDeltaVisitor visitor = new PatternBasedDeltaVisitor(project, referencedProject,
                                "POST:RefedProject");

                        ChangedFileSet javaResCfs = ChangedFileSetHelper.getJavaResCfs(referencedProject);
                        visitor.addSet(javaResCfs);

                        ChangedFileSet bytecodeCfs = ChangedFileSetHelper.getByteCodeCfs(referencedProject);
                        visitor.addSet(bytecodeCfs);

                        delta.accept(visitor);

                        // save the state
                        mConvertToDex |= visitor.checkSet(bytecodeCfs);
                        mBuildFinalPackage |= visitor.checkSet(javaResCfs);
                    }
                }
            }

            // store the build status in the persistent storage
            saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex);
            saveProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, mPackageResources);
            saveProjectBooleanProperty(PROPERTY_BUILD_APK, mBuildFinalPackage);

            // Top level check to make sure the build can move forward. Only do this after recording
            // delta changes.
            abortOnBadSetup(javaProject, projectState);

            // Get the output stream. Since the builder is created for the life of the
            // project, they can be kept around.
            if (mOutStream == null) {
                mOutStream = new AndroidPrintStream(project, null /*prefix*/, AdtPlugin.getOutStream());
                mErrStream = new AndroidPrintStream(project, null /*prefix*/, AdtPlugin.getOutStream());
            }

            // remove older packaging markers.
            removeMarkersFromContainer(javaProject.getProject(), AdtConstants.MARKER_PACKAGING);

            // finished with the common init and tests. Special case of the library.
            if (isLibrary) {
                // check the jar output file is present, if not create it.
                IFile jarIFile = androidOutputFolder
                        .getFile(project.getName().toLowerCase() + SdkConstants.DOT_JAR);
                if (mConvertToDex == false && jarIFile.exists() == false) {
                    mConvertToDex = true;
                }

                // also update the crunch cache always since aapt does it smartly only
                // on the files that need it.
                if (DEBUG_LOG) {
                    AdtPlugin.log(IStatus.INFO, "%s running crunch!", project.getName());
                }
                BuildHelper helper = new BuildHelper(projectState, mBuildToolInfo, mOutStream, mErrStream,
                        false /*jumbo mode doesn't matter here*/, false /*dex merger doesn't matter here*/,
                        true /*debugMode*/, AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE,
                        mResourceMarker);
                updateCrunchCache(project, helper);

                // refresh recursively bin/res folder
                resOutputFolder.refreshLocal(IResource.DEPTH_INFINITE, monitor);

                if (mConvertToDex) { // in this case this means some class files changed and
                                     // we need to update the jar file.
                    if (DEBUG_LOG) {
                        AdtPlugin.log(IStatus.INFO, "%s updating jar!", project.getName());
                    }

                    // resource to the AndroidManifest.xml file
                    IFile manifestFile = project.getFile(SdkConstants.FN_ANDROID_MANIFEST_XML);
                    String appPackage = AndroidManifest.getPackage(new IFileWrapper(manifestFile));

                    IFolder javaOutputFolder = BaseProjectHelper.getJavaOutputFolder(project);

                    writeLibraryPackage(jarIFile, project, appPackage, javaOutputFolder);
                    saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex = false);

                    // refresh the bin folder content with no recursion to update the library
                    // jar file.
                    androidOutputFolder.refreshLocal(IResource.DEPTH_ONE, monitor);

                    // Also update the projects. The only way to force recompile them is to
                    // reset the library container.
                    List<ProjectState> parentProjects = projectState.getParentProjects();
                    LibraryClasspathContainerInitializer.updateProject(parentProjects);
                }

                return allRefProjects;
            }

            // Check to see if we're going to launch or export. If not, we can skip
            // the packaging and dexing process.
            if (!args.containsKey(POST_C_REQUESTED) && AdtPrefs.getPrefs().getBuildSkipPostCompileOnFileSave()) {
                AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, Messages.Skip_Post_Compiler);
                return allRefProjects;
            } else {
                AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, Messages.Start_Full_Post_Compiler);
            }

            // first thing we do is check that the SDK directory has been setup.
            String osSdkFolder = AdtPlugin.getOsSdkFolder();

            if (osSdkFolder.length() == 0) {
                // this has already been checked in the precompiler. Therefore,
                // while we do have to cancel the build, we don't have to return
                // any error or throw anything.
                return allRefProjects;
            }

            // do some extra check, in case the output files are not present. This
            // will force to recreate them.
            IResource tmp = null;

            if (mPackageResources == false) {
                // check the full resource package
                tmp = androidOutputFolder.findMember(AdtConstants.FN_RESOURCES_AP_);
                if (tmp == null || tmp.exists() == false) {
                    mPackageResources = true;
                }
            }

            // check classes.dex is present. If not we force to recreate it.
            if (mConvertToDex == false) {
                tmp = androidOutputFolder.findMember(SdkConstants.FN_APK_CLASSES_DEX);
                if (tmp == null || tmp.exists() == false) {
                    mConvertToDex = true;
                }
            }

            // also check the final file(s)!
            String finalPackageName = ProjectHelper.getApkFilename(project, null /*config*/);
            if (mBuildFinalPackage == false) {
                tmp = androidOutputFolder.findMember(finalPackageName);
                if (tmp == null || (tmp instanceof IFile && tmp.exists() == false)) {
                    String msg = String.format(Messages.s_Missing_Repackaging, finalPackageName);
                    AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, msg);
                    mBuildFinalPackage = true;
                }
            }

            // at this point we know if we need to recreate the temporary apk
            // or the dex file, but we don't know if we simply need to recreate them
            // because they are missing

            // refresh the output directory first
            IContainer ic = androidOutputFolder.getParent();
            if (ic != null) {
                ic.refreshLocal(IResource.DEPTH_ONE, monitor);
            }

            // we need to test all three, as we may need to make the final package
            // but not the intermediary ones.
            if (mPackageResources || mConvertToDex || mBuildFinalPackage) {
                String forceJumboStr = projectState.getProperty(AdtConstants.DEX_OPTIONS_FORCEJUMBO);
                Boolean jumbo = Boolean.valueOf(forceJumboStr);

                String dexMergerStr = projectState.getProperty(AdtConstants.DEX_OPTIONS_DISABLE_MERGER);
                Boolean dexMerger = Boolean.valueOf(dexMergerStr);

                BuildHelper helper = new BuildHelper(projectState, mBuildToolInfo, mOutStream, mErrStream,
                        jumbo.booleanValue(), dexMerger.booleanValue(), true /*debugMode*/,
                        AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE, mResourceMarker);

                IPath androidBinLocation = androidOutputFolder.getLocation();
                if (androidBinLocation == null) {
                    markProject(AdtConstants.MARKER_PACKAGING, Messages.Output_Missing, IMarker.SEVERITY_ERROR);
                    return allRefProjects;
                }
                String osAndroidBinPath = androidBinLocation.toOSString();

                // resource to the AndroidManifest.xml file
                IFile manifestFile = androidOutputFolder.getFile(SdkConstants.FN_ANDROID_MANIFEST_XML);

                if (manifestFile == null || manifestFile.exists() == false) {
                    // mark project and exit
                    String msg = String.format(Messages.s_File_Missing, SdkConstants.FN_ANDROID_MANIFEST_XML);
                    markProject(AdtConstants.MARKER_PACKAGING, msg, IMarker.SEVERITY_ERROR);
                    return allRefProjects;
                }

                // Remove the old .apk.
                // This make sure that if the apk is corrupted, then dx (which would attempt
                // to open it), will not fail.
                String osFinalPackagePath = osAndroidBinPath + File.separator + finalPackageName;
                File finalPackage = new File(osFinalPackagePath);

                // if delete failed, this is not really a problem, as the final package generation
                // handle already present .apk, and if that one failed as well, the user will be
                // notified.
                finalPackage.delete();

                // Check if we need to package the resources.
                if (mPackageResources) {
                    // also update the crunch cache always since aapt does it smartly only
                    // on the files that need it.
                    if (DEBUG_LOG) {
                        AdtPlugin.log(IStatus.INFO, "%s running crunch!", project.getName());
                    }
                    if (updateCrunchCache(project, helper) == false) {
                        return allRefProjects;
                    }

                    // refresh recursively bin/res folder
                    resOutputFolder.refreshLocal(IResource.DEPTH_INFINITE, monitor);

                    if (DEBUG_LOG) {
                        AdtPlugin.log(IStatus.INFO, "%s packaging resources!", project.getName());
                    }
                    // remove some aapt_package only markers.
                    removeMarkersFromContainer(project, AdtConstants.MARKER_AAPT_PACKAGE);

                    try {
                        helper.packageResources(manifestFile, libProjects, null /*resfilter*/, 0 /*versionCode */,
                                osAndroidBinPath, AdtConstants.FN_RESOURCES_AP_);
                    } catch (AaptExecException e) {
                        BaseProjectHelper.markResource(project, AdtConstants.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, AdtConstants.MARKER_PACKAGING,
                                    Messages.Unparsed_AAPT_Errors, IMarker.SEVERITY_ERROR);
                        }
                    }

                    // build has been done. reset the state of the builder
                    mPackageResources = false;

                    // and store it
                    saveProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, mPackageResources);
                }

                String classesDexPath = osAndroidBinPath + File.separator + SdkConstants.FN_APK_CLASSES_DEX;

                // then we check if we need to package the .class into classes.dex
                if (mConvertToDex) {
                    if (DEBUG_LOG) {
                        AdtPlugin.log(IStatus.INFO, "%s running dex!", project.getName());
                    }
                    try {
                        Collection<String> dxInputPaths = helper.getCompiledCodePaths();

                        helper.executeDx(javaProject, dxInputPaths, classesDexPath);
                    } catch (DexException e) {
                        String message = e.getMessage();

                        AdtPlugin.printErrorToConsole(project, message);
                        BaseProjectHelper.markResource(project, AdtConstants.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;
                    }

                    // build has been done. reset the state of the builder
                    mConvertToDex = false;

                    // and store it
                    saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex);
                }

                // now we need to make the final package from the intermediary apk
                // and classes.dex.
                // This is the default package with all the resources.

                try {
                    if (DEBUG_LOG) {
                        AdtPlugin.log(IStatus.INFO, "%s making final package!", project.getName());
                    }
                    helper.finalDebugPackage(osAndroidBinPath + File.separator + AdtConstants.FN_RESOURCES_AP_,
                            classesDexPath, osFinalPackagePath, libProjects, 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, AdtConstants.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());

                    AdtPlugin.log(e, msg);

                    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, AdtConstants.MARKER_PACKAGING, msg,
                            IMarker.SEVERITY_ERROR);

                    AdtPlugin.log(e, msg);
                } 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, AdtConstants.MARKER_PACKAGING, msg,
                            IMarker.SEVERITY_ERROR);
                    AdtPlugin.log(e, msg);
                } catch (NativeLibInJarException e) {
                    String msg = e.getMessage();

                    BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg,
                            IMarker.SEVERITY_ERROR);

                    AdtPlugin.printErrorToConsole(project, (Object[]) e.getAdditionalInfo());
                } 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, AdtConstants.MARKER_PACKAGING, msg,
                            IMarker.SEVERITY_ERROR);
                    AdtPlugin.log(e, msg);
                } 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, AdtConstants.MARKER_PACKAGING, msg2,
                            IMarker.SEVERITY_ERROR);
                }

                // we are done.

                // refresh the bin folder content with no recursion.
                androidOutputFolder.refreshLocal(IResource.DEPTH_ONE, monitor);

                // build has been done. reset the state of the builder
                mBuildFinalPackage = false;

                // and store it
                saveProjectBooleanProperty(PROPERTY_BUILD_APK, mBuildFinalPackage);

                // reset the installation manager to force new installs of this project
                ApkInstallManager.getInstance().resetInstallationFor(project);

                AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, getProject(), "Build Success!");
            }
        } catch (AbortBuildException e) {
            return allRefProjects;
        } catch (Exception exception) {
            // 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.

            // first check if this is a CoreException we threw to cancel the build.
            if (exception instanceof CoreException) {
                if (((CoreException) exception).getStatus().getSeverity() == IStatus.CANCEL) {
                    // Project is already marked with an error. Nothing to do
                    return allRefProjects;
                }
            }

            String msg = exception.getMessage();
            if (msg == null) {
                msg = exception.getClass().getCanonicalName();
            }

            msg = String.format("Unknown error: %1$s", msg);
            AdtPlugin.logAndPrintError(exception, project.getName(), msg);
            markProject(AdtConstants.MARKER_PACKAGING, msg, IMarker.SEVERITY_ERROR);
        }

        // Benchmarking end
        if (BuildHelper.BENCHMARK_FLAG) {
            String msg = "BENCHMARK ADT: Ending PostCompilation. \n BENCHMARK ADT: Time Elapsed: " + //$NON-NLS-1$
                    ((System.nanoTime() - startBuildTime) / Math.pow(10, 6)) + "ms"; //$NON-NLS-1$
            AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, project, msg);
            // End Overall Timer
            msg = "BENCHMARK ADT: Done with everything! \n BENCHMARK ADT: Time Elapsed: " + //$NON-NLS-1$
                    (System.nanoTime() - BuildHelper.sStartOverallTime) / Math.pow(10, 6) + "ms"; //$NON-NLS-1$
            AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, project, msg);
        }

        return allRefProjects;
    }

    private static class JarBuilder implements IArchiveBuilder {

        private static Pattern R_PATTERN = Pattern.compile("R(\\$.*)?\\.class"); //$NON-NLS-1$
        private static String BUILD_CONFIG_CLASS = "BuildConfig.class"; //$NON-NLS-1$

        private final byte[] buffer = new byte[1024];
        private final JarOutputStream mOutputStream;
        private final String mAppPackage;

        JarBuilder(JarOutputStream outputStream, String appPackage) {
            mOutputStream = outputStream;
            mAppPackage = appPackage.replace('.', '/');
        }

        public void addFile(IFile file, IFolder rootFolder) throws ApkCreationException {
            // we only package class file from the output folder
            if (SdkConstants.EXT_CLASS.equals(file.getFileExtension()) == false) {
                return;
            }

            IPath packageApp = file.getParent().getFullPath().makeRelativeTo(rootFolder.getFullPath());

            String name = file.getName();
            // Ignore the library's R/Manifest/BuildConfig classes.
            if (mAppPackage.equals(packageApp.toString())
                    && (BUILD_CONFIG_CLASS.equals(name) || R_PATTERN.matcher(name).matches())) {
                return;
            }

            IPath path = file.getFullPath().makeRelativeTo(rootFolder.getFullPath());
            try {
                addFile(file.getContents(), file.getLocalTimeStamp(), path.toString());
            } catch (ApkCreationException e) {
                throw e;
            } catch (Exception e) {
                throw new ApkCreationException(e, "Failed to add %s", file);
            }
        }

        @Override
        public void addFile(File file, String archivePath)
                throws ApkCreationException, SealedApkException, DuplicateFileException {
            try {
                FileInputStream inputStream = new FileInputStream(file);
                long lastModified = file.lastModified();
                addFile(inputStream, lastModified, archivePath);
            } catch (ApkCreationException e) {
                throw e;
            } catch (Exception e) {
                throw new ApkCreationException(e, "Failed to add %s", file);
            }
        }

        private void addFile(InputStream content, long lastModified, String archivePath)
                throws IOException, ApkCreationException {
            // create the jar entry
            JarEntry entry = new JarEntry(archivePath);
            entry.setTime(lastModified);

            try {
                // add the entry to the jar archive
                mOutputStream.putNextEntry(entry);

                // read the content of the entry from the input stream, and write
                // it into the archive.
                int count;
                while ((count = content.read(buffer)) != -1) {
                    mOutputStream.write(buffer, 0, count);
                }
            } finally {
                try {
                    if (content != null) {
                        content.close();
                    }
                } catch (Exception e) {
                    throw new ApkCreationException(e, "Failed to close stream");
                }
            }
        }
    }

    /**
     * Updates the crunch cache if needed and return true if the build must continue.
     */
    private boolean updateCrunchCache(IProject project, BuildHelper helper) {
        try {
            helper.updateCrunchCache();
        } catch (AaptExecException e) {
            BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, e.getMessage(),
                    IMarker.SEVERITY_ERROR);
            return false;
        } 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);
            }
        }

        return true;
    }

    /**
     * Writes the library jar file.
     * @param jarIFile the destination file
     * @param project the library project
     * @param appPackage the library android package
     * @param javaOutputFolder the JDT output folder.
     */
    private void writeLibraryPackage(IFile jarIFile, IProject project, String appPackage,
            IFolder javaOutputFolder) {

        JarOutputStream jos = null;
        try {
            Manifest manifest = new Manifest();
            Attributes mainAttributes = manifest.getMainAttributes();
            mainAttributes.put(Attributes.Name.CLASS_PATH, "Android ADT"); //$NON-NLS-1$
            mainAttributes.putValue("Created-By", "1.0 (Android)"); //$NON-NLS-1$  //$NON-NLS-2$
            jos = new JarOutputStream(new FileOutputStream(jarIFile.getLocation().toFile()), manifest);

            JarBuilder jarBuilder = new JarBuilder(jos, appPackage);

            // write the class files
            writeClassFilesIntoJar(jarBuilder, javaOutputFolder, javaOutputFolder);

            // now write the standard Java resources from the output folder
            ApkBuilder.addSourceFolder(jarBuilder, javaOutputFolder.getLocation().toFile());

            saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex);
        } catch (Exception e) {
            AdtPlugin.log(e, "Failed to write jar file %s", jarIFile.getLocation().toOSString());
        } finally {
            if (jos != null) {
                try {
                    jos.close();
                } catch (IOException e) {
                    // pass
                }
            }
        }
    }

    private void writeClassFilesIntoJar(JarBuilder builder, IFolder folder, IFolder rootFolder)
            throws CoreException, IOException, ApkCreationException {
        IResource[] members = folder.members();
        for (IResource member : members) {
            if (member.getType() == IResource.FOLDER) {
                writeClassFilesIntoJar(builder, (IFolder) member, rootFolder);
            } else if (member.getType() == IResource.FILE) {
                IFile file = (IFile) member;
                builder.addFile(file, rootFolder);
            }
        }
    }

    @Override
    protected void startupOnInitialize() {
        super.startupOnInitialize();

        // load the build status. We pass true as the default value to
        // force a recompile in case the property was not found
        mConvertToDex = loadProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, true);
        mPackageResources = loadProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, true);
        mBuildFinalPackage = loadProjectBooleanProperty(PROPERTY_BUILD_APK, true);
    }

    @Override
    protected void abortOnBadSetup(@NonNull IJavaProject javaProject, @Nullable ProjectState projectState)
            throws AbortBuildException, CoreException {
        super.abortOnBadSetup(javaProject, projectState);

        IProject iProject = getProject();

        // do a (hopefully quick) search for Precompiler type markers. Those are always only
        // errors.
        stopOnMarker(iProject, AdtConstants.MARKER_AAPT_COMPILE, IResource.DEPTH_INFINITE, false /*checkSeverity*/);
        stopOnMarker(iProject, AdtConstants.MARKER_AIDL, IResource.DEPTH_INFINITE, false /*checkSeverity*/);
        stopOnMarker(iProject, AdtConstants.MARKER_RENDERSCRIPT, IResource.DEPTH_INFINITE, false /*checkSeverity*/);
        stopOnMarker(iProject, AdtConstants.MARKER_ANDROID, IResource.DEPTH_ZERO, false /*checkSeverity*/);

        // do a search for JDT markers. Those can be errors or warnings
        stopOnMarker(iProject, IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, IResource.DEPTH_INFINITE,
                true /*checkSeverity*/);
        stopOnMarker(iProject, IJavaModelMarker.BUILDPATH_PROBLEM_MARKER, IResource.DEPTH_INFINITE,
                true /*checkSeverity*/);
    }
}