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(
    375                         projectState,
    376                         mBuildToolInfo,
    377                         mOutStream, mErrStream,
    378                         false /*jumbo mode doesn't matter here*/,
    379                         false /*dex merger doesn't matter here*/,
    380                         true /*debugMode*/,
    381                         AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE,
    382                         mResourceMarker);
    383                 updateCrunchCache(project, helper);
    384 
    385                 // refresh recursively bin/res folder
    386                 resOutputFolder.refreshLocal(IResource.DEPTH_INFINITE, monitor);
    387 
    388                 if (mConvertToDex) { // in this case this means some class files changed and
    389                                      // we need to update the jar file.
    390                     if (DEBUG_LOG) {
    391                         AdtPlugin.log(IStatus.INFO, "%s updating jar!", project.getName());
    392                     }
    393 
    394                     // resource to the AndroidManifest.xml file
    395                     IFile manifestFile = project.getFile(SdkConstants.FN_ANDROID_MANIFEST_XML);
    396                     String appPackage = AndroidManifest.getPackage(new IFileWrapper(manifestFile));
    397 
    398                     IFolder javaOutputFolder = BaseProjectHelper.getJavaOutputFolder(project);
    399 
    400                     writeLibraryPackage(jarIFile, project, appPackage, javaOutputFolder);
    401                     saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex = false);
    402 
    403                     // refresh the bin folder content with no recursion to update the library
    404                     // jar file.
    405                     androidOutputFolder.refreshLocal(IResource.DEPTH_ONE, monitor);
    406 
    407                     // Also update the projects. The only way to force recompile them is to
    408                     // reset the library container.
    409                     List<ProjectState> parentProjects = projectState.getParentProjects();
    410                     LibraryClasspathContainerInitializer.updateProject(parentProjects);
    411                 }
    412 
    413                 return allRefProjects;
    414             }
    415 
    416             // Check to see if we're going to launch or export. If not, we can skip
    417             // the packaging and dexing process.
    418             if (!args.containsKey(POST_C_REQUESTED)
    419                     && AdtPrefs.getPrefs().getBuildSkipPostCompileOnFileSave()) {
    420                 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project,
    421                         Messages.Skip_Post_Compiler);
    422                 return allRefProjects;
    423             } else {
    424                 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project,
    425                         Messages.Start_Full_Post_Compiler);
    426             }
    427 
    428             // first thing we do is check that the SDK directory has been setup.
    429             String osSdkFolder = AdtPlugin.getOsSdkFolder();
    430 
    431             if (osSdkFolder.length() == 0) {
    432                 // this has already been checked in the precompiler. Therefore,
    433                 // while we do have to cancel the build, we don't have to return
    434                 // any error or throw anything.
    435                 return allRefProjects;
    436             }
    437 
    438             // do some extra check, in case the output files are not present. This
    439             // will force to recreate them.
    440             IResource tmp = null;
    441 
    442             if (mPackageResources == false) {
    443                 // check the full resource package
    444                 tmp = androidOutputFolder.findMember(AdtConstants.FN_RESOURCES_AP_);
    445                 if (tmp == null || tmp.exists() == false) {
    446                     mPackageResources = true;
    447                 }
    448             }
    449 
    450             // check classes.dex is present. If not we force to recreate it.
    451             if (mConvertToDex == false) {
    452                 tmp = androidOutputFolder.findMember(SdkConstants.FN_APK_CLASSES_DEX);
    453                 if (tmp == null || tmp.exists() == false) {
    454                     mConvertToDex = true;
    455                 }
    456             }
    457 
    458             // also check the final file(s)!
    459             String finalPackageName = ProjectHelper.getApkFilename(project, null /*config*/);
    460             if (mBuildFinalPackage == false) {
    461                 tmp = androidOutputFolder.findMember(finalPackageName);
    462                 if (tmp == null || (tmp instanceof IFile &&
    463                         tmp.exists() == false)) {
    464                     String msg = String.format(Messages.s_Missing_Repackaging, finalPackageName);
    465                     AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, msg);
    466                     mBuildFinalPackage = true;
    467                 }
    468             }
    469 
    470             // at this point we know if we need to recreate the temporary apk
    471             // or the dex file, but we don't know if we simply need to recreate them
    472             // because they are missing
    473 
    474             // refresh the output directory first
    475             IContainer ic = androidOutputFolder.getParent();
    476             if (ic != null) {
    477                 ic.refreshLocal(IResource.DEPTH_ONE, monitor);
    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                 String forceJumboStr = projectState.getProperty(
    484                         AdtConstants.DEX_OPTIONS_FORCEJUMBO);
    485                 Boolean jumbo = Boolean.valueOf(forceJumboStr);
    486 
    487                 String dexMergerStr = projectState.getProperty(
    488                         AdtConstants.DEX_OPTIONS_DISABLE_MERGER);
    489                 Boolean dexMerger = Boolean.valueOf(dexMergerStr);
    490 
    491                 BuildHelper helper = new BuildHelper(
    492                         projectState,
    493                         mBuildToolInfo,
    494                         mOutStream, mErrStream,
    495                         jumbo.booleanValue(),
    496                         dexMerger.booleanValue(),
    497                         true /*debugMode*/,
    498                         AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE,
    499                         mResourceMarker);
    500 
    501                 IPath androidBinLocation = androidOutputFolder.getLocation();
    502                 if (androidBinLocation == null) {
    503                     markProject(AdtConstants.MARKER_PACKAGING, Messages.Output_Missing,
    504                             IMarker.SEVERITY_ERROR);
    505                     return allRefProjects;
    506                 }
    507                 String osAndroidBinPath = androidBinLocation.toOSString();
    508 
    509                 // resource to the AndroidManifest.xml file
    510                 IFile manifestFile = androidOutputFolder.getFile(
    511                         SdkConstants.FN_ANDROID_MANIFEST_XML);
    512 
    513                 if (manifestFile == null || manifestFile.exists() == false) {
    514                     // mark project and exit
    515                     String msg = String.format(Messages.s_File_Missing,
    516                             SdkConstants.FN_ANDROID_MANIFEST_XML);
    517                     markProject(AdtConstants.MARKER_PACKAGING, msg, IMarker.SEVERITY_ERROR);
    518                     return allRefProjects;
    519                 }
    520 
    521                 // Remove the old .apk.
    522                 // This make sure that if the apk is corrupted, then dx (which would attempt
    523                 // to open it), will not fail.
    524                 String osFinalPackagePath = osAndroidBinPath + File.separator + finalPackageName;
    525                 File finalPackage = new File(osFinalPackagePath);
    526 
    527                 // if delete failed, this is not really a problem, as the final package generation
    528                 // handle already present .apk, and if that one failed as well, the user will be
    529                 // notified.
    530                 finalPackage.delete();
    531 
    532                 // Check if we need to package the resources.
    533                 if (mPackageResources) {
    534                     // also update the crunch cache always since aapt does it smartly only
    535                     // on the files that need it.
    536                     if (DEBUG_LOG) {
    537                         AdtPlugin.log(IStatus.INFO, "%s running crunch!", project.getName());
    538                     }
    539                     if (updateCrunchCache(project, helper) == false) {
    540                         return allRefProjects;
    541                     }
    542 
    543                     // refresh recursively bin/res folder
    544                     resOutputFolder.refreshLocal(IResource.DEPTH_INFINITE, monitor);
    545 
    546                     if (DEBUG_LOG) {
    547                         AdtPlugin.log(IStatus.INFO, "%s packaging resources!", project.getName());
    548                     }
    549                     // remove some aapt_package only markers.
    550                     removeMarkersFromContainer(project, AdtConstants.MARKER_AAPT_PACKAGE);
    551 
    552                     try {
    553                         helper.packageResources(manifestFile, libProjects, null /*resfilter*/,
    554                                 0 /*versionCode */, osAndroidBinPath,
    555                                 AdtConstants.FN_RESOURCES_AP_);
    556                     } catch (AaptExecException e) {
    557                         BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING,
    558                                 e.getMessage(), IMarker.SEVERITY_ERROR);
    559                         return allRefProjects;
    560                     } catch (AaptResultException e) {
    561                         // attempt to parse the error output
    562                         String[] aaptOutput = e.getOutput();
    563                         boolean parsingError = AaptParser.parseOutput(aaptOutput, project);
    564 
    565                         // if we couldn't parse the output we display it in the console.
    566                         if (parsingError) {
    567                             AdtPlugin.printErrorToConsole(project, (Object[]) aaptOutput);
    568 
    569                             // if the exec failed, and we couldn't parse the error output (and
    570                             // therefore not all files that should have been marked, were marked),
    571                             // we put a generic marker on the project and abort.
    572                             BaseProjectHelper.markResource(project,
    573                                     AdtConstants.MARKER_PACKAGING,
    574                                     Messages.Unparsed_AAPT_Errors,
    575                                     IMarker.SEVERITY_ERROR);
    576                         }
    577                     }
    578 
    579                     // build has been done. reset the state of the builder
    580                     mPackageResources = false;
    581 
    582                     // and store it
    583                     saveProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, mPackageResources);
    584                 }
    585 
    586                 String classesDexPath = osAndroidBinPath + File.separator +
    587                         SdkConstants.FN_APK_CLASSES_DEX;
    588 
    589                 // then we check if we need to package the .class into classes.dex
    590                 if (mConvertToDex) {
    591                     if (DEBUG_LOG) {
    592                         AdtPlugin.log(IStatus.INFO, "%s running dex!", project.getName());
    593                     }
    594                     try {
    595                         Collection<String> dxInputPaths = helper.getCompiledCodePaths();
    596 
    597                         helper.executeDx(javaProject, dxInputPaths, classesDexPath);
    598                     } catch (DexException e) {
    599                         String message = e.getMessage();
    600 
    601                         AdtPlugin.printErrorToConsole(project, message);
    602                         BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING,
    603                                 message, IMarker.SEVERITY_ERROR);
    604 
    605                         Throwable cause = e.getCause();
    606 
    607                         if (cause instanceof NoClassDefFoundError
    608                                 || cause instanceof NoSuchMethodError) {
    609                             AdtPlugin.printErrorToConsole(project, Messages.Incompatible_VM_Warning,
    610                                     Messages.Requires_1_5_Error);
    611                         }
    612 
    613                         // dx failed, we return
    614                         return allRefProjects;
    615                     }
    616 
    617                     // build has been done. reset the state of the builder
    618                     mConvertToDex = false;
    619 
    620                     // and store it
    621                     saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex);
    622                 }
    623 
    624                 // now we need to make the final package from the intermediary apk
    625                 // and classes.dex.
    626                 // This is the default package with all the resources.
    627 
    628                 try {
    629                     if (DEBUG_LOG) {
    630                         AdtPlugin.log(IStatus.INFO, "%s making final package!", project.getName());
    631                     }
    632                     helper.finalDebugPackage(
    633                             osAndroidBinPath + File.separator + AdtConstants.FN_RESOURCES_AP_,
    634                         classesDexPath, osFinalPackagePath, libProjects, mResourceMarker);
    635                 } catch (KeytoolException e) {
    636                     String eMessage = e.getMessage();
    637 
    638                     // mark the project with the standard message
    639                     String msg = String.format(Messages.Final_Archive_Error_s, eMessage);
    640                     BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg,
    641                             IMarker.SEVERITY_ERROR);
    642 
    643                     // output more info in the console
    644                     AdtPlugin.printErrorToConsole(project,
    645                             msg,
    646                             String.format(Messages.ApkBuilder_JAVA_HOME_is_s, e.getJavaHome()),
    647                             Messages.ApkBuilder_Update_or_Execute_manually_s,
    648                             e.getCommandLine());
    649 
    650                     AdtPlugin.log(e, msg);
    651 
    652                     return allRefProjects;
    653                 } catch (ApkCreationException e) {
    654                     String eMessage = e.getMessage();
    655 
    656                     // mark the project with the standard message
    657                     String msg = String.format(Messages.Final_Archive_Error_s, eMessage);
    658                     BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg,
    659                             IMarker.SEVERITY_ERROR);
    660 
    661                     AdtPlugin.log(e, msg);
    662                 } catch (AndroidLocationException e) {
    663                     String eMessage = e.getMessage();
    664 
    665                     // mark the project with the standard message
    666                     String msg = String.format(Messages.Final_Archive_Error_s, eMessage);
    667                     BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg,
    668                             IMarker.SEVERITY_ERROR);
    669                     AdtPlugin.log(e, msg);
    670                 } catch (NativeLibInJarException e) {
    671                     String msg = e.getMessage();
    672 
    673                     BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING,
    674                             msg, IMarker.SEVERITY_ERROR);
    675 
    676                     AdtPlugin.printErrorToConsole(project, (Object[]) e.getAdditionalInfo());
    677                 } catch (CoreException e) {
    678                     // mark project and return
    679                     String msg = String.format(Messages.Final_Archive_Error_s, e.getMessage());
    680                     AdtPlugin.printErrorToConsole(project, msg);
    681                     BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg,
    682                             IMarker.SEVERITY_ERROR);
    683                     AdtPlugin.log(e, msg);
    684                 } catch (DuplicateFileException e) {
    685                     String msg1 = String.format(
    686                             "Found duplicate file for APK: %1$s\nOrigin 1: %2$s\nOrigin 2: %3$s",
    687                             e.getArchivePath(), e.getFile1(), e.getFile2());
    688                     String msg2 = String.format(Messages.Final_Archive_Error_s, msg1);
    689                     AdtPlugin.printErrorToConsole(project, msg2);
    690                     BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg2,
    691                             IMarker.SEVERITY_ERROR);
    692                 }
    693 
    694                 // we are done.
    695 
    696                 // refresh the bin folder content with no recursion.
    697                 androidOutputFolder.refreshLocal(IResource.DEPTH_ONE, monitor);
    698 
    699                 // build has been done. reset the state of the builder
    700                 mBuildFinalPackage = false;
    701 
    702                 // and store it
    703                 saveProjectBooleanProperty(PROPERTY_BUILD_APK, mBuildFinalPackage);
    704 
    705                 // reset the installation manager to force new installs of this project
    706                 ApkInstallManager.getInstance().resetInstallationFor(project);
    707 
    708                 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, getProject(),
    709                         "Build Success!");
    710             }
    711         } catch (AbortBuildException e) {
    712             return allRefProjects;
    713         } catch (Exception exception) {
    714             // try to catch other exception to actually display an error. This will be useful
    715             // if we get an NPE or something so that we can at least notify the user that something
    716             // went wrong.
    717 
    718             // first check if this is a CoreException we threw to cancel the build.
    719             if (exception instanceof CoreException) {
    720                 if (((CoreException)exception).getStatus().getSeverity() == IStatus.CANCEL) {
    721                     // Project is already marked with an error. Nothing to do
    722                     return allRefProjects;
    723                 }
    724             }
    725 
    726             String msg = exception.getMessage();
    727             if (msg == null) {
    728                 msg = exception.getClass().getCanonicalName();
    729             }
    730 
    731             msg = String.format("Unknown error: %1$s", msg);
    732             AdtPlugin.logAndPrintError(exception, project.getName(), msg);
    733             markProject(AdtConstants.MARKER_PACKAGING, msg, IMarker.SEVERITY_ERROR);
    734         }
    735 
    736         // Benchmarking end
    737         if (BuildHelper.BENCHMARK_FLAG) {
    738             String msg = "BENCHMARK ADT: Ending PostCompilation. \n BENCHMARK ADT: Time Elapsed: " + //$NON-NLS-1$
    739                          ((System.nanoTime() - startBuildTime)/Math.pow(10, 6)) + "ms";              //$NON-NLS-1$
    740             AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, project, msg);
    741             // End Overall Timer
    742             msg = "BENCHMARK ADT: Done with everything! \n BENCHMARK ADT: Time Elapsed: " +          //$NON-NLS-1$
    743                   (System.nanoTime() - BuildHelper.sStartOverallTime)/Math.pow(10, 6) + "ms";        //$NON-NLS-1$
    744             AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, project, msg);
    745         }
    746 
    747         return allRefProjects;
    748     }
    749 
    750     private static class JarBuilder implements IArchiveBuilder {
    751 
    752         private static Pattern R_PATTERN = Pattern.compile("R(\\$.*)?\\.class"); //$NON-NLS-1$
    753         private static String BUILD_CONFIG_CLASS = "BuildConfig.class"; //$NON-NLS-1$
    754 
    755         private final byte[] buffer = new byte[1024];
    756         private final JarOutputStream mOutputStream;
    757         private final String mAppPackage;
    758 
    759         JarBuilder(JarOutputStream outputStream, String appPackage) {
    760             mOutputStream = outputStream;
    761             mAppPackage = appPackage.replace('.', '/');
    762         }
    763 
    764         public void addFile(IFile file, IFolder rootFolder) throws ApkCreationException {
    765             // we only package class file from the output folder
    766             if (SdkConstants.EXT_CLASS.equals(file.getFileExtension()) == false) {
    767                 return;
    768             }
    769 
    770             IPath packageApp = file.getParent().getFullPath().makeRelativeTo(
    771                     rootFolder.getFullPath());
    772 
    773             String name = file.getName();
    774             // Ignore the library's R/Manifest/BuildConfig classes.
    775             if (mAppPackage.equals(packageApp.toString()) &&
    776                             (BUILD_CONFIG_CLASS.equals(name) ||
    777                             R_PATTERN.matcher(name).matches())) {
    778                 return;
    779             }
    780 
    781             IPath path = file.getFullPath().makeRelativeTo(rootFolder.getFullPath());
    782             try {
    783                 addFile(file.getContents(), file.getLocalTimeStamp(), path.toString());
    784             } catch (ApkCreationException e) {
    785                 throw e;
    786             } catch (Exception e) {
    787                 throw new ApkCreationException(e, "Failed to add %s", file);
    788             }
    789         }
    790 
    791         @Override
    792         public void addFile(File file, String archivePath) throws ApkCreationException,
    793                 SealedApkException, DuplicateFileException {
    794             try {
    795                 FileInputStream inputStream = new FileInputStream(file);
    796                 long lastModified = file.lastModified();
    797                 addFile(inputStream, lastModified, archivePath);
    798             } catch (ApkCreationException e) {
    799                 throw e;
    800             } catch (Exception e) {
    801                 throw new ApkCreationException(e, "Failed to add %s", file);
    802             }
    803         }
    804 
    805         private void addFile(InputStream content, long lastModified, String archivePath)
    806                 throws IOException, ApkCreationException {
    807             // create the jar entry
    808             JarEntry entry = new JarEntry(archivePath);
    809             entry.setTime(lastModified);
    810 
    811             try {
    812                 // add the entry to the jar archive
    813                 mOutputStream.putNextEntry(entry);
    814 
    815                 // read the content of the entry from the input stream, and write
    816                 // it into the archive.
    817                 int count;
    818                 while ((count = content.read(buffer)) != -1) {
    819                     mOutputStream.write(buffer, 0, count);
    820                 }
    821             } finally {
    822                 try {
    823                     if (content != null) {
    824                         content.close();
    825                     }
    826                 } catch (Exception e) {
    827                     throw new ApkCreationException(e, "Failed to close stream");
    828                 }
    829             }
    830         }
    831     }
    832 
    833     /**
    834      * Updates the crunch cache if needed and return true if the build must continue.
    835      */
    836     private boolean updateCrunchCache(IProject project, BuildHelper helper) {
    837         try {
    838             helper.updateCrunchCache();
    839         } catch (AaptExecException e) {
    840             BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING,
    841                     e.getMessage(), IMarker.SEVERITY_ERROR);
    842             return false;
    843         } catch (AaptResultException e) {
    844             // attempt to parse the error output
    845             String[] aaptOutput = e.getOutput();
    846             boolean parsingError = AaptParser.parseOutput(aaptOutput, project);
    847             // if we couldn't parse the output we display it in the console.
    848             if (parsingError) {
    849                 AdtPlugin.printErrorToConsole(project, (Object[]) aaptOutput);
    850             }
    851         }
    852 
    853         return true;
    854     }
    855 
    856     /**
    857      * Writes the library jar file.
    858      * @param jarIFile the destination file
    859      * @param project the library project
    860      * @param appPackage the library android package
    861      * @param javaOutputFolder the JDT output folder.
    862      */
    863     private void writeLibraryPackage(IFile jarIFile, IProject project, String appPackage,
    864             IFolder javaOutputFolder) {
    865 
    866         JarOutputStream jos = null;
    867         try {
    868             Manifest manifest = new Manifest();
    869             Attributes mainAttributes = manifest.getMainAttributes();
    870             mainAttributes.put(Attributes.Name.CLASS_PATH, "Android ADT"); //$NON-NLS-1$
    871             mainAttributes.putValue("Created-By", "1.0 (Android)"); //$NON-NLS-1$  //$NON-NLS-2$
    872             jos = new JarOutputStream(
    873                     new FileOutputStream(jarIFile.getLocation().toFile()), manifest);
    874 
    875             JarBuilder jarBuilder = new JarBuilder(jos, appPackage);
    876 
    877             // write the class files
    878             writeClassFilesIntoJar(jarBuilder, javaOutputFolder, javaOutputFolder);
    879 
    880             // now write the standard Java resources from the output folder
    881             ApkBuilder.addSourceFolder(jarBuilder, javaOutputFolder.getLocation().toFile());
    882 
    883             saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex);
    884         } catch (Exception e) {
    885             AdtPlugin.log(e, "Failed to write jar file %s", jarIFile.getLocation().toOSString());
    886         } finally {
    887             if (jos != null) {
    888                 try {
    889                     jos.close();
    890                 } catch (IOException e) {
    891                     // pass
    892                 }
    893             }
    894         }
    895     }
    896 
    897     private void writeClassFilesIntoJar(JarBuilder builder, IFolder folder, IFolder rootFolder)
    898             throws CoreException, IOException, ApkCreationException {
    899         IResource[] members = folder.members();
    900         for (IResource member : members) {
    901             if (member.getType() == IResource.FOLDER) {
    902                 writeClassFilesIntoJar(builder, (IFolder) member, rootFolder);
    903             } else if (member.getType() == IResource.FILE) {
    904                 IFile file = (IFile) member;
    905                 builder.addFile(file, rootFolder);
    906             }
    907         }
    908     }
    909 
    910     @Override
    911     protected void startupOnInitialize() {
    912         super.startupOnInitialize();
    913 
    914         // load the build status. We pass true as the default value to
    915         // force a recompile in case the property was not found
    916         mConvertToDex = loadProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, true);
    917         mPackageResources = loadProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, true);
    918         mBuildFinalPackage = loadProjectBooleanProperty(PROPERTY_BUILD_APK, true);
    919     }
    920 
    921     @Override
    922     protected void abortOnBadSetup(
    923             @NonNull IJavaProject javaProject,
    924             @Nullable ProjectState projectState) throws AbortBuildException, CoreException {
    925         super.abortOnBadSetup(javaProject, projectState);
    926 
    927         IProject iProject = getProject();
    928 
    929         // do a (hopefully quick) search for Precompiler type markers. Those are always only
    930         // errors.
    931         stopOnMarker(iProject, AdtConstants.MARKER_AAPT_COMPILE, IResource.DEPTH_INFINITE,
    932                 false /*checkSeverity*/);
    933         stopOnMarker(iProject, AdtConstants.MARKER_AIDL, IResource.DEPTH_INFINITE,
    934                 false /*checkSeverity*/);
    935         stopOnMarker(iProject, AdtConstants.MARKER_RENDERSCRIPT, IResource.DEPTH_INFINITE,
    936                 false /*checkSeverity*/);
    937         stopOnMarker(iProject, AdtConstants.MARKER_ANDROID, IResource.DEPTH_ZERO,
    938                 false /*checkSeverity*/);
    939 
    940         // do a search for JDT markers. Those can be errors or warnings
    941         stopOnMarker(iProject, IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER,
    942                 IResource.DEPTH_INFINITE, true /*checkSeverity*/);
    943         stopOnMarker(iProject, IJavaModelMarker.BUILDPATH_PROBLEM_MARKER,
    944                 IResource.DEPTH_INFINITE, true /*checkSeverity*/);
    945     }
    946 }
    947