• 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.annotations.NonNull;
20 import com.android.annotations.Nullable;
21 import com.android.ide.common.sdk.LoadStatus;
22 import com.android.ide.eclipse.adt.AdtConstants;
23 import com.android.ide.eclipse.adt.AdtPlugin;
24 import com.android.ide.eclipse.adt.internal.build.BuildHelper;
25 import com.android.ide.eclipse.adt.internal.build.Messages;
26 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity;
27 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
28 import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
29 import com.android.ide.eclipse.adt.internal.project.XmlErrorHandler;
30 import com.android.ide.eclipse.adt.internal.project.XmlErrorHandler.XmlErrorListener;
31 import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
32 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
33 import com.android.ide.eclipse.adt.io.IFileWrapper;
34 import com.android.io.IAbstractFile;
35 import com.android.io.StreamException;
36 import com.android.sdklib.BuildToolInfo;
37 import com.android.sdklib.IAndroidTarget;
38 
39 import org.eclipse.core.resources.IContainer;
40 import org.eclipse.core.resources.IFile;
41 import org.eclipse.core.resources.IFolder;
42 import org.eclipse.core.resources.IMarker;
43 import org.eclipse.core.resources.IProject;
44 import org.eclipse.core.resources.IResource;
45 import org.eclipse.core.resources.IncrementalProjectBuilder;
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.jobs.Job;
51 import org.eclipse.jdt.core.IJavaProject;
52 import org.xml.sax.SAXException;
53 
54 import java.util.ArrayList;
55 
56 import javax.xml.parsers.ParserConfigurationException;
57 import javax.xml.parsers.SAXParser;
58 import javax.xml.parsers.SAXParserFactory;
59 
60 /**
61  * Base builder for XML files. This class allows for basic XML parsing with
62  * error checking and marking the files for errors/warnings.
63  */
64 public abstract class BaseBuilder extends IncrementalProjectBuilder {
65 
66     protected static final boolean DEBUG_LOG = "1".equals(              //$NON-NLS-1$
67             System.getenv("ANDROID_BUILD_DEBUG"));                      //$NON-NLS-1$
68 
69     /** SAX Parser factory. */
70     private SAXParserFactory mParserFactory;
71 
72     /**
73      * The build tool to use to build. This is guaranteed to be non null after a call to
74      * {@link #abortOnBadSetup(IJavaProject, ProjectState)} since this will throw if it can't be
75      * queried.
76      */
77     protected BuildToolInfo mBuildToolInfo;
78 
79     /**
80      * Base Resource Delta Visitor to handle XML error
81      */
82     protected static class BaseDeltaVisitor implements XmlErrorListener {
83 
84         /** The Xml builder used to validate XML correctness. */
85         protected BaseBuilder mBuilder;
86 
87         /**
88          * XML error flag. if true, we keep parsing the ResourceDelta but the
89          * compilation will not happen (we're putting markers)
90          */
91         public boolean mXmlError = false;
92 
BaseDeltaVisitor(BaseBuilder builder)93         public BaseDeltaVisitor(BaseBuilder builder) {
94             mBuilder = builder;
95         }
96 
97         /**
98          * Finds a matching Source folder for the current path. This checks if the current path
99          * leads to, or is a source folder.
100          * @param sourceFolders The list of source folders
101          * @param pathSegments The segments of the current path
102          * @return The segments of the source folder, or null if no match was found
103          */
findMatchingSourceFolder(ArrayList<IPath> sourceFolders, String[] pathSegments)104         protected static String[] findMatchingSourceFolder(ArrayList<IPath> sourceFolders,
105                 String[] pathSegments) {
106 
107             for (IPath p : sourceFolders) {
108                 // check if we are inside one of those source class path
109 
110                 // get the segments
111                 String[] srcSegments = p.segments();
112 
113                 // compare segments. We want the path of the resource
114                 // we're visiting to be
115                 boolean valid = true;
116                 int segmentCount = pathSegments.length;
117 
118                 for (int i = 0 ; i < segmentCount; i++) {
119                     String s1 = pathSegments[i];
120                     String s2 = srcSegments[i];
121 
122                     if (s1.equalsIgnoreCase(s2) == false) {
123                         valid = false;
124                         break;
125                     }
126                 }
127 
128                 if (valid) {
129                     // this folder, or one of this children is a source
130                     // folder!
131                     // we return its segments
132                     return srcSegments;
133                 }
134             }
135 
136             return null;
137         }
138 
139         /**
140          * Sent when an XML error is detected.
141          * @see XmlErrorListener
142          */
143         @Override
errorFound()144         public void errorFound() {
145             mXmlError = true;
146         }
147     }
148 
149     protected static class AbortBuildException extends Exception {
150         private static final long serialVersionUID = 1L;
151     }
152 
BaseBuilder()153     public BaseBuilder() {
154         super();
155         mParserFactory = SAXParserFactory.newInstance();
156 
157         // FIXME when the compiled XML support for namespace is in, set this to true.
158         mParserFactory.setNamespaceAware(false);
159     }
160 
161     /**
162      * Checks an Xml file for validity. Errors/warnings will be marked on the
163      * file
164      * @param resource the resource to check
165      * @param visitor a valid resource delta visitor
166      */
checkXML(IResource resource, BaseDeltaVisitor visitor)167     protected final void checkXML(IResource resource, BaseDeltaVisitor visitor) {
168 
169         // first make sure this is an xml file
170         if (resource instanceof IFile) {
171             IFile file = (IFile)resource;
172 
173             // remove previous markers
174             removeMarkersFromResource(file, AdtConstants.MARKER_XML);
175 
176             // create  the error handler
177             XmlErrorHandler reporter = new XmlErrorHandler(file, visitor);
178             try {
179                 // parse
180                 getParser().parse(file.getContents(), reporter);
181             } catch (Exception e1) {
182             }
183         }
184     }
185 
186     /**
187      * Returns the SAXParserFactory, instantiating it first if it's not already
188      * created.
189      * @return the SAXParserFactory object
190      * @throws ParserConfigurationException
191      * @throws SAXException
192      */
getParser()193     protected final SAXParser getParser() throws ParserConfigurationException,
194             SAXException {
195         return mParserFactory.newSAXParser();
196     }
197 
198     /**
199      * Adds a marker to the current project.  This methods catches thrown {@link CoreException},
200      * and returns null instead.
201      *
202      * @param markerId The id of the marker to add.
203      * @param message the message associated with the mark
204      * @param severity the severity of the marker.
205      * @return the marker that was created (or null if failure)
206      * @see IMarker
207      */
markProject(String markerId, String message, int severity)208     protected final IMarker markProject(String markerId, String message, int severity) {
209         return BaseProjectHelper.markResource(getProject(), markerId, message, severity);
210     }
211 
212     /**
213      * Removes markers from a resource and only the resource (not its children).
214      * @param file The file from which to delete the markers.
215      * @param markerId The id of the markers to remove. If null, all marker of
216      * type <code>IMarker.PROBLEM</code> will be removed.
217      */
removeMarkersFromResource(IResource resource, String markerId)218     public final void removeMarkersFromResource(IResource resource, String markerId) {
219         try {
220             if (resource.exists()) {
221                 resource.deleteMarkers(markerId, true, IResource.DEPTH_ZERO);
222             }
223         } catch (CoreException ce) {
224             String msg = String.format(Messages.Marker_Delete_Error, markerId, resource.toString());
225             AdtPlugin.printErrorToConsole(getProject(), msg);
226         }
227     }
228 
229     /**
230      * Removes markers from a container and its children.
231      * @param folder The container from which to delete the markers.
232      * @param markerId The id of the markers to remove. If null, all marker of
233      * type <code>IMarker.PROBLEM</code> will be removed.
234      */
removeMarkersFromContainer(IContainer folder, String markerId)235     protected final void removeMarkersFromContainer(IContainer folder, String markerId) {
236         try {
237             if (folder.exists()) {
238                 folder.deleteMarkers(markerId, true, IResource.DEPTH_INFINITE);
239             }
240         } catch (CoreException ce) {
241             String msg = String.format(Messages.Marker_Delete_Error, markerId, folder.toString());
242             AdtPlugin.printErrorToConsole(getProject(), msg);
243         }
244     }
245 
246     /**
247      * Get the stderr output of a process and return when the process is done.
248      * @param process The process to get the ouput from
249      * @param stdErr The array to store the stderr output
250      * @return the process return code.
251      * @throws InterruptedException
252      */
grabProcessOutput(final Process process, final ArrayList<String> stdErr)253     protected final int grabProcessOutput(final Process process,
254             final ArrayList<String> stdErr) throws InterruptedException {
255         return BuildHelper.grabProcessOutput(getProject(), process, stdErr);
256     }
257 
258 
259 
260     /**
261      * Saves a String property into the persistent storage of the project.
262      * @param propertyName the name of the property. The id of the plugin is added to this string.
263      * @param value the value to save
264      * @return true if the save succeeded.
265      */
saveProjectStringProperty(String propertyName, String value)266     protected boolean saveProjectStringProperty(String propertyName, String value) {
267         IProject project = getProject();
268         return ProjectHelper.saveStringProperty(project, propertyName, value);
269     }
270 
271 
272     /**
273      * Loads a String property from the persistent storage of the project.
274      * @param propertyName the name of the property. The id of the plugin is added to this string.
275      * @return the property value or null if it was not found.
276      */
loadProjectStringProperty(String propertyName)277     protected String loadProjectStringProperty(String propertyName) {
278         IProject project = getProject();
279         return ProjectHelper.loadStringProperty(project, propertyName);
280     }
281 
282     /**
283      * Saves a property into the persistent storage of the project.
284      * @param propertyName the name of the property. The id of the plugin is added to this string.
285      * @param value the value to save
286      * @return true if the save succeeded.
287      */
saveProjectBooleanProperty(String propertyName, boolean value)288     protected boolean saveProjectBooleanProperty(String propertyName, boolean value) {
289         IProject project = getProject();
290         return ProjectHelper.saveStringProperty(project, propertyName, Boolean.toString(value));
291     }
292 
293     /**
294      * Loads a boolean property from the persistent storage of the project.
295      * @param propertyName the name of the property. The id of the plugin is added to this string.
296      * @param defaultValue The default value to return if the property was not found.
297      * @return the property value or the default value if the property was not found.
298      */
loadProjectBooleanProperty(String propertyName, boolean defaultValue)299     protected boolean loadProjectBooleanProperty(String propertyName, boolean defaultValue) {
300         IProject project = getProject();
301         return ProjectHelper.loadBooleanProperty(project, propertyName, defaultValue);
302     }
303 
304     /**
305      * Aborts the build if the SDK/project setups are broken. This does not
306      * display any errors.
307      *
308      * @param javaProject The {@link IJavaProject} being compiled.
309      * @param projectState the project state, optional. will be queried if null.
310      * @throws CoreException
311      */
abortOnBadSetup(@onNull IJavaProject javaProject, @Nullable ProjectState projectState)312     protected void abortOnBadSetup(@NonNull IJavaProject javaProject,
313             @Nullable ProjectState projectState) throws AbortBuildException, CoreException {
314         IProject iProject = javaProject.getProject();
315         // check if we have finished loading the project target.
316         Sdk sdk = Sdk.getCurrent();
317         if (sdk == null) {
318             throw new AbortBuildException();
319         }
320 
321         if (projectState == null) {
322             projectState = Sdk.getProjectState(javaProject.getProject());
323         }
324 
325         // get the target for the project
326         IAndroidTarget target = projectState.getTarget();
327 
328         if (target == null) {
329             throw new AbortBuildException();
330         }
331 
332         // check on the target data.
333         if (sdk.checkAndLoadTargetData(target, javaProject) != LoadStatus.LOADED) {
334             throw new AbortBuildException();
335        }
336 
337         mBuildToolInfo = projectState.getBuildToolInfo();
338         if (mBuildToolInfo == null) {
339             mBuildToolInfo = sdk.getLatestBuildTool();
340 
341             if (mBuildToolInfo == null) {
342                 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, iProject,
343                         "No \"Build Tools\" package available; use SDK Manager to install one.");
344                 throw new AbortBuildException();
345             } else {
346                 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, iProject,
347                         String.format("Using default Build Tools revision %s",
348                                 mBuildToolInfo.getRevision())
349                         );
350             }
351         }
352 
353         // abort if there are TARGET or ADT type markers
354         stopOnMarker(iProject, AdtConstants.MARKER_TARGET, IResource.DEPTH_ZERO,
355                 false /*checkSeverity*/);
356         stopOnMarker(iProject, AdtConstants.MARKER_ADT, IResource.DEPTH_ZERO,
357                 false /*checkSeverity*/);
358     }
359 
stopOnMarker(IProject project, String markerType, int depth, boolean checkSeverity)360     protected void stopOnMarker(IProject project, String markerType, int depth,
361             boolean checkSeverity)
362             throws AbortBuildException {
363         try {
364             IMarker[] markers = project.findMarkers(markerType, false /*includeSubtypes*/, depth);
365 
366             if (markers.length > 0) {
367                 if (checkSeverity == false) {
368                     throw new AbortBuildException();
369                 } else {
370                     for (IMarker marker : markers) {
371                         int severity = marker.getAttribute(IMarker.SEVERITY, -1 /*defaultValue*/);
372                         if (severity == IMarker.SEVERITY_ERROR) {
373                             throw new AbortBuildException();
374                         }
375                     }
376                 }
377             }
378         } catch (CoreException e) {
379             // don't stop, something's really screwed up and the build will break later with
380             // a better error message.
381         }
382     }
383 
384     /**
385      * Handles a {@link StreamException} by logging the info and marking the project.
386      * This should generally be followed by exiting the build process.
387      *
388      * @param e the exception
389      */
handleStreamException(StreamException e)390     protected void handleStreamException(StreamException e) {
391         IAbstractFile file = e.getFile();
392 
393         String msg;
394 
395         IResource target = getProject();
396         if (file instanceof IFileWrapper) {
397             target = ((IFileWrapper) file).getIFile();
398 
399             if (e.getError() == StreamException.Error.OUTOFSYNC) {
400                 msg = "File is Out of sync";
401             } else {
402                 msg = "Error reading file. Read log for details";
403             }
404 
405         } else {
406             if (e.getError() == StreamException.Error.OUTOFSYNC) {
407                 msg = String.format("Out of sync file: %s", file.getOsLocation());
408             } else {
409                 msg = String.format("Error reading file %s. Read log for details",
410                         file.getOsLocation());
411             }
412         }
413 
414         AdtPlugin.logAndPrintError(e, getProject().getName(), msg);
415         BaseProjectHelper.markResource(target, AdtConstants.MARKER_ADT, msg,
416                 IMarker.SEVERITY_ERROR);
417     }
418 
419     /**
420      * Handles a generic {@link Throwable} by logging the info and marking the project.
421      * This should generally be followed by exiting the build process.
422      *
423      * @param t the {@link Throwable}.
424      * @param message the message to log and to associate with the marker.
425      */
handleException(Throwable t, String message)426     protected void handleException(Throwable t, String message) {
427         AdtPlugin.logAndPrintError(t, getProject().getName(), message);
428         markProject(AdtConstants.MARKER_ADT, message, IMarker.SEVERITY_ERROR);
429     }
430 
431     /**
432      * Recursively delete all the derived resources from a root resource. The root resource is not
433      * deleted.
434      * @param rootResource the root resource
435      * @param monitor a progress monitor.
436      * @throws CoreException
437      *
438      */
removeDerivedResources(IResource rootResource, IProgressMonitor monitor)439     protected void removeDerivedResources(IResource rootResource, IProgressMonitor monitor)
440             throws CoreException {
441         removeDerivedResources(rootResource, false, monitor);
442     }
443 
444     /**
445      * delete a resource and its children. returns true if the root resource was deleted. All
446      * sub-folders *will* be deleted if they were emptied (not if they started empty).
447      * @param rootResource the root resource
448      * @param deleteRoot whether to delete the root folder.
449      * @param monitor a progress monitor.
450      * @throws CoreException
451      */
removeDerivedResources(IResource rootResource, boolean deleteRoot, IProgressMonitor monitor)452     private void removeDerivedResources(IResource rootResource, boolean deleteRoot,
453             IProgressMonitor monitor) throws CoreException {
454         if (rootResource.exists()) {
455             // if it's a folder, delete derived member.
456             if (rootResource.getType() == IResource.FOLDER) {
457                 IFolder folder = (IFolder)rootResource;
458                 IResource[] members = folder.members();
459                 boolean wasNotEmpty = members.length > 0;
460                 for (IResource member : members) {
461                     removeDerivedResources(member, true /*deleteRoot*/, monitor);
462                 }
463 
464                 // if the folder had content that is now all removed, delete the folder.
465                 if (deleteRoot && wasNotEmpty && folder.members().length == 0) {
466                     rootResource.getLocation().toFile().delete();
467                 }
468             }
469 
470             // if the root resource is derived, delete it.
471             if (rootResource.isDerived()) {
472                 rootResource.getLocation().toFile().delete();
473             }
474         }
475     }
476 
launchJob(Job newJob)477     protected void launchJob(Job newJob) {
478         newJob.setPriority(Job.BUILD);
479         newJob.setRule(ResourcesPlugin.getWorkspace().getRoot());
480         newJob.schedule();
481     }
482 }
483