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