• 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;
18 
19 import com.android.ide.eclipse.adt.AdtPlugin;
20 import com.android.ide.eclipse.adt.AndroidConstants;
21 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
22 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity;
23 import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
24 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
25 import com.android.ide.eclipse.adt.internal.project.FixLaunchConfig;
26 import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
27 import com.android.ide.eclipse.adt.internal.project.XmlErrorHandler.BasicXmlErrorListener;
28 import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
29 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
30 import com.android.ide.eclipse.adt.io.IFileWrapper;
31 import com.android.ide.eclipse.adt.io.IFolderWrapper;
32 import com.android.sdklib.AndroidVersion;
33 import com.android.sdklib.IAndroidTarget;
34 import com.android.sdklib.SdkConstants;
35 import com.android.sdklib.xml.AndroidManifest;
36 import com.android.sdklib.xml.ManifestData;
37 
38 import org.eclipse.core.resources.IContainer;
39 import org.eclipse.core.resources.IFile;
40 import org.eclipse.core.resources.IFolder;
41 import org.eclipse.core.resources.IMarker;
42 import org.eclipse.core.resources.IProject;
43 import org.eclipse.core.resources.IResource;
44 import org.eclipse.core.resources.IResourceDelta;
45 import org.eclipse.core.resources.IWorkspaceRoot;
46 import org.eclipse.core.resources.ResourcesPlugin;
47 import org.eclipse.core.runtime.CoreException;
48 import org.eclipse.core.runtime.IPath;
49 import org.eclipse.core.runtime.IProgressMonitor;
50 import org.eclipse.core.runtime.Path;
51 import org.eclipse.core.runtime.SubProgressMonitor;
52 import org.eclipse.jdt.core.IJavaProject;
53 import org.eclipse.jdt.core.JavaCore;
54 
55 import java.io.IOException;
56 import java.util.ArrayList;
57 import java.util.Map;
58 import java.util.regex.Matcher;
59 import java.util.regex.Pattern;
60 
61 /**
62  * Pre Java Compiler.
63  * This incremental builder performs 2 tasks:
64  * <ul>
65  * <li>compiles the resources located in the res/ folder, along with the
66  * AndroidManifest.xml file into the R.java class.</li>
67  * <li>compiles any .aidl files into a corresponding java file.</li>
68  * </ul>
69  *
70  */
71 public class PreCompilerBuilder extends BaseBuilder {
72 
73     /** This ID is used in plugin.xml and in each project's .project file.
74      * It cannot be changed even if the class is renamed/moved */
75     public static final String ID = "com.android.ide.eclipse.adt.PreCompilerBuilder"; //$NON-NLS-1$
76 
77     private static final String PROPERTY_PACKAGE = "manifestPackage"; //$NON-NLS-1$
78 
79     private static final String PROPERTY_COMPILE_RESOURCES = "compileResources"; //$NON-NLS-1$
80     private static final String PROPERTY_COMPILE_AIDL = "compileAidl"; //$NON-NLS-1$
81 
82     /**
83      * Single line aidl error<br>
84      * "&lt;path&gt;:&lt;line&gt;: &lt;error&gt;"
85      * or
86      * "&lt;path&gt;:&lt;line&gt; &lt;error&gt;"
87      */
88     private static Pattern sAidlPattern1 = Pattern.compile("^(.+?):(\\d+):?\\s(.+)$"); //$NON-NLS-1$
89 
90     /**
91      * Data to temporarly store aidl source file information
92      */
93     static class AidlData {
94         IFile aidlFile;
95         IFolder sourceFolder;
96 
AidlData(IFolder sourceFolder, IFile aidlFile)97         AidlData(IFolder sourceFolder, IFile aidlFile) {
98             this.sourceFolder = sourceFolder;
99             this.aidlFile = aidlFile;
100         }
101 
102         @Override
equals(Object obj)103         public boolean equals(Object obj) {
104             if (this == obj) {
105                 return true;
106             }
107 
108             if (obj instanceof AidlData) {
109                 AidlData file = (AidlData)obj;
110                 return aidlFile.equals(file.aidlFile) && sourceFolder.equals(file.sourceFolder);
111             }
112 
113             return false;
114         }
115     }
116 
117     /**
118      * Resource Compile flag. This flag is reset to false after each successful compilation, and
119      * stored in the project persistent properties. This allows the builder to remember its state
120      * when the project is closed/opened.
121      */
122     private boolean mMustCompileResources = false;
123 
124     /** List of .aidl files found that are modified or new. */
125     private final ArrayList<AidlData> mAidlToCompile = new ArrayList<AidlData>();
126 
127     /** List of .aidl files that have been removed. */
128     private final ArrayList<AidlData> mAidlToRemove = new ArrayList<AidlData>();
129 
130     /** cache of the java package defined in the manifest */
131     private String mManifestPackage;
132 
133     /** Output folder for generated Java File. Created on the Builder init
134      * @see #startupOnInitialize()
135      */
136     private IFolder mGenFolder;
137 
138     /**
139      * Progress monitor used at the end of every build to refresh the content of the 'gen' folder
140      * and set the generated files as derived.
141      */
142     private DerivedProgressMonitor mDerivedProgressMonitor;
143 
144     /**
145      * Progress monitor waiting the end of the process to set a persistent value
146      * in a file. This is typically used in conjunction with <code>IResource.refresh()</code>,
147      * since this call is asysnchronous, and we need to wait for it to finish for the file
148      * to be known by eclipse, before we can call <code>resource.setPersistentProperty</code> on
149      * a new file.
150      */
151     private static class DerivedProgressMonitor implements IProgressMonitor {
152         private boolean mCancelled = false;
153         private final ArrayList<IFile> mFileList = new ArrayList<IFile>();
154         private boolean mDone = false;
DerivedProgressMonitor()155         public DerivedProgressMonitor() {
156         }
157 
addFile(IFile file)158         void addFile(IFile file) {
159             mFileList.add(file);
160         }
161 
reset()162         void reset() {
163             mFileList.clear();
164             mDone = false;
165         }
166 
beginTask(String name, int totalWork)167         public void beginTask(String name, int totalWork) {
168         }
169 
done()170         public void done() {
171             if (mDone == false) {
172                 mDone = true;
173                 for (IFile file : mFileList) {
174                     if (file.exists()) {
175                         try {
176                             file.setDerived(true);
177                         } catch (CoreException e) {
178                             // This really shouldn't happen since we check that the resource exist.
179                             // Worst case scenario, the resource isn't marked as derived.
180                         }
181                     }
182                 }
183             }
184         }
185 
internalWorked(double work)186         public void internalWorked(double work) {
187         }
188 
isCanceled()189         public boolean isCanceled() {
190             return mCancelled;
191         }
192 
setCanceled(boolean value)193         public void setCanceled(boolean value) {
194             mCancelled = value;
195         }
196 
setTaskName(String name)197         public void setTaskName(String name) {
198         }
199 
subTask(String name)200         public void subTask(String name) {
201         }
202 
worked(int work)203         public void worked(int work) {
204         }
205     }
206 
PreCompilerBuilder()207     public PreCompilerBuilder() {
208         super();
209     }
210 
211     // build() returns a list of project from which this project depends for future compilation.
212     @SuppressWarnings("unchecked")
213     @Override
build(int kind, Map args, IProgressMonitor monitor)214     protected IProject[] build(int kind, Map args, IProgressMonitor monitor)
215             throws CoreException {
216         // get a project object
217         IProject project = getProject();
218 
219         // list of referenced projects.
220         IProject[] libProjects = null;
221 
222         try {
223             mDerivedProgressMonitor.reset();
224 
225             // get the project info
226             ProjectState projectState = Sdk.getProjectState(project);
227 
228             // this can happen if the project has no default.properties.
229             if (projectState == null) {
230                 return null;
231             }
232 
233             IAndroidTarget projectTarget = projectState.getTarget();
234 
235             // get the libraries
236             libProjects = projectState.getFullLibraryProjects();
237 
238             IJavaProject javaProject = JavaCore.create(project);
239 
240             // Top level check to make sure the build can move forward.
241             abortOnBadSetup(javaProject);
242 
243             // now we need to get the classpath list
244             ArrayList<IPath> sourceFolderPathList = BaseProjectHelper.getSourceClasspaths(
245                     javaProject);
246 
247             PreCompilerDeltaVisitor dv = null;
248             String javaPackage = null;
249             String minSdkVersion = null;
250 
251             if (kind == FULL_BUILD) {
252                 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project,
253                         Messages.Start_Full_Pre_Compiler);
254 
255                 // do some clean up.
256                 doClean(project, monitor);
257 
258                 mMustCompileResources = true;
259                 buildAidlCompilationList(project, sourceFolderPathList);
260             } else {
261                 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project,
262                         Messages.Start_Inc_Pre_Compiler);
263 
264                 // Go through the resources and see if something changed.
265                 // Even if the mCompileResources flag is true from a previously aborted
266                 // build, we need to go through the Resource delta to get a possible
267                 // list of aidl files to compile/remove.
268                 IResourceDelta delta = getDelta(project);
269                 if (delta == null) {
270                     mMustCompileResources = true;
271                     buildAidlCompilationList(project, sourceFolderPathList);
272                 } else {
273                     dv = new PreCompilerDeltaVisitor(this, sourceFolderPathList);
274                     delta.accept(dv);
275 
276                     // record the state
277                     mMustCompileResources |= dv.getCompileResources();
278 
279                     if (dv.getForceAidlCompile()) {
280                         buildAidlCompilationList(project, sourceFolderPathList);
281                     } else {
282                         // handle aidl modification, and update mMustCompileAidl
283                         mergeAidlFileModifications(dv.getAidlToCompile(),
284                                 dv.getAidlToRemove());
285                     }
286 
287                     // get the java package from the visitor
288                     javaPackage = dv.getManifestPackage();
289                     minSdkVersion = dv.getMinSdkVersion();
290 
291                     // if the main resources didn't change, then we check for the library
292                     // ones (will trigger resource recompilation too)
293                     if (mMustCompileResources == false && libProjects.length > 0) {
294                         for (IProject libProject : libProjects) {
295                             delta = getDelta(libProject);
296                             if (delta != null) {
297                                 LibraryDeltaVisitor visitor = new LibraryDeltaVisitor();
298                                 delta.accept(visitor);
299 
300                                 mMustCompileResources = visitor.getResChange();
301 
302                                 if (mMustCompileResources) {
303                                     break;
304                                 }
305                             }
306                         }
307                     }
308                 }
309             }
310 
311             // store the build status in the persistent storage
312             saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES , mMustCompileResources);
313 
314             // if there was some XML errors, we just return w/o doing
315             // anything since we've put some markers in the files anyway.
316             if (dv != null && dv.mXmlError) {
317                 AdtPlugin.printErrorToConsole(project, Messages.Xml_Error);
318 
319                 // This interrupts the build. The next builders will not run.
320                 stopBuild(Messages.Xml_Error);
321             }
322 
323 
324             // get the manifest file
325             IFile manifestFile = ProjectHelper.getManifest(project);
326 
327             if (manifestFile == null) {
328                 String msg = String.format(Messages.s_File_Missing,
329                         SdkConstants.FN_ANDROID_MANIFEST_XML);
330                 AdtPlugin.printErrorToConsole(project, msg);
331                 markProject(AndroidConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
332 
333                 // This interrupts the build. The next builders will not run.
334                 stopBuild(msg);
335 
336                 // TODO: document whether code below that uses manifest (which is now guaranteed
337                 // to be null) will actually be executed or not.
338             }
339 
340             // lets check the XML of the manifest first, if that hasn't been done by the
341             // resource delta visitor yet.
342             if (dv == null || dv.getCheckedManifestXml() == false) {
343                 BasicXmlErrorListener errorListener = new BasicXmlErrorListener();
344                 ManifestData parser = AndroidManifestHelper.parse(new IFileWrapper(manifestFile),
345                         true /*gather data*/,
346                         errorListener);
347 
348                 if (errorListener.mHasXmlError == true) {
349                     // there was an error in the manifest, its file has been marked,
350                     // by the XmlErrorHandler.
351                     // We return;
352                     String msg = String.format(Messages.s_Contains_Xml_Error,
353                             SdkConstants.FN_ANDROID_MANIFEST_XML);
354                     AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, msg);
355 
356                     // This interrupts the build. The next builders will not run.
357                     stopBuild(msg);
358                 }
359 
360                 // get the java package from the parser
361                 javaPackage = parser.getPackage();
362                 minSdkVersion = parser.getMinSdkVersionString();
363             }
364 
365             if (minSdkVersion != null) {
366                 int minSdkValue = -1;
367                 try {
368                     minSdkValue = Integer.parseInt(minSdkVersion);
369                 } catch (NumberFormatException e) {
370                     // it's ok, it means minSdkVersion contains a (hopefully) valid codename.
371                 }
372 
373                 AndroidVersion projectVersion = projectTarget.getVersion();
374 
375                 // remove earlier marker from the manifest
376                 removeMarkersFromFile(manifestFile, AndroidConstants.MARKER_ADT);
377 
378                 if (minSdkValue != -1) {
379                     String codename = projectVersion.getCodename();
380                     if (codename != null) {
381                         // integer minSdk when the target is a preview => fatal error
382                         String msg = String.format(
383                                 "Platform %1$s is a preview and requires appication manifest to set %2$s to '%1$s'",
384                                 codename, AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION);
385                         AdtPlugin.printErrorToConsole(project, msg);
386                         BaseProjectHelper.markResource(manifestFile, AndroidConstants.MARKER_ADT,
387                                 msg, IMarker.SEVERITY_ERROR);
388                         stopBuild(msg);
389                     } else if (minSdkValue < projectVersion.getApiLevel()) {
390                         // integer minSdk is not high enough for the target => warning
391                         String msg = String.format(
392                                 "Attribute %1$s (%2$d) is lower than the project target API level (%3$d)",
393                                 AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION,
394                                 minSdkValue, projectVersion.getApiLevel());
395                         AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, msg);
396                         BaseProjectHelper.markResource(manifestFile, AndroidConstants.MARKER_ADT,
397                                 msg, IMarker.SEVERITY_WARNING);
398                     } else if (minSdkValue > projectVersion.getApiLevel()) {
399                         // integer minSdk is too high for the target => warning
400                         String msg = String.format(
401                                 "Attribute %1$s (%2$d) is higher than the project target API level (%3$d)",
402                                 AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION,
403                                 minSdkValue, projectVersion.getApiLevel());
404                         AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, msg);
405                         BaseProjectHelper.markResource(manifestFile, AndroidConstants.MARKER_ADT,
406                                 msg, IMarker.SEVERITY_WARNING);
407                     }
408                 } else {
409                     // looks like the min sdk is a codename, check it matches the codename
410                     // of the platform
411                     String codename = projectVersion.getCodename();
412                     if (codename == null) {
413                         // platform is not a preview => fatal error
414                         String msg = String.format(
415                                 "Manifest attribute '%1$s' is set to '%2$s'. Integer is expected.",
416                                 AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION, minSdkVersion);
417                         AdtPlugin.printErrorToConsole(project, msg);
418                         BaseProjectHelper.markResource(manifestFile, AndroidConstants.MARKER_ADT,
419                                 msg, IMarker.SEVERITY_ERROR);
420                         stopBuild(msg);
421                     } else if (codename.equals(minSdkVersion) == false) {
422                         // platform and manifest codenames don't match => fatal error.
423                         String msg = String.format(
424                                 "Value of manifest attribute '%1$s' does not match platform codename '%2$s'",
425                                 AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION, codename);
426                         AdtPlugin.printErrorToConsole(project, msg);
427                         BaseProjectHelper.markResource(manifestFile, AndroidConstants.MARKER_ADT,
428                                 msg, IMarker.SEVERITY_ERROR);
429                         stopBuild(msg);
430                     }
431                 }
432             } else if (projectTarget.getVersion().isPreview()) {
433                 // else the minSdkVersion is not set but we are using a preview target.
434                 // Display an error
435                 String codename = projectTarget.getVersion().getCodename();
436                 String msg = String.format(
437                         "Platform %1$s is a preview and requires appication manifests to set %2$s to '%1$s'",
438                         codename, AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION);
439                 AdtPlugin.printErrorToConsole(project, msg);
440                 BaseProjectHelper.markResource(manifestFile, AndroidConstants.MARKER_ADT, msg,
441                         IMarker.SEVERITY_ERROR);
442                 stopBuild(msg);
443             }
444 
445             if (javaPackage == null || javaPackage.length() == 0) {
446                 // looks like the AndroidManifest file isn't valid.
447                 String msg = String.format(Messages.s_Doesnt_Declare_Package_Error,
448                         SdkConstants.FN_ANDROID_MANIFEST_XML);
449                 AdtPlugin.printErrorToConsole(project, msg);
450                 BaseProjectHelper.markResource(manifestFile, AndroidConstants.MARKER_ADT,
451                         msg, IMarker.SEVERITY_ERROR);
452 
453                 // This interrupts the build. The next builders will not run.
454                 // This also throws an exception and nothing beyond this line will run.
455                 stopBuild(msg);
456             } else if (javaPackage.indexOf('.') == -1) {
457                 // The application package name does not contain 2+ segments!
458                 String msg = String.format(
459                         "Application package '%1$s' must have a minimum of 2 segments.",
460                         SdkConstants.FN_ANDROID_MANIFEST_XML);
461                 AdtPlugin.printErrorToConsole(project, msg);
462                 BaseProjectHelper.markResource(manifestFile, AndroidConstants.MARKER_ADT,
463                         msg, IMarker.SEVERITY_ERROR);
464 
465                 // This interrupts the build. The next builders will not run.
466                 // This also throws an exception and nothing beyond this line will run.
467                 stopBuild(msg);
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                 buildAidlCompilationList(project, sourceFolderPathList);
492 
493                 saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES , mMustCompileResources);
494             }
495 
496             if (mMustCompileResources) {
497                 handleResources(project, javaPackage, projectTarget, manifestFile, libProjects);
498             }
499 
500             // now handle the aidl stuff.
501             boolean aidlStatus = handleAidl(projectTarget, sourceFolderPathList, monitor);
502 
503             if (aidlStatus == false && mMustCompileResources == false) {
504                 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project,
505                         Messages.Nothing_To_Compile);
506             }
507         } finally {
508             // refresh the 'gen' source folder. Once this is done with the custom progress
509             // monitor to mark all new files as derived
510             mGenFolder.refreshLocal(IResource.DEPTH_INFINITE, mDerivedProgressMonitor);
511         }
512 
513         return libProjects;
514     }
515 
516     @Override
clean(IProgressMonitor monitor)517     protected void clean(IProgressMonitor monitor) throws CoreException {
518         super.clean(monitor);
519 
520         doClean(getProject(), monitor);
521         if (mGenFolder != null) {
522             mGenFolder.refreshLocal(IResource.DEPTH_INFINITE, monitor);
523         }
524     }
525 
doClean(IProject project, IProgressMonitor monitor)526     private void doClean(IProject project, IProgressMonitor monitor) throws CoreException {
527         AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project,
528                 Messages.Removing_Generated_Classes);
529 
530         // remove all the derived resources from the 'gen' source folder.
531         if (mGenFolder != null) {
532             removeDerivedResources(mGenFolder, monitor);
533         }
534 
535         // Clear the project of the generic markers
536         removeMarkersFromProject(project, AndroidConstants.MARKER_AAPT_COMPILE);
537         removeMarkersFromProject(project, AndroidConstants.MARKER_XML);
538         removeMarkersFromProject(project, AndroidConstants.MARKER_AIDL);
539 
540     }
541 
542     @Override
startupOnInitialize()543     protected void startupOnInitialize() {
544         super.startupOnInitialize();
545 
546         mDerivedProgressMonitor = new DerivedProgressMonitor();
547 
548         IProject project = getProject();
549 
550         // load the previous IFolder and java package.
551         mManifestPackage = loadProjectStringProperty(PROPERTY_PACKAGE);
552 
553         // get the source folder in which all the Java files are created
554         mGenFolder = project.getFolder(SdkConstants.FD_GEN_SOURCES);
555 
556         // Load the current compile flags. We ask for true if not found to force a
557         // recompile.
558         mMustCompileResources = loadProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES, true);
559         boolean mustCompileAidl = loadProjectBooleanProperty(PROPERTY_COMPILE_AIDL, true);
560 
561         // if we stored that we have to compile some aidl, we build the list that will compile them
562         // all
563         if (mustCompileAidl) {
564             IJavaProject javaProject = JavaCore.create(project);
565             ArrayList<IPath> sourceFolderPathList = BaseProjectHelper.getSourceClasspaths(
566                     javaProject);
567 
568             buildAidlCompilationList(project, sourceFolderPathList);
569         }
570     }
571 
572     /**
573      * Handles resource changes and regenerate whatever files need regenerating.
574      * @param project the main project
575      * @param javaPackage the app package for the main project
576      * @param projectTarget the target of the main project
577      * @param manifest the {@link IFile} representing the project manifest
578      * @param libProjects the library dependencies
579      * @throws CoreException
580      */
handleResources(IProject project, String javaPackage, IAndroidTarget projectTarget, IFile manifest, IProject[] libProjects)581     private void handleResources(IProject project, String javaPackage, IAndroidTarget projectTarget,
582             IFile manifest, IProject[] libProjects) throws CoreException {
583         // get the resource folder
584         IFolder resFolder = project.getFolder(AndroidConstants.WS_RESOURCES);
585 
586         // get the file system path
587         IPath outputLocation = mGenFolder.getLocation();
588         IPath resLocation = resFolder.getLocation();
589         IPath manifestLocation = manifest == null ? null : manifest.getLocation();
590 
591         // those locations have to exist for us to do something!
592         if (outputLocation != null && resLocation != null
593                 && manifestLocation != null) {
594             String osOutputPath = outputLocation.toOSString();
595             String osResPath = resLocation.toOSString();
596             String osManifestPath = manifestLocation.toOSString();
597 
598             // remove the aapt markers
599             removeMarkersFromFile(manifest, AndroidConstants.MARKER_AAPT_COMPILE);
600             removeMarkersFromContainer(resFolder, AndroidConstants.MARKER_AAPT_COMPILE);
601 
602             AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project,
603                     Messages.Preparing_Generated_Files);
604 
605             // we need to figure out where to store the R class.
606             // get the parent folder for R.java and update mManifestPackageSourceFolder
607             IFolder mainPackageFolder = getGenManifestPackageFolder();
608 
609             // handle libraries
610             ArrayList<IFolder> libResFolders = new ArrayList<IFolder>();
611             ArrayList<IFolder> libOutputFolders = new ArrayList<IFolder>();
612             ArrayList<String> libJavaPackages = new ArrayList<String>();
613             if (libProjects != null) {
614                 for (IProject lib : libProjects) {
615                     IFolder libResFolder = lib.getFolder(SdkConstants.FD_RES);
616                     if (libResFolder.exists()) {
617                         libResFolders.add(libResFolder);
618                     }
619 
620                     try {
621                         String libJavaPackage = AndroidManifest.getPackage(new IFolderWrapper(lib));
622                         if (libJavaPackage.equals(javaPackage) == false) {
623                             libJavaPackages.add(libJavaPackage);
624                             libOutputFolders.add(getGenManifestPackageFolder(libJavaPackage));
625                         }
626                     } catch (Exception e) {
627                     }
628                 }
629             }
630 
631             execAapt(project, projectTarget, osOutputPath, osResPath, osManifestPath,
632                     mainPackageFolder, libResFolders, null /* custom java package */);
633 
634             final int count = libOutputFolders.size();
635             if (count > 0) {
636                 for (int i = 0 ; i < count ; i++) {
637                     IFolder libFolder = libOutputFolders.get(i);
638                     String libJavaPackage = libJavaPackages.get(i);
639                     execAapt(project, projectTarget, osOutputPath, osResPath, osManifestPath,
640                             libFolder, libResFolders, libJavaPackage);
641                 }
642             }
643         }
644     }
645 
646     /**
647      * Executes AAPT to generate R.java/Manifest.java
648      * @param project the main project
649      * @param projectTarget the main project target
650      * @param osOutputPath the OS output path for the generated file. This is the source folder, not
651      * the package folder.
652      * @param osResPath the OS path to the res folder for the main project
653      * @param osManifestPath the OS path to the manifest of the main project
654      * @param packageFolder the IFolder that will contain the generated file. Unlike
655      * <var>osOutputPath</var> this is the direct parent of the geenerated files.
656      * If <var>customJavaPackage</var> is not null, this must match the new destination triggered
657      * by its value.
658      * @param libResFolders the list of res folders for the library.
659      * @param customJavaPackage an optional javapackage to replace the main project java package.
660      * can be null.
661      * @throws CoreException
662      */
execAapt(IProject project, IAndroidTarget projectTarget, String osOutputPath, String osResPath, String osManifestPath, IFolder packageFolder, ArrayList<IFolder> libResFolders, String customJavaPackage)663     private void execAapt(IProject project, IAndroidTarget projectTarget, String osOutputPath,
664             String osResPath, String osManifestPath, IFolder packageFolder,
665             ArrayList<IFolder> libResFolders, String customJavaPackage) throws CoreException {
666         // since the R.java file may be already existing in read-only
667         // mode we need to make it readable so that aapt can overwrite it
668         IFile rJavaFile = packageFolder.getFile(AndroidConstants.FN_RESOURCE_CLASS);
669 
670         // do the same for the Manifest.java class
671         IFile manifestJavaFile = packageFolder.getFile(AndroidConstants.FN_MANIFEST_CLASS);
672 
673         // we actually need to delete the manifest.java as it may become empty and
674         // in this case aapt doesn't generate an empty one, but instead doesn't
675         // touch it.
676         manifestJavaFile.getLocation().toFile().delete();
677 
678         // launch aapt: create the command line
679         ArrayList<String> array = new ArrayList<String>();
680         array.add(projectTarget.getPath(IAndroidTarget.AAPT));
681         array.add("package"); //$NON-NLS-1$
682         array.add("-m"); //$NON-NLS-1$
683         if (AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE) {
684             array.add("-v"); //$NON-NLS-1$
685         }
686 
687         if (libResFolders.size() > 0) {
688             array.add("--auto-add-overlay"); //$NON-NLS-1$
689         }
690 
691         if (customJavaPackage != null) {
692             array.add("--custom-package"); //$NON-NLS-1$
693             array.add(customJavaPackage);
694         }
695 
696         array.add("-J"); //$NON-NLS-1$
697         array.add(osOutputPath);
698         array.add("-M"); //$NON-NLS-1$
699         array.add(osManifestPath);
700         array.add("-S"); //$NON-NLS-1$
701         array.add(osResPath);
702         for (IFolder libResFolder : libResFolders) {
703             array.add("-S"); //$NON-NLS-1$
704             array.add(libResFolder.getLocation().toOSString());
705         }
706 
707         array.add("-I"); //$NON-NLS-1$
708         array.add(projectTarget.getPath(IAndroidTarget.ANDROID_JAR));
709 
710         if (AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE) {
711             StringBuilder sb = new StringBuilder();
712             for (String c : array) {
713                 sb.append(c);
714                 sb.append(' ');
715             }
716             String cmd_line = sb.toString();
717             AdtPlugin.printToConsole(project, cmd_line);
718         }
719 
720         // launch
721         int execError = 1;
722         try {
723             // launch the command line process
724             Process process = Runtime.getRuntime().exec(
725                     array.toArray(new String[array.size()]));
726 
727             // list to store each line of stderr
728             ArrayList<String> results = new ArrayList<String>();
729 
730             // get the output and return code from the process
731             execError = grabProcessOutput(process, results);
732 
733             // attempt to parse the error output
734             boolean parsingError = AaptParser.parseOutput(results, project);
735 
736             // if we couldn't parse the output we display it in the console.
737             if (parsingError) {
738                 if (execError != 0) {
739                     AdtPlugin.printErrorToConsole(project, results.toArray());
740                 } else {
741                     AdtPlugin.printBuildToConsole(BuildVerbosity.NORMAL,
742                             project, results.toArray());
743                 }
744             }
745 
746             if (execError != 0) {
747                 // if the exec failed, and we couldn't parse the error output
748                 // (and therefore not all files that should have been marked,
749                 // were marked), we put a generic marker on the project and abort.
750                 if (parsingError) {
751                     markProject(AndroidConstants.MARKER_ADT,
752                             Messages.Unparsed_AAPT_Errors, IMarker.SEVERITY_ERROR);
753                 }
754 
755                 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project,
756                         Messages.AAPT_Error);
757 
758                 // abort if exec failed.
759                 // This interrupts the build. The next builders will not run.
760                 stopBuild(Messages.AAPT_Error);
761             }
762         } catch (IOException e1) {
763             // something happen while executing the process,
764             // mark the project and exit
765             String msg = String.format(Messages.AAPT_Exec_Error, array.get(0));
766             markProject(AndroidConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
767 
768             // This interrupts the build. The next builders will not run.
769             stopBuild(msg);
770         } catch (InterruptedException e) {
771             // we got interrupted waiting for the process to end...
772             // mark the project and exit
773             String msg = String.format(Messages.AAPT_Exec_Error, array.get(0));
774             markProject(AndroidConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
775 
776             // This interrupts the build. The next builders will not run.
777             stopBuild(msg);
778         }
779 
780         // if the return code was OK, we refresh the folder that
781         // contains R.java to force a java recompile.
782         if (execError == 0) {
783             // now add the R.java/Manifest.java to the list of file to be marked
784             // as derived.
785             mDerivedProgressMonitor.addFile(rJavaFile);
786             mDerivedProgressMonitor.addFile(manifestJavaFile);
787 
788             // build has been done. reset the state of the builder
789             mMustCompileResources = false;
790 
791             // and store it
792             saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES,
793                     mMustCompileResources);
794         }
795     }
796 
797     /**
798      * Creates a relative {@link IPath} from a java package.
799      * @param javaPackageName the java package.
800      */
getJavaPackagePath(String javaPackageName)801     private IPath getJavaPackagePath(String javaPackageName) {
802         // convert the java package into path
803         String[] segments = javaPackageName.split(AndroidConstants.RE_DOT);
804 
805         StringBuilder path = new StringBuilder();
806         for (String s : segments) {
807            path.append(AndroidConstants.WS_SEP_CHAR);
808            path.append(s);
809         }
810 
811         return new Path(path.toString());
812     }
813 
814     /**
815      * Returns an {@link IFolder} (located inside the 'gen' source folder), that matches the
816      * package defined in the manifest. This {@link IFolder} may not actually exist
817      * (aapt will create it anyway).
818      * @return the {@link IFolder} that will contain the R class or null if
819      * the folder was not found.
820      * @throws CoreException
821      */
getGenManifestPackageFolder()822     private IFolder getGenManifestPackageFolder() throws CoreException {
823         // get the path for the package
824         IPath packagePath = getJavaPackagePath(mManifestPackage);
825 
826         // get a folder for this path under the 'gen' source folder, and return it.
827         // This IFolder may not reference an actual existing folder.
828         return mGenFolder.getFolder(packagePath);
829     }
830 
831     /**
832      * Returns an {@link IFolder} (located inside the 'gen' source folder), that matches the
833      * given package. This {@link IFolder} may not actually exist
834      * (aapt will create it anyway).
835      * @param javaPackage the java package that must match the folder.
836      * @return the {@link IFolder} that will contain the R class or null if
837      * the folder was not found.
838      * @throws CoreException
839      */
getGenManifestPackageFolder(String javaPackage)840     private IFolder getGenManifestPackageFolder(String javaPackage) throws CoreException {
841         // get the path for the package
842         IPath packagePath = getJavaPackagePath(javaPackage);
843 
844         // get a folder for this path under the 'gen' source folder, and return it.
845         // This IFolder may not reference an actual existing folder.
846         return mGenFolder.getFolder(packagePath);
847     }
848 
849     /**
850      * Compiles aidl files into java. This will also removes old java files
851      * created from aidl files that are now gone.
852      * @param projectTarget Target of the project
853      * @param sourceFolders the list of source folders, relative to the workspace.
854      * @param monitor the projess monitor
855      * @returns true if it did something
856      * @throws CoreException
857      */
handleAidl(IAndroidTarget projectTarget, ArrayList<IPath> sourceFolders, IProgressMonitor monitor)858     private boolean handleAidl(IAndroidTarget projectTarget, ArrayList<IPath> sourceFolders,
859             IProgressMonitor monitor) throws CoreException {
860         if (mAidlToCompile.size() == 0 && mAidlToRemove.size() == 0) {
861             return false;
862         }
863 
864         // create the command line
865         String[] command = new String[4 + sourceFolders.size()];
866         int index = 0;
867         command[index++] = projectTarget.getPath(IAndroidTarget.AIDL);
868         command[index++] = "-p" + Sdk.getCurrent().getTarget(getProject()).getPath( //$NON-NLS-1$
869                 IAndroidTarget.ANDROID_AIDL);
870 
871         // since the path are relative to the workspace and not the project itself, we need
872         // the workspace root.
873         IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot();
874         for (IPath p : sourceFolders) {
875             IFolder f = wsRoot.getFolder(p);
876             command[index++] = "-I" + f.getLocation().toOSString(); //$NON-NLS-1$
877         }
878 
879         // list of files that have failed compilation.
880         ArrayList<AidlData> stillNeedCompilation = new ArrayList<AidlData>();
881 
882         // if an aidl file is being removed before we managed to compile it, it'll be in
883         // both list. We *need* to remove it from the compile list or it'll never go away.
884         for (AidlData aidlFile : mAidlToRemove) {
885             int pos = mAidlToCompile.indexOf(aidlFile);
886             if (pos != -1) {
887                 mAidlToCompile.remove(pos);
888             }
889         }
890 
891         // loop until we've compile them all
892         for (AidlData aidlData : mAidlToCompile) {
893             // Remove the AIDL error markers from the aidl file
894             removeMarkersFromFile(aidlData.aidlFile, AndroidConstants.MARKER_AIDL);
895 
896             // get the path of the source file.
897             IPath sourcePath = aidlData.aidlFile.getLocation();
898             String osSourcePath = sourcePath.toOSString();
899 
900             IFile javaFile = getGenDestinationFile(aidlData, true /*createFolders*/, monitor);
901 
902             // finish to set the command line.
903             command[index] = osSourcePath;
904             command[index + 1] = javaFile.getLocation().toOSString();
905 
906             // launch the process
907             if (execAidl(command, aidlData.aidlFile) == false) {
908                 // aidl failed. File should be marked. We add the file to the list
909                 // of file that will need compilation again.
910                 stillNeedCompilation.add(aidlData);
911 
912                 // and we move on to the next one.
913                 continue;
914             } else {
915                 // make sure the file will be marked as derived once we refresh the 'gen' source
916                 // folder.
917                 mDerivedProgressMonitor.addFile(javaFile);
918             }
919         }
920 
921         // change the list to only contains the file that have failed compilation
922         mAidlToCompile.clear();
923         mAidlToCompile.addAll(stillNeedCompilation);
924 
925         // Remove the java files created from aidl files that have been removed.
926         for (AidlData aidlData : mAidlToRemove) {
927             IFile javaFile = getGenDestinationFile(aidlData, false /*createFolders*/, monitor);
928             if (javaFile.exists()) {
929                 // This confirms the java file was generated by the builder,
930                 // we can delete the aidlFile.
931                 javaFile.getLocation().toFile().delete();
932             }
933         }
934 
935         mAidlToRemove.clear();
936 
937         // store the build state. If there are any files that failed to compile, we will
938         // force a full aidl compile on the next project open. (unless a full compilation succeed
939         // before the project is closed/re-opened.)
940         // TODO: Optimize by saving only the files that need compilation
941         saveProjectBooleanProperty(PROPERTY_COMPILE_AIDL , mAidlToCompile.size() > 0);
942 
943         return true;
944     }
945 
946     /**
947      * Returns the {@link IFile} handle to the destination file for a given aild source file
948      * ({@link AidlData}).
949      * @param aidlData the data for the aidl source file.
950      * @param createFolders whether or not the parent folder of the destination should be created
951      * if it does not exist.
952      * @param monitor the progress monitor
953      * @return the handle to the destination file.
954      * @throws CoreException
955      */
getGenDestinationFile(AidlData aidlData, boolean createFolders, IProgressMonitor monitor)956     private IFile getGenDestinationFile(AidlData aidlData, boolean createFolders,
957             IProgressMonitor monitor) throws CoreException {
958         // build the destination folder path.
959         // Use the path of the source file, except for the path leading to its source folder,
960         // and for the last segment which is the filename.
961         int segmentToSourceFolderCount = aidlData.sourceFolder.getFullPath().segmentCount();
962         IPath packagePath = aidlData.aidlFile.getFullPath().removeFirstSegments(
963                 segmentToSourceFolderCount).removeLastSegments(1);
964         Path destinationPath = new Path(packagePath.toString());
965 
966         // get an IFolder for this path. It's relative to the 'gen' folder already
967         IFolder destinationFolder = mGenFolder.getFolder(destinationPath);
968 
969         // create it if needed.
970         if (destinationFolder.exists() == false && createFolders) {
971             createFolder(destinationFolder, monitor);
972         }
973 
974         // Build the Java file name from the aidl name.
975         String javaName = aidlData.aidlFile.getName().replaceAll(AndroidConstants.RE_AIDL_EXT,
976                 AndroidConstants.DOT_JAVA);
977 
978         // get the resource for the java file.
979         IFile javaFile = destinationFolder.getFile(javaName);
980         return javaFile;
981     }
982 
983     /**
984      * Creates the destination folder. Because
985      * {@link IFolder#create(boolean, boolean, IProgressMonitor)} only works if the parent folder
986      * already exists, this goes and ensure that all the parent folders actually exist, or it
987      * creates them as well.
988      * @param destinationFolder The folder to create
989      * @param monitor the {@link IProgressMonitor},
990      * @throws CoreException
991      */
createFolder(IFolder destinationFolder, IProgressMonitor monitor)992     private void createFolder(IFolder destinationFolder, IProgressMonitor monitor)
993             throws CoreException {
994 
995         // check the parent exist and create if necessary.
996         IContainer parent = destinationFolder.getParent();
997         if (parent.getType() == IResource.FOLDER && parent.exists() == false) {
998             createFolder((IFolder)parent, monitor);
999         }
1000 
1001         // create the folder.
1002         destinationFolder.create(true /*force*/, true /*local*/,
1003                 new SubProgressMonitor(monitor, 10));
1004     }
1005 
1006     /**
1007      * Execute the aidl command line, parse the output, and mark the aidl file
1008      * with any reported errors.
1009      * @param command the String array containing the command line to execute.
1010      * @param file The IFile object representing the aidl file being
1011      *      compiled.
1012      * @return false if the exec failed, and build needs to be aborted.
1013      */
execAidl(String[] command, IFile file)1014     private boolean execAidl(String[] command, IFile file) {
1015         // do the exec
1016         try {
1017             Process p = Runtime.getRuntime().exec(command);
1018 
1019             // list to store each line of stderr
1020             ArrayList<String> results = new ArrayList<String>();
1021 
1022             // get the output and return code from the process
1023             int result = grabProcessOutput(p, results);
1024 
1025             // attempt to parse the error output
1026             boolean error = parseAidlOutput(results, file);
1027 
1028             // If the process failed and we couldn't parse the output
1029             // we pring a message, mark the project and exit
1030             if (result != 0 && error == true) {
1031                 // display the message in the console.
1032                 AdtPlugin.printErrorToConsole(getProject(), results.toArray());
1033 
1034                 // mark the project and exit
1035                 markProject(AndroidConstants.MARKER_ADT, Messages.Unparsed_AIDL_Errors,
1036                         IMarker.SEVERITY_ERROR);
1037                 return false;
1038             }
1039         } catch (IOException e) {
1040             // mark the project and exit
1041             String msg = String.format(Messages.AIDL_Exec_Error, command[0]);
1042             markProject(AndroidConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
1043             return false;
1044         } catch (InterruptedException e) {
1045             // mark the project and exit
1046             String msg = String.format(Messages.AIDL_Exec_Error, command[0]);
1047             markProject(AndroidConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
1048             return false;
1049         }
1050 
1051         return true;
1052     }
1053 
1054     /**
1055      * Goes through the build paths and fills the list of aidl files to compile
1056      * ({@link #mAidlToCompile}).
1057      * @param project The project.
1058      * @param sourceFolderPathList The list of source folder paths.
1059      */
buildAidlCompilationList(IProject project, ArrayList<IPath> sourceFolderPathList)1060     private void buildAidlCompilationList(IProject project,
1061             ArrayList<IPath> sourceFolderPathList) {
1062         IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
1063         for (IPath sourceFolderPath : sourceFolderPathList) {
1064             IFolder sourceFolder = root.getFolder(sourceFolderPath);
1065             // we don't look in the 'gen' source folder as there will be no source in there.
1066             if (sourceFolder.exists() && sourceFolder.equals(mGenFolder) == false) {
1067                 scanFolderForAidl(sourceFolder, sourceFolder);
1068             }
1069         }
1070     }
1071 
1072     /**
1073      * Scans a folder and fills the list of aidl files to compile.
1074      * @param sourceFolder the root source folder.
1075      * @param folder The folder to scan.
1076      */
scanFolderForAidl(IFolder sourceFolder, IFolder folder)1077     private void scanFolderForAidl(IFolder sourceFolder, IFolder folder) {
1078         try {
1079             IResource[] members = folder.members();
1080             for (IResource r : members) {
1081                 // get the type of the resource
1082                switch (r.getType()) {
1083                    case IResource.FILE:
1084                        // if this a file, check that the file actually exist
1085                        // and that it's an aidl file
1086                        if (r.exists() &&
1087                                AndroidConstants.EXT_AIDL.equalsIgnoreCase(r.getFileExtension())) {
1088                            mAidlToCompile.add(new AidlData(sourceFolder, (IFile)r));
1089                        }
1090                        break;
1091                    case IResource.FOLDER:
1092                        // recursively go through children
1093                        scanFolderForAidl(sourceFolder, (IFolder)r);
1094                        break;
1095                    default:
1096                        // this would mean it's a project or the workspace root
1097                        // which is unlikely to happen. we do nothing
1098                        break;
1099                }
1100             }
1101         } catch (CoreException e) {
1102             // Couldn't get the members list for some reason. Just return.
1103         }
1104     }
1105 
1106 
1107     /**
1108      * Parse the output of aidl and mark the file with any errors.
1109      * @param lines The output to parse.
1110      * @param file The file to mark with error.
1111      * @return true if the parsing failed, false if success.
1112      */
parseAidlOutput(ArrayList<String> lines, IFile file)1113     private boolean parseAidlOutput(ArrayList<String> lines, IFile file) {
1114         // nothing to parse? just return false;
1115         if (lines.size() == 0) {
1116             return false;
1117         }
1118 
1119         Matcher m;
1120 
1121         for (int i = 0; i < lines.size(); i++) {
1122             String p = lines.get(i);
1123 
1124             m = sAidlPattern1.matcher(p);
1125             if (m.matches()) {
1126                 // we can ignore group 1 which is the location since we already
1127                 // have a IFile object representing the aidl file.
1128                 String lineStr = m.group(2);
1129                 String msg = m.group(3);
1130 
1131                 // get the line number
1132                 int line = 0;
1133                 try {
1134                     line = Integer.parseInt(lineStr);
1135                 } catch (NumberFormatException e) {
1136                     // looks like the string we extracted wasn't a valid
1137                     // file number. Parsing failed and we return true
1138                     return true;
1139                 }
1140 
1141                 // mark the file
1142                 BaseProjectHelper.markResource(file, AndroidConstants.MARKER_AIDL, msg, line,
1143                         IMarker.SEVERITY_ERROR);
1144 
1145                 // success, go to the next line
1146                 continue;
1147             }
1148 
1149             // invalid line format, flag as error, and bail
1150             return true;
1151         }
1152 
1153         return false;
1154     }
1155 
1156     /**
1157      * Merge the current list of aidl file to compile/remove with the new one.
1158      * @param toCompile List of file to compile
1159      * @param toRemove List of file to remove
1160      */
mergeAidlFileModifications(ArrayList<AidlData> toCompile, ArrayList<AidlData> toRemove)1161     private void mergeAidlFileModifications(ArrayList<AidlData> toCompile,
1162             ArrayList<AidlData> toRemove) {
1163         // loop through the new toRemove list, and add it to the old one,
1164         // plus remove any file that was still to compile and that are now
1165         // removed
1166         for (AidlData r : toRemove) {
1167             if (mAidlToRemove.indexOf(r) == -1) {
1168                 mAidlToRemove.add(r);
1169             }
1170 
1171             int index = mAidlToCompile.indexOf(r);
1172             if (index != -1) {
1173                 mAidlToCompile.remove(index);
1174             }
1175         }
1176 
1177         // now loop through the new files to compile and add it to the list.
1178         // Also look for them in the remove list, this would mean that they
1179         // were removed, then added back, and we shouldn't remove them, just
1180         // recompile them.
1181         for (AidlData r : toCompile) {
1182             if (mAidlToCompile.indexOf(r) == -1) {
1183                 mAidlToCompile.add(r);
1184             }
1185 
1186             int index = mAidlToRemove.indexOf(r);
1187             if (index != -1) {
1188                 mAidlToRemove.remove(index);
1189             }
1190         }
1191     }
1192 }
1193