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