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