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.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 
     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          */
    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
    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 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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      */
    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      */
    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      */
    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      */
    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 
    474     protected void launchJob(Job newJob) {
    475         newJob.setPriority(Job.BUILD);
    476         newJob.schedule();
    477     }
    478 }
    479