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