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