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