1 /* 2 * Copyright (C) 2011 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.SdkConstants; 20 import com.android.annotations.NonNull; 21 import com.android.ide.eclipse.adt.internal.build.builders.BaseBuilder; 22 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; 23 import com.android.ide.eclipse.adt.internal.project.ProjectHelper; 24 import com.android.sdklib.BuildToolInfo; 25 import com.android.sdklib.IAndroidTarget; 26 27 import org.eclipse.core.resources.IFile; 28 import org.eclipse.core.resources.IFolder; 29 import org.eclipse.core.resources.IProject; 30 import org.eclipse.core.resources.IResource; 31 import org.eclipse.core.resources.IWorkspaceRoot; 32 import org.eclipse.core.resources.ResourcesPlugin; 33 import org.eclipse.core.runtime.CoreException; 34 import org.eclipse.core.runtime.IPath; 35 import org.eclipse.core.runtime.IProgressMonitor; 36 import org.eclipse.jdt.core.IJavaProject; 37 38 import java.io.File; 39 import java.util.ArrayList; 40 import java.util.Collection; 41 import java.util.HashMap; 42 import java.util.List; 43 import java.util.Locale; 44 import java.util.Map; 45 import java.util.Set; 46 47 /** 48 * Base class to handle generated java code. 49 * 50 * It provides management for modified source file list, deleted source file list, reconciliation 51 * of previous lists, storing the current state of the build. 52 * 53 */ 54 public abstract class SourceProcessor { 55 56 public final static int COMPILE_STATUS_NONE = 0; 57 public final static int COMPILE_STATUS_CODE = 0x1; 58 public final static int COMPILE_STATUS_RES = 0x2; 59 60 /** List of all source files, their dependencies, and their output. */ 61 private final Map<IFile, SourceFileData> mFiles = new HashMap<IFile, SourceFileData>(); 62 63 private final IJavaProject mJavaProject; 64 private BuildToolInfo mBuildToolInfo; 65 private final IFolder mGenFolder; 66 private final DefaultSourceChangeHandler mDeltaVisitor; 67 68 /** List of source files pending compilation at the next build */ 69 private final List<IFile> mToCompile = new ArrayList<IFile>(); 70 71 /** List of removed source files pending cleaning at the next build. */ 72 private final List<IFile> mRemoved = new ArrayList<IFile>(); 73 74 private int mLastCompilationStatus = COMPILE_STATUS_NONE; 75 76 /** 77 * Quotes a path inside "". If the platform is not windows, the path is returned as is. 78 * @param path the path to quote 79 * @return the quoted path. 80 */ quote(String path)81 public static String quote(String path) { 82 if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) { 83 if (path.endsWith(File.separator)) { 84 path = path.substring(0, path.length() -1); 85 } 86 return "\"" + path + "\""; 87 } 88 89 return path; 90 } 91 SourceProcessor( @onNull IJavaProject javaProject, @NonNull BuildToolInfo buildToolInfo, @NonNull IFolder genFolder, @NonNull DefaultSourceChangeHandler deltaVisitor)92 protected SourceProcessor( 93 @NonNull IJavaProject javaProject, 94 @NonNull BuildToolInfo buildToolInfo, 95 @NonNull IFolder genFolder, 96 @NonNull DefaultSourceChangeHandler deltaVisitor) { 97 mJavaProject = javaProject; 98 mBuildToolInfo = buildToolInfo; 99 mGenFolder = genFolder; 100 mDeltaVisitor = deltaVisitor; 101 102 mDeltaVisitor.init(this); 103 104 IProject project = javaProject.getProject(); 105 106 // get all the source files 107 buildSourceFileList(); 108 109 // load the known dependencies 110 loadOutputAndDependencies(); 111 112 boolean mustCompile = loadState(project); 113 114 // if we stored that we have to compile some files, we build the list that will compile them 115 // all. For now we have to reuse the full list since we don't know which files needed 116 // compilation. 117 if (mustCompile) { 118 mToCompile.addAll(mFiles.keySet()); 119 } 120 } 121 SourceProcessor( @onNull IJavaProject javaProject, @NonNull BuildToolInfo buildToolInfo, @NonNull IFolder genFolder)122 protected SourceProcessor( 123 @NonNull IJavaProject javaProject, 124 @NonNull BuildToolInfo buildToolInfo, 125 @NonNull IFolder genFolder) { 126 this(javaProject, buildToolInfo, genFolder, new DefaultSourceChangeHandler()); 127 } 128 setBuildToolInfo(BuildToolInfo buildToolInfo)129 public void setBuildToolInfo(BuildToolInfo buildToolInfo) { 130 mBuildToolInfo = buildToolInfo; 131 } 132 133 134 /** 135 * Returns whether the given file is an output of this processor by return the source 136 * file that generated it. 137 * @param file the file to test. 138 * @return the source file that generated the given file or null. 139 */ isOutput(IFile file)140 IFile isOutput(IFile file) { 141 for (SourceFileData data : mFiles.values()) { 142 if (data.generated(file)) { 143 return data.getSourceFile(); 144 } 145 } 146 147 return null; 148 } 149 150 /** 151 * Returns whether the given file is a dependency for other files by returning a list 152 * of file depending on the given file. 153 * @param file the file to test. 154 * @return a list of files that depend on the given file or an empty list if there 155 * are no matches. 156 */ isDependency(IFile file)157 List<IFile> isDependency(IFile file) { 158 ArrayList<IFile> files = new ArrayList<IFile>(); 159 for (SourceFileData data : mFiles.values()) { 160 if (data.dependsOn(file)) { 161 files.add(data.getSourceFile()); 162 } 163 } 164 165 return files; 166 } 167 addData(SourceFileData data)168 void addData(SourceFileData data) { 169 mFiles.put(data.getSourceFile(), data); 170 } 171 getFileData(IFile file)172 SourceFileData getFileData(IFile file) { 173 return mFiles.get(file); 174 } 175 getAllFileData()176 Collection<SourceFileData> getAllFileData() { 177 return mFiles.values(); 178 } 179 getChangeHandler()180 public final DefaultSourceChangeHandler getChangeHandler() { 181 return mDeltaVisitor; 182 } 183 getJavaProject()184 final IJavaProject getJavaProject() { 185 return mJavaProject; 186 } 187 getBuildToolInfo()188 final BuildToolInfo getBuildToolInfo() { 189 return mBuildToolInfo; 190 } 191 getGenFolder()192 final IFolder getGenFolder() { 193 return mGenFolder; 194 } 195 getToCompile()196 final List<IFile> getToCompile() { 197 return mToCompile; 198 } 199 getRemovedFile()200 final List<IFile> getRemovedFile() { 201 return mRemoved; 202 } 203 addFileToCompile(IFile file)204 final void addFileToCompile(IFile file) { 205 mToCompile.add(file); 206 } 207 prepareFullBuild(IProject project)208 public final void prepareFullBuild(IProject project) { 209 mDeltaVisitor.reset(); 210 211 mToCompile.clear(); 212 mRemoved.clear(); 213 214 // get all the source files 215 buildSourceFileList(); 216 217 mToCompile.addAll(mFiles.keySet()); 218 219 saveState(project); 220 } 221 doneVisiting(IProject project)222 public final void doneVisiting(IProject project) { 223 // merge the previous file modification lists and the new one. 224 mergeFileModifications(mDeltaVisitor); 225 226 mDeltaVisitor.reset(); 227 228 saveState(project); 229 } 230 231 /** 232 * Returns the extension of the source files handled by this processor. 233 * @return 234 */ getExtensions()235 protected abstract Set<String> getExtensions(); 236 getSavePropertyName()237 protected abstract String getSavePropertyName(); 238 239 /** 240 * Compiles the source files and return a status bitmask of the type of file that was generated. 241 * 242 */ compileFiles(BaseBuilder builder, IProject project, IAndroidTarget projectTarget, List<IPath> sourceFolders, List<File> libraryProjectsOut, IProgressMonitor monitor)243 public final int compileFiles(BaseBuilder builder, 244 IProject project, IAndroidTarget projectTarget, 245 List<IPath> sourceFolders, List<File> libraryProjectsOut, IProgressMonitor monitor) 246 throws CoreException { 247 248 mLastCompilationStatus = COMPILE_STATUS_NONE; 249 250 if (mToCompile.size() == 0 && mRemoved.size() == 0) { 251 return mLastCompilationStatus; 252 } 253 254 // if a source file is being removed before we managed to compile it, it'll be in 255 // both list. We *need* to remove it from the compile list or it'll never go away. 256 for (IFile sourceFile : mRemoved) { 257 int pos = mToCompile.indexOf(sourceFile); 258 if (pos != -1) { 259 mToCompile.remove(pos); 260 } 261 } 262 263 // list of files that have failed compilation. 264 List<IFile> stillNeedCompilation = new ArrayList<IFile>(); 265 266 doCompileFiles(mToCompile, builder, project, projectTarget, sourceFolders, 267 stillNeedCompilation, libraryProjectsOut, monitor); 268 269 mToCompile.clear(); 270 mToCompile.addAll(stillNeedCompilation); 271 272 // Remove the files created from source files that have been removed. 273 for (IFile sourceFile : mRemoved) { 274 // look if we already know the output 275 SourceFileData data = getFileData(sourceFile); 276 if (data != null) { 277 doRemoveFiles(data); 278 } 279 } 280 281 // remove the associated file data. 282 for (IFile removedFile : mRemoved) { 283 mFiles.remove(removedFile); 284 } 285 286 mRemoved.clear(); 287 288 // store the build state. If there are any files that failed to compile, we will 289 // force a full aidl compile on the next project open. (unless a full compilation succeed 290 // before the project is closed/re-opened.) 291 saveState(project); 292 293 return mLastCompilationStatus; 294 } 295 doCompileFiles( List<IFile> filesToCompile, BaseBuilder builder, IProject project, IAndroidTarget projectTarget, List<IPath> sourceFolders, List<IFile> notCompiledOut, List<File> libraryProjectsOut, IProgressMonitor monitor)296 protected abstract void doCompileFiles( 297 List<IFile> filesToCompile, BaseBuilder builder, 298 IProject project, IAndroidTarget projectTarget, 299 List<IPath> sourceFolders, List<IFile> notCompiledOut, 300 List<File> libraryProjectsOut, IProgressMonitor monitor) throws CoreException; 301 302 /** 303 * Adds a compilation status. It can be any of (in combination too): 304 * <p/> 305 * {@link #COMPILE_STATUS_CODE} means this processor created source code files. 306 * {@link #COMPILE_STATUS_RES} means this process created resources. 307 */ setCompilationStatus(int status)308 protected void setCompilationStatus(int status) { 309 mLastCompilationStatus |= status; 310 } 311 doRemoveFiles(SourceFileData data)312 protected void doRemoveFiles(SourceFileData data) throws CoreException { 313 List<IFile> outputFiles = data.getOutputFiles(); 314 for (IFile outputFile : outputFiles) { 315 if (outputFile.exists()) { 316 outputFile.getLocation().toFile().delete(); 317 } 318 } 319 } 320 loadState(IProject project)321 public final boolean loadState(IProject project) { 322 return ProjectHelper.loadBooleanProperty(project, getSavePropertyName(), 323 true /*defaultValue*/); 324 } 325 saveState(IProject project)326 public final void saveState(IProject project) { 327 // TODO: Optimize by saving only the files that need compilation 328 ProjectHelper.saveStringProperty(project, getSavePropertyName(), 329 Boolean.toString(mToCompile.size() > 0)); 330 } 331 loadOutputAndDependencies()332 protected abstract void loadOutputAndDependencies(); 333 334 getSourceFolderFor(IFile file)335 protected IPath getSourceFolderFor(IFile file) { 336 // find the source folder for the class so that we can infer the package from the 337 // difference between the file and its source folder. 338 List<IPath> sourceFolders = BaseProjectHelper.getSourceClasspaths(getJavaProject()); 339 IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); 340 341 for (IPath sourceFolderPath : sourceFolders) { 342 IFolder sourceFolder = root.getFolder(sourceFolderPath); 343 // we don't look in the 'gen' source folder as there will be no source in there. 344 if (sourceFolder.exists() && sourceFolder.equals(getGenFolder()) == false) { 345 // look for the source file parent, until we find this source folder. 346 IResource parent = file; 347 while ((parent = parent.getParent()) != null) { 348 if (parent.equals(sourceFolder)) { 349 return sourceFolderPath; 350 } 351 } 352 } 353 } 354 355 return null; 356 } 357 358 /** 359 * Goes through the build paths and fills the list of files to compile. 360 * 361 * @param project The project. 362 * @param sourceFolderPathList The list of source folder paths. 363 */ buildSourceFileList()364 private final void buildSourceFileList() { 365 mFiles.clear(); 366 367 IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); 368 List<IPath> sourceFolderPathList = BaseProjectHelper.getSourceClasspaths(mJavaProject); 369 370 for (IPath sourceFolderPath : sourceFolderPathList) { 371 IFolder sourceFolder = root.getFolder(sourceFolderPath); 372 // we don't look in the 'gen' source folder as there will be no source in there. 373 if (sourceFolder.exists() && sourceFolder.equals(getGenFolder()) == false) { 374 scanFolderForSourceFiles(sourceFolder, sourceFolder); 375 } 376 } 377 } 378 379 /** 380 * Scans a folder and fills the list of files to compile. 381 * @param sourceFolder the root source folder. 382 * @param folder The folder to scan. 383 */ scanFolderForSourceFiles(IFolder sourceFolder, IFolder folder)384 private void scanFolderForSourceFiles(IFolder sourceFolder, IFolder folder) { 385 try { 386 IResource[] members = folder.members(); 387 for (IResource r : members) { 388 // get the type of the resource 389 switch (r.getType()) { 390 case IResource.FILE: { 391 // if this a file, check that the file actually exist 392 // and that it's the type of of file that's used in this processor 393 String extension = r.exists() ? r.getFileExtension() : null; 394 if (extension != null && 395 getExtensions().contains(extension.toLowerCase(Locale.US))) { 396 mFiles.put((IFile) r, new SourceFileData((IFile) r)); 397 } 398 break; 399 } 400 case IResource.FOLDER: 401 // recursively go through children 402 scanFolderForSourceFiles(sourceFolder, (IFolder)r); 403 break; 404 default: 405 // this would mean it's a project or the workspace root 406 // which is unlikely to happen. we do nothing 407 break; 408 } 409 } 410 } catch (CoreException e) { 411 // Couldn't get the members list for some reason. Just return. 412 } 413 } 414 415 416 /** 417 * Merge the current list of source file to compile/remove with the one coming from the 418 * delta visitor 419 * @param visitor the delta visitor. 420 */ mergeFileModifications(DefaultSourceChangeHandler visitor)421 private void mergeFileModifications(DefaultSourceChangeHandler visitor) { 422 Set<IFile> toRemove = visitor.getRemovedFiles(); 423 Set<IFile> toCompile = visitor.getFilesToCompile(); 424 425 // loop through the new toRemove list, and add it to the old one, 426 // plus remove any file that was still to compile and that are now 427 // removed 428 for (IFile r : toRemove) { 429 if (mRemoved.indexOf(r) == -1) { 430 mRemoved.add(r); 431 } 432 433 int index = mToCompile.indexOf(r); 434 if (index != -1) { 435 mToCompile.remove(index); 436 } 437 } 438 439 // now loop through the new files to compile and add it to the list. 440 // Also look for them in the remove list, this would mean that they 441 // were removed, then added back, and we shouldn't remove them, just 442 // recompile them. 443 for (IFile r : toCompile) { 444 if (mToCompile.indexOf(r) == -1) { 445 mToCompile.add(r); 446 } 447 448 int index = mRemoved.indexOf(r); 449 if (index != -1) { 450 mRemoved.remove(index); 451 } 452 } 453 } 454 } 455