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