Home | History | Annotate | Download | only in builders
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ide.eclipse.adt.internal.build.builders;
     18 
     19 import com.android.ide.eclipse.adt.AdtConstants;
     20 import com.android.ide.eclipse.adt.AdtPlugin;
     21 import com.android.ide.eclipse.adt.internal.build.AaptParser;
     22 import com.android.ide.eclipse.adt.internal.build.AidlProcessor;
     23 import com.android.ide.eclipse.adt.internal.build.Messages;
     24 import com.android.ide.eclipse.adt.internal.build.RenderScriptProcessor;
     25 import com.android.ide.eclipse.adt.internal.build.SourceProcessor;
     26 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
     27 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity;
     28 import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
     29 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
     30 import com.android.ide.eclipse.adt.internal.project.FixLaunchConfig;
     31 import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
     32 import com.android.ide.eclipse.adt.internal.project.XmlErrorHandler.BasicXmlErrorListener;
     33 import com.android.ide.eclipse.adt.internal.resources.manager.IdeScanningContext;
     34 import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
     35 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
     36 import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
     37 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
     38 import com.android.ide.eclipse.adt.io.IFileWrapper;
     39 import com.android.ide.eclipse.adt.io.IFolderWrapper;
     40 import com.android.io.StreamException;
     41 import com.android.sdklib.AndroidVersion;
     42 import com.android.sdklib.IAndroidTarget;
     43 import com.android.sdklib.SdkConstants;
     44 import com.android.sdklib.internal.build.BuildConfigGenerator;
     45 import com.android.sdklib.xml.AndroidManifest;
     46 import com.android.sdklib.xml.ManifestData;
     47 
     48 import org.eclipse.core.resources.IFile;
     49 import org.eclipse.core.resources.IFolder;
     50 import org.eclipse.core.resources.IMarker;
     51 import org.eclipse.core.resources.IProject;
     52 import org.eclipse.core.resources.IResource;
     53 import org.eclipse.core.resources.IResourceDelta;
     54 import org.eclipse.core.runtime.CoreException;
     55 import org.eclipse.core.runtime.IPath;
     56 import org.eclipse.core.runtime.IProgressMonitor;
     57 import org.eclipse.core.runtime.NullProgressMonitor;
     58 import org.eclipse.core.runtime.Path;
     59 import org.eclipse.jdt.core.IJavaProject;
     60 import org.eclipse.jdt.core.JavaCore;
     61 import org.xml.sax.SAXException;
     62 
     63 import java.io.File;
     64 import java.io.IOException;
     65 import java.util.ArrayList;
     66 import java.util.List;
     67 import java.util.Map;
     68 
     69 import javax.xml.parsers.ParserConfigurationException;
     70 
     71 /**
     72  * Pre Java Compiler.
     73  * This incremental builder performs 2 tasks:
     74  * <ul>
     75  * <li>compiles the resources located in the res/ folder, along with the
     76  * AndroidManifest.xml file into the R.java class.</li>
     77  * <li>compiles any .aidl files into a corresponding java file.</li>
     78  * </ul>
     79  *
     80  */
     81 public class PreCompilerBuilder extends BaseBuilder {
     82 
     83     /** This ID is used in plugin.xml and in each project's .project file.
     84      * It cannot be changed even if the class is renamed/moved */
     85     public static final String ID = "com.android.ide.eclipse.adt.PreCompilerBuilder"; //$NON-NLS-1$
     86 
     87     /** Flag to pass to PreCompiler builder that the build is a release build.
     88      */
     89     public final static String RELEASE_REQUESTED = "android.releaseBuild"; //$NON-NLS-1$
     90 
     91 
     92     private static final String PROPERTY_PACKAGE = "manifestPackage"; //$NON-NLS-1$
     93     private static final String PROPERTY_COMPILE_RESOURCES = "compileResources"; //$NON-NLS-1$
     94     private static final String PROPERTY_COMPILE_BUILDCONFIG = "createBuildConfig"; //$NON-NLS-1$
     95     private static final String PROPERTY_BUILDCONFIG_MODE = "buildConfigMode"; //$NON-NLS-1$
     96 
     97     /**
     98      * Resource Compile flag. This flag is reset to false after each successful compilation, and
     99      * stored in the project persistent properties. This allows the builder to remember its state
    100      * when the project is closed/opened.
    101      */
    102     private boolean mMustCompileResources = false;
    103     private boolean mMustCreateBuildConfig = false;
    104     private boolean mLastBuildConfigMode;
    105 
    106     private final List<SourceProcessor> mProcessors = new ArrayList<SourceProcessor>(2);
    107 
    108     /** cache of the java package defined in the manifest */
    109     private String mManifestPackage;
    110 
    111     /** Output folder for generated Java File. Created on the Builder init
    112      * @see #startupOnInitialize()
    113      */
    114     private IFolder mGenFolder;
    115 
    116     /**
    117      * Progress monitor used at the end of every build to refresh the content of the 'gen' folder
    118      * and set the generated files as derived.
    119      */
    120     private DerivedProgressMonitor mDerivedProgressMonitor;
    121 
    122 
    123     /**
    124      * Progress monitor waiting the end of the process to set a persistent value
    125      * in a file. This is typically used in conjunction with <code>IResource.refresh()</code>,
    126      * since this call is asynchronous, and we need to wait for it to finish for the file
    127      * to be known by eclipse, before we can call <code>resource.setPersistentProperty</code> on
    128      * a new file.
    129      */
    130     private static class DerivedProgressMonitor implements IProgressMonitor {
    131         private boolean mCancelled = false;
    132         private boolean mDone = false;
    133         private final IFolder mGenFolder;
    134 
    135         public DerivedProgressMonitor(IFolder genFolder) {
    136             mGenFolder = genFolder;
    137         }
    138 
    139         void reset() {
    140             mDone = false;
    141         }
    142 
    143         @Override
    144         public void beginTask(String name, int totalWork) {
    145         }
    146 
    147         @Override
    148         public void done() {
    149             if (mDone == false) {
    150                 mDone = true;
    151                 processChildrenOf(mGenFolder);
    152             }
    153         }
    154 
    155         private void processChildrenOf(IFolder folder) {
    156             IResource[] list;
    157             try {
    158                 list = folder.members();
    159             } catch (CoreException e) {
    160                 return;
    161             }
    162 
    163             for (IResource member : list) {
    164                 if (member.exists()) {
    165                     if (member.getType() == IResource.FOLDER) {
    166                         processChildrenOf((IFolder) member);
    167                     }
    168 
    169                     try {
    170                         member.setDerived(true, new NullProgressMonitor());
    171                     } catch (CoreException e) {
    172                         // This really shouldn't happen since we check that the resource
    173                         // exist.
    174                         // Worst case scenario, the resource isn't marked as derived.
    175                     }
    176                 }
    177             }
    178         }
    179 
    180         @Override
    181         public void internalWorked(double work) {
    182         }
    183 
    184         @Override
    185         public boolean isCanceled() {
    186             return mCancelled;
    187         }
    188 
    189         @Override
    190         public void setCanceled(boolean value) {
    191             mCancelled = value;
    192         }
    193 
    194         @Override
    195         public void setTaskName(String name) {
    196         }
    197 
    198         @Override
    199         public void subTask(String name) {
    200         }
    201 
    202         @Override
    203         public void worked(int work) {
    204         }
    205     }
    206 
    207     public PreCompilerBuilder() {
    208         super();
    209     }
    210 
    211     // build() returns a list of project from which this project depends for future compilation.
    212     @Override
    213     protected IProject[] build(
    214             int kind,
    215             @SuppressWarnings("rawtypes") Map args,
    216             IProgressMonitor monitor)
    217             throws CoreException {
    218         // get a project object
    219         IProject project = getProject();
    220 
    221         if (DEBUG) {
    222             System.out.println("BUILD(PRE) " + project.getName());
    223         }
    224 
    225         // For the PreCompiler, only the library projects are considered Referenced projects,
    226         // as only those projects have an impact on what is generated by this builder.
    227         IProject[] result = null;
    228 
    229         try {
    230             assert mDerivedProgressMonitor != null;
    231 
    232             mDerivedProgressMonitor.reset();
    233 
    234             // get the project info
    235             ProjectState projectState = Sdk.getProjectState(project);
    236 
    237             // this can happen if the project has no project.properties.
    238             if (projectState == null) {
    239                 return null;
    240             }
    241 
    242             IAndroidTarget projectTarget = projectState.getTarget();
    243 
    244             // get the libraries
    245             List<IProject> libProjects = projectState.getFullLibraryProjects();
    246             result = libProjects.toArray(new IProject[libProjects.size()]);
    247 
    248             IJavaProject javaProject = JavaCore.create(project);
    249 
    250             // Top level check to make sure the build can move forward.
    251             abortOnBadSetup(javaProject);
    252 
    253             // now we need to get the classpath list
    254             List<IPath> sourceFolderPathList = BaseProjectHelper.getSourceClasspaths(javaProject);
    255 
    256             PreCompilerDeltaVisitor dv = null;
    257             String javaPackage = null;
    258             String minSdkVersion = null;
    259 
    260             if (kind == FULL_BUILD) {
    261                 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project,
    262                         Messages.Start_Full_Pre_Compiler);
    263 
    264                 if (DEBUG) {
    265                     System.out.println("\tfull build!");
    266                 }
    267 
    268                 // do some clean up.
    269                 doClean(project, monitor);
    270 
    271                 mMustCompileResources = true;
    272                 mMustCreateBuildConfig = true;
    273 
    274                 for (SourceProcessor processor : mProcessors) {
    275                     processor.prepareFullBuild(project);
    276                 }
    277             } else {
    278                 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project,
    279                         Messages.Start_Inc_Pre_Compiler);
    280 
    281                 // Go through the resources and see if something changed.
    282                 // Even if the mCompileResources flag is true from a previously aborted
    283                 // build, we need to go through the Resource delta to get a possible
    284                 // list of aidl files to compile/remove.
    285                 IResourceDelta delta = getDelta(project);
    286                 if (delta == null) {
    287                     mMustCompileResources = true;
    288 
    289                     for (SourceProcessor processor : mProcessors) {
    290                         processor.prepareFullBuild(project);
    291                     }
    292                 } else {
    293                     dv = new PreCompilerDeltaVisitor(this, sourceFolderPathList, mProcessors);
    294                     delta.accept(dv);
    295 
    296                     // Check to see if Manifest.xml, Manifest.java, or R.java have changed:
    297                     mMustCompileResources |= dv.getCompileResources();
    298 
    299                     // Notify the ResourceManager:
    300                     ResourceManager resManager = ResourceManager.getInstance();
    301                     ProjectResources projectResources = resManager.getProjectResources(project);
    302 
    303                     if (ResourceManager.isAutoBuilding()) {
    304                         IdeScanningContext context = new IdeScanningContext(projectResources, project);
    305 
    306                         resManager.processDelta(delta, context);
    307 
    308                         // Check whether this project or its dependencies (libraries) have
    309                         // resources that need compilation
    310                         if (context.needsFullAapt()) {
    311                             mMustCompileResources = true;
    312 
    313                             // Must also call markAaptRequested on the project to not just
    314                             // store "aapt required" on this project, but also on any projects
    315                             // depending on this project if it's a library project
    316                             ResourceManager.markAaptRequested(project);
    317                         }
    318 
    319                         // Update error markers in the source editor
    320                         if (!mMustCompileResources) {
    321                             context.updateMarkers(false /* async */);
    322                         }
    323                     } // else: already processed the deltas in ResourceManager's IRawDeltaListener
    324 
    325                     for (SourceProcessor processor : mProcessors) {
    326                         processor.doneVisiting(project);
    327                     }
    328 
    329                     // get the java package from the visitor
    330                     javaPackage = dv.getManifestPackage();
    331                     minSdkVersion = dv.getMinSdkVersion();
    332                 }
    333             }
    334 
    335             // Has anyone marked this project as needing aapt? Typically done when
    336             // one of the library projects this project depends on has changed
    337             mMustCompileResources |= ResourceManager.isAaptRequested(project);
    338 
    339             // store the build status in the persistent storage
    340             saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES, mMustCompileResources);
    341             saveProjectBooleanProperty(PROPERTY_COMPILE_BUILDCONFIG, mMustCreateBuildConfig);
    342 
    343             // if there was some XML errors, we just return w/o doing
    344             // anything since we've put some markers in the files anyway.
    345             if (dv != null && dv.mXmlError) {
    346                 AdtPlugin.printErrorToConsole(project, Messages.Xml_Error);
    347 
    348                 return result;
    349             }
    350 
    351 
    352             // get the manifest file
    353             IFile manifestFile = ProjectHelper.getManifest(project);
    354 
    355             if (manifestFile == null) {
    356                 String msg = String.format(Messages.s_File_Missing,
    357                         SdkConstants.FN_ANDROID_MANIFEST_XML);
    358                 AdtPlugin.printErrorToConsole(project, msg);
    359                 markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
    360 
    361                 return result;
    362 
    363                 // TODO: document whether code below that uses manifest (which is now guaranteed
    364                 // to be null) will actually be executed or not.
    365             }
    366 
    367             // lets check the XML of the manifest first, if that hasn't been done by the
    368             // resource delta visitor yet.
    369             if (dv == null || dv.getCheckedManifestXml() == false) {
    370                 BasicXmlErrorListener errorListener = new BasicXmlErrorListener();
    371                 try {
    372                     ManifestData parser = AndroidManifestHelper.parseUnchecked(
    373                             new IFileWrapper(manifestFile),
    374                             true /*gather data*/,
    375                             errorListener);
    376 
    377                     if (errorListener.mHasXmlError == true) {
    378                         // There was an error in the manifest, its file has been marked
    379                         // by the XmlErrorHandler. The stopBuild() call below will abort
    380                         // this with an exception.
    381                         String msg = String.format(Messages.s_Contains_Xml_Error,
    382                                 SdkConstants.FN_ANDROID_MANIFEST_XML);
    383                         AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, msg);
    384                         markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
    385 
    386                         return result;
    387                     }
    388 
    389                     // Get the java package from the parser.
    390                     // This can be null if the parsing failed because the resource is out of sync,
    391                     // in which case the error will already have been logged anyway.
    392                     if (parser != null) {
    393                         javaPackage = parser.getPackage();
    394                         minSdkVersion = parser.getMinSdkVersionString();
    395                     }
    396                 } catch (StreamException e) {
    397                     handleStreamException(e);
    398 
    399                     return result;
    400                 } catch (ParserConfigurationException e) {
    401                     String msg = String.format(
    402                             "Bad parser configuration for %s: %s",
    403                             manifestFile.getFullPath(),
    404                             e.getMessage());
    405 
    406                     handleException(e, msg);
    407                     return result;
    408 
    409                 } catch (SAXException e) {
    410                     String msg = String.format(
    411                             "Parser exception for %s: %s",
    412                             manifestFile.getFullPath(),
    413                             e.getMessage());
    414 
    415                     handleException(e, msg);
    416                     return result;
    417                 } catch (IOException e) {
    418                     String msg = String.format(
    419                             "I/O error for %s: %s",
    420                             manifestFile.getFullPath(),
    421                             e.getMessage());
    422 
    423                     handleException(e, msg);
    424                     return result;
    425                 }
    426             }
    427 
    428             int minSdkValue = -1;
    429 
    430             if (minSdkVersion != null) {
    431                 try {
    432                     minSdkValue = Integer.parseInt(minSdkVersion);
    433                 } catch (NumberFormatException e) {
    434                     // it's ok, it means minSdkVersion contains a (hopefully) valid codename.
    435                 }
    436 
    437                 AndroidVersion targetVersion = projectTarget.getVersion();
    438 
    439                 // remove earlier marker from the manifest
    440                 removeMarkersFromResource(manifestFile, AdtConstants.MARKER_ADT);
    441 
    442                 if (minSdkValue != -1) {
    443                     String codename = targetVersion.getCodename();
    444                     if (codename != null) {
    445                         // integer minSdk when the target is a preview => fatal error
    446                         String msg = String.format(
    447                                 "Platform %1$s is a preview and requires application manifest to set %2$s to '%1$s'",
    448                                 codename, AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION);
    449                         AdtPlugin.printErrorToConsole(project, msg);
    450                         BaseProjectHelper.markResource(manifestFile, AdtConstants.MARKER_ADT,
    451                                 msg, IMarker.SEVERITY_ERROR);
    452                         return result;
    453                     } else if (minSdkValue > targetVersion.getApiLevel()) {
    454                         // integer minSdk is too high for the target => warning
    455                         String msg = String.format(
    456                                 "Attribute %1$s (%2$d) is higher than the project target API level (%3$d)",
    457                                 AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION,
    458                                 minSdkValue, targetVersion.getApiLevel());
    459                         AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, msg);
    460                         BaseProjectHelper.markResource(manifestFile, AdtConstants.MARKER_ADT,
    461                                 msg, IMarker.SEVERITY_WARNING);
    462                     }
    463                 } else {
    464                     // looks like the min sdk is a codename, check it matches the codename
    465                     // of the platform
    466                     String codename = targetVersion.getCodename();
    467                     if (codename == null) {
    468                         // platform is not a preview => fatal error
    469                         String msg = String.format(
    470                                 "Manifest attribute '%1$s' is set to '%2$s'. Integer is expected.",
    471                                 AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION, minSdkVersion);
    472                         AdtPlugin.printErrorToConsole(project, msg);
    473                         BaseProjectHelper.markResource(manifestFile, AdtConstants.MARKER_ADT,
    474                                 msg, IMarker.SEVERITY_ERROR);
    475                         return result;
    476                     } else if (codename.equals(minSdkVersion) == false) {
    477                         // platform and manifest codenames don't match => fatal error.
    478                         String msg = String.format(
    479                                 "Value of manifest attribute '%1$s' does not match platform codename '%2$s'",
    480                                 AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION, codename);
    481                         AdtPlugin.printErrorToConsole(project, msg);
    482                         BaseProjectHelper.markResource(manifestFile, AdtConstants.MARKER_ADT,
    483                                 msg, IMarker.SEVERITY_ERROR);
    484                         return result;
    485                     }
    486 
    487                     // if we get there, the minSdkVersion is a codename matching the target
    488                     // platform codename. In this case we set minSdkValue to the previous API
    489                     // level, as it's used by source processors.
    490                     minSdkValue = targetVersion.getApiLevel();
    491                 }
    492             } else if (projectTarget.getVersion().isPreview()) {
    493                 // else the minSdkVersion is not set but we are using a preview target.
    494                 // Display an error
    495                 String codename = projectTarget.getVersion().getCodename();
    496                 String msg = String.format(
    497                         "Platform %1$s is a preview and requires application manifests to set %2$s to '%1$s'",
    498                         codename, AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION);
    499                 AdtPlugin.printErrorToConsole(project, msg);
    500                 BaseProjectHelper.markResource(manifestFile, AdtConstants.MARKER_ADT, msg,
    501                         IMarker.SEVERITY_ERROR);
    502                 return result;
    503             }
    504 
    505             if (javaPackage == null || javaPackage.length() == 0) {
    506                 // looks like the AndroidManifest file isn't valid.
    507                 String msg = String.format(Messages.s_Doesnt_Declare_Package_Error,
    508                         SdkConstants.FN_ANDROID_MANIFEST_XML);
    509                 AdtPlugin.printErrorToConsole(project, msg);
    510                 BaseProjectHelper.markResource(manifestFile, AdtConstants.MARKER_ADT,
    511                         msg, IMarker.SEVERITY_ERROR);
    512 
    513                 return result;
    514             } else if (javaPackage.indexOf('.') == -1) {
    515                 // The application package name does not contain 2+ segments!
    516                 String msg = String.format(
    517                         "Application package '%1$s' must have a minimum of 2 segments.",
    518                         SdkConstants.FN_ANDROID_MANIFEST_XML);
    519                 AdtPlugin.printErrorToConsole(project, msg);
    520                 BaseProjectHelper.markResource(manifestFile, AdtConstants.MARKER_ADT,
    521                         msg, IMarker.SEVERITY_ERROR);
    522 
    523                 return result;
    524             }
    525 
    526             // at this point we have the java package. We need to make sure it's not a different
    527             // package than the previous one that were built.
    528             if (javaPackage.equals(mManifestPackage) == false) {
    529                 // The manifest package has changed, the user may want to update
    530                 // the launch configuration
    531                 if (mManifestPackage != null) {
    532                     AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project,
    533                             Messages.Checking_Package_Change);
    534 
    535                     FixLaunchConfig flc = new FixLaunchConfig(project, mManifestPackage,
    536                             javaPackage);
    537                     flc.start();
    538                 }
    539 
    540                 // record the new manifest package, and save it.
    541                 mManifestPackage = javaPackage;
    542                 saveProjectStringProperty(PROPERTY_PACKAGE, mManifestPackage);
    543 
    544                 // force a clean
    545                 doClean(project, monitor);
    546                 mMustCompileResources = true;
    547                 mMustCreateBuildConfig = true;
    548                 for (SourceProcessor processor : mProcessors) {
    549                     processor.prepareFullBuild(project);
    550                 }
    551 
    552                 saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES, mMustCompileResources);
    553                 saveProjectBooleanProperty(PROPERTY_COMPILE_BUILDCONFIG, mMustCreateBuildConfig);
    554             }
    555 
    556             try {
    557                 handleBuildConfig(args);
    558             } catch (IOException e) {
    559                 handleException(e, "Failed to create BuildConfig class");
    560                 return result;
    561             }
    562 
    563             // run the source processors
    564             int processorStatus = SourceProcessor.COMPILE_STATUS_NONE;
    565             for (SourceProcessor processor : mProcessors) {
    566                 try {
    567                     processorStatus |= processor.compileFiles(this,
    568                             project, projectTarget, minSdkValue, sourceFolderPathList, monitor);
    569                 } catch (Throwable t) {
    570                     handleException(t, String.format(
    571                             "Failed to run %s. Check workspace log for detail.",
    572                             processor.getClass().getName()));
    573                     return result;
    574                 }
    575             }
    576 
    577             // if a processor created some resources file, force recompilation of the resources.
    578             if ((processorStatus & SourceProcessor.COMPILE_STATUS_RES) != 0) {
    579                 mMustCompileResources = true;
    580                 // save the current state before attempting the compilation
    581                 saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES, mMustCompileResources);
    582             }
    583 
    584             // handle the resources, after the processors are run since some (renderscript)
    585             // generate resources.
    586             boolean compiledTheResources = mMustCompileResources;
    587             if (mMustCompileResources) {
    588                 if (DEBUG) {
    589                     System.out.println("\tcompiling resources!");
    590                 }
    591                 handleResources(project, javaPackage, projectTarget, manifestFile, libProjects,
    592                         projectState.isLibrary());
    593             }
    594 
    595             if (processorStatus == SourceProcessor.COMPILE_STATUS_NONE &&
    596                     compiledTheResources == false) {
    597                 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project,
    598                         Messages.Nothing_To_Compile);
    599             }
    600         } catch (AbortBuildException e) {
    601             return result;
    602         } finally {
    603             // refresh the 'gen' source folder. Once this is done with the custom progress
    604             // monitor to mark all new files as derived
    605             mGenFolder.refreshLocal(IResource.DEPTH_INFINITE, mDerivedProgressMonitor);
    606         }
    607 
    608         return result;
    609     }
    610 
    611     @Override
    612     protected void clean(IProgressMonitor monitor) throws CoreException {
    613         super.clean(monitor);
    614 
    615         if (DEBUG) {
    616             System.out.println("CLEAN(PRE) " + getProject().getName());
    617         }
    618 
    619         doClean(getProject(), monitor);
    620         if (mGenFolder != null) {
    621             mGenFolder.refreshLocal(IResource.DEPTH_INFINITE, monitor);
    622         }
    623     }
    624 
    625     private void doClean(IProject project, IProgressMonitor monitor) throws CoreException {
    626         AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project,
    627                 Messages.Removing_Generated_Classes);
    628 
    629         // remove all the derived resources from the 'gen' source folder.
    630         if (mGenFolder != null && mGenFolder.exists()) {
    631             // gen folder should not be derived, but previous version could set it to derived
    632             // so we make sure this isn't the case (or it'll get deleted by the clean)
    633             mGenFolder.setDerived(false, monitor);
    634 
    635             removeDerivedResources(mGenFolder, monitor);
    636         }
    637 
    638         // Clear the project of the generic markers
    639         removeMarkersFromContainer(project, AdtConstants.MARKER_AAPT_COMPILE);
    640         removeMarkersFromContainer(project, AdtConstants.MARKER_XML);
    641         removeMarkersFromContainer(project, AdtConstants.MARKER_AIDL);
    642         removeMarkersFromContainer(project, AdtConstants.MARKER_RENDERSCRIPT);
    643         removeMarkersFromContainer(project, AdtConstants.MARKER_ANDROID);
    644     }
    645 
    646     @Override
    647     protected void startupOnInitialize() {
    648         try {
    649             super.startupOnInitialize();
    650 
    651             IProject project = getProject();
    652 
    653             // load the previous IFolder and java package.
    654             mManifestPackage = loadProjectStringProperty(PROPERTY_PACKAGE);
    655 
    656             // get the source folder in which all the Java files are created
    657             mGenFolder = project.getFolder(SdkConstants.FD_GEN_SOURCES);
    658             mDerivedProgressMonitor = new DerivedProgressMonitor(mGenFolder);
    659 
    660             // Load the current compile flags. We ask for true if not found to force a recompile.
    661             mMustCompileResources = loadProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES, true);
    662             mMustCreateBuildConfig = loadProjectBooleanProperty(PROPERTY_COMPILE_BUILDCONFIG, true);
    663             Boolean v = ProjectHelper.loadBooleanProperty(project, PROPERTY_BUILDCONFIG_MODE);
    664             if (v == null) {
    665                 // no previous build config mode? force regenerate
    666                 mMustCreateBuildConfig = true;
    667             } else {
    668                 mLastBuildConfigMode = v;
    669             }
    670 
    671 
    672             IJavaProject javaProject = JavaCore.create(project);
    673 
    674             // load the source processors
    675             SourceProcessor aidlProcessor = new AidlProcessor(javaProject, mGenFolder);
    676             SourceProcessor renderScriptProcessor = new RenderScriptProcessor(javaProject,
    677                     mGenFolder);
    678             mProcessors.add(aidlProcessor);
    679             mProcessors.add(renderScriptProcessor);
    680 
    681         } catch (Throwable throwable) {
    682             AdtPlugin.log(throwable, "Failed to finish PrecompilerBuilder#startupOnInitialize()");
    683         }
    684     }
    685 
    686     private void handleBuildConfig(@SuppressWarnings("rawtypes") Map args)
    687             throws IOException, CoreException {
    688         boolean debugMode = !args.containsKey(RELEASE_REQUESTED);
    689 
    690         BuildConfigGenerator generator = new BuildConfigGenerator(
    691                 mGenFolder.getLocation().toOSString(), mManifestPackage, debugMode);
    692 
    693         if (mMustCreateBuildConfig == false) {
    694             // check the file is present.
    695             IFolder folder = getGenManifestPackageFolder();
    696             if (folder.exists(new Path(BuildConfigGenerator.BUILD_CONFIG_NAME)) == false) {
    697                 mMustCreateBuildConfig = true;
    698                 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, getProject(),
    699                         String.format("Class %1$s is missing!",
    700                                 BuildConfigGenerator.BUILD_CONFIG_NAME));
    701             } else if (debugMode != mLastBuildConfigMode) {
    702                 // else if the build mode changed, force creation
    703                 mMustCreateBuildConfig = true;
    704                 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, getProject(),
    705                         String.format("Different build mode, must update %1$s!",
    706                                 BuildConfigGenerator.BUILD_CONFIG_NAME));
    707             }
    708         }
    709 
    710         if (mMustCreateBuildConfig) {
    711             AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, getProject(),
    712                     String.format("Generating %1$s...", BuildConfigGenerator.BUILD_CONFIG_NAME));
    713             generator.generate();
    714 
    715             mMustCreateBuildConfig = false;
    716             saveProjectBooleanProperty(PROPERTY_COMPILE_BUILDCONFIG, mMustCreateBuildConfig);
    717             saveProjectBooleanProperty(PROPERTY_BUILDCONFIG_MODE, mLastBuildConfigMode = debugMode);
    718         }
    719     }
    720 
    721 
    722     /**
    723      * Handles resource changes and regenerate whatever files need regenerating.
    724      * @param project the main project
    725      * @param javaPackage the app package for the main project
    726      * @param projectTarget the target of the main project
    727      * @param manifest the {@link IFile} representing the project manifest
    728      * @param libProjects the library dependencies
    729      * @param isLibrary if the project is a library project
    730      * @throws CoreException
    731      * @throws AbortBuildException
    732      */
    733     private void handleResources(IProject project, String javaPackage, IAndroidTarget projectTarget,
    734             IFile manifest, List<IProject> libProjects, boolean isLibrary)
    735             throws CoreException, AbortBuildException {
    736         // get the resource folder
    737         IFolder resFolder = project.getFolder(AdtConstants.WS_RESOURCES);
    738 
    739         // get the file system path
    740         IPath outputLocation = mGenFolder.getLocation();
    741         IPath resLocation = resFolder.getLocation();
    742         IPath manifestLocation = manifest == null ? null : manifest.getLocation();
    743 
    744         // those locations have to exist for us to do something!
    745         if (outputLocation != null && resLocation != null
    746                 && manifestLocation != null) {
    747             String osOutputPath = outputLocation.toOSString();
    748             String osResPath = resLocation.toOSString();
    749             String osManifestPath = manifestLocation.toOSString();
    750 
    751             // remove the aapt markers
    752             removeMarkersFromResource(manifest, AdtConstants.MARKER_AAPT_COMPILE);
    753             removeMarkersFromContainer(resFolder, AdtConstants.MARKER_AAPT_COMPILE);
    754 
    755             AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project,
    756                     Messages.Preparing_Generated_Files);
    757 
    758             // we need to figure out where to store the R class.
    759             // get the parent folder for R.java and update mManifestPackageSourceFolder
    760             IFolder mainPackageFolder = getGenManifestPackageFolder();
    761 
    762             // handle libraries
    763             ArrayList<IFolder> libResFolders = new ArrayList<IFolder>();
    764             StringBuilder libJavaPackages = null;
    765             if (libProjects != null) {
    766                 for (IProject lib : libProjects) {
    767                     IFolder libResFolder = lib.getFolder(SdkConstants.FD_RES);
    768                     if (libResFolder.exists()) {
    769                         libResFolders.add(libResFolder);
    770                     }
    771 
    772                     try {
    773                         String libJavaPackage = AndroidManifest.getPackage(new IFolderWrapper(lib));
    774                         if (libJavaPackage.equals(javaPackage) == false) {
    775                             if (libJavaPackages == null) {
    776                                 libJavaPackages = new StringBuilder(libJavaPackage);
    777                             } else {
    778                                 libJavaPackages.append(":");
    779                                 libJavaPackages.append(libJavaPackage);
    780                             }
    781                         }
    782                     } catch (Exception e) {
    783                     }
    784                 }
    785             }
    786 
    787             String libPackages = null;
    788             if (libJavaPackages != null) {
    789                 libPackages = libJavaPackages.toString();
    790 
    791             }
    792 
    793             execAapt(project, projectTarget, osOutputPath, osResPath, osManifestPath,
    794                     mainPackageFolder, libResFolders, libPackages, isLibrary);
    795         }
    796     }
    797 
    798     /**
    799      * Executes AAPT to generate R.java/Manifest.java
    800      * @param project the main project
    801      * @param projectTarget the main project target
    802      * @param osOutputPath the OS output path for the generated file. This is the source folder, not
    803      * the package folder.
    804      * @param osResPath the OS path to the res folder for the main project
    805      * @param osManifestPath the OS path to the manifest of the main project
    806      * @param packageFolder the IFolder that will contain the generated file. Unlike
    807      * <var>osOutputPath</var> this is the direct parent of the generated files.
    808      * If <var>customJavaPackage</var> is not null, this must match the new destination triggered
    809      * by its value.
    810      * @param libResFolders the list of res folders for the library.
    811      * @param libraryPackages an optional list of javapackages to replace the main project java package.
    812      * can be null.
    813      * @param isLibrary if the project is a library project
    814      * @throws AbortBuildException
    815      */
    816     private void execAapt(IProject project, IAndroidTarget projectTarget, String osOutputPath,
    817             String osResPath, String osManifestPath, IFolder packageFolder,
    818             ArrayList<IFolder> libResFolders, String libraryPackages, boolean isLibrary)
    819             throws AbortBuildException {
    820 
    821         // We actually need to delete the manifest.java as it may become empty and
    822         // in this case aapt doesn't generate an empty one, but instead doesn't
    823         // touch it.
    824         IFile manifestJavaFile = packageFolder.getFile(AdtConstants.FN_MANIFEST_CLASS);
    825         manifestJavaFile.getLocation().toFile().delete();
    826 
    827         // launch aapt: create the command line
    828         ArrayList<String> array = new ArrayList<String>();
    829 
    830         @SuppressWarnings("deprecation")
    831         String aaptPath = projectTarget.getPath(IAndroidTarget.AAPT);
    832 
    833         array.add(aaptPath);
    834         array.add("package"); //$NON-NLS-1$
    835         array.add("-m"); //$NON-NLS-1$
    836         if (AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE) {
    837             array.add("-v"); //$NON-NLS-1$
    838         }
    839 
    840         if (isLibrary) {
    841             array.add("--non-constant-id"); //$NON-NLS-1$
    842         }
    843 
    844         if (libResFolders.size() > 0) {
    845             array.add("--auto-add-overlay"); //$NON-NLS-1$
    846         }
    847 
    848         // there's no need to generate the R class of the libraries if this is a library too.
    849         if (isLibrary == false && libraryPackages != null) {
    850             array.add("--extra-packages"); //$NON-NLS-1$
    851             array.add(libraryPackages);
    852         }
    853 
    854         array.add("-J"); //$NON-NLS-1$
    855         array.add(osOutputPath);
    856         array.add("-M"); //$NON-NLS-1$
    857         array.add(osManifestPath);
    858         array.add("-S"); //$NON-NLS-1$
    859         array.add(osResPath);
    860         for (IFolder libResFolder : libResFolders) {
    861             array.add("-S"); //$NON-NLS-1$
    862             array.add(libResFolder.getLocation().toOSString());
    863         }
    864 
    865         array.add("-I"); //$NON-NLS-1$
    866         array.add(projectTarget.getPath(IAndroidTarget.ANDROID_JAR));
    867 
    868         if (AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE) {
    869             StringBuilder sb = new StringBuilder();
    870             for (String c : array) {
    871                 sb.append(c);
    872                 sb.append(' ');
    873             }
    874             String cmd_line = sb.toString();
    875             AdtPlugin.printToConsole(project, cmd_line);
    876         }
    877 
    878         // launch
    879         int execError = 1;
    880         try {
    881             // launch the command line process
    882             Process process = Runtime.getRuntime().exec(
    883                     array.toArray(new String[array.size()]));
    884 
    885             // list to store each line of stderr
    886             ArrayList<String> results = new ArrayList<String>();
    887 
    888             // get the output and return code from the process
    889             execError = grabProcessOutput(process, results);
    890 
    891             // attempt to parse the error output
    892             boolean parsingError = AaptParser.parseOutput(results, project);
    893 
    894             // if we couldn't parse the output we display it in the console.
    895             if (parsingError) {
    896                 if (execError != 0) {
    897                     AdtPlugin.printErrorToConsole(project, results.toArray());
    898                 } else {
    899                     AdtPlugin.printBuildToConsole(BuildVerbosity.NORMAL,
    900                             project, results.toArray());
    901                 }
    902             }
    903 
    904             if (execError != 0) {
    905                 // if the exec failed, and we couldn't parse the error output
    906                 // (and therefore not all files that should have been marked,
    907                 // were marked), we put a generic marker on the project and abort.
    908                 if (parsingError) {
    909                     markProject(AdtConstants.MARKER_ADT,
    910                             Messages.Unparsed_AAPT_Errors, IMarker.SEVERITY_ERROR);
    911                 }
    912 
    913                 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project,
    914                         Messages.AAPT_Error);
    915 
    916                 // abort if exec failed.
    917                 throw new AbortBuildException();
    918             }
    919         } catch (IOException e1) {
    920             // something happen while executing the process,
    921             // mark the project and exit
    922             String msg = String.format(Messages.AAPT_Exec_Error, array.get(0));
    923             markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
    924 
    925             // Add workaround for the Linux problem described here:
    926             //    http://developer.android.com/sdk/installing.html#troubleshooting
    927             // There are various posts on StackOverflow elsewhere where people are asking
    928             // about aapt failing to run, so even though this is documented in the
    929             // Troubleshooting section add an error message to help with this
    930             // scenario.
    931             if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_LINUX
    932                     && System.getProperty("os.arch").endsWith("64") //$NON-NLS-1$ //$NON-NLS-2$
    933                     && new File(aaptPath).exists()
    934                     && new File("/usr/bin/apt-get").exists()) {     //$NON-NLS-1$
    935                 markProject(AdtConstants.MARKER_ADT,
    936                         "Hint: On 64-bit systems, make sure the 32-bit libraries are installed: sudo apt-get install ia32-libs",
    937                         IMarker.SEVERITY_ERROR);
    938                 // Note - this uses SEVERITY_ERROR even though it's really SEVERITY_INFO because
    939                 // we want this error message to show up adjacent to the aapt error message
    940                 // (and Eclipse sorts by priority)
    941             }
    942 
    943             // This interrupts the build.
    944             throw new AbortBuildException();
    945         } catch (InterruptedException e) {
    946             // we got interrupted waiting for the process to end...
    947             // mark the project and exit
    948             String msg = String.format(Messages.AAPT_Exec_Error, array.get(0));
    949             markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
    950 
    951             // This interrupts the build.
    952             throw new AbortBuildException();
    953         } finally {
    954             // we've at least attempted to run aapt, save the fact that we don't have to
    955             // run it again, unless there's a new resource change.
    956             saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES,
    957                     mMustCompileResources = false);
    958             ResourceManager.clearAaptRequest(project);
    959         }
    960     }
    961 
    962     /**
    963      * Creates a relative {@link IPath} from a java package.
    964      * @param javaPackageName the java package.
    965      */
    966     private IPath getJavaPackagePath(String javaPackageName) {
    967         // convert the java package into path
    968         String[] segments = javaPackageName.split(AdtConstants.RE_DOT);
    969 
    970         StringBuilder path = new StringBuilder();
    971         for (String s : segments) {
    972            path.append(AdtConstants.WS_SEP_CHAR);
    973            path.append(s);
    974         }
    975 
    976         return new Path(path.toString());
    977     }
    978 
    979     /**
    980      * Returns an {@link IFolder} (located inside the 'gen' source folder), that matches the
    981      * package defined in the manifest. This {@link IFolder} may not actually exist
    982      * (aapt will create it anyway).
    983      * @return the {@link IFolder} that will contain the R class or null if
    984      * the folder was not found.
    985      * @throws CoreException
    986      */
    987     private IFolder getGenManifestPackageFolder() throws CoreException {
    988         // get the path for the package
    989         IPath packagePath = getJavaPackagePath(mManifestPackage);
    990 
    991         // get a folder for this path under the 'gen' source folder, and return it.
    992         // This IFolder may not reference an actual existing folder.
    993         return mGenFolder.getFolder(packagePath);
    994     }
    995 }
    996