| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Eclipse Public License, Version 1.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.eclipse.org/org/documents/epl-v10.php |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.ide.eclipse.adt.internal.build; |
| |
| import com.android.SdkConstants; |
| import com.android.annotations.NonNull; |
| import com.android.ide.eclipse.adt.internal.build.builders.BaseBuilder; |
| import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; |
| import com.android.ide.eclipse.adt.internal.project.ProjectHelper; |
| import com.android.sdklib.BuildToolInfo; |
| import com.android.sdklib.IAndroidTarget; |
| |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IFolder; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.IWorkspaceRoot; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.jdt.core.IJavaProject; |
| |
| import java.io.File; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * Base class to handle generated java code. |
| * |
| * It provides management for modified source file list, deleted source file list, reconciliation |
| * of previous lists, storing the current state of the build. |
| * |
| */ |
| public abstract class SourceProcessor { |
| |
| public final static int COMPILE_STATUS_NONE = 0; |
| public final static int COMPILE_STATUS_CODE = 0x1; |
| public final static int COMPILE_STATUS_RES = 0x2; |
| |
| /** List of all source files, their dependencies, and their output. */ |
| private final Map<IFile, SourceFileData> mFiles = new HashMap<IFile, SourceFileData>(); |
| |
| private final IJavaProject mJavaProject; |
| private BuildToolInfo mBuildToolInfo; |
| private final IFolder mGenFolder; |
| private final DefaultSourceChangeHandler mDeltaVisitor; |
| |
| /** List of source files pending compilation at the next build */ |
| private final List<IFile> mToCompile = new ArrayList<IFile>(); |
| |
| /** List of removed source files pending cleaning at the next build. */ |
| private final List<IFile> mRemoved = new ArrayList<IFile>(); |
| |
| private int mLastCompilationStatus = COMPILE_STATUS_NONE; |
| |
| /** |
| * Quotes a path inside "". If the platform is not windows, the path is returned as is. |
| * @param path the path to quote |
| * @return the quoted path. |
| */ |
| public static String quote(String path) { |
| if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) { |
| if (path.endsWith(File.separator)) { |
| path = path.substring(0, path.length() -1); |
| } |
| return "\"" + path + "\""; |
| } |
| |
| return path; |
| } |
| |
| protected SourceProcessor( |
| @NonNull IJavaProject javaProject, |
| @NonNull BuildToolInfo buildToolInfo, |
| @NonNull IFolder genFolder, |
| @NonNull DefaultSourceChangeHandler deltaVisitor) { |
| mJavaProject = javaProject; |
| mBuildToolInfo = buildToolInfo; |
| mGenFolder = genFolder; |
| mDeltaVisitor = deltaVisitor; |
| |
| mDeltaVisitor.init(this); |
| |
| IProject project = javaProject.getProject(); |
| |
| // get all the source files |
| buildSourceFileList(); |
| |
| // load the known dependencies |
| loadOutputAndDependencies(); |
| |
| boolean mustCompile = loadState(project); |
| |
| // if we stored that we have to compile some files, we build the list that will compile them |
| // all. For now we have to reuse the full list since we don't know which files needed |
| // compilation. |
| if (mustCompile) { |
| mToCompile.addAll(mFiles.keySet()); |
| } |
| } |
| |
| protected SourceProcessor( |
| @NonNull IJavaProject javaProject, |
| @NonNull BuildToolInfo buildToolInfo, |
| @NonNull IFolder genFolder) { |
| this(javaProject, buildToolInfo, genFolder, new DefaultSourceChangeHandler()); |
| } |
| |
| public void setBuildToolInfo(BuildToolInfo buildToolInfo) { |
| mBuildToolInfo = buildToolInfo; |
| } |
| |
| |
| /** |
| * Returns whether the given file is an output of this processor by return the source |
| * file that generated it. |
| * @param file the file to test. |
| * @return the source file that generated the given file or null. |
| */ |
| IFile isOutput(IFile file) { |
| for (SourceFileData data : mFiles.values()) { |
| if (data.generated(file)) { |
| return data.getSourceFile(); |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Returns whether the given file is a dependency for other files by returning a list |
| * of file depending on the given file. |
| * @param file the file to test. |
| * @return a list of files that depend on the given file or an empty list if there |
| * are no matches. |
| */ |
| List<IFile> isDependency(IFile file) { |
| ArrayList<IFile> files = new ArrayList<IFile>(); |
| for (SourceFileData data : mFiles.values()) { |
| if (data.dependsOn(file)) { |
| files.add(data.getSourceFile()); |
| } |
| } |
| |
| return files; |
| } |
| |
| void addData(SourceFileData data) { |
| mFiles.put(data.getSourceFile(), data); |
| } |
| |
| SourceFileData getFileData(IFile file) { |
| return mFiles.get(file); |
| } |
| |
| Collection<SourceFileData> getAllFileData() { |
| return mFiles.values(); |
| } |
| |
| public final DefaultSourceChangeHandler getChangeHandler() { |
| return mDeltaVisitor; |
| } |
| |
| final IJavaProject getJavaProject() { |
| return mJavaProject; |
| } |
| |
| final BuildToolInfo getBuildToolInfo() { |
| return mBuildToolInfo; |
| } |
| |
| final IFolder getGenFolder() { |
| return mGenFolder; |
| } |
| |
| final List<IFile> getToCompile() { |
| return mToCompile; |
| } |
| |
| final List<IFile> getRemovedFile() { |
| return mRemoved; |
| } |
| |
| final void addFileToCompile(IFile file) { |
| mToCompile.add(file); |
| } |
| |
| public final void prepareFullBuild(IProject project) { |
| mDeltaVisitor.reset(); |
| |
| mToCompile.clear(); |
| mRemoved.clear(); |
| |
| // get all the source files |
| buildSourceFileList(); |
| |
| mToCompile.addAll(mFiles.keySet()); |
| |
| saveState(project); |
| } |
| |
| public final void doneVisiting(IProject project) { |
| // merge the previous file modification lists and the new one. |
| mergeFileModifications(mDeltaVisitor); |
| |
| mDeltaVisitor.reset(); |
| |
| saveState(project); |
| } |
| |
| /** |
| * Returns the extension of the source files handled by this processor. |
| * @return |
| */ |
| protected abstract Set<String> getExtensions(); |
| |
| protected abstract String getSavePropertyName(); |
| |
| /** |
| * Compiles the source files and return a status bitmask of the type of file that was generated. |
| * |
| */ |
| public final int compileFiles(BaseBuilder builder, |
| IProject project, IAndroidTarget projectTarget, |
| List<IPath> sourceFolders, List<File> libraryProjectsOut, IProgressMonitor monitor) |
| throws CoreException { |
| |
| mLastCompilationStatus = COMPILE_STATUS_NONE; |
| |
| if (mToCompile.size() == 0 && mRemoved.size() == 0) { |
| return mLastCompilationStatus; |
| } |
| |
| // if a source file is being removed before we managed to compile it, it'll be in |
| // both list. We *need* to remove it from the compile list or it'll never go away. |
| for (IFile sourceFile : mRemoved) { |
| int pos = mToCompile.indexOf(sourceFile); |
| if (pos != -1) { |
| mToCompile.remove(pos); |
| } |
| } |
| |
| // list of files that have failed compilation. |
| List<IFile> stillNeedCompilation = new ArrayList<IFile>(); |
| |
| doCompileFiles(mToCompile, builder, project, projectTarget, sourceFolders, |
| stillNeedCompilation, libraryProjectsOut, monitor); |
| |
| mToCompile.clear(); |
| mToCompile.addAll(stillNeedCompilation); |
| |
| // Remove the files created from source files that have been removed. |
| for (IFile sourceFile : mRemoved) { |
| // look if we already know the output |
| SourceFileData data = getFileData(sourceFile); |
| if (data != null) { |
| doRemoveFiles(data); |
| } |
| } |
| |
| // remove the associated file data. |
| for (IFile removedFile : mRemoved) { |
| mFiles.remove(removedFile); |
| } |
| |
| mRemoved.clear(); |
| |
| // store the build state. If there are any files that failed to compile, we will |
| // force a full aidl compile on the next project open. (unless a full compilation succeed |
| // before the project is closed/re-opened.) |
| saveState(project); |
| |
| return mLastCompilationStatus; |
| } |
| |
| protected abstract void doCompileFiles( |
| List<IFile> filesToCompile, BaseBuilder builder, |
| IProject project, IAndroidTarget projectTarget, |
| List<IPath> sourceFolders, List<IFile> notCompiledOut, |
| List<File> libraryProjectsOut, IProgressMonitor monitor) throws CoreException; |
| |
| /** |
| * Adds a compilation status. It can be any of (in combination too): |
| * <p/> |
| * {@link #COMPILE_STATUS_CODE} means this processor created source code files. |
| * {@link #COMPILE_STATUS_RES} means this process created resources. |
| */ |
| protected void setCompilationStatus(int status) { |
| mLastCompilationStatus |= status; |
| } |
| |
| protected void doRemoveFiles(SourceFileData data) throws CoreException { |
| List<IFile> outputFiles = data.getOutputFiles(); |
| for (IFile outputFile : outputFiles) { |
| if (outputFile.exists()) { |
| outputFile.getLocation().toFile().delete(); |
| } |
| } |
| } |
| |
| public final boolean loadState(IProject project) { |
| return ProjectHelper.loadBooleanProperty(project, getSavePropertyName(), |
| true /*defaultValue*/); |
| } |
| |
| public final void saveState(IProject project) { |
| // TODO: Optimize by saving only the files that need compilation |
| ProjectHelper.saveStringProperty(project, getSavePropertyName(), |
| Boolean.toString(mToCompile.size() > 0)); |
| } |
| |
| protected abstract void loadOutputAndDependencies(); |
| |
| |
| protected IPath getSourceFolderFor(IFile file) { |
| // find the source folder for the class so that we can infer the package from the |
| // difference between the file and its source folder. |
| List<IPath> sourceFolders = BaseProjectHelper.getSourceClasspaths(getJavaProject()); |
| IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); |
| |
| for (IPath sourceFolderPath : sourceFolders) { |
| IFolder sourceFolder = root.getFolder(sourceFolderPath); |
| // we don't look in the 'gen' source folder as there will be no source in there. |
| if (sourceFolder.exists() && sourceFolder.equals(getGenFolder()) == false) { |
| // look for the source file parent, until we find this source folder. |
| IResource parent = file; |
| while ((parent = parent.getParent()) != null) { |
| if (parent.equals(sourceFolder)) { |
| return sourceFolderPath; |
| } |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Goes through the build paths and fills the list of files to compile. |
| * |
| * @param project The project. |
| * @param sourceFolderPathList The list of source folder paths. |
| */ |
| private final void buildSourceFileList() { |
| mFiles.clear(); |
| |
| IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); |
| List<IPath> sourceFolderPathList = BaseProjectHelper.getSourceClasspaths(mJavaProject); |
| |
| for (IPath sourceFolderPath : sourceFolderPathList) { |
| IFolder sourceFolder = root.getFolder(sourceFolderPath); |
| // we don't look in the 'gen' source folder as there will be no source in there. |
| if (sourceFolder.exists() && sourceFolder.equals(getGenFolder()) == false) { |
| scanFolderForSourceFiles(sourceFolder, sourceFolder); |
| } |
| } |
| } |
| |
| /** |
| * Scans a folder and fills the list of files to compile. |
| * @param sourceFolder the root source folder. |
| * @param folder The folder to scan. |
| */ |
| private void scanFolderForSourceFiles(IFolder sourceFolder, IFolder folder) { |
| try { |
| IResource[] members = folder.members(); |
| for (IResource r : members) { |
| // get the type of the resource |
| switch (r.getType()) { |
| case IResource.FILE: { |
| // if this a file, check that the file actually exist |
| // and that it's the type of of file that's used in this processor |
| String extension = r.exists() ? r.getFileExtension() : null; |
| if (extension != null && |
| getExtensions().contains(extension.toLowerCase(Locale.US))) { |
| mFiles.put((IFile) r, new SourceFileData((IFile) r)); |
| } |
| break; |
| } |
| case IResource.FOLDER: |
| // recursively go through children |
| scanFolderForSourceFiles(sourceFolder, (IFolder)r); |
| break; |
| default: |
| // this would mean it's a project or the workspace root |
| // which is unlikely to happen. we do nothing |
| break; |
| } |
| } |
| } catch (CoreException e) { |
| // Couldn't get the members list for some reason. Just return. |
| } |
| } |
| |
| |
| /** |
| * Merge the current list of source file to compile/remove with the one coming from the |
| * delta visitor |
| * @param visitor the delta visitor. |
| */ |
| private void mergeFileModifications(DefaultSourceChangeHandler visitor) { |
| Set<IFile> toRemove = visitor.getRemovedFiles(); |
| Set<IFile> toCompile = visitor.getFilesToCompile(); |
| |
| // loop through the new toRemove list, and add it to the old one, |
| // plus remove any file that was still to compile and that are now |
| // removed |
| for (IFile r : toRemove) { |
| if (mRemoved.indexOf(r) == -1) { |
| mRemoved.add(r); |
| } |
| |
| int index = mToCompile.indexOf(r); |
| if (index != -1) { |
| mToCompile.remove(index); |
| } |
| } |
| |
| // now loop through the new files to compile and add it to the list. |
| // Also look for them in the remove list, this would mean that they |
| // were removed, then added back, and we shouldn't remove them, just |
| // recompile them. |
| for (IFile r : toCompile) { |
| if (mToCompile.indexOf(r) == -1) { |
| mToCompile.add(r); |
| } |
| |
| int index = mRemoved.indexOf(r); |
| if (index != -1) { |
| mRemoved.remove(index); |
| } |
| } |
| } |
| } |