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