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