OSDN Git Service

Precompilation only executes AAPT when necessary
authorJosiah Gaskin <josiahgaskin@google.com>
Thu, 18 Aug 2011 18:39:50 +0000 (11:39 -0700)
committerJosiah Gaskin <josiahgaskin@google.com>
Thu, 18 Aug 2011 21:25:45 +0000 (14:25 -0700)
This change adds resource tracking to the ResourceManager.
Each ResourceRepository now has new methods:

void markForIdRefresh() to set the repository as "dirty"
boolean needsIdRefresh() to check whether the repository is dirty
void setIdsRefreshed() to set the repository as "clean"

During the precompilation step, the PreCompiler will query the
ResourceManager to see if any of the repositories included in the build
are marked as dirty. AAPT will only be run if one or more dirty repositories
are found.

Repositories are marked as clean when R.java is regenerated and IDs are
set in ProjectResources.

Change-Id: I575ab819702508eacd247b282c3de8979f2f0ab9

eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/build_messages.properties
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerDeltaVisitor.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/CompiledResourcesMonitor.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceManager.java
ide_common/src/com/android/ide/common/resources/IdGeneratingResourceFile.java
ide_common/src/com/android/ide/common/resources/MultiResourceFile.java
ide_common/src/com/android/ide/common/resources/ResourceRepository.java
ide_common/src/com/android/ide/common/resources/SingleResourceFile.java

index aabe2a4..28ceebd 100644 (file)
@@ -37,7 +37,7 @@ AIDL_Java_Conflict=%1$s is in the way of %2$s, remove it or rename of one the fi
 AIDL_Exec_Error=Error executing aidl. Please check aidl is present at %1$s
 s_Removed_Recreating_s=%1$s was removed\! Recreating %1$s\!
 s_Modified_Manually_Recreating_s=%1$s was modified manually\! Reverting to generated version\!
-s_Modified_Recreating_s='%1$s' was modified, %2$s needs to be updated.
+s_Modified_Recreating_s='%1$s' was modified.
 Added_s_s_Needs_Updating=New resource file: '%1$s', %2$s needs to be updated.
 s_Removed_s_Needs_Updating='%1$s' was removed, %2$s needs to be updated.
 Requires_Compiler_Compliance_s=Android requires compiler compliance level 5.0 or 6.0. Found '%1$s' instead. Please use Android Tools > Fix Project Properties.
index 2a988d1..3ece0e5 100644 (file)
@@ -255,10 +255,15 @@ public class PreCompilerBuilder extends BaseBuilder {
                     dv = new PreCompilerDeltaVisitor(this, sourceFolderPathList, mProcessors);
                     delta.accept(dv);
                     // Notify the ResourceManager:
-                    ResourceManager.getInstance().processDelta(delta);
+                    ResourceManager resManager = ResourceManager.getInstance();
+                    resManager.processDelta(delta);
 
-                    // record the state
+                    // Check whether this project or its dependencies (libraries) have
+                    // resources that need compilation
+                    mMustCompileResources |= resManager.projectNeedsIdGeneration(project);
+                    // Check to see if Manifest.xml, Manifest.java, or R.java have changed:
                     mMustCompileResources |= dv.getCompileResources();
+
                     for (SourceProcessor processor : mProcessors) {
                         processor.doneVisiting(project);
                     }
@@ -266,24 +271,6 @@ public class PreCompilerBuilder extends BaseBuilder {
                     // get the java package from the visitor
                     javaPackage = dv.getManifestPackage();
                     minSdkVersion = dv.getMinSdkVersion();
-
-                    // if the main resources didn't change, then we check for the library
-                    // ones (will trigger resource recompilation too)
-                    if (mMustCompileResources == false && libProjects.size() > 0) {
-                        for (IProject libProject : libProjects) {
-                            delta = getDelta(libProject);
-                            if (delta != null) {
-                                LibraryDeltaVisitor visitor = new LibraryDeltaVisitor();
-                                delta.accept(visitor);
-
-                                mMustCompileResources = visitor.getResChange();
-
-                                if (mMustCompileResources) {
-                                    break;
-                                }
-                            }
-                        }
-                    }
                 }
             }
 
