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