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 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 
     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          */
    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
    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 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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      */
    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      */
    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      */
    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      */
    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 
    478     protected void launchJob(Job newJob) {
    479         newJob.setPriority(Job.BUILD);
    480         newJob.setRule(ResourcesPlugin.getWorkspace().getRoot());
    481         newJob.schedule();
    482     }
    483 }
    484