diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/BaseBuilder.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/BaseBuilder.java | 483 |
1 files changed, 483 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/BaseBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/BaseBuilder.java new file mode 100644 index 000000000..162591406 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/BaseBuilder.java @@ -0,0 +1,483 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.adt.internal.build.builders; + +import com.android.annotations.NonNull; +import com.android.annotations.Nullable; +import com.android.ide.common.sdk.LoadStatus; +import com.android.ide.eclipse.adt.AdtConstants; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.build.BuildHelper; +import com.android.ide.eclipse.adt.internal.build.Messages; +import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity; +import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; +import com.android.ide.eclipse.adt.internal.project.ProjectHelper; +import com.android.ide.eclipse.adt.internal.project.XmlErrorHandler; +import com.android.ide.eclipse.adt.internal.project.XmlErrorHandler.XmlErrorListener; +import com.android.ide.eclipse.adt.internal.sdk.ProjectState; +import com.android.ide.eclipse.adt.internal.sdk.Sdk; +import com.android.ide.eclipse.adt.io.IFileWrapper; +import com.android.io.IAbstractFile; +import com.android.io.StreamException; +import com.android.sdklib.BuildToolInfo; +import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.repository.FullRevision; + +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IncrementalProjectBuilder; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jdt.core.IJavaProject; +import org.xml.sax.SAXException; + +import java.util.ArrayList; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +/** + * Base builder for XML files. This class allows for basic XML parsing with + * error checking and marking the files for errors/warnings. + */ +public abstract class BaseBuilder extends IncrementalProjectBuilder { + + protected static final boolean DEBUG_LOG = "1".equals( //$NON-NLS-1$ + System.getenv("ANDROID_BUILD_DEBUG")); //$NON-NLS-1$ + + /** SAX Parser factory. */ + private SAXParserFactory mParserFactory; + + /** + * The build tool to use to build. This is guaranteed to be non null after a call to + * {@link #abortOnBadSetup(IJavaProject, ProjectState)} since this will throw if it can't be + * queried. + */ + protected BuildToolInfo mBuildToolInfo; + + /** + * Base Resource Delta Visitor to handle XML error + */ + protected static class BaseDeltaVisitor implements XmlErrorListener { + + /** The Xml builder used to validate XML correctness. */ + protected BaseBuilder mBuilder; + + /** + * XML error flag. if true, we keep parsing the ResourceDelta but the + * compilation will not happen (we're putting markers) + */ + public boolean mXmlError = false; + + public BaseDeltaVisitor(BaseBuilder builder) { + mBuilder = builder; + } + + /** + * Finds a matching Source folder for the current path. This checks if the current path + * leads to, or is a source folder. + * @param sourceFolders The list of source folders + * @param pathSegments The segments of the current path + * @return The segments of the source folder, or null if no match was found + */ + protected static String[] findMatchingSourceFolder(ArrayList<IPath> sourceFolders, + String[] pathSegments) { + + for (IPath p : sourceFolders) { + // check if we are inside one of those source class path + + // get the segments + String[] srcSegments = p.segments(); + + // compare segments. We want the path of the resource + // we're visiting to be + boolean valid = true; + int segmentCount = pathSegments.length; + + for (int i = 0 ; i < segmentCount; i++) { + String s1 = pathSegments[i]; + String s2 = srcSegments[i]; + + if (s1.equalsIgnoreCase(s2) == false) { + valid = false; + break; + } + } + + if (valid) { + // this folder, or one of this children is a source + // folder! + // we return its segments + return srcSegments; + } + } + + return null; + } + + /** + * Sent when an XML error is detected. + * @see XmlErrorListener + */ + @Override + public void errorFound() { + mXmlError = true; + } + } + + protected static class AbortBuildException extends Exception { + private static final long serialVersionUID = 1L; + } + + public BaseBuilder() { + super(); + mParserFactory = SAXParserFactory.newInstance(); + + // FIXME when the compiled XML support for namespace is in, set this to true. + mParserFactory.setNamespaceAware(false); + } + + /** + * Checks an Xml file for validity. Errors/warnings will be marked on the + * file + * @param resource the resource to check + * @param visitor a valid resource delta visitor + */ + protected final void checkXML(IResource resource, BaseDeltaVisitor visitor) { + + // first make sure this is an xml file + if (resource instanceof IFile) { + IFile file = (IFile)resource; + + // remove previous markers + removeMarkersFromResource(file, AdtConstants.MARKER_XML); + + // create the error handler + XmlErrorHandler reporter = new XmlErrorHandler(file, visitor); + try { + // parse + getParser().parse(file.getContents(), reporter); + } catch (Exception e1) { + } + } + } + + /** + * Returns the SAXParserFactory, instantiating it first if it's not already + * created. + * @return the SAXParserFactory object + * @throws ParserConfigurationException + * @throws SAXException + */ + protected final SAXParser getParser() throws ParserConfigurationException, + SAXException { + return mParserFactory.newSAXParser(); + } + + /** + * Adds a marker to the current project. This methods catches thrown {@link CoreException}, + * and returns null instead. + * + * @param markerId The id of the marker to add. + * @param message the message associated with the mark + * @param severity the severity of the marker. + * @return the marker that was created (or null if failure) + * @see IMarker + */ + protected final IMarker markProject(String markerId, String message, int severity) { + return BaseProjectHelper.markResource(getProject(), markerId, message, severity); + } + + /** + * Removes markers from a resource and only the resource (not its children). + * @param file The file from which to delete the markers. + * @param markerId The id of the markers to remove. If null, all marker of + * type <code>IMarker.PROBLEM</code> will be removed. + */ + public final void removeMarkersFromResource(IResource resource, String markerId) { + try { + if (resource.exists()) { + resource.deleteMarkers(markerId, true, IResource.DEPTH_ZERO); + } + } catch (CoreException ce) { + String msg = String.format(Messages.Marker_Delete_Error, markerId, resource.toString()); + AdtPlugin.printErrorToConsole(getProject(), msg); + } + } + + /** + * Removes markers from a container and its children. + * @param folder The container from which to delete the markers. + * @param markerId The id of the markers to remove. If null, all marker of + * type <code>IMarker.PROBLEM</code> will be removed. + */ + protected final void removeMarkersFromContainer(IContainer folder, String markerId) { + try { + if (folder.exists()) { + folder.deleteMarkers(markerId, true, IResource.DEPTH_INFINITE); + } + } catch (CoreException ce) { + String msg = String.format(Messages.Marker_Delete_Error, markerId, folder.toString()); + AdtPlugin.printErrorToConsole(getProject(), msg); + } + } + + /** + * Get the stderr output of a process and return when the process is done. + * @param process The process to get the ouput from + * @param stdErr The array to store the stderr output + * @return the process return code. + * @throws InterruptedException + */ + protected final int grabProcessOutput(final Process process, + final ArrayList<String> stdErr) throws InterruptedException { + return BuildHelper.grabProcessOutput(getProject(), process, stdErr); + } + + + + /** + * Saves a String property into the persistent storage of the project. + * @param propertyName the name of the property. The id of the plugin is added to this string. + * @param value the value to save + * @return true if the save succeeded. + */ + protected boolean saveProjectStringProperty(String propertyName, String value) { + IProject project = getProject(); + return ProjectHelper.saveStringProperty(project, propertyName, value); + } + + + /** + * Loads a String property from the persistent storage of the project. + * @param propertyName the name of the property. The id of the plugin is added to this string. + * @return the property value or null if it was not found. + */ + protected String loadProjectStringProperty(String propertyName) { + IProject project = getProject(); + return ProjectHelper.loadStringProperty(project, propertyName); + } + + /** + * Saves a property into the persistent storage of the project. + * @param propertyName the name of the property. The id of the plugin is added to this string. + * @param value the value to save + * @return true if the save succeeded. + */ + protected boolean saveProjectBooleanProperty(String propertyName, boolean value) { + IProject project = getProject(); + return ProjectHelper.saveStringProperty(project, propertyName, Boolean.toString(value)); + } + + /** + * Loads a boolean property from the persistent storage of the project. + * @param propertyName the name of the property. The id of the plugin is added to this string. + * @param defaultValue The default value to return if the property was not found. + * @return the property value or the default value if the property was not found. + */ + protected boolean loadProjectBooleanProperty(String propertyName, boolean defaultValue) { + IProject project = getProject(); + return ProjectHelper.loadBooleanProperty(project, propertyName, defaultValue); + } + + /** + * Aborts the build if the SDK/project setups are broken. This does not + * display any errors. + * + * @param javaProject The {@link IJavaProject} being compiled. + * @param projectState the project state, optional. will be queried if null. + * @throws CoreException + */ + protected void abortOnBadSetup(@NonNull IJavaProject javaProject, + @Nullable ProjectState projectState) throws AbortBuildException, CoreException { + IProject iProject = javaProject.getProject(); + // check if we have finished loading the project target. + Sdk sdk = Sdk.getCurrent(); + if (sdk == null) { + throw new AbortBuildException(); + } + + if (projectState == null) { + projectState = Sdk.getProjectState(javaProject.getProject()); + } + + // get the target for the project + IAndroidTarget target = projectState.getTarget(); + + if (target == null) { + throw new AbortBuildException(); + } + + // check on the target data. + if (sdk.checkAndLoadTargetData(target, javaProject) != LoadStatus.LOADED) { + throw new AbortBuildException(); + } + + mBuildToolInfo = projectState.getBuildToolInfo(); + if (mBuildToolInfo == null) { + mBuildToolInfo = sdk.getLatestBuildTool(); + + if (mBuildToolInfo == null) { + AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, iProject, + "No \"Build Tools\" package available; use SDK Manager to install one."); + throw new AbortBuildException(); + } else { + AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, iProject, + String.format("Using default Build Tools revision %s", + mBuildToolInfo.getRevision()) + ); + } + } + + // abort if there are TARGET or ADT type markers + stopOnMarker(iProject, AdtConstants.MARKER_TARGET, IResource.DEPTH_ZERO, + false /*checkSeverity*/); + stopOnMarker(iProject, AdtConstants.MARKER_ADT, IResource.DEPTH_ZERO, + false /*checkSeverity*/); + } + + protected void stopOnMarker(IProject project, String markerType, int depth, + boolean checkSeverity) + throws AbortBuildException { + try { + IMarker[] markers = project.findMarkers(markerType, false /*includeSubtypes*/, depth); + + if (markers.length > 0) { + if (checkSeverity == false) { + throw new AbortBuildException(); + } else { + for (IMarker marker : markers) { + int severity = marker.getAttribute(IMarker.SEVERITY, -1 /*defaultValue*/); + if (severity == IMarker.SEVERITY_ERROR) { + throw new AbortBuildException(); + } + } + } + } + } catch (CoreException e) { + // don't stop, something's really screwed up and the build will break later with + // a better error message. + } + } + + /** + * Handles a {@link StreamException} by logging the info and marking the project. + * This should generally be followed by exiting the build process. + * + * @param e the exception + */ + protected void handleStreamException(StreamException e) { + IAbstractFile file = e.getFile(); + + String msg; + + IResource target = getProject(); + if (file instanceof IFileWrapper) { + target = ((IFileWrapper) file).getIFile(); + + if (e.getError() == StreamException.Error.OUTOFSYNC) { + msg = "File is Out of sync"; + } else { + msg = "Error reading file. Read log for details"; + } + + } else { + if (e.getError() == StreamException.Error.OUTOFSYNC) { + msg = String.format("Out of sync file: %s", file.getOsLocation()); + } else { + msg = String.format("Error reading file %s. Read log for details", + file.getOsLocation()); + } + } + + AdtPlugin.logAndPrintError(e, getProject().getName(), msg); + BaseProjectHelper.markResource(target, AdtConstants.MARKER_ADT, msg, + IMarker.SEVERITY_ERROR); + } + + /** + * Handles a generic {@link Throwable} by logging the info and marking the project. + * This should generally be followed by exiting the build process. + * + * @param t the {@link Throwable}. + * @param message the message to log and to associate with the marker. + */ + protected void handleException(Throwable t, String message) { + AdtPlugin.logAndPrintError(t, getProject().getName(), message); + markProject(AdtConstants.MARKER_ADT, message, IMarker.SEVERITY_ERROR); + } + + /** + * Recursively delete all the derived resources from a root resource. The root resource is not + * deleted. + * @param rootResource the root resource + * @param monitor a progress monitor. + * @throws CoreException + * + */ + protected void removeDerivedResources(IResource rootResource, IProgressMonitor monitor) + throws CoreException { + removeDerivedResources(rootResource, false, monitor); + } + + /** + * delete a resource and its children. returns true if the root resource was deleted. All + * sub-folders *will* be deleted if they were emptied (not if they started empty). + * @param rootResource the root resource + * @param deleteRoot whether to delete the root folder. + * @param monitor a progress monitor. + * @throws CoreException + */ + private void removeDerivedResources(IResource rootResource, boolean deleteRoot, + IProgressMonitor monitor) throws CoreException { + if (rootResource.exists()) { + // if it's a folder, delete derived member. + if (rootResource.getType() == IResource.FOLDER) { + IFolder folder = (IFolder)rootResource; + IResource[] members = folder.members(); + boolean wasNotEmpty = members.length > 0; + for (IResource member : members) { + removeDerivedResources(member, true /*deleteRoot*/, monitor); + } + + // if the folder had content that is now all removed, delete the folder. + if (deleteRoot && wasNotEmpty && folder.members().length == 0) { + rootResource.getLocation().toFile().delete(); + } + } + + // if the root resource is derived, delete it. + if (rootResource.isDerived()) { + rootResource.getLocation().toFile().delete(); + } + } + } + + protected void launchJob(Job newJob) { + newJob.setPriority(Job.BUILD); + newJob.setRule(ResourcesPlugin.getWorkspace().getRoot()); + newJob.schedule(); + } +} |