Home | History | Annotate | Download | only in builders
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ide.eclipse.adt.internal.build.builders;
     18 
     19 import com.android.ide.eclipse.adt.AdtConstants;
     20 import com.android.ide.eclipse.adt.AdtPlugin;
     21 import com.android.ide.eclipse.adt.AndroidPrintStream;
     22 import com.android.ide.eclipse.adt.internal.build.AaptExecException;
     23 import com.android.ide.eclipse.adt.internal.build.AaptParser;
     24 import com.android.ide.eclipse.adt.internal.build.AaptResultException;
     25 import com.android.ide.eclipse.adt.internal.build.BuildHelper;
     26 import com.android.ide.eclipse.adt.internal.build.BuildHelper.ResourceMarker;
     27 import com.android.ide.eclipse.adt.internal.build.DexException;
     28 import com.android.ide.eclipse.adt.internal.build.Messages;
     29 import com.android.ide.eclipse.adt.internal.build.NativeLibInJarException;
     30 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
     31 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity;
     32 import com.android.ide.eclipse.adt.internal.project.ApkInstallManager;
     33 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
     34 import com.android.ide.eclipse.adt.internal.project.LibraryClasspathContainerInitializer;
     35 import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
     36 import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
     37 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
     38 import com.android.ide.eclipse.adt.io.IFileWrapper;
     39 import com.android.prefs.AndroidLocation.AndroidLocationException;
     40 import com.android.sdklib.SdkConstants;
     41 import com.android.sdklib.build.ApkBuilder;
     42 import com.android.sdklib.build.ApkCreationException;
     43 import com.android.sdklib.build.DuplicateFileException;
     44 import com.android.sdklib.build.IArchiveBuilder;
     45 import com.android.sdklib.build.SealedApkException;
     46 import com.android.sdklib.internal.build.DebugKeyProvider.KeytoolException;
     47 import com.android.sdklib.xml.AndroidManifest;
     48 
     49 import org.eclipse.core.resources.IContainer;
     50 import org.eclipse.core.resources.IFile;
     51 import org.eclipse.core.resources.IFolder;
     52 import org.eclipse.core.resources.IMarker;
     53 import org.eclipse.core.resources.IProject;
     54 import org.eclipse.core.resources.IResource;
     55 import org.eclipse.core.resources.IResourceDelta;
     56 import org.eclipse.core.resources.IResourceDeltaVisitor;
     57 import org.eclipse.core.runtime.CoreException;
     58 import org.eclipse.core.runtime.IPath;
     59 import org.eclipse.core.runtime.IProgressMonitor;
     60 import org.eclipse.core.runtime.IStatus;
     61 import org.eclipse.jdt.core.IJavaModelMarker;
     62 import org.eclipse.jdt.core.IJavaProject;
     63 import org.eclipse.jdt.core.JavaCore;
     64 import org.eclipse.jdt.core.JavaModelException;
     65 
     66 import java.io.File;
     67 import java.io.FileInputStream;
     68 import java.io.FileOutputStream;
     69 import java.io.IOException;
     70 import java.io.InputStream;
     71 import java.util.ArrayList;
     72 import java.util.Collection;
     73 import java.util.List;
     74 import java.util.Map;
     75 import java.util.jar.Attributes;
     76 import java.util.jar.JarEntry;
     77 import java.util.jar.JarOutputStream;
     78 import java.util.jar.Manifest;
     79 import java.util.regex.Pattern;
     80 
     81 public class PostCompilerBuilder extends BaseBuilder {
     82 
     83     /** This ID is used in plugin.xml and in each project's .project file.
     84      * It cannot be changed even if the class is renamed/moved */
     85     public static final String ID = "com.android.ide.eclipse.adt.ApkBuilder"; //$NON-NLS-1$
     86 
     87     private static final String PROPERTY_CONVERT_TO_DEX = "convertToDex"; //$NON-NLS-1$
     88     private static final String PROPERTY_PACKAGE_RESOURCES = "packageResources"; //$NON-NLS-1$
     89     private static final String PROPERTY_BUILD_APK = "buildApk"; //$NON-NLS-1$
     90 
     91     /** Flag to pass to PostCompiler builder that sets if it runs or not.
     92      *  Set this flag whenever calling build if PostCompiler is to run
     93      */
     94     public final static String POST_C_REQUESTED = "RunPostCompiler"; //$NON-NLS-1$
     95 
     96     /**
     97      * Dex conversion flag. This is set to true if one of the changed/added/removed
     98      * file is a .class file. Upon visiting all the delta resource, if this
     99      * flag is true, then we know we'll have to make the "classes.dex" file.
    100      */
    101     private boolean mConvertToDex = false;
    102 
    103     /**
    104      * Package resources flag. This is set to true if one of the changed/added/removed
    105      * file is a resource file. Upon visiting all the delta resource, if
    106      * this flag is true, then we know we'll have to repackage the resources.
    107      */
    108     private boolean mPackageResources = false;
    109 
    110     /**
    111      * Final package build flag.
    112      */
    113     private boolean mBuildFinalPackage = false;
    114 
    115     private AndroidPrintStream mOutStream = null;
    116     private AndroidPrintStream mErrStream = null;
    117 
    118     /**
    119      * Basic Resource Delta Visitor class to check if a referenced project had a change in its
    120      * compiled java files.
    121      */
    122     private static class ReferencedProjectDeltaVisitor implements IResourceDeltaVisitor {
    123 
    124         private boolean mConvertToDex = false;
    125         private boolean mMakeFinalPackage;
    126 
    127         private IPath mOutputFolder;
    128         private List<IPath> mSourceFolders;
    129 
    130         private ReferencedProjectDeltaVisitor(IJavaProject javaProject) {
    131             try {
    132                 mOutputFolder = javaProject.getOutputLocation();
    133                 mSourceFolders = BaseProjectHelper.getSourceClasspaths(javaProject);
    134             } catch (JavaModelException e) {
    135             }
    136         }
    137 
    138         /**
    139          * {@inheritDoc}
    140          * @throws CoreException
    141          */
    142         @Override
    143         public boolean visit(IResourceDelta delta) throws CoreException {
    144             //  no need to keep looking if we already know we need to convert
    145             // to dex and make the final package.
    146             if (mConvertToDex && mMakeFinalPackage) {
    147                 return false;
    148             }
    149 
    150             // get the resource and the path segments.
    151             IResource resource = delta.getResource();
    152             IPath resourceFullPath = resource.getFullPath();
    153 
    154             if (mOutputFolder.isPrefixOf(resourceFullPath)) {
    155                 int type = resource.getType();
    156                 if (type == IResource.FILE) {
    157                     String ext = resource.getFileExtension();
    158                     if (AdtConstants.EXT_CLASS.equals(ext)) {
    159                         mConvertToDex = true;
    160                     }
    161                 }
    162                 return true;
    163             } else {
    164                 for (IPath sourceFullPath : mSourceFolders) {
    165                     if (sourceFullPath.isPrefixOf(resourceFullPath)) {
    166                         int type = resource.getType();
    167                         if (type == IResource.FILE) {
    168                             // check if the file is a valid file that would be
    169                             // included during the final packaging.
    170                             if (BuildHelper.checkFileForPackaging((IFile)resource)) {
    171                                 mMakeFinalPackage = true;
    172                             }
    173 
    174                             return false;
    175                         } else if (type == IResource.FOLDER) {
    176                             // if this is a folder, we check if this is a valid folder as well.
    177                             // If this is a folder that needs to be ignored, we must return false,
    178                             // so that we ignore its content.
    179                             return BuildHelper.checkFolderForPackaging((IFolder)resource);
    180                         }
    181                     }
    182                 }
    183             }
    184 
    185             return true;
    186         }
    187 
    188         /**
    189          * Returns if one of the .class file was modified.
    190          */
    191         boolean needDexConvertion() {
    192             return mConvertToDex;
    193         }
    194 
    195         boolean needMakeFinalPackage() {
    196             return mMakeFinalPackage;
    197         }
    198     }
    199 
    200     private ResourceMarker mResourceMarker = new ResourceMarker() {
    201         @Override
    202         public void setWarning(IResource resource, String message) {
    203             BaseProjectHelper.markResource(resource, AdtConstants.MARKER_PACKAGING,
    204                     message, IMarker.SEVERITY_WARNING);
    205         }
    206     };
    207 
    208 
    209     public PostCompilerBuilder() {
    210         super();
    211     }
    212 
    213     @Override
    214     protected void clean(IProgressMonitor monitor) throws CoreException {
    215         super.clean(monitor);
    216 
    217         // Get the project.
    218         IProject project = getProject();
    219 
    220         if (DEBUG) {
    221             System.out.println("CLEAN(POST) " + project.getName());
    222         }
    223 
    224         // Clear the project of the generic markers
    225         removeMarkersFromContainer(project, AdtConstants.MARKER_AAPT_PACKAGE);
    226         removeMarkersFromContainer(project, AdtConstants.MARKER_PACKAGING);
    227 
    228         // also remove the files in the output folder (but not the Eclipse output folder).
    229         IFolder javaOutput = BaseProjectHelper.getJavaOutputFolder(project);
    230         IFolder androidOutput = BaseProjectHelper.getAndroidOutputFolder(project);
    231 
    232         if (javaOutput.equals(androidOutput) == false) {
    233             // get the content
    234             IResource[] members = androidOutput.members();
    235             for (IResource member : members) {
    236                 if (member.equals(javaOutput) == false) {
    237                     member.delete(true /*force*/, monitor);
    238                 }
    239             }
    240         }
    241     }
    242 
    243     // build() returns a list of project from which this project depends for future compilation.
    244     @Override
    245     protected IProject[] build(
    246             int kind,
    247             @SuppressWarnings("rawtypes") Map args,
    248             IProgressMonitor monitor)
    249             throws CoreException {
    250         // get a project object
    251         IProject project = getProject();
    252 
    253         if (DEBUG) {
    254             System.out.println("BUILD(POST) " + project.getName());
    255         }
    256 
    257         // Benchmarking start
    258         long startBuildTime = 0;
    259         if (BuildHelper.BENCHMARK_FLAG) {
    260             // End JavaC Timer
    261             String msg = "BENCHMARK ADT: Ending Compilation \n BENCHMARK ADT: Time Elapsed: " +    //$NON-NLS-1$
    262                          (System.nanoTime() - BuildHelper.sStartJavaCTime)/Math.pow(10, 6) + "ms"; //$NON-NLS-1$
    263             AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, project, msg);
    264             msg = "BENCHMARK ADT: Starting PostCompilation";                                        //$NON-NLS-1$
    265             AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, project, msg);
    266             startBuildTime = System.nanoTime();
    267         }
    268 
    269         // list of referenced projects. This is a mix of java projects and library projects
    270         // and is computed below.
    271         IProject[] allRefProjects = null;
    272 
    273         try {
    274             // get the project info
    275             ProjectState projectState = Sdk.getProjectState(project);
    276 
    277             // this can happen if the project has no project.properties.
    278             if (projectState == null) {
    279                 return null;
    280             }
    281 
    282             boolean isLibrary = projectState.isLibrary();
    283 
    284             // get the libraries
    285             List<IProject> libProjects = projectState.getFullLibraryProjects();
    286 
    287             IJavaProject javaProject = JavaCore.create(project);
    288 
    289             // get the list of referenced projects.
    290             List<IProject> javaProjects = ProjectHelper.getReferencedProjects(project);
    291             List<IJavaProject> referencedJavaProjects = BuildHelper.getJavaProjects(
    292                     javaProjects);
    293 
    294             // mix the java project and the library projects
    295             final int size = libProjects.size() + javaProjects.size();
    296             ArrayList<IProject> refList = new ArrayList<IProject>(size);
    297             refList.addAll(libProjects);
    298             refList.addAll(javaProjects);
    299             allRefProjects = refList.toArray(new IProject[size]);
    300 
    301             // get the android output folder
    302             IFolder androidOutputFolder = BaseProjectHelper.getAndroidOutputFolder(project);
    303             IFolder resOutputFolder = androidOutputFolder.getFolder(SdkConstants.FD_RES);
    304 
    305             // now we need to get the classpath list
    306             List<IPath> sourceList = BaseProjectHelper.getSourceClasspaths(javaProject);
    307 
    308             // First thing we do is go through the resource delta to not
    309             // lose it if we have to abort the build for any reason.
    310             PostCompilerDeltaVisitor dv = null;
    311             if (args.containsKey(POST_C_REQUESTED)
    312                     && AdtPrefs.getPrefs().getBuildSkipPostCompileOnFileSave()) {
    313                 // Skip over flag setting
    314             } else if (kind == FULL_BUILD) {
    315                 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project,
    316                         Messages.Start_Full_Apk_Build);
    317 
    318                 if (DEBUG) {
    319                     System.out.println("\tfull build!");
    320                 }
    321 
    322                 // Full build: we do all the steps.
    323                 mPackageResources = true;
    324                 mConvertToDex = true;
    325                 mBuildFinalPackage = true;
    326             } else {
    327                 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project,
    328                         Messages.Start_Inc_Apk_Build);
    329 
    330                 // go through the resources and see if something changed.
    331                 IResourceDelta delta = getDelta(project);
    332                 if (delta == null) {
    333                     // no delta? Same as full build: we do all the steps.
    334                     mPackageResources = true;
    335                     mConvertToDex = true;
    336                     mBuildFinalPackage = true;
    337                 } else {
    338                     dv = new PostCompilerDeltaVisitor(this, sourceList, androidOutputFolder);
    339                     delta.accept(dv);
    340 
    341                     // save the state
    342                     mPackageResources |= dv.getPackageResources();
    343                     mConvertToDex |= dv.getConvertToDex();
    344                     mBuildFinalPackage |= dv.getMakeFinalPackage();
    345                 }
    346 
    347                 // if the main resources didn't change, then we check for the library
    348                 // ones (will trigger resource repackaging too)
    349                 if ((mPackageResources == false || mBuildFinalPackage == false) &&
    350                         libProjects.size() > 0) {
    351                     for (IProject libProject : libProjects) {
    352                         delta = getDelta(libProject);
    353                         if (delta != null) {
    354                             LibraryDeltaVisitor visitor = new LibraryDeltaVisitor();
    355                             delta.accept(visitor);
    356 
    357                             mPackageResources |= visitor.getResChange();
    358                             mBuildFinalPackage |= visitor.getLibChange();
    359 
    360                             if (mPackageResources && mBuildFinalPackage) {
    361                                 break;
    362                             }
    363                         }
    364                     }
    365                 }
    366 
    367                 // also go through the delta for all the referenced projects, until we are forced to
    368                 // compile anyway
    369                 final int referencedCount = referencedJavaProjects.size();
    370                 for (int i = 0 ; i < referencedCount &&
    371                         (mBuildFinalPackage == false || mConvertToDex == false); i++) {
    372                     IJavaProject referencedJavaProject = referencedJavaProjects.get(i);
    373                     delta = getDelta(referencedJavaProject.getProject());
    374                     if (delta != null) {
    375                         ReferencedProjectDeltaVisitor refProjectDv =
    376                                 new ReferencedProjectDeltaVisitor(referencedJavaProject);
    377 
    378                         delta.accept(refProjectDv);
    379 
    380                         // save the state
    381                         mConvertToDex |= refProjectDv.needDexConvertion();
    382                         mBuildFinalPackage |= refProjectDv.needMakeFinalPackage();
    383                     }
    384                 }
    385             }
    386 
    387             // store the build status in the persistent storage
    388             saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex);
    389             saveProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, mPackageResources);
    390             saveProjectBooleanProperty(PROPERTY_BUILD_APK, mBuildFinalPackage);
    391 
    392             // Top level check to make sure the build can move forward. Only do this after recording
    393             // delta changes.
    394             abortOnBadSetup(javaProject);
    395 
    396             if (dv != null && dv.mXmlError) {
    397                 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project,
    398                 Messages.Xml_Error);
    399 
    400                 // if there was some XML errors, we just return w/o doing
    401                 // anything since we've put some markers in the files anyway
    402                 return allRefProjects;
    403             }
    404 
    405             // remove older packaging markers.
    406             removeMarkersFromContainer(javaProject.getProject(), AdtConstants.MARKER_PACKAGING);
    407 
    408             if (androidOutputFolder == null) {
    409                 // mark project and exit
    410                 markProject(AdtConstants.MARKER_PACKAGING, Messages.Failed_To_Get_Output,
    411                         IMarker.SEVERITY_ERROR);
    412                 return allRefProjects;
    413             }
    414 
    415             // finished with the common init and tests. Special case of the library.
    416             if (isLibrary) {
    417                 // check the jar output file is present, if not create it.
    418                 IFile jarIFile = androidOutputFolder.getFile(
    419                         project.getName().toLowerCase() + AdtConstants.DOT_JAR);
    420                 if (mConvertToDex == false && jarIFile.exists() == false) {
    421                     mConvertToDex = true;
    422                 }
    423 
    424                 // also update the crunch cache always since aapt does it smartly only
    425                 // on the files that need it.
    426                 if (DEBUG) {
    427                     System.out.println("\trunning crunch!");
    428                 }
    429                 BuildHelper helper = new BuildHelper(project,
    430                         mOutStream, mErrStream,
    431                         true /*debugMode*/,
    432                         AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE,
    433                         mResourceMarker);
    434                 updateCrunchCache(project, helper);
    435 
    436                 // refresh recursively bin/res folder
    437                 resOutputFolder.refreshLocal(IResource.DEPTH_INFINITE, monitor);
    438 
    439                 if (mConvertToDex) { // in this case this means some class files changed and
    440                                      // we need to update the jar file.
    441                     if (DEBUG) {
    442                         System.out.println("\tupdating jar!");
    443                     }
    444 
    445                     // resource to the AndroidManifest.xml file
    446                     IFile manifestFile = project.getFile(SdkConstants.FN_ANDROID_MANIFEST_XML);
    447                     String appPackage = AndroidManifest.getPackage(new IFileWrapper(manifestFile));
    448 
    449                     IFolder javaOutputFolder = BaseProjectHelper.getJavaOutputFolder(project);
    450 
    451                     writeLibraryPackage(jarIFile, project, appPackage, javaOutputFolder);
    452                     saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex = false);
    453 
    454                     // refresh the bin folder content with no recursion to update the library
    455                     // jar file.
    456                     androidOutputFolder.refreshLocal(IResource.DEPTH_ONE, monitor);
    457 
    458                     // Also update the projects. The only way to force recompile them is to
    459                     // reset the library container.
    460                     List<ProjectState> parentProjects = projectState.getParentProjects();
    461                     LibraryClasspathContainerInitializer.updateProject(parentProjects);
    462                 }
    463 
    464                 return allRefProjects;
    465             }
    466 
    467             // Check to see if we're going to launch or export. If not, we can skip
    468             // the packaging and dexing process.
    469             if (!args.containsKey(POST_C_REQUESTED)
    470                     && AdtPrefs.getPrefs().getBuildSkipPostCompileOnFileSave()) {
    471                 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project,
    472                         Messages.Skip_Post_Compiler);
    473                 return allRefProjects;
    474             } else {
    475                 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project,
    476                         Messages.Start_Full_Post_Compiler);
    477             }
    478 
    479             // first thing we do is check that the SDK directory has been setup.
    480             String osSdkFolder = AdtPlugin.getOsSdkFolder();
    481 
    482             if (osSdkFolder.length() == 0) {
    483                 // this has already been checked in the precompiler. Therefore,
    484                 // while we do have to cancel the build, we don't have to return
    485                 // any error or throw anything.
    486                 return allRefProjects;
    487             }
    488 
    489             // do some extra check, in case the output files are not present. This
    490             // will force to recreate them.
    491             IResource tmp = null;
    492 
    493             if (mPackageResources == false) {
    494                 // check the full resource package
    495                 tmp = androidOutputFolder.findMember(AdtConstants.FN_RESOURCES_AP_);
    496                 if (tmp == null || tmp.exists() == false) {
    497                     mPackageResources = true;
    498                     mBuildFinalPackage = true;
    499                 }
    500             }
    501 
    502             // check classes.dex is present. If not we force to recreate it.
    503             if (mConvertToDex == false) {
    504                 tmp = androidOutputFolder.findMember(SdkConstants.FN_APK_CLASSES_DEX);
    505                 if (tmp == null || tmp.exists() == false) {
    506                     mConvertToDex = true;
    507                     mBuildFinalPackage = true;
    508                 }
    509             }
    510 
    511             // also check the final file(s)!
    512             String finalPackageName = ProjectHelper.getApkFilename(project, null /*config*/);
    513             if (mBuildFinalPackage == false) {
    514                 tmp = androidOutputFolder.findMember(finalPackageName);
    515                 if (tmp == null || (tmp instanceof IFile &&
    516                         tmp.exists() == false)) {
    517                     String msg = String.format(Messages.s_Missing_Repackaging, finalPackageName);
    518                     AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, msg);
    519                     mBuildFinalPackage = true;
    520                 }
    521             }
    522 
    523             // at this point we know if we need to recreate the temporary apk
    524             // or the dex file, but we don't know if we simply need to recreate them
    525             // because they are missing
    526 
    527             // refresh the output directory first
    528             IContainer ic = androidOutputFolder.getParent();
    529             if (ic != null) {
    530                 ic.refreshLocal(IResource.DEPTH_ONE, monitor);
    531             }
    532 
    533             // Get the DX output stream. Since the builder is created for the life of the
    534             // project, they can be kept around.
    535             if (mOutStream == null) {
    536                 mOutStream = new AndroidPrintStream(project, null /*prefix*/,
    537                         AdtPlugin.getOutStream());
    538                 mErrStream = new AndroidPrintStream(project, null /*prefix*/,
    539                         AdtPlugin.getOutStream());
    540             }
    541 
    542             // we need to test all three, as we may need to make the final package
    543             // but not the intermediary ones.
    544             if (mPackageResources || mConvertToDex || mBuildFinalPackage) {
    545                 BuildHelper helper = new BuildHelper(project,
    546                         mOutStream, mErrStream,
    547                         true /*debugMode*/,
    548                         AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE,
    549                         mResourceMarker);
    550 
    551                 // resource to the AndroidManifest.xml file
    552                 IFile manifestFile = project.getFile(SdkConstants.FN_ANDROID_MANIFEST_XML);
    553 
    554                 if (manifestFile == null || manifestFile.exists() == false) {
    555                     // mark project and exit
    556                     String msg = String.format(Messages.s_File_Missing,
    557                             SdkConstants.FN_ANDROID_MANIFEST_XML);
    558                     markProject(AdtConstants.MARKER_PACKAGING, msg, IMarker.SEVERITY_ERROR);
    559                     return allRefProjects;
    560                 }
    561 
    562                 IPath androidBinLocation = androidOutputFolder.getLocation();
    563                 if (androidBinLocation == null) {
    564                     markProject(AdtConstants.MARKER_PACKAGING, Messages.Output_Missing,
    565                             IMarker.SEVERITY_ERROR);
    566                     return allRefProjects;
    567                 }
    568                 String osAndroidBinPath = androidBinLocation.toOSString();
    569 
    570                 // Remove the old .apk.
    571                 // This make sure that if the apk is corrupted, then dx (which would attempt
    572                 // to open it), will not fail.
    573                 String osFinalPackagePath = osAndroidBinPath + File.separator + finalPackageName;
    574                 File finalPackage = new File(osFinalPackagePath);
    575 
    576                 // if delete failed, this is not really a problem, as the final package generation
    577                 // handle already present .apk, and if that one failed as well, the user will be
    578                 // notified.
    579                 finalPackage.delete();
    580 
    581                 // Check if we need to package the resources.
    582                 if (mPackageResources) {
    583                     // also update the crunch cache always since aapt does it smartly only
    584                     // on the files that need it.
    585                     if (DEBUG) {
    586                         System.out.println("\trunning crunch!");
    587                     }
    588                     if (updateCrunchCache(project, helper) == false) {
    589                         return allRefProjects;
    590                     }
    591 
    592                     // refresh recursively bin/res folder
    593                     resOutputFolder.refreshLocal(IResource.DEPTH_INFINITE, monitor);
    594 
    595                     if (DEBUG) {
    596                         System.out.println("\tpackaging resources!");
    597                     }
    598                     // remove some aapt_package only markers.
    599                     removeMarkersFromContainer(project, AdtConstants.MARKER_AAPT_PACKAGE);
    600 
    601                     try {
    602                         helper.packageResources(manifestFile, libProjects, null /*resfilter*/,
    603                                 0 /*versionCode */, osAndroidBinPath,
    604                                 AdtConstants.FN_RESOURCES_AP_);
    605                     } catch (AaptExecException e) {
    606                         BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING,
    607                                 e.getMessage(), IMarker.SEVERITY_ERROR);
    608                         return allRefProjects;
    609                     } catch (AaptResultException e) {
    610                         // attempt to parse the error output
    611                         String[] aaptOutput = e.getOutput();
    612                         boolean parsingError = AaptParser.parseOutput(aaptOutput, project);
    613 
    614                         // if we couldn't parse the output we display it in the console.
    615                         if (parsingError) {
    616                             AdtPlugin.printErrorToConsole(project, (Object[]) aaptOutput);
    617 
    618                             // if the exec failed, and we couldn't parse the error output (and
    619                             // therefore not all files that should have been marked, were marked),
    620                             // we put a generic marker on the project and abort.
    621                             BaseProjectHelper.markResource(project,
    622                                     AdtConstants.MARKER_PACKAGING,
    623                                     Messages.Unparsed_AAPT_Errors,
    624                                     IMarker.SEVERITY_ERROR);
    625                         }
    626                     }
    627 
    628                     // build has been done. reset the state of the builder
    629                     mPackageResources = false;
    630 
    631                     // and store it
    632                     saveProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, mPackageResources);
    633                 }
    634 
    635                 String classesDexPath = osAndroidBinPath + File.separator +
    636                         SdkConstants.FN_APK_CLASSES_DEX;
    637 
    638                 // then we check if we need to package the .class into classes.dex
    639                 if (mConvertToDex) {
    640                     if (DEBUG) {
    641                         System.out.println("\trunning dex!");
    642                     }
    643                     try {
    644                         Collection<String> dxInputPaths = helper.getCompiledCodePaths();
    645 
    646                         helper.executeDx(javaProject, dxInputPaths, classesDexPath);
    647                     } catch (DexException e) {
    648                         String message = e.getMessage();
    649 
    650                         AdtPlugin.printErrorToConsole(project, message);
    651                         BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING,
    652                                 message, IMarker.SEVERITY_ERROR);
    653 
    654                         Throwable cause = e.getCause();
    655 
    656                         if (cause instanceof NoClassDefFoundError
    657                                 || cause instanceof NoSuchMethodError) {
    658                             AdtPlugin.printErrorToConsole(project, Messages.Incompatible_VM_Warning,
    659                                     Messages.Requires_1_5_Error);
    660                         }
    661 
    662                         // dx failed, we return
    663                         return allRefProjects;
    664                     }
    665 
    666                     // build has been done. reset the state of the builder
    667                     mConvertToDex = false;
    668 
    669                     // and store it
    670                     saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex);
    671                 }
    672 
    673                 // now we need to make the final package from the intermediary apk
    674                 // and classes.dex.
    675                 // This is the default package with all the resources.
    676 
    677                 try {
    678                     if (DEBUG) {
    679                         System.out.println("\tmaking final package!");
    680                     }
    681                     helper.finalDebugPackage(
    682                             osAndroidBinPath + File.separator + AdtConstants.FN_RESOURCES_AP_,
    683                         classesDexPath, osFinalPackagePath, libProjects, mResourceMarker);
    684                 } catch (KeytoolException e) {
    685                     String eMessage = e.getMessage();
    686 
    687                     // mark the project with the standard message
    688                     String msg = String.format(Messages.Final_Archive_Error_s, eMessage);
    689                     BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg,
    690                             IMarker.SEVERITY_ERROR);
    691 
    692                     // output more info in the console
    693                     AdtPlugin.printErrorToConsole(project,
    694                             msg,
    695                             String.format(Messages.ApkBuilder_JAVA_HOME_is_s, e.getJavaHome()),
    696                             Messages.ApkBuilder_Update_or_Execute_manually_s,
    697                             e.getCommandLine());
    698 
    699                     return allRefProjects;
    700                 } catch (ApkCreationException e) {
    701                     String eMessage = e.getMessage();
    702 
    703                     // mark the project with the standard message
    704                     String msg = String.format(Messages.Final_Archive_Error_s, eMessage);
    705                     BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg,
    706                             IMarker.SEVERITY_ERROR);
    707                 } catch (AndroidLocationException e) {
    708                     String eMessage = e.getMessage();
    709 
    710                     // mark the project with the standard message
    711                     String msg = String.format(Messages.Final_Archive_Error_s, eMessage);
    712                     BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg,
    713                             IMarker.SEVERITY_ERROR);
    714                 } catch (NativeLibInJarException e) {
    715                     String msg = e.getMessage();
    716 
    717                     BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING,
    718                             msg, IMarker.SEVERITY_ERROR);
    719 
    720                     AdtPlugin.printErrorToConsole(project, (Object[]) e.getAdditionalInfo());
    721                 } catch (CoreException e) {
    722                     // mark project and return
    723                     String msg = String.format(Messages.Final_Archive_Error_s, e.getMessage());
    724                     AdtPlugin.printErrorToConsole(project, msg);
    725                     BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg,
    726                             IMarker.SEVERITY_ERROR);
    727                 } catch (DuplicateFileException e) {
    728                     String msg1 = String.format(
    729                             "Found duplicate file for APK: %1$s\nOrigin 1: %2$s\nOrigin 2: %3$s",
    730                             e.getArchivePath(), e.getFile1(), e.getFile2());
    731                     String msg2 = String.format(Messages.Final_Archive_Error_s, msg1);
    732                     AdtPlugin.printErrorToConsole(project, msg2);
    733                     BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg2,
    734                             IMarker.SEVERITY_ERROR);
    735                 }
    736 
    737                 // we are done.
    738 
    739                 // refresh the bin folder content with no recursion.
    740                 androidOutputFolder.refreshLocal(IResource.DEPTH_ONE, monitor);
    741 
    742                 // build has been done. reset the state of the builder
    743                 mBuildFinalPackage = false;
    744 
    745                 // and store it
    746                 saveProjectBooleanProperty(PROPERTY_BUILD_APK, mBuildFinalPackage);
    747 
    748                 // reset the installation manager to force new installs of this project
    749                 ApkInstallManager.getInstance().resetInstallationFor(project);
    750 
    751                 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, getProject(),
    752                         "Build Success!");
    753             }
    754         } catch (AbortBuildException e) {
    755             return allRefProjects;
    756         } catch (Exception exception) {
    757             // try to catch other exception to actually display an error. This will be useful
    758             // if we get an NPE or something so that we can at least notify the user that something
    759             // went wrong.
    760 
    761             // first check if this is a CoreException we threw to cancel the build.
    762             if (exception instanceof CoreException) {
    763                 if (((CoreException)exception).getStatus().getSeverity() == IStatus.CANCEL) {
    764                     // Project is already marked with an error. Nothing to do
    765                     return allRefProjects;
    766                 }
    767             }
    768 
    769             String msg = exception.getMessage();
    770             if (msg == null) {
    771                 msg = exception.getClass().getCanonicalName();
    772             }
    773 
    774             msg = String.format("Unknown error: %1$s", msg);
    775             AdtPlugin.logAndPrintError(exception, project.getName(), msg);
    776             markProject(AdtConstants.MARKER_PACKAGING, msg, IMarker.SEVERITY_ERROR);
    777         }
    778 
    779         // Benchmarking end
    780         if (BuildHelper.BENCHMARK_FLAG) {
    781             String msg = "BENCHMARK ADT: Ending PostCompilation. \n BENCHMARK ADT: Time Elapsed: " + //$NON-NLS-1$
    782                          ((System.nanoTime() - startBuildTime)/Math.pow(10, 6)) + "ms";              //$NON-NLS-1$
    783             AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, project, msg);
    784             // End Overall Timer
    785             msg = "BENCHMARK ADT: Done with everything! \n BENCHMARK ADT: Time Elapsed: " +          //$NON-NLS-1$
    786                   (System.nanoTime() - BuildHelper.sStartOverallTime)/Math.pow(10, 6) + "ms";        //$NON-NLS-1$
    787             AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, project, msg);
    788         }
    789 
    790         return allRefProjects;
    791     }
    792 
    793     private static class JarBuilder implements IArchiveBuilder {
    794 
    795         private static Pattern R_PATTERN = Pattern.compile("R(\\$.*)?\\.class"); //$NON-NLS-1$
    796         private static Pattern MANIFEST_PATTERN = Pattern.compile("Manifest(\\$.*)?\\.class"); //$NON-NLS-1$
    797         private static String BUILD_CONFIG_CLASS = "BuildConfig.class"; //$NON-NLS-1$
    798 
    799         private final byte[] buffer = new byte[1024];
    800         private final JarOutputStream mOutputStream;
    801         private final String mAppPackage;
    802 
    803         JarBuilder(JarOutputStream outputStream, String appPackage) {
    804             mOutputStream = outputStream;
    805             mAppPackage = appPackage.replace('.', '/');
    806         }
    807 
    808         public void addFile(IFile file, IFolder rootFolder) throws ApkCreationException {
    809             // we only package class file from the output folder
    810             if (AdtConstants.EXT_CLASS.equals(file.getFileExtension()) == false) {
    811                 return;
    812             }
    813 
    814             IPath packageApp = file.getParent().getFullPath().makeRelativeTo(
    815                     rootFolder.getFullPath());
    816 
    817             String name = file.getName();
    818             // Ignore the library's R/Manifest/BuildConfig classes.
    819             if (mAppPackage.equals(packageApp.toString()) &&
    820                             (BUILD_CONFIG_CLASS.equals(name) ||
    821                             MANIFEST_PATTERN.matcher(name).matches() ||
    822                             R_PATTERN.matcher(name).matches())) {
    823                 return;
    824             }
    825 
    826             IPath path = file.getFullPath().makeRelativeTo(rootFolder.getFullPath());
    827             try {
    828                 addFile(file.getContents(), file.getLocalTimeStamp(), path.toString());
    829             } catch (ApkCreationException e) {
    830                 throw e;
    831             } catch (Exception e) {
    832                 throw new ApkCreationException(e, "Failed to add %s", file);
    833             }
    834         }
    835 
    836         @Override
    837         public void addFile(File file, String archivePath) throws ApkCreationException,
    838                 SealedApkException, DuplicateFileException {
    839             try {
    840                 FileInputStream inputStream = new FileInputStream(file);
    841                 long lastModified = file.lastModified();
    842                 addFile(inputStream, lastModified, archivePath);
    843             } catch (ApkCreationException e) {
    844                 throw e;
    845             } catch (Exception e) {
    846                 throw new ApkCreationException(e, "Failed to add %s", file);
    847             }
    848         }
    849 
    850         private void addFile(InputStream content, long lastModified, String archivePath)
    851                 throws IOException, ApkCreationException {
    852             // create the jar entry
    853             JarEntry entry = new JarEntry(archivePath);
    854             entry.setTime(lastModified);
    855 
    856             try {
    857                 // add the entry to the jar archive
    858                 mOutputStream.putNextEntry(entry);
    859 
    860                 // read the content of the entry from the input stream, and write
    861                 // it into the archive.
    862                 int count;
    863                 while ((count = content.read(buffer)) != -1) {
    864                     mOutputStream.write(buffer, 0, count);
    865                 }
    866             } finally {
    867                 try {
    868                     if (content != null) {
    869                         content.close();
    870                     }
    871                 } catch (Exception e) {
    872                     throw new ApkCreationException(e, "Failed to close stream");
    873                 }
    874             }
    875         }
    876     }
    877 
    878     /**
    879      * Updates the crunch cache if needed and return true if the build must continue.
    880      */
    881     private boolean updateCrunchCache(IProject project, BuildHelper helper) {
    882         try {
    883             helper.updateCrunchCache();
    884         } catch (AaptExecException e) {
    885             BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING,
    886                     e.getMessage(), IMarker.SEVERITY_ERROR);
    887             return false;
    888         } catch (AaptResultException e) {
    889             // attempt to parse the error output
    890             String[] aaptOutput = e.getOutput();
    891             boolean parsingError = AaptParser.parseOutput(aaptOutput, project);
    892             // if we couldn't parse the output we display it in the console.
    893             if (parsingError) {
    894                 AdtPlugin.printErrorToConsole(project, (Object[]) aaptOutput);
    895             }
    896         }
    897 
    898         return true;
    899     }
    900 
    901     /**
    902      * Writes the library jar file.
    903      * @param jarIFile the destination file
    904      * @param project the library project
    905      * @param appPackage the library android package
    906      * @param javaOutputFolder the JDT output folder.
    907      */
    908     private void writeLibraryPackage(IFile jarIFile, IProject project, String appPackage,
    909             IFolder javaOutputFolder) {
    910 
    911         JarOutputStream jos = null;
    912         try {
    913             Manifest manifest = new Manifest();
    914             Attributes mainAttributes = manifest.getMainAttributes();
    915             mainAttributes.put(Attributes.Name.CLASS_PATH, "Android ADT"); //$NON-NLS-1$
    916             mainAttributes.putValue("Created-By", "1.0 (Android)"); //$NON-NLS-1$  //$NON-NLS-2$
    917             jos = new JarOutputStream(
    918                     new FileOutputStream(jarIFile.getLocation().toFile()), manifest);
    919 
    920             JarBuilder jarBuilder = new JarBuilder(jos, appPackage);
    921 
    922             // write the class files
    923             writeClassFilesIntoJar(jarBuilder, javaOutputFolder, javaOutputFolder);
    924 
    925             // now write the standard Java resources from the output folder
    926             ApkBuilder.addSourceFolder(jarBuilder, javaOutputFolder.getLocation().toFile());
    927 
    928             saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex);
    929         } catch (Exception e) {
    930             AdtPlugin.log(e, "Failed to write jar file %s", jarIFile.getLocation().toOSString());
    931         } finally {
    932             if (jos != null) {
    933                 try {
    934                     jos.close();
    935                 } catch (IOException e) {
    936                     // pass
    937                 }
    938             }
    939         }
    940     }
    941 
    942     private void writeClassFilesIntoJar(JarBuilder builder, IFolder folder, IFolder rootFolder)
    943             throws CoreException, IOException, ApkCreationException {
    944         IResource[] members = folder.members();
    945         for (IResource member : members) {
    946             if (member.getType() == IResource.FOLDER) {
    947                 writeClassFilesIntoJar(builder, (IFolder) member, rootFolder);
    948             } else if (member.getType() == IResource.FILE) {
    949                 IFile file = (IFile) member;
    950                 builder.addFile(file, rootFolder);
    951             }
    952         }
    953     }
    954 
    955     @Override
    956     protected void startupOnInitialize() {
    957         super.startupOnInitialize();
    958 
    959         // load the build status. We pass true as the default value to
    960         // force a recompile in case the property was not found
    961         mConvertToDex = loadProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, true);
    962         mPackageResources = loadProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, true);
    963         mBuildFinalPackage = loadProjectBooleanProperty(PROPERTY_BUILD_APK, true);
    964     }
    965 
    966     @Override
    967     protected void abortOnBadSetup(IJavaProject javaProject) throws AbortBuildException {
    968         super.abortOnBadSetup(javaProject);
    969 
    970         IProject iProject = getProject();
    971 
    972         // do a (hopefully quick) search for Precompiler type markers. Those are always only
    973         // errors.
    974         stopOnMarker(iProject, AdtConstants.MARKER_AAPT_COMPILE, IResource.DEPTH_INFINITE,
    975                 false /*checkSeverity*/);
    976         stopOnMarker(iProject, AdtConstants.MARKER_AIDL, IResource.DEPTH_INFINITE,
    977                 false /*checkSeverity*/);
    978         stopOnMarker(iProject, AdtConstants.MARKER_RENDERSCRIPT, IResource.DEPTH_INFINITE,
    979                 false /*checkSeverity*/);
    980         stopOnMarker(iProject, AdtConstants.MARKER_ANDROID, IResource.DEPTH_ZERO,
    981                 false /*checkSeverity*/);
    982 
    983         // do a search for JDT markers. Those can be errors or warnings
    984         stopOnMarker(iProject, IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER,
    985                 IResource.DEPTH_INFINITE, true /*checkSeverity*/);
    986         stopOnMarker(iProject, IJavaModelMarker.BUILDPATH_PROBLEM_MARKER,
    987                 IResource.DEPTH_INFINITE, true /*checkSeverity*/);
    988     }
    989 }
    990