From 882e673462566249a538d72b16917bc6cac8315d Mon Sep 17 00:00:00 2001 From: Josiah Gaskin Date: Thu, 18 Aug 2011 11:39:50 -0700 Subject: [PATCH] Precompilation only executes AAPT when necessary 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 --- .../adt/internal/build/build_messages.properties | 2 +- .../build/builders/PreCompilerBuilder.java | 28 ++++-------- .../build/builders/PreCompilerDeltaVisitor.java | 45 ++++++++----------- .../manager/CompiledResourcesMonitor.java | 5 +-- .../resources/manager/ProjectResources.java | 5 ++- .../resources/manager/ResourceManager.java | 50 +++++++++++++++++++++- .../common/resources/IdGeneratingResourceFile.java | 24 ++++++++--- .../ide/common/resources/MultiResourceFile.java | 46 ++++++++++++++++++-- .../ide/common/resources/ResourceRepository.java | 24 +++++++++++ .../ide/common/resources/SingleResourceFile.java | 10 ++++- 10 files changed, 173 insertions(+), 66 deletions(-) diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/build_messages.properties b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/build_messages.properties index aabe2a4e9..28ceebda6 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/build_messages.properties +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/build_messages.properties @@ -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. diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java index 2a988d104..3ece0e589 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java @@ -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 && diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerDeltaVisitor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerDeltaVisitor.java index cd99fbebc..5f0885647 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerDeltaVisitor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerDeltaVisitor.java @@ -16,11 +16,11 @@ 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 mSourceFolders; + private final List mSourceFolders; private boolean mIsGenSourceFolder = false; private final List mSourceChangeHandlers = new ArrayList(); - 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 diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/CompiledResourcesMonitor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/CompiledResourcesMonitor.java index c917d1c7d..6ef171091 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/CompiledResourcesMonitor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/CompiledResourcesMonitor.java @@ -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()); } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java index 6d15f8925..6967e6746 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java @@ -72,7 +72,6 @@ public class ProjectResources extends ResourceRepository { private final IProject mProject; - /** * Makes a ProjectResources for a given project. * @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(); } + } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceManager.java index c014601d1..edf55cd94 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceManager.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceManager.java @@ -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 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 getAllProjectResourcesAssociatedWith(IProject project) { + List toRet = new ArrayList(); + // 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 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 */ diff --git a/ide_common/src/com/android/ide/common/resources/IdGeneratingResourceFile.java b/ide_common/src/com/android/ide/common/resources/IdGeneratingResourceFile.java index fa8d0e7a2..67067156c 100644 --- a/ide_common/src/com/android/ide/common/resources/IdGeneratingResourceFile.java +++ b/ide_common/src/com/android/ide/common/resources/IdGeneratingResourceFile.java @@ -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 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(); } /** diff --git a/ide_common/src/com/android/ide/common/resources/MultiResourceFile.java b/ide_common/src/com/android/ide/common/resources/MultiResourceFile.java index 6d8ca0a47..b3e35d976 100644 --- a/ide_common/src/com/android/ide/common/resources/MultiResourceFile.java +++ b/ide_common/src/com/android/ide/common/resources/MultiResourceFile.java @@ -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> oldResourceItems + = new EnumMap>(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 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(); + } } /** diff --git a/ide_common/src/com/android/ide/common/resources/ResourceRepository.java b/ide_common/src/com/android/ide/common/resources/ResourceRepository.java index fa533cb65..4af4a1a94 100644 --- a/ide_common/src/com/android/ide/common/resources/ResourceRepository.java +++ b/ide_common/src/com/android/ide/common/resources/ResourceRepository.java @@ -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. diff --git a/ide_common/src/com/android/ide/common/resources/SingleResourceFile.java b/ide_common/src/com/android/ide/common/resources/SingleResourceFile.java index 9c8977ee5..b589b3501 100644 --- a/ide_common/src/com/android/ide/common/resources/SingleResourceFile.java +++ b/ide_common/src/com/android/ide/common/resources/SingleResourceFile.java @@ -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. } -- 2.11.0