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