Home | History | Annotate | Download | only in builders
      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 
     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          */
    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
    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 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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      */
    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      */
    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      */
    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      */
    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 
    477     protected void launchJob(Job newJob) {
    478         newJob.setPriority(Job.BUILD);
    479         newJob.setRule(ResourcesPlugin.getWorkspace().getRoot());
    480         newJob.schedule();
    481     }
    482 }
    483