• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
DerivedProgressMonitor(IFolder genFolder)162         public DerivedProgressMonitor(IFolder genFolder) {
163             mGenFolder = genFolder;
164         }
165 
reset()166         void reset() {
167             mDone = false;
168         }
169 
170         @Override
beginTask(String name, int totalWork)171         public void beginTask(String name, int totalWork) {
172         }
173 
174         @Override
done()175         public void done() {
176             if (mDone == false) {
177                 mDone = true;
178                 processChildrenOf(mGenFolder);
179             }
180         }
181 
processChildrenOf(IFolder folder)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
internalWorked(double work)208         public void internalWorked(double work) {
209         }
210 
211         @Override
isCanceled()212         public boolean isCanceled() {
213             return mCancelled;
214         }
215 
216         @Override
setCanceled(boolean value)217         public void setCanceled(boolean value) {
218             mCancelled = value;
219         }
220 
221         @Override
setTaskName(String name)222         public void setTaskName(String name) {
223         }
224 
225         @Override
subTask(String name)226         public void subTask(String name) {
227         }
228 
229         @Override
worked(int work)230         public void worked(int work) {
231         }
232     }
233 
PreCompilerBuilder()234     public PreCompilerBuilder() {
235         super();
236     }
237 
238     // build() returns a list of project from which this project depends for future compilation.
239     @Override
build( int kind, @SuppressWarnings("rawtypes") Map args, IProgressMonitor monitor)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
clean(IProgressMonitor monitor)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 
doClean(IProject project, IProgressMonitor monitor)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
startupOnInitialize()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 
setupSourceProcessors(@onNull IJavaProject javaProject, @NonNull ProjectState projectState)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")
handleBuildConfig(@uppressWarnings"rawtypes") Map args)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 
mergeManifest(IFolder androidOutFolder, List<IProject> libProjects, boolean enabled)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      */
handleResources(IProject project, String javaPackage, IAndroidTarget projectTarget, IFile manifest, List<IProject> libProjects, boolean isLibrary, IFile proguardFile)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")
execAapt(IProject project, IAndroidTarget projectTarget, String osOutputPath, String osResPath, String osManifestPath, IFolder packageFolder, ArrayList<IFolder> libResFolders, List<Pair<File, String>> libRFiles, boolean isLibrary, String proguardFile)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      */
getJavaPackagePath(String javaPackageName)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      */
getGenManifestPackageFolder()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