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.common.xml.ManifestData;
     23 import com.android.ide.eclipse.adt.AdtConstants;
     24 import com.android.ide.eclipse.adt.AdtPlugin;
     25 import com.android.ide.eclipse.adt.internal.build.AaptParser;
     26 import com.android.ide.eclipse.adt.internal.build.AidlProcessor;
     27 import com.android.ide.eclipse.adt.internal.build.Messages;
     28 import com.android.ide.eclipse.adt.internal.build.RenderScriptLauncher;
     29 import com.android.ide.eclipse.adt.internal.build.RsSourceChangeHandler;
     30 import com.android.ide.eclipse.adt.internal.build.SourceProcessor;
     31 import com.android.ide.eclipse.adt.internal.build.builders.BaseBuilder.AbortBuildException;
     32 import com.android.ide.eclipse.adt.internal.lint.EclipseLintClient;
     33 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
     34 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity;
     35 import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
     36 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
     37 import com.android.ide.eclipse.adt.internal.project.FixLaunchConfig;
     38 import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
     39 import com.android.ide.eclipse.adt.internal.project.XmlErrorHandler.BasicXmlErrorListener;
     40 import com.android.ide.eclipse.adt.internal.resources.manager.IdeScanningContext;
     41 import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
     42 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
     43 import com.android.ide.eclipse.adt.internal.sdk.AdtManifestMergeCallback;
     44 import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
     45 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
     46 import com.android.ide.eclipse.adt.io.IFileWrapper;
     47 import com.android.ide.eclipse.adt.io.IFolderWrapper;
     48 import com.android.io.StreamException;
     49 import com.android.manifmerger.ManifestMerger;
     50 import com.android.manifmerger.MergerLog;
     51 import com.android.sdklib.AndroidVersion;
     52 import com.android.sdklib.BuildToolInfo;
     53 import com.android.sdklib.IAndroidTarget;
     54 import com.android.sdklib.build.RenderScriptChecker;
     55 import com.android.sdklib.build.RenderScriptProcessor;
     56 import com.android.sdklib.internal.build.BuildConfigGenerator;
     57 import com.android.sdklib.internal.build.SymbolLoader;
     58 import com.android.sdklib.internal.build.SymbolWriter;
     59 import com.android.sdklib.internal.project.ProjectProperties;
     60 import com.android.sdklib.io.FileOp;
     61 import com.android.sdklib.repository.FullRevision;
     62 import com.android.utils.ILogger;
     63 import com.android.utils.Pair;
     64 import com.android.xml.AndroidManifest;
     65 import com.google.common.collect.ArrayListMultimap;
     66 import com.google.common.collect.Lists;
     67 import com.google.common.collect.Multimap;
     68 
     69 import org.eclipse.core.resources.IFile;
     70 import org.eclipse.core.resources.IFolder;
     71 import org.eclipse.core.resources.IMarker;
     72 import org.eclipse.core.resources.IProject;
     73 import org.eclipse.core.resources.IResource;
     74 import org.eclipse.core.resources.IResourceDelta;
     75 import org.eclipse.core.resources.IWorkspaceRoot;
     76 import org.eclipse.core.resources.ResourcesPlugin;
     77 import org.eclipse.core.runtime.CoreException;
     78 import org.eclipse.core.runtime.IPath;
     79 import org.eclipse.core.runtime.IProgressMonitor;
     80 import org.eclipse.core.runtime.IStatus;
     81 import org.eclipse.core.runtime.NullProgressMonitor;
     82 import org.eclipse.core.runtime.Path;
     83 import org.eclipse.jdt.core.IJavaProject;
     84 import org.eclipse.jdt.core.JavaCore;
     85 import org.xml.sax.SAXException;
     86 
     87 import java.io.File;
     88 import java.io.IOException;
     89 import java.util.ArrayList;
     90 import java.util.Collection;
     91 import java.util.List;
     92 import java.util.Map;
     93 
     94 import javax.xml.parsers.ParserConfigurationException;
     95 
     96 /**
     97  * Pre Java Compiler.
     98  * This incremental builder performs 2 tasks:
     99  * <ul>
    100  * <li>compiles the resources located in the res/ folder, along with the
    101  * AndroidManifest.xml file into the R.java class.</li>
    102  * <li>compiles any .aidl files into a corresponding java file.</li>
    103  * </ul>
    104  *
    105  */
    106 public class PreCompilerBuilder extends BaseBuilder {
    107 
    108     /** This ID is used in plugin.xml and in each project's .project file.
    109      * It cannot be changed even if the class is renamed/moved */
    110     public static final String ID = "com.android.ide.eclipse.adt.PreCompilerBuilder"; //$NON-NLS-1$
    111 
    112     /** Flag to pass to PreCompiler builder that the build is a release build.
    113      */
    114     public final static String RELEASE_REQUESTED = "android.releaseBuild"; //$NON-NLS-1$
    115 
    116     private static final String PROPERTY_PACKAGE = "manifestPackage"; //$NON-NLS-1$
    117     private static final String PROPERTY_MERGE_MANIFEST = "mergeManifest"; //$NON-NLS-1$
    118     private static final String PROPERTY_COMPILE_RESOURCES = "compileResources"; //$NON-NLS-1$
    119     private static final String PROPERTY_COMPILE_BUILDCONFIG = "createBuildConfig"; //$NON-NLS-1$
    120     private static final String PROPERTY_BUILDCONFIG_MODE = "buildConfigMode"; //$NON-NLS-1$
    121 
    122     private static final boolean MANIFEST_MERGER_ENABLED_DEFAULT = false;
    123     private static final String MANIFEST_MERGER_PROPERTY = "manifestmerger.enabled"; //$NON-NLS-1$
    124 
    125     /** Merge Manifest Flag. Computed from resource delta, reset after action is taken.
    126      * Stored persistently in the project. */
    127     private boolean mMustMergeManifest = false;
    128     /** Resource compilation Flag. Computed from resource delta, reset after action is taken.
    129      * Stored persistently in the project. */
    130     private boolean mMustCompileResources = false;
    131     /** BuildConfig Flag. Computed from resource delta, reset after action is taken.
    132      * Stored persistently in the project. */
    133     private boolean mMustCreateBuildConfig = false;
    134     /** BuildConfig last more Flag. Computed from resource delta, reset after action is taken.
    135      * Stored persistently in the project. */
    136     private boolean mLastBuildConfigMode;
    137 
    138     /** cache of the java package defined in the manifest */
    139     private String mManifestPackage;
    140 
    141     /** Output folder for generated Java File. Created on the Builder init
    142      * @see #startupOnInitialize()
    143      */
    144     private IFolder mGenFolder;
    145 
    146     /**
    147      * Progress monitor used at the end of every build to refresh the content of the 'gen' folder
    148      * and set the generated files as derived.
    149      */
    150     private DerivedProgressMonitor mDerivedProgressMonitor;
    151 
    152     private AidlProcessor mAidlProcessor;
    153     private RsSourceChangeHandler mRenderScriptSourceChangeHandler;
    154 
    155     /**
    156      * Progress monitor waiting the end of the process to set a persistent value
    157      * in a file. This is typically used in conjunction with <code>IResource.refresh()</code>,
    158      * since this call is asynchronous, and we need to wait for it to finish for the file
    159      * to be known by eclipse, before we can call <code>resource.setPersistentProperty</code> on
    160      * a new file.
    161      */
    162     private static class DerivedProgressMonitor implements IProgressMonitor {
    163         private boolean mCancelled = false;
    164         private boolean mDone = false;
    165         private final IFolder mGenFolder;
    166 
    167         public DerivedProgressMonitor(IFolder genFolder) {
    168             mGenFolder = genFolder;
    169         }
    170 
    171         void reset() {
    172             mDone = false;
    173         }
    174 
    175         @Override
    176         public void beginTask(String name, int totalWork) {
    177         }
    178 
    179         @Override
    180         public void done() {
    181             if (mDone == false) {
    182                 mDone = true;
    183                 processChildrenOf(mGenFolder);
    184             }
    185         }
    186 
    187         private void processChildrenOf(IFolder folder) {
    188             IResource[] list;
    189             try {
    190                 list = folder.members();
    191             } catch (CoreException e) {
    192                 return;
    193             }
    194 
    195             for (IResource member : list) {
    196                 if (member.exists()) {
    197                     if (member.getType() == IResource.FOLDER) {
    198                         processChildrenOf((IFolder) member);
    199                     }
    200 
    201                     try {
    202                         member.setDerived(true, new NullProgressMonitor());
    203                     } catch (CoreException e) {
    204                         // This really shouldn't happen since we check that the resource
    205                         // exist.
    206                         // Worst case scenario, the resource isn't marked as derived.
    207                     }
    208                 }
    209             }
    210         }
    211 
    212         @Override
    213         public void internalWorked(double work) {
    214         }
    215 
    216         @Override
    217         public boolean isCanceled() {
    218             return mCancelled;
    219         }
    220 
    221         @Override
    222         public void setCanceled(boolean value) {
    223             mCancelled = value;
    224         }
    225 
    226         @Override
    227         public void setTaskName(String name) {
    228         }
    229 
    230         @Override
    231         public void subTask(String name) {
    232         }
    233 
    234         @Override
    235         public void worked(int work) {
    236         }
    237     }
    238 
    239     public PreCompilerBuilder() {
    240         super();
    241     }
    242 
    243     // build() returns a list of project from which this project depends for future compilation.
    244     @Override
    245     protected IProject[] build(
    246             int kind,
    247             @SuppressWarnings("rawtypes") Map args,
    248             IProgressMonitor monitor)
    249             throws CoreException {
    250         // get a project object
    251         IProject project = getProject();
    252 
    253         if (DEBUG_LOG) {
    254             AdtPlugin.log(IStatus.INFO, "%s BUILD(PRE)", project.getName());
    255         }
    256 
    257         // For the PreCompiler, only the library projects are considered Referenced projects,
    258         // as only those projects have an impact on what is generated by this builder.
    259         IProject[] result = null;
    260 
    261         IFolder resOutFolder = null;
    262 
    263         try {
    264             assert mDerivedProgressMonitor != null;
    265 
    266             mDerivedProgressMonitor.reset();
    267 
    268             // get the project info
    269             ProjectState projectState = Sdk.getProjectState(project);
    270 
    271             // this can happen if the project has no project.properties.
    272             if (projectState == null) {
    273                 return null;
    274             }
    275 
    276             boolean isLibrary = projectState.isLibrary();
    277 
    278             IAndroidTarget projectTarget = projectState.getTarget();
    279 
    280             // get the libraries
    281             List<IProject> libProjects = projectState.getFullLibraryProjects();
    282             result = libProjects.toArray(new IProject[libProjects.size()]);
    283 
    284             IJavaProject javaProject = JavaCore.create(project);
    285 
    286             // Top level check to make sure the build can move forward.
    287             abortOnBadSetup(javaProject, projectState);
    288 
    289             // now we need to get the classpath list
    290             List<IPath> sourceFolderPathList = BaseProjectHelper.getSourceClasspaths(javaProject);
    291 
    292             IFolder androidOutputFolder = BaseProjectHelper.getAndroidOutputFolder(project);
    293 
    294             resOutFolder = getResOutFolder(androidOutputFolder);
    295 
    296             setupSourceProcessors(javaProject, projectState, sourceFolderPathList,
    297                     androidOutputFolder);
    298 
    299             PreCompilerDeltaVisitor dv = null;
    300             String javaPackage = null;
    301             String minSdkVersion = null;
    302 
    303             if (kind == FULL_BUILD) {
    304                 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project,
    305                         Messages.Start_Full_Pre_Compiler);
    306 
    307                 if (DEBUG_LOG) {
    308                     AdtPlugin.log(IStatus.INFO, "%s full build!", project.getName());
    309                 }
    310 
    311                 // do some clean up.
    312                 doClean(project, monitor);
    313 
    314                 mMustMergeManifest = true;
    315                 mMustCompileResources = true;
    316                 mMustCreateBuildConfig = true;
    317 
    318                 mAidlProcessor.prepareFullBuild(project);
    319                 mRenderScriptSourceChangeHandler.prepareFullBuild();
    320             } else {
    321                 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project,
    322                         Messages.Start_Inc_Pre_Compiler);
    323 
    324                 // Go through the resources and see if something changed.
    325                 // Even if the mCompileResources flag is true from a previously aborted
    326                 // build, we need to go through the Resource delta to get a possible
    327                 // list of aidl files to compile/remove.
    328                 IResourceDelta delta = getDelta(project);
    329                 if (delta == null) {
    330                     mMustCompileResources = true;
    331 
    332                     mAidlProcessor.prepareFullBuild(project);
    333                     mRenderScriptSourceChangeHandler.prepareFullBuild();
    334                 } else {
    335                     dv = new PreCompilerDeltaVisitor(this, sourceFolderPathList,
    336                             mAidlProcessor.getChangeHandler(),
    337                             mRenderScriptSourceChangeHandler);
    338                     delta.accept(dv);
    339 
    340                     // Check to see if Manifest.xml, Manifest.java, or R.java have changed:
    341                     mMustCompileResources |= dv.getCompileResources();
    342                     mMustMergeManifest |= dv.hasManifestChanged();
    343 
    344                     // Notify the ResourceManager:
    345                     ResourceManager resManager = ResourceManager.getInstance();
    346 
    347                     if (ResourceManager.isAutoBuilding()) {
    348                         ProjectResources projectResources = resManager.getProjectResources(project);
    349 
    350                         IdeScanningContext context = new IdeScanningContext(projectResources,
    351                                 project, true);
    352 
    353                         boolean wasCleared = projectResources.ensureInitialized();
    354 
    355                         if (!wasCleared) {
    356                             resManager.processDelta(delta, context);
    357                         }
    358 
    359                         // Check whether this project or its dependencies (libraries) have
    360                         // resources that need compilation
    361                         if (wasCleared || context.needsFullAapt()) {
    362                             mMustCompileResources = true;
    363 
    364                             // Must also call markAaptRequested on the project to not just
    365                             // store "aapt required" on this project, but also on any projects
    366                             // depending on this project if it's a library project
    367                             ResourceManager.markAaptRequested(project);
    368                         }
    369 
    370                         // Update error markers in the source editor
    371                         if (!mMustCompileResources) {
    372                             context.updateMarkers(false /* async */);
    373                         }
    374                     } // else: already processed the deltas in ResourceManager's IRawDeltaListener
    375 
    376                     mAidlProcessor.doneVisiting(project);
    377 
    378                     // get the java package from the visitor
    379                     javaPackage = dv.getManifestPackage();
    380                     minSdkVersion = dv.getMinSdkVersion();
    381                 }
    382             }
    383 
    384             // Has anyone marked this project as needing aapt? Typically done when
    385             // one of the library projects this project depends on has changed
    386             mMustCompileResources |= ResourceManager.isAaptRequested(project);
    387 
    388             // if the main manifest didn't change, then we check for the library
    389             // ones (will trigger manifest merging too)
    390             if (libProjects.size() > 0) {
    391                 for (IProject libProject : libProjects) {
    392                     IResourceDelta delta = getDelta(libProject);
    393                     if (delta != null) {
    394                         PatternBasedDeltaVisitor visitor = new PatternBasedDeltaVisitor(
    395                                 project, libProject,
    396                                 "PRE:LibManifest"); //$NON-NLS-1$
    397                         visitor.addSet(ChangedFileSetHelper.MANIFEST);
    398 
    399                         ChangedFileSet textSymbolCFS = null;
    400                         if (isLibrary == false) {
    401                             textSymbolCFS = ChangedFileSetHelper.getTextSymbols(
    402                                     libProject);
    403                             visitor.addSet(textSymbolCFS);
    404                         }
    405 
    406                         delta.accept(visitor);
    407 
    408                         mMustMergeManifest |= visitor.checkSet(ChangedFileSetHelper.MANIFEST);
    409 
    410                         if (textSymbolCFS != null) {
    411                             mMustCompileResources |= visitor.checkSet(textSymbolCFS);
    412                         }
    413 
    414                         // no need to test others if we have all flags at true.
    415                         if (mMustMergeManifest &&
    416                                 (mMustCompileResources || textSymbolCFS == null)) {
    417                             break;
    418                         }
    419                     }
    420                 }
    421             }
    422 
    423             // store the build status in the persistent storage
    424             saveProjectBooleanProperty(PROPERTY_MERGE_MANIFEST, mMustMergeManifest);
    425             saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES, mMustCompileResources);
    426             saveProjectBooleanProperty(PROPERTY_COMPILE_BUILDCONFIG, mMustCreateBuildConfig);
    427 
    428             // if there was some XML errors, we just return w/o doing
    429             // anything since we've put some markers in the files anyway.
    430             if (dv != null && dv.mXmlError) {
    431                 AdtPlugin.printErrorToConsole(project, Messages.Xml_Error);
    432 
    433                 return result;
    434             }
    435 
    436             if (projectState.getRenderScriptSupportMode()) {
    437                 FullRevision minBuildToolsRev = new FullRevision(19,0,3);
    438                 if (mBuildToolInfo.getRevision().compareTo(minBuildToolsRev) == -1) {
    439                     String msg = "RenderScript support mode requires Build-Tools 19.0.3 or later.";
    440                     AdtPlugin.printErrorToConsole(project, msg);
    441                     markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
    442 
    443                     return result;
    444                 }
    445             }
    446 
    447             // get the manifest file
    448             IFile manifestFile = ProjectHelper.getManifest(project);
    449 
    450             if (manifestFile == null) {
    451                 String msg = String.format(Messages.s_File_Missing,
    452                         SdkConstants.FN_ANDROID_MANIFEST_XML);
    453                 AdtPlugin.printErrorToConsole(project, msg);
    454                 markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
    455 
    456                 return result;
    457 
    458                 // TODO: document whether code below that uses manifest (which is now guaranteed
    459                 // to be null) will actually be executed or not.
    460             }
    461 
    462             // lets check the XML of the manifest first, if that hasn't been done by the
    463             // resource delta visitor yet.
    464             if (dv == null || dv.getCheckedManifestXml() == false) {
    465                 BasicXmlErrorListener errorListener = new BasicXmlErrorListener();
    466                 try {
    467                     ManifestData parser = AndroidManifestHelper.parseUnchecked(
    468                             new IFileWrapper(manifestFile),
    469                             true /*gather data*/,
    470                             errorListener);
    471 
    472                     if (errorListener.mHasXmlError == true) {
    473                         // There was an error in the manifest, its file has been marked
    474                         // by the XmlErrorHandler. The stopBuild() call below will abort
    475                         // this with an exception.
    476                         String msg = String.format(Messages.s_Contains_Xml_Error,
    477                                 SdkConstants.FN_ANDROID_MANIFEST_XML);
    478                         AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, msg);
    479                         markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
    480 
    481                         return result;
    482                     }
    483 
    484                     // Get the java package from the parser.
    485                     // This can be null if the parsing failed because the resource is out of sync,
    486                     // in which case the error will already have been logged anyway.
    487                     if (parser != null) {
    488                         javaPackage = parser.getPackage();
    489                         minSdkVersion = parser.getMinSdkVersionString();
    490                     }
    491                 } catch (StreamException e) {
    492                     handleStreamException(e);
    493 
    494                     return result;
    495                 } catch (ParserConfigurationException e) {
    496                     String msg = String.format(
    497                             "Bad parser configuration for %s: %s",
    498                             manifestFile.getFullPath(),
    499                             e.getMessage());
    500 
    501                     handleException(e, msg);
    502                     return result;
    503 
    504                 } catch (SAXException e) {
    505                     String msg = String.format(
    506                             "Parser exception for %s: %s",
    507                             manifestFile.getFullPath(),
    508                             e.getMessage());
    509 
    510                     handleException(e, msg);
    511                     return result;
    512                 } catch (IOException e) {
    513                     String msg = String.format(
    514                             "I/O error for %s: %s",
    515                             manifestFile.getFullPath(),
    516                             e.getMessage());
    517 
    518                     handleException(e, msg);
    519                     return result;
    520                 }
    521             }
    522 
    523             int minSdkValue = -1;
    524 
    525             if (minSdkVersion != null) {
    526                 try {
    527                     minSdkValue = Integer.parseInt(minSdkVersion);
    528                 } catch (NumberFormatException e) {
    529                     // it's ok, it means minSdkVersion contains a (hopefully) valid codename.
    530                 }
    531 
    532                 AndroidVersion targetVersion = projectTarget.getVersion();
    533 
    534                 // remove earlier marker from the manifest
    535                 removeMarkersFromResource(manifestFile, AdtConstants.MARKER_ADT);
    536 
    537                 if (minSdkValue != -1) {
    538                     String codename = targetVersion.getCodename();
    539                     if (codename != null) {
    540                         // integer minSdk when the target is a preview => fatal error
    541                         String msg = String.format(
    542                                 "Platform %1$s is a preview and requires application manifest to set %2$s to '%1$s'",
    543                                 codename, AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION);
    544                         AdtPlugin.printErrorToConsole(project, msg);
    545                         BaseProjectHelper.markResource(manifestFile, AdtConstants.MARKER_ADT,
    546                                 msg, IMarker.SEVERITY_ERROR);
    547                         return result;
    548                     } else if (minSdkValue > targetVersion.getApiLevel()) {
    549                         // integer minSdk is too high for the target => warning
    550                         String msg = String.format(
    551                                 "Attribute %1$s (%2$d) is higher than the project target API level (%3$d)",
    552                                 AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION,
    553                                 minSdkValue, targetVersion.getApiLevel());
    554                         AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, msg);
    555                         BaseProjectHelper.markResource(manifestFile, AdtConstants.MARKER_ADT,
    556                                 msg, IMarker.SEVERITY_WARNING);
    557                     }
    558                 } else {
    559                     // looks like the min sdk is a codename, check it matches the codename
    560                     // of the platform
    561                     String codename = targetVersion.getCodename();
    562                     if (codename == null) {
    563                         // platform is not a preview => fatal error
    564                         String msg = String.format(
    565                                 "Manifest attribute '%1$s' is set to '%2$s'. Integer is expected.",
    566                                 AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION, minSdkVersion);
    567                         AdtPlugin.printErrorToConsole(project, msg);
    568                         BaseProjectHelper.markResource(manifestFile, AdtConstants.MARKER_ADT,
    569                                 msg, IMarker.SEVERITY_ERROR);
    570                         return result;
    571                     } else if (codename.equals(minSdkVersion) == false) {
    572                         // platform and manifest codenames don't match => fatal error.
    573                         String msg = String.format(
    574                                 "Value of manifest attribute '%1$s' does not match platform codename '%2$s'",
    575                                 AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION, codename);
    576                         AdtPlugin.printErrorToConsole(project, msg);
    577                         BaseProjectHelper.markResource(manifestFile, AdtConstants.MARKER_ADT,
    578                                 msg, IMarker.SEVERITY_ERROR);
    579                         return result;
    580                     }
    581 
    582                     // if we get there, the minSdkVersion is a codename matching the target
    583                     // platform codename. In this case we set minSdkValue to the previous API
    584                     // level, as it's used by source processors.
    585                     minSdkValue = targetVersion.getApiLevel();
    586                 }
    587             } else if (projectTarget.getVersion().isPreview()) {
    588                 // else the minSdkVersion is not set but we are using a preview target.
    589                 // Display an error
    590                 String codename = projectTarget.getVersion().getCodename();
    591                 String msg = String.format(
    592                         "Platform %1$s is a preview and requires application manifests to set %2$s to '%1$s'",
    593                         codename, AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION);
    594                 AdtPlugin.printErrorToConsole(project, msg);
    595                 BaseProjectHelper.markResource(manifestFile, AdtConstants.MARKER_ADT, msg,
    596                         IMarker.SEVERITY_ERROR);
    597                 return result;
    598             }
    599 
    600             if (javaPackage == null || javaPackage.length() == 0) {
    601                 // looks like the AndroidManifest file isn't valid.
    602                 String msg = String.format(Messages.s_Doesnt_Declare_Package_Error,
    603                         SdkConstants.FN_ANDROID_MANIFEST_XML);
    604                 AdtPlugin.printErrorToConsole(project, msg);
    605                 BaseProjectHelper.markResource(manifestFile, AdtConstants.MARKER_ADT,
    606                         msg, IMarker.SEVERITY_ERROR);
    607 
    608                 return result;
    609             } else if (javaPackage.indexOf('.') == -1) {
    610                 // The application package name does not contain 2+ segments!
    611                 String msg = String.format(
    612                         "Application package '%1$s' must have a minimum of 2 segments.",
    613                         SdkConstants.FN_ANDROID_MANIFEST_XML);
    614                 AdtPlugin.printErrorToConsole(project, msg);
    615                 BaseProjectHelper.markResource(manifestFile, AdtConstants.MARKER_ADT,
    616                         msg, IMarker.SEVERITY_ERROR);
    617 
    618                 return result;
    619             }
    620 
    621             // at this point we have the java package. We need to make sure it's not a different
    622             // package than the previous one that were built.
    623             if (javaPackage.equals(mManifestPackage) == false) {
    624                 // The manifest package has changed, the user may want to update
    625                 // the launch configuration
    626                 if (mManifestPackage != null) {
    627                     AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project,
    628                             Messages.Checking_Package_Change);
    629 
    630                     FixLaunchConfig flc = new FixLaunchConfig(project, mManifestPackage,
    631                             javaPackage);
    632                     flc.start();
    633                 }
    634 
    635                 // record the new manifest package, and save it.
    636                 mManifestPackage = javaPackage;
    637                 saveProjectStringProperty(PROPERTY_PACKAGE, mManifestPackage);
    638 
    639                 // force a clean
    640                 doClean(project, monitor);
    641                 mMustMergeManifest = true;
    642                 mMustCompileResources = true;
    643                 mMustCreateBuildConfig = true;
    644                 mAidlProcessor.prepareFullBuild(project);
    645                 mRenderScriptSourceChangeHandler.prepareFullBuild();
    646 
    647                 saveProjectBooleanProperty(PROPERTY_MERGE_MANIFEST, mMustMergeManifest);
    648                 saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES, mMustCompileResources);
    649                 saveProjectBooleanProperty(PROPERTY_COMPILE_BUILDCONFIG, mMustCreateBuildConfig);
    650             }
    651 
    652             try {
    653                 handleBuildConfig(args);
    654             } catch (IOException e) {
    655                 handleException(e, "Failed to create BuildConfig class");
    656                 return result;
    657             }
    658 
    659             // merge the manifest
    660             if (mMustMergeManifest) {
    661                 boolean enabled = MANIFEST_MERGER_ENABLED_DEFAULT;
    662                 String propValue = projectState.getProperty(MANIFEST_MERGER_PROPERTY);
    663                 if (propValue != null) {
    664                     enabled = Boolean.valueOf(propValue);
    665                 }
    666 
    667                 if (mergeManifest(androidOutputFolder, libProjects, enabled) == false) {
    668                     return result;
    669                 }
    670             }
    671 
    672             List<File> libProjectsOut = new ArrayList<File>(libProjects.size());
    673             for (IProject libProject : libProjects) {
    674                 libProjectsOut.add(
    675                         BaseProjectHelper.getAndroidOutputFolder(libProject)
    676                             .getLocation().toFile());
    677             }
    678 
    679             // run the source processors
    680             int processorStatus = SourceProcessor.COMPILE_STATUS_NONE;
    681 
    682 
    683             try {
    684                 processorStatus |= mAidlProcessor.compileFiles(this,
    685                         project, projectTarget, sourceFolderPathList,
    686                         libProjectsOut, monitor);
    687             } catch (Throwable t) {
    688                 handleException(t, "Failed to run aidl. Check workspace log for detail.");
    689                 return result;
    690             }
    691 
    692             try {
    693                 processorStatus |= compileRs(minSdkValue, projectState, androidOutputFolder,
    694                         resOutFolder, monitor);
    695             } catch (Throwable t) {
    696                 handleException(t, "Failed to run renderscript. Check workspace log for detail.");
    697                 return result;
    698             }
    699 
    700             // if a processor created some resources file, force recompilation of the resources.
    701             if ((processorStatus & SourceProcessor.COMPILE_STATUS_RES) != 0) {
    702                 mMustCompileResources = true;
    703                 // save the current state before attempting the compilation
    704                 saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES, mMustCompileResources);
    705             }
    706 
    707             // handle the resources, after the processors are run since some (renderscript)
    708             // generate resources.
    709             boolean compiledTheResources = mMustCompileResources;
    710             if (mMustCompileResources) {
    711                 if (DEBUG_LOG) {
    712                     AdtPlugin.log(IStatus.INFO, "%s compiling resources!", project.getName());
    713                 }
    714 
    715                 IFile proguardFile = null;
    716                 if (projectState.getProperty(ProjectProperties.PROPERTY_PROGUARD_CONFIG) != null) {
    717                     proguardFile = androidOutputFolder.getFile(AdtConstants.FN_AAPT_PROGUARD);
    718                 }
    719 
    720                 handleResources(project, javaPackage, projectTarget, manifestFile, resOutFolder,
    721                         libProjects, isLibrary, proguardFile);
    722             }
    723 
    724             if (processorStatus == SourceProcessor.COMPILE_STATUS_NONE &&
    725                     compiledTheResources == false) {
    726                 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project,
    727                         Messages.Nothing_To_Compile);
    728             }
    729         } catch (AbortBuildException e) {
    730             return result;
    731         } finally {
    732             // refresh the 'gen' source folder. Once this is done with the custom progress
    733             // monitor to mark all new files as derived
    734             mGenFolder.refreshLocal(IResource.DEPTH_INFINITE, mDerivedProgressMonitor);
    735             if (resOutFolder != null) {
    736                 resOutFolder.refreshLocal(IResource.DEPTH_INFINITE, mDerivedProgressMonitor);
    737             }
    738         }
    739 
    740         return result;
    741     }
    742 
    743     private IFolder getResOutFolder(IFolder androidOutputFolder) {
    744         return androidOutputFolder.getFolder(AdtConstants.WS_BIN_RELATIVE_BC);
    745     }
    746 
    747     @Override
    748     protected void clean(IProgressMonitor monitor) throws CoreException {
    749         super.clean(monitor);
    750 
    751         if (DEBUG_LOG) {
    752             AdtPlugin.log(IStatus.INFO, "%s CLEAN(PRE)", getProject().getName());
    753         }
    754 
    755         doClean(getProject(), monitor);
    756         if (mGenFolder != null) {
    757             mGenFolder.refreshLocal(IResource.DEPTH_INFINITE, monitor);
    758         }
    759     }
    760 
    761     private void doClean(IProject project, IProgressMonitor monitor) throws CoreException {
    762         AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project,
    763                 Messages.Removing_Generated_Classes);
    764 
    765         // remove all the derived resources from the 'gen' source folder.
    766         if (mGenFolder != null && mGenFolder.exists()) {
    767             // gen folder should not be derived, but previous version could set it to derived
    768             // so we make sure this isn't the case (or it'll get deleted by the clean)
    769             mGenFolder.setDerived(false, monitor);
    770 
    771             removeDerivedResources(mGenFolder, monitor);
    772         }
    773 
    774         // Clear the project of the generic markers
    775         removeMarkersFromContainer(project, AdtConstants.MARKER_AAPT_COMPILE);
    776         removeMarkersFromContainer(project, AdtConstants.MARKER_XML);
    777         removeMarkersFromContainer(project, AdtConstants.MARKER_AIDL);
    778         removeMarkersFromContainer(project, AdtConstants.MARKER_RENDERSCRIPT);
    779         removeMarkersFromContainer(project, AdtConstants.MARKER_MANIFMERGER);
    780         removeMarkersFromContainer(project, AdtConstants.MARKER_ANDROID);
    781 
    782         // Also clean up lint
    783         EclipseLintClient.clearMarkers(project);
    784 
    785         // clean the project repo
    786         ProjectResources res = ResourceManager.getInstance().getProjectResources(project);
    787         res.clear();
    788     }
    789 
    790     @Override
    791     protected void startupOnInitialize() {
    792         try {
    793             super.startupOnInitialize();
    794 
    795             IProject project = getProject();
    796 
    797             // load the previous IFolder and java package.
    798             mManifestPackage = loadProjectStringProperty(PROPERTY_PACKAGE);
    799 
    800             // get the source folder in which all the Java files are created
    801             mGenFolder = project.getFolder(SdkConstants.FD_GEN_SOURCES);
    802             mDerivedProgressMonitor = new DerivedProgressMonitor(mGenFolder);
    803 
    804             // Load the current compile flags. We ask for true if not found to force a recompile.
    805             mMustMergeManifest = loadProjectBooleanProperty(PROPERTY_MERGE_MANIFEST, true);
    806             mMustCompileResources = loadProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES, true);
    807             mMustCreateBuildConfig = loadProjectBooleanProperty(PROPERTY_COMPILE_BUILDCONFIG, true);
    808             Boolean v = ProjectHelper.loadBooleanProperty(project, PROPERTY_BUILDCONFIG_MODE);
    809             if (v == null) {
    810                 // no previous build config mode? force regenerate
    811                 mMustCreateBuildConfig = true;
    812             } else {
    813                 mLastBuildConfigMode = v;
    814             }
    815 
    816         } catch (Throwable throwable) {
    817             AdtPlugin.log(throwable, "Failed to finish PrecompilerBuilder#startupOnInitialize()");
    818         }
    819     }
    820 
    821     private void setupSourceProcessors(@NonNull IJavaProject javaProject,
    822             @NonNull ProjectState projectState,
    823             @NonNull List<IPath> sourceFolderPathList,
    824             @NonNull IFolder androidOutputFolder) {
    825         if (mAidlProcessor == null) {
    826             mAidlProcessor = new AidlProcessor(javaProject, mBuildToolInfo, mGenFolder);
    827         } else {
    828             mAidlProcessor.setBuildToolInfo(mBuildToolInfo);
    829         }
    830 
    831         List<File> sourceFolders = Lists.newArrayListWithCapacity(sourceFolderPathList.size());
    832         IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
    833 
    834         for (IPath path : sourceFolderPathList) {
    835             IResource resource = root.findMember(path);
    836             if (resource != null && resource.exists() && resource.getType() == IResource.FOLDER) {
    837                 IPath fullPath = resource.getLocation();
    838                 if (fullPath != null) {
    839                     sourceFolders.add(fullPath.toFile());
    840                 }
    841             }
    842         }
    843 
    844         RenderScriptChecker checker = new RenderScriptChecker(sourceFolders,
    845                 androidOutputFolder.getLocation().toFile());
    846         mRenderScriptSourceChangeHandler = new RsSourceChangeHandler(checker);
    847     }
    848 
    849     private int compileRs(int minSdkValue,
    850             @NonNull ProjectState projectState,
    851             @NonNull IFolder androidOutputFolder,
    852             @NonNull IFolder resOutFolder,
    853             @NonNull IProgressMonitor monitor)
    854             throws IOException, InterruptedException {
    855         if (!mRenderScriptSourceChangeHandler.mustCompile()) {
    856             return SourceProcessor.COMPILE_STATUS_NONE;
    857         }
    858 
    859         RenderScriptChecker checker = mRenderScriptSourceChangeHandler.getChecker();
    860 
    861         List<File> inputs = checker.findInputFiles();
    862         List<File> importFolders = checker.getSourceFolders();
    863         File buildFolder = androidOutputFolder.getLocation().toFile();
    864 
    865 
    866         // get the renderscript target
    867         int rsTarget = minSdkValue == -1 ? 11 : minSdkValue;
    868         String rsTargetStr = projectState.getProperty(ProjectProperties.PROPERTY_RS_TARGET);
    869         if (rsTargetStr != null) {
    870             try {
    871                 rsTarget = Integer.parseInt(rsTargetStr);
    872             } catch (NumberFormatException e) {
    873                 handleException(e, String.format(
    874                         "Property %s is not an integer.",
    875                         ProjectProperties.PROPERTY_RS_TARGET));
    876                 return SourceProcessor.COMPILE_STATUS_NONE;
    877             }
    878         }
    879 
    880         RenderScriptProcessor processor = new RenderScriptProcessor(
    881                 inputs,
    882                 importFolders,
    883                 buildFolder,
    884                 mGenFolder.getLocation().toFile(),
    885                 resOutFolder.getLocation().toFile(),
    886                 new File(buildFolder, SdkConstants.FD_RS_OBJ),
    887                 new File(buildFolder, SdkConstants.FD_RS_LIBS),
    888                 mBuildToolInfo,
    889                 rsTarget,
    890                 false /*debugBuild, always false for now*/,
    891                 3,
    892                 projectState.getRenderScriptSupportMode());
    893 
    894         // clean old dependency files fiest
    895         checker.cleanDependencies();
    896 
    897         // then clean old output files
    898         processor.cleanOldOutput(checker.getOldOutputs());
    899 
    900         RenderScriptLauncher launcher = new RenderScriptLauncher(
    901                 getProject(),
    902                 mGenFolder,
    903                 resOutFolder,
    904                 monitor,
    905                 AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE /*verbose*/);
    906 
    907         // and run the build
    908         processor.build(launcher);
    909 
    910         return SourceProcessor.COMPILE_STATUS_CODE | SourceProcessor.COMPILE_STATUS_RES;
    911     }
    912 
    913     @SuppressWarnings("deprecation")
    914     private void handleBuildConfig(@SuppressWarnings("rawtypes") Map args)
    915             throws IOException, CoreException {
    916         boolean debugMode = !args.containsKey(RELEASE_REQUESTED);
    917 
    918         BuildConfigGenerator generator = new BuildConfigGenerator(
    919                 mGenFolder.getLocation().toOSString(), mManifestPackage, debugMode);
    920 
    921         if (mMustCreateBuildConfig == false) {
    922             // check the file is present.
    923             IFolder folder = getGenManifestPackageFolder();
    924             if (folder.exists(new Path(BuildConfigGenerator.BUILD_CONFIG_NAME)) == false) {
    925                 mMustCreateBuildConfig = true;
    926                 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, getProject(),
    927                         String.format("Class %1$s is missing!",
    928                                 BuildConfigGenerator.BUILD_CONFIG_NAME));
    929             } else if (debugMode != mLastBuildConfigMode) {
    930                 // else if the build mode changed, force creation
    931                 mMustCreateBuildConfig = true;
    932                 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, getProject(),
    933                         String.format("Different build mode, must update %1$s!",
    934                                 BuildConfigGenerator.BUILD_CONFIG_NAME));
    935             }
    936         }
    937 
    938         if (mMustCreateBuildConfig) {
    939             if (DEBUG_LOG) {
    940                 AdtPlugin.log(IStatus.INFO, "%s generating BuilderConfig!", getProject().getName());
    941             }
    942 
    943             AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, getProject(),
    944                     String.format("Generating %1$s...", BuildConfigGenerator.BUILD_CONFIG_NAME));
    945             generator.generate();
    946 
    947             mMustCreateBuildConfig = false;
    948             saveProjectBooleanProperty(PROPERTY_COMPILE_BUILDCONFIG, mMustCreateBuildConfig);
    949             saveProjectBooleanProperty(PROPERTY_BUILDCONFIG_MODE, mLastBuildConfigMode = debugMode);
    950         }
    951     }
    952 
    953     private boolean mergeManifest(IFolder androidOutFolder, List<IProject> libProjects,
    954             boolean enabled) throws CoreException {
    955         if (DEBUG_LOG) {
    956             AdtPlugin.log(IStatus.INFO, "%s merging manifests!", getProject().getName());
    957         }
    958 
    959         IFile outFile = androidOutFolder.getFile(SdkConstants.FN_ANDROID_MANIFEST_XML);
    960         IFile manifest = getProject().getFile(SdkConstants.FN_ANDROID_MANIFEST_XML);
    961 
    962         // remove existing markers from the manifest.
    963         // FIXME: only remove from manifest once the markers are put there.
    964         removeMarkersFromResource(getProject(), AdtConstants.MARKER_MANIFMERGER);
    965 
    966         // If the merging is not enabled or if there's no library then we simply copy the
    967         // manifest over.
    968         if (enabled == false || libProjects.size() == 0) {
    969             try {
    970                 new FileOp().copyFile(manifest.getLocation().toFile(),
    971                         outFile.getLocation().toFile());
    972 
    973                 outFile.refreshLocal(IResource.DEPTH_INFINITE, mDerivedProgressMonitor);
    974 
    975                 saveProjectBooleanProperty(PROPERTY_MERGE_MANIFEST, mMustMergeManifest = false);
    976             } catch (IOException e) {
    977                 handleException(e, "Failed to copy Manifest");
    978                 return false;
    979             }
    980         } else {
    981             final ArrayList<String> errors = new ArrayList<String>();
    982 
    983             // TODO change MergerLog.wrapSdkLog by a custom IMergerLog that will create
    984             // and maintain error markers.
    985             ManifestMerger merger = new ManifestMerger(
    986                 MergerLog.wrapSdkLog(new ILogger() {
    987                     @Override
    988                     public void warning(@NonNull String warningFormat, Object... args) {
    989                         AdtPlugin.printToConsole(getProject(), String.format(warningFormat, args));
    990                     }
    991 
    992                     @Override
    993                     public void info(@NonNull String msgFormat, Object... args) {
    994                         AdtPlugin.printToConsole(getProject(), String.format(msgFormat, args));
    995                     }
    996 
    997                     @Override
    998                     public void verbose(@NonNull String msgFormat, Object... args) {
    999                         info(msgFormat, args);
   1000                     }
   1001 
   1002                     @Override
   1003                     public void error(@Nullable Throwable t, @Nullable String errorFormat,
   1004                             Object... args) {
   1005                         errors.add(String.format(errorFormat, args));
   1006                     }
   1007                 }),
   1008                 new AdtManifestMergeCallback());
   1009 
   1010             File[] libManifests = new File[libProjects.size()];
   1011             int libIndex = 0;
   1012             for (IProject lib : libProjects) {
   1013                 libManifests[libIndex++] = lib.getFile(SdkConstants.FN_ANDROID_MANIFEST_XML)
   1014                         .getLocation().toFile();
   1015             }
   1016 
   1017             if (merger.process(
   1018                     outFile.getLocation().toFile(),
   1019                     manifest.getLocation().toFile(),
   1020                     libManifests,
   1021                     null /*injectAttributes*/, null /*packageOverride*/) == false) {
   1022                 if (errors.size() > 1) {
   1023                     StringBuilder sb = new StringBuilder();
   1024                     for (String s : errors) {
   1025                         sb.append(s).append('\n');
   1026                     }
   1027 
   1028                     markProject(AdtConstants.MARKER_MANIFMERGER, sb.toString(),
   1029                             IMarker.SEVERITY_ERROR);
   1030 
   1031                 } else if (errors.size() == 1) {
   1032                     markProject(AdtConstants.MARKER_MANIFMERGER, errors.get(0),
   1033                             IMarker.SEVERITY_ERROR);
   1034                 } else {
   1035                     markProject(AdtConstants.MARKER_MANIFMERGER, "Unknown error merging manifest",
   1036                             IMarker.SEVERITY_ERROR);
   1037                 }
   1038                 return false;
   1039             }
   1040 
   1041             outFile.refreshLocal(IResource.DEPTH_INFINITE, mDerivedProgressMonitor);
   1042             saveProjectBooleanProperty(PROPERTY_MERGE_MANIFEST, mMustMergeManifest = false);
   1043         }
   1044 
   1045         return true;
   1046     }
   1047 
   1048     /**
   1049      * Handles resource changes and regenerate whatever files need regenerating.
   1050      * @param project the main project
   1051      * @param javaPackage the app package for the main project
   1052      * @param projectTarget the target of the main project
   1053      * @param manifest the {@link IFile} representing the project manifest
   1054      * @param libProjects the library dependencies
   1055      * @param isLibrary if the project is a library project
   1056      * @throws CoreException
   1057      * @throws AbortBuildException
   1058      */
   1059     private void handleResources(IProject project, String javaPackage, IAndroidTarget projectTarget,
   1060             IFile manifest, IFolder resOutFolder, List<IProject> libProjects, boolean isLibrary,
   1061             IFile proguardFile) throws CoreException, AbortBuildException {
   1062         // get the resource folder
   1063         IFolder resFolder = project.getFolder(AdtConstants.WS_RESOURCES);
   1064 
   1065         // get the file system path
   1066         IPath outputLocation = mGenFolder.getLocation();
   1067         IPath resLocation = resFolder.getLocation();
   1068         IPath manifestLocation = manifest == null ? null : manifest.getLocation();
   1069 
   1070         // those locations have to exist for us to do something!
   1071         if (outputLocation != null && resLocation != null
   1072                 && manifestLocation != null) {
   1073             String osOutputPath = outputLocation.toOSString();
   1074             String osResPath = resLocation.toOSString();
   1075             String osManifestPath = manifestLocation.toOSString();
   1076 
   1077             // remove the aapt markers
   1078             removeMarkersFromResource(manifest, AdtConstants.MARKER_AAPT_COMPILE);
   1079             removeMarkersFromContainer(resFolder, AdtConstants.MARKER_AAPT_COMPILE);
   1080 
   1081             AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project,
   1082                     Messages.Preparing_Generated_Files);
   1083 
   1084             // we need to figure out where to store the R class.
   1085             // get the parent folder for R.java and update mManifestPackageSourceFolder
   1086             IFolder mainPackageFolder = getGenManifestPackageFolder();
   1087 
   1088             // handle libraries
   1089             ArrayList<IFolder> libResFolders = Lists.newArrayList();
   1090             ArrayList<Pair<File, String>> libRFiles = Lists.newArrayList();
   1091             if (libProjects != null) {
   1092                 for (IProject lib : libProjects) {
   1093                     IFolder libResFolder = lib.getFolder(SdkConstants.FD_RES);
   1094                     if (libResFolder.exists()) {
   1095                         libResFolders.add(libResFolder);
   1096                     }
   1097 
   1098                     try {
   1099                         // get the package of the library, and if it's different form the
   1100                         // main project, generate the R class for it too.
   1101                         String libJavaPackage = AndroidManifest.getPackage(new IFolderWrapper(lib));
   1102                         if (libJavaPackage.equals(javaPackage) == false) {
   1103 
   1104                             IFolder libOutput = BaseProjectHelper.getAndroidOutputFolder(lib);
   1105                             File libOutputFolder = libOutput.getLocation().toFile();
   1106 
   1107                             libRFiles.add(Pair.of(
   1108                                     new File(libOutputFolder, "R.txt"),
   1109                                     libJavaPackage));
   1110 
   1111                         }
   1112                     } catch (Exception e) {
   1113                     }
   1114                 }
   1115             }
   1116 
   1117             String proguardFilePath = proguardFile != null ?
   1118                     proguardFile.getLocation().toOSString(): null;
   1119 
   1120             File resOutFile = resOutFolder.getLocation().toFile();
   1121             String resOutPath = resOutFile.isDirectory() ? resOutFile.getAbsolutePath() : null;
   1122 
   1123             execAapt(project, projectTarget, osOutputPath, resOutPath, osResPath, osManifestPath,
   1124                     mainPackageFolder, libResFolders, libRFiles, isLibrary, proguardFilePath);
   1125         }
   1126     }
   1127 
   1128     /**
   1129      * Executes AAPT to generate R.java/Manifest.java
   1130      * @param project the main project
   1131      * @param projectTarget the main project target
   1132      * @param osOutputPath the OS output path for the generated file. This is the source folder, not
   1133      * the package folder.
   1134      * @param osResPath the OS path to the res folder for the main project
   1135      * @param osManifestPath the OS path to the manifest of the main project
   1136      * @param packageFolder the IFolder that will contain the generated file. Unlike
   1137      * <var>osOutputPath</var> this is the direct parent of the generated files.
   1138      * If <var>customJavaPackage</var> is not null, this must match the new destination triggered
   1139      * by its value.
   1140      * @param libResFolders the list of res folders for the library.
   1141      * @param libRFiles a list of R files for the libraries.
   1142      * @param isLibrary if the project is a library project
   1143      * @param proguardFile an optional path to store proguard information
   1144      * @throws AbortBuildException
   1145      */
   1146     @SuppressWarnings("deprecation")
   1147     private void execAapt(IProject project, IAndroidTarget projectTarget, String osOutputPath,
   1148             String osBcOutPath, String osResPath, String osManifestPath, IFolder packageFolder,
   1149             ArrayList<IFolder> libResFolders, List<Pair<File, String>> libRFiles,
   1150             boolean isLibrary, String proguardFile)
   1151             throws AbortBuildException {
   1152 
   1153         // We actually need to delete the manifest.java as it may become empty and
   1154         // in this case aapt doesn't generate an empty one, but instead doesn't
   1155         // touch it.
   1156         IFile manifestJavaFile = packageFolder.getFile(SdkConstants.FN_MANIFEST_CLASS);
   1157         manifestJavaFile.getLocation().toFile().delete();
   1158 
   1159         // launch aapt: create the command line
   1160         ArrayList<String> array = new ArrayList<String>();
   1161 
   1162         String aaptPath = mBuildToolInfo.getPath(BuildToolInfo.PathId.AAPT);
   1163 
   1164         array.add(aaptPath);
   1165         array.add("package"); //$NON-NLS-1$
   1166         array.add("-m"); //$NON-NLS-1$
   1167         if (AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE) {
   1168             array.add("-v"); //$NON-NLS-1$
   1169         }
   1170 
   1171         if (isLibrary) {
   1172             array.add("--non-constant-id"); //$NON-NLS-1$
   1173         }
   1174 
   1175         if (libResFolders.size() > 0) {
   1176             array.add("--auto-add-overlay"); //$NON-NLS-1$
   1177         }
   1178 
   1179         // If a library or has libraries, generate a text version of the R symbols.
   1180         File outputFolder = BaseProjectHelper.getAndroidOutputFolder(project).getLocation()
   1181                 .toFile();
   1182 
   1183         if (isLibrary || !libRFiles.isEmpty()) {
   1184             array.add("--output-text-symbols"); //$NON-NLS-1$
   1185             array.add(outputFolder.getAbsolutePath());
   1186         }
   1187 
   1188         array.add("-J"); //$NON-NLS-1$
   1189         array.add(osOutputPath);
   1190         array.add("-M"); //$NON-NLS-1$
   1191         array.add(osManifestPath);
   1192         if (osBcOutPath != null) {
   1193             array.add("-S"); //$NON-NLS-1$
   1194             array.add(osBcOutPath);
   1195         }
   1196         array.add("-S"); //$NON-NLS-1$
   1197         array.add(osResPath);
   1198         for (IFolder libResFolder : libResFolders) {
   1199             array.add("-S"); //$NON-NLS-1$
   1200             array.add(libResFolder.getLocation().toOSString());
   1201         }
   1202 
   1203         array.add("-I"); //$NON-NLS-1$
   1204         array.add(projectTarget.getPath(IAndroidTarget.ANDROID_JAR));
   1205 
   1206         // use the proguard file
   1207         if (proguardFile != null && proguardFile.length() > 0) {
   1208             array.add("-G");
   1209             array.add(proguardFile);
   1210         }
   1211 
   1212         if (AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE) {
   1213             StringBuilder sb = new StringBuilder();
   1214             for (String c : array) {
   1215                 sb.append(c);
   1216                 sb.append(' ');
   1217             }
   1218             String cmd_line = sb.toString();
   1219             AdtPlugin.printToConsole(project, cmd_line);
   1220         }
   1221 
   1222         // launch
   1223         try {
   1224             // launch the command line process
   1225             Process process = Runtime.getRuntime().exec(
   1226                     array.toArray(new String[array.size()]));
   1227 
   1228             // list to store each line of stderr
   1229             ArrayList<String> stdErr = new ArrayList<String>();
   1230 
   1231             // get the output and return code from the process
   1232             int returnCode = grabProcessOutput(process, stdErr);
   1233 
   1234             // attempt to parse the error output
   1235             boolean parsingError = AaptParser.parseOutput(stdErr, project);
   1236 
   1237             // if we couldn't parse the output we display it in the console.
   1238             if (parsingError) {
   1239                 if (returnCode != 0) {
   1240                     AdtPlugin.printErrorToConsole(project, stdErr.toArray());
   1241                 } else {
   1242                     AdtPlugin.printBuildToConsole(BuildVerbosity.NORMAL,
   1243                             project, stdErr.toArray());
   1244                 }
   1245             }
   1246 
   1247             if (returnCode != 0) {
   1248                 // if the exec failed, and we couldn't parse the error output
   1249                 // (and therefore not all files that should have been marked,
   1250                 // were marked), we put a generic marker on the project and abort.
   1251                 if (parsingError) {
   1252                     markProject(AdtConstants.MARKER_ADT,
   1253                             Messages.Unparsed_AAPT_Errors, IMarker.SEVERITY_ERROR);
   1254                 } else if (stdErr.size() == 0) {
   1255                     // no parsing error because sdterr was empty. We still need to put
   1256                     // a marker otherwise there's no user visible feedback.
   1257                     markProject(AdtConstants.MARKER_ADT,
   1258                             String.format(Messages.AAPT_Exec_Error_d, returnCode),
   1259                             IMarker.SEVERITY_ERROR);
   1260                 }
   1261 
   1262                 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project,
   1263                         Messages.AAPT_Error);
   1264 
   1265                 // abort if exec failed.
   1266                 throw new AbortBuildException();
   1267             }
   1268 
   1269             // now if the project has libraries, R needs to be created for each libraries
   1270             // unless this is a library.
   1271             if (isLibrary == false && !libRFiles.isEmpty()) {
   1272                 File rFile = new File(outputFolder, SdkConstants.FN_RESOURCE_TEXT);
   1273                 // if the project has no resources, the file could not exist.
   1274                 if (rFile.isFile()) {
   1275                     // Load the full symbols from the full R.txt file.
   1276                     SymbolLoader fullSymbolValues = new SymbolLoader(rFile);
   1277                     fullSymbolValues.load();
   1278 
   1279                     Multimap<String, SymbolLoader> libMap = ArrayListMultimap.create();
   1280 
   1281                     // First pass processing the libraries, collecting them by packageName,
   1282                     // and ignoring the ones that have the same package name as the application
   1283                     // (since that R class was already created).
   1284 
   1285                     for (Pair<File, String> lib : libRFiles) {
   1286                         String libPackage = lib.getSecond();
   1287                         File rText = lib.getFirst();
   1288 
   1289                         if (rText.isFile()) {
   1290                             // load the lib symbols
   1291                             SymbolLoader libSymbols = new SymbolLoader(rText);
   1292                             libSymbols.load();
   1293 
   1294                             // store these symbols by associating them with the package name.
   1295                             libMap.put(libPackage, libSymbols);
   1296                         }
   1297                     }
   1298 
   1299                     // now loop on all the package names, merge all the symbols to write,
   1300                     // and write them
   1301                     for (String packageName : libMap.keySet()) {
   1302                         Collection<SymbolLoader> symbols = libMap.get(packageName);
   1303 
   1304                         SymbolWriter writer = new SymbolWriter(osOutputPath, packageName,
   1305                                 fullSymbolValues);
   1306                         for (SymbolLoader symbolLoader : symbols) {
   1307                             writer.addSymbolsToWrite(symbolLoader);
   1308                         }
   1309                         writer.write();
   1310                     }
   1311                 }
   1312             }
   1313 
   1314         } catch (IOException e1) {
   1315             // something happen while executing the process,
   1316             // mark the project and exit
   1317             String msg;
   1318             String path = array.get(0);
   1319             if (!new File(path).exists()) {
   1320                 msg = String.format(Messages.AAPT_Exec_Error_s, path);
   1321             } else {
   1322                 String description = e1.getLocalizedMessage();
   1323                 if (e1.getCause() != null && e1.getCause() != e1) {
   1324                     description = description + ": " + e1.getCause().getLocalizedMessage();
   1325                 }
   1326                 msg = String.format(Messages.AAPT_Exec_Error_Other_s, description);
   1327             }
   1328 
   1329             markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
   1330 
   1331             // Add workaround for the Linux problem described here:
   1332             //    http://developer.android.com/sdk/installing.html#troubleshooting
   1333             // There are various posts on StackOverflow elsewhere where people are asking
   1334             // about aapt failing to run, so even though this is documented in the
   1335             // Troubleshooting section add an error message to help with this
   1336             // scenario.
   1337             if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_LINUX
   1338                     && System.getProperty("os.arch").endsWith("64") //$NON-NLS-1$ //$NON-NLS-2$
   1339                     && new File(aaptPath).exists()
   1340                     && new File("/usr/bin/apt-get").exists()) {     //$NON-NLS-1$
   1341                 markProject(AdtConstants.MARKER_ADT,
   1342                         "Hint: On 64-bit systems, make sure the 32-bit libraries are installed: \"sudo apt-get install ia32-libs\" or on some systems, \"sudo apt-get install lib32z1\"",
   1343                         IMarker.SEVERITY_ERROR);
   1344                 // Note - this uses SEVERITY_ERROR even though it's really SEVERITY_INFO because
   1345                 // we want this error message to show up adjacent to the aapt error message
   1346                 // (and Eclipse sorts by priority)
   1347             }
   1348 
   1349             // This interrupts the build.
   1350             throw new AbortBuildException();
   1351         } catch (InterruptedException e) {
   1352             // we got interrupted waiting for the process to end...
   1353             // mark the project and exit
   1354             String msg = String.format(Messages.AAPT_Exec_Error_s, array.get(0));
   1355             markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
   1356 
   1357             // This interrupts the build.
   1358             throw new AbortBuildException();
   1359         } finally {
   1360             // we've at least attempted to run aapt, save the fact that we don't have to
   1361             // run it again, unless there's a new resource change.
   1362             saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES,
   1363                     mMustCompileResources = false);
   1364             ResourceManager.clearAaptRequest(project);
   1365         }
   1366     }
   1367 
   1368     /**
   1369      * Creates a relative {@link IPath} from a java package.
   1370      * @param javaPackageName the java package.
   1371      */
   1372     private IPath getJavaPackagePath(String javaPackageName) {
   1373         // convert the java package into path
   1374         String[] segments = javaPackageName.split(AdtConstants.RE_DOT);
   1375 
   1376         StringBuilder path = new StringBuilder();
   1377         for (String s : segments) {
   1378            path.append(AdtConstants.WS_SEP_CHAR);
   1379            path.append(s);
   1380         }
   1381 
   1382         return new Path(path.toString());
   1383     }
   1384 
   1385     /**
   1386      * Returns an {@link IFolder} (located inside the 'gen' source folder), that matches the
   1387      * package defined in the manifest. This {@link IFolder} may not actually exist
   1388      * (aapt will create it anyway).
   1389      * @return the {@link IFolder} that will contain the R class or null if
   1390      * the folder was not found.
   1391      * @throws CoreException
   1392      */
   1393     private IFolder getGenManifestPackageFolder() throws CoreException {
   1394         // get the path for the package
   1395         IPath packagePath = getJavaPackagePath(mManifestPackage);
   1396 
   1397         // get a folder for this path under the 'gen' source folder, and return it.
   1398         // This IFolder may not reference an actual existing folder.
   1399         return mGenFolder.getFolder(packagePath);
   1400     }
   1401 }
   1402