@@ -496,6 +483,7 @@ public class PreCompilerBuilder extends BaseBuilder {
                 handleResources(project, javaPackage, projectTarget, manifestFile, libProjects,
                         projectState.isLibrary());
                 saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES , false);
+                // The project resources will find out that they're in sync when their IDs are set
             }
 
             if (processorStatus == SourceProcessor.COMPILE_STATUS_NONE &&
index cd99fbe..5f08856 100644 (file)
 
 package com.android.ide.eclipse.adt.internal.build.builders;
 
-import com.android.ide.eclipse.adt.AdtPlugin;
 import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.build.Messages;
 import com.android.ide.eclipse.adt.internal.build.SourceChangeHandler;
 import com.android.ide.eclipse.adt.internal.build.SourceProcessor;
-import com.android.ide.eclipse.adt.internal.build.Messages;
 import com.android.ide.eclipse.adt.internal.build.builders.BaseBuilder.BaseDeltaVisitor;
 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity;
 import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
@@ -60,9 +60,8 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements IResourceDelta
     // Result fields.
     /**
      * Compile flag. This is set to true if one of the changed/added/removed
-     * file is a resource file. Upon visiting all the delta resources, if
-     * this flag is true, then we know we'll have to compile the resources
-     * into R.java
+     * files is Manifest.xml, Manifest.java, or R.java. All other file changes
+     * will be taken care of by ResourceManager.
      */
     private boolean mCompileResources = false;
 
@@ -88,12 +87,12 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements IResourceDelta
     private IFolder mSourceFolder = null;
 
     /** List of source folders. */
-    private List<IPath> mSourceFolders;
+    private final List<IPath> mSourceFolders;
     private boolean mIsGenSourceFolder = false;
 
     private final List<SourceChangeHandler> mSourceChangeHandlers =
         new ArrayList<SourceChangeHandler>();
-    private IWorkspaceRoot mRoot;
+    private final IWorkspaceRoot mRoot;
 
 
 
@@ -109,6 +108,10 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements IResourceDelta
         }
     }
 
+    /**
+     * Get whether Manifest.java, Manifest.xml, or R.java have changed
+     * @return true if any of Manifest.xml, Manifest.java, or R.java have been modified
+     */
     public boolean getCompileResources() {
         return mCompileResources;
     }
@@ -323,8 +326,7 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements IResourceDelta
             switch (kind) {
                 case IResourceDelta.CHANGED:
                     // display verbose message
-                    message = String.format(Messages.s_Modified_Recreating_s, p,
-                            AdtConstants.FN_RESOURCE_CLASS);
+                    message = String.format(Messages.s_Modified_Recreating_s, p);
                     break;
                 case IResourceDelta.ADDED:
                     // display verbose message
@@ -345,26 +347,13 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements IResourceDelta
             for (SourceChangeHandler handler : mSourceChangeHandlers) {
                 handler.handleResourceFile((IFile)resource, kind);
             }
-
-            if (AdtConstants.EXT_XML.equalsIgnoreCase(ext)) {
-                if (kind != IResourceDelta.REMOVED) {
-                    // check xml Validity
-                    mBuilder.checkXML(resource, this);
-                }
-
-                // if we are going through this resource, it was modified
-                // somehow.
-                // we don't care if it was an added/removed/changed event
-                mCompileResources = true;
-                return false;
-            } else {
-                // this is a non xml resource.
-                if (kind == IResourceDelta.ADDED
-                        || kind == IResourceDelta.REMOVED) {
-                    mCompileResources = true;
-                    return false;
-                }
+            // If it's an XML resource, check the syntax
+            if (AdtConstants.EXT_XML.equalsIgnoreCase(ext) && kind != IResourceDelta.REMOVED) {
+                // check xml Validity
+                mBuilder.checkXML(resource, this);
             }
+            // Whether or not to generate R.java for a changed resource is taken care of by the
+            // Resource Manager.
         } else if (resource instanceof IFolder) {
             // in this case we may be inside a folder that contains a source
             // folder, go through the list of known source folders
index c917d1c..6ef1710 100644 (file)
@@ -32,7 +32,6 @@ import org.eclipse.core.resources.IMarkerDelta;
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.IResource;
 import org.eclipse.core.resources.IResourceDelta;
-import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.IStatus;
@@ -79,9 +78,7 @@ public final class CompiledResourcesMonitor implements IFileListener, IProjectLi
      * @see IFileListener#fileChanged
      */
     public void fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind) {
-        // Don't execute if we're autobuilding, let the precompiler take care of the delta
-        if (file.getName().equals(AdtConstants.FN_COMPILED_RESOURCE_CLASS)
-                && ResourcesPlugin.getWorkspace().getDescription().isAutoBuilding() == false) {
+        if (file.getName().equals(AdtConstants.FN_COMPILED_RESOURCE_CLASS)) {
             loadAndParseRClass(file.getProject());
         }
     }
index 6d15f89..6967e67 100644 (file)
@@ -72,7 +72,6 @@ public class ProjectResources extends ResourceRepository {
 
     private final IProject mProject;
 
-
     /**
      * Makes a ProjectResources for a given <var>project</var>.
      * @param project the project.
@@ -288,5 +287,9 @@ public class ProjectResources extends ResourceRepository {
         mResourceValueMap = resourceValueMap;
         mResIdValueToNameMap = resIdValueToNameMap;
         mStyleableValueToNameMap = styleableValueMap;
+
+        // Our resource IDs should now be in sync
+        setIdsRefreshed();
     }
+
 }
index c014601..edf55cd 100644 (file)
@@ -26,6 +26,8 @@ import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IProjectListener;
 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IRawDeltaListener;
 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IResourceEventListener;
+import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
 import com.android.ide.eclipse.adt.io.IFileWrapper;
 import com.android.ide.eclipse.adt.io.IFolderWrapper;
 import com.android.io.FolderWrapper;
@@ -337,10 +339,10 @@ public final class ResourceManager {
             return;
         }
 
-        // checks if the file is under res/something.
+        // checks if the file is under res/something or bin/res/something
         IPath path = file.getFullPath();
 
-        if (path.segmentCount() == 4) {
+        if (path.segmentCount() == 4 || path.segmentCount() == 5) {
             if (isInResFolder(path)) {
                 IContainer container = file.getParent();
                 if (container instanceof IFolder) {
@@ -464,6 +466,50 @@ public final class ResourceManager {
     }
 
     /**
+     * Checks the ResourceRepositories associated with the given project and its dependencies
+     * and returns whether or not a resource regeneration is needed for that project
+     * @param project the project to check
+     * @return true if the project or any of its dependencies says it has new or deleted resources
+     */
+    public boolean projectNeedsIdGeneration(IProject project) {
+        // Get a list of repositories to check through
+        List<ProjectResources> repositories = getAllProjectResourcesAssociatedWith(project);
+        for (ProjectResources repository : repositories) {
+            if (repository.needsIdRefresh()) {
+                return true;
+            }
+        }
+        // If we've gotten to here, all repositories are in sync, return false
+        return false;
+    }
+
+    /**
+     * Get all the resource repositories representing this project and any included libraries
+     * @param project the project to get along with its dependencies
+     * @return a list of all ProjectResources ordered lowest to highest priority that need to be
+     *         included in this project.
+     */
+    private List<ProjectResources> getAllProjectResourcesAssociatedWith(IProject project) {
+        List<ProjectResources> toRet = new ArrayList<ProjectResources>();
+        // if the project contains libraries, we need to add the libraries resources here
+        if (project != null) {
+            ProjectState state = Sdk.getProjectState(project);
+            if (state != null) {
+                List<IProject> libraries = state.getFullLibraryProjects();
+                for (IProject library : libraries) {
+                    ProjectResources libRes = mMap.get(library);
+                    if (libRes != null) {
+                        toRet.add(libRes);
+                    }
+                }
+            }
+        }
+        // Add the queried current project last
+        toRet.add(mMap.get(project));
+        return toRet;
+    }
+
+    /**
      * Initial project parsing to gather resource info.
      * @param project
      */
index fa8d0e7..6706715 100644 (file)
@@ -31,6 +31,7 @@ import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 
 import javax.xml.parsers.ParserConfigurationException;
 import javax.xml.parsers.SAXParser;
@@ -88,8 +89,8 @@ public final class IdGeneratingResourceFile extends ResourceFile
 
     @Override
     protected void update() {
-        // remove this file from all existing ResourceItem.
-        getFolder().getRepository().removeFile(mResourceTypeList, this);
+        // Copy the previous list of ID names
+        Set<String> oldIdNames = mIdResources.keySet();
 
         // reset current content.
         mIdResources.clear();
@@ -97,14 +98,21 @@ public final class IdGeneratingResourceFile extends ResourceFile
         // need to parse the file and find the IDs.
         parseFileForIds();
 
-        // Notify the repository about any changes
-        updateResourceItems();
+        // We only need to update the repository if our IDs have changed
+        if (oldIdNames.equals(mIdResources.keySet()) == false) {
+            updateResourceItems();
+        }
     }
 
     @Override
     protected void dispose() {
+        ResourceRepository repository = getRepository();
+
         // Remove declarations from this file from the repository
-        getFolder().getRepository().removeFile(mResourceTypeList, this);
+        repository.removeFile(mResourceTypeList, this);
+
+        // Ask for an ID refresh since we'll be taking away ID generating items
+        repository.markForIdRefresh();
     }
 
     @Override
@@ -155,6 +163,9 @@ public final class IdGeneratingResourceFile extends ResourceFile
     private void updateResourceItems() {
         ResourceRepository repository = getRepository();
 
+        // remove this file from all existing ResourceItem.
+        repository.removeFile(mResourceTypeList, this);
+
         // First add this as a layout file
         ResourceItem item = repository.getResourceItem(mFileType, mFileName);
         item.add(this);
@@ -165,6 +176,9 @@ public final class IdGeneratingResourceFile extends ResourceFile
             // add this file to the list of files generating ID resources.
             item.add(this);
         }
+
+        //  Ask the repository for an ID refresh
+        repository.markForIdRefresh();
     }
 
     /**
index 6d8ca0a..b3e35d9 100644 (file)
@@ -54,6 +54,10 @@ public final class MultiResourceFile extends ResourceFile implements IValueResou
         super(file, folder);
     }
 
+    // Boolean flag to track whether a named element has been added or removed, thus requiring
+    // a new ID table to be generated
+    private boolean mNeedIdRefresh;
+
     @Override
     protected void load() {
         // need to parse the file and find the content.
@@ -62,14 +66,21 @@ public final class MultiResourceFile extends ResourceFile implements IValueResou
         // create new ResourceItems for the new content.
         mResourceTypeList = Collections.unmodifiableCollection(mResourceItems.keySet());
 
+        // We need an ID generation step
+        mNeedIdRefresh = true;
+
         // create/update the resource items.
         updateResourceItems();
     }
 
     @Override
     protected void update() {
-        // remove this file from all existing ResourceItem.
-        getFolder().getRepository().removeFile(mResourceTypeList, this);
+        // Reset the ID generation flag
+        mNeedIdRefresh = false;
+
+        // Copy the previous version of our list of ResourceItems and types
+        Map<ResourceType, Map<String, ResourceValue>> oldResourceItems
+                        = new EnumMap<ResourceType, Map<String, ResourceValue>>(mResourceItems);
 
         // reset current content.
         mResourceItems.clear();
@@ -80,14 +91,34 @@ public final class MultiResourceFile extends ResourceFile implements IValueResou
         // create new ResourceItems for the new content.
         mResourceTypeList = Collections.unmodifiableCollection(mResourceItems.keySet());
 
+        // Check to see if any names have changed. If so, mark the flag so updateResourceItems
+        // can notify the ResourceRepository that an ID refresh is needed
+        if (oldResourceItems.keySet().equals(mResourceItems.keySet())) {
+            for (ResourceType type : mResourceTypeList) {
+                // We just need to check the names of the items.
+                // If there are new or removed names then we'll have to regenerate IDs
+                if (mResourceItems.get(type).keySet()
+                                          .equals(oldResourceItems.get(type).keySet()) == false) {
+                    mNeedIdRefresh = true;
+                }
+            }
+        } else {
+            // If our type list is different, obviously the names will be different
+            mNeedIdRefresh = true;
+        }
         // create/update the resource items.
         updateResourceItems();
     }
 
     @Override
     protected void dispose() {
+        ResourceRepository repository = getRepository();
+
         // only remove this file from all existing ResourceItem.
-        getFolder().getRepository().removeFile(mResourceTypeList, this);
+        repository.removeFile(mResourceTypeList, this);
+
+        // We'll need an ID refresh because we deleted items
+        repository.markForIdRefresh();
 
         // don't need to touch the content, it'll get reclaimed as this objects disappear.
         // In the mean time other objects may need to access it.
@@ -106,6 +137,10 @@ public final class MultiResourceFile extends ResourceFile implements IValueResou
 
     private void updateResourceItems() {
         ResourceRepository repository = getRepository();
+
+        // remove this file from all existing ResourceItem.
+        repository.removeFile(mResourceTypeList, this);
+
         for (ResourceType type : mResourceTypeList) {
             Map<String, ResourceValue> list = mResourceItems.get(type);
 
@@ -119,6 +154,11 @@ public final class MultiResourceFile extends ResourceFile implements IValueResou
                 }
             }
         }
+
+        // If we need an ID refresh, ask the repository for that now
+        if (mNeedIdRefresh) {
+            repository.markForIdRefresh();
+        }
     }
 
     /**
index fa533cb..4af4a1a 100644 (file)
@@ -68,6 +68,7 @@ public abstract class ResourceRepository {
 
     protected final IntArrayWrapper mWrapper = new IntArrayWrapper(null);
 
+    private boolean mNeedsIdRefresh;
 
     /**
      * Makes a resource repository
@@ -195,6 +196,29 @@ public abstract class ResourceRepository {
     protected abstract ResourceItem createResourceItem(String name);
 
     /**
+     * Sets a flag which determines whether aapt needs to be run to regenerate resource IDs
+     */
+    protected void markForIdRefresh() {
+        mNeedsIdRefresh = true;
+    }
+
+    /**
+     * Returns whether this repository has been marked as "dirty"; if one or more of the constituent
+     * files have declared that the resource item names that they provide have changed.
+     */
+    public boolean needsIdRefresh() {
+        return mNeedsIdRefresh;
+    }
+
+    /**
+     * Indicates that the resources IDs have been regenerated, so the repository is now in a clean
+     * state
+     */
+    public void setIdsRefreshed() {
+        mNeedsIdRefresh = false;
+    }
+
+    /**
      * Processes a folder and adds it to the list of existing folders.
      * @param folder the folder to process
      * @return the ResourceFolder created from this folder, or null if the process failed.
index 9c8977e..b589b35 100644 (file)
@@ -41,8 +41,8 @@ public class SingleResourceFile extends ResourceFile {
         sParserFactory.setNamespaceAware(true);
     }
 
-    private String mResourceName;
-    private ResourceType mType;
+    private final String mResourceName;
+    private final ResourceType mType;
     private ResourceValue mValue;
 
     public SingleResourceFile(IAbstractFile file, ResourceFolder folder) {
@@ -79,6 +79,9 @@ public class SingleResourceFile extends ResourceFile {
 
         // add this file to the list of files generating this resource item.
         item.add(this);
+
+        // Ask for an ID refresh since we're adding an item that will generate an ID
+        getRepository().markForIdRefresh();
     }
 
     @Override
@@ -92,6 +95,9 @@ public class SingleResourceFile extends ResourceFile {
         // only remove this file from the existing ResourceItem.
         getFolder().getRepository().removeFile(mType, this);
 
+        // Ask for an ID refresh since we're removing an item that previously generated an ID
+        getRepository().markForIdRefresh();
+
         // don't need to touch the content, it'll get reclaimed as this objects disappear.
         // In the mean time other objects may need to access it.
     }