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