aboutsummaryrefslogtreecommitdiffstats
path: root/anttasks
diff options
context:
space:
mode:
authorJosiah Gaskin <josiahgaskin@google.com>2011-06-22 14:32:09 -0700
committerJosiah Gaskin <josiahgaskin@google.com>2011-07-18 17:00:24 -0700
commit065dfacbf0c6e46c80f53d9207fac17afcb1fca7 (patch)
treec00c001afd84d4ab53dec913e516f821d75740c6 /anttasks
parentad94adc4e79ee99741635db328e0b85c4ed712ec (diff)
downloadsdk-065dfacbf0c6e46c80f53d9207fac17afcb1fca7.zip
sdk-065dfacbf0c6e46c80f53d9207fac17afcb1fca7.tar.gz
sdk-065dfacbf0c6e46c80f53d9207fac17afcb1fca7.tar.bz2
Add support to Ant for dependency checking
This change adds dependency checking to the Ant generation of R.java. If no resources have been changed or added since the last generation of R.java then that call to aapt will be skipped. Change-Id: I262b94111316496fac54da8030a6d474dc30b584
Diffstat (limited to 'anttasks')
-rw-r--r--anttasks/src/com/android/ant/AaptExecLoopTask.java36
-rw-r--r--anttasks/src/com/android/ant/BaseTask.java70
-rw-r--r--anttasks/src/com/android/ant/DependencyGraph.java254
3 files changed, 355 insertions, 5 deletions
diff --git a/anttasks/src/com/android/ant/AaptExecLoopTask.java b/anttasks/src/com/android/ant/AaptExecLoopTask.java
index 725f6b6..ed0277a 100644
--- a/anttasks/src/com/android/ant/AaptExecLoopTask.java
+++ b/anttasks/src/com/android/ant/AaptExecLoopTask.java
@@ -18,7 +18,6 @@ package com.android.ant;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
-import org.apache.tools.ant.Task;
import org.apache.tools.ant.taskdefs.ExecTask;
import org.apache.tools.ant.types.Path;
@@ -51,7 +50,7 @@ import java.util.ArrayList;
* <tr><td></td><td></td><td></td></tr>
* </table>
*/
-public final class AaptExecLoopTask extends Task {
+public final class AaptExecLoopTask extends BaseTask {
/**
* Class representing a &lt;nocompress&gt; node in the main task XML.
@@ -259,8 +258,10 @@ public final class AaptExecLoopTask extends Task {
// more R classes need to be created for libraries.
if (mRFolder != null && new File(mRFolder).isDirectory()) {
libPkgProp = taskProject.getProperty(AntConstants.PROP_PROJECT_LIBS_PKG);
- // Replace ";" with ":" since that's what aapt expects
- libPkgProp = libPkgProp.replace(';', ':');
+ if (libPkgProp != null) {
+ // Replace ";" with ":" since that's what aapt expects
+ libPkgProp = libPkgProp.replace(';', ':');
+ }
}
// Call aapt. If there are libraries, we'll pass a non-null string of libs.
callAapt(libPkgProp);
@@ -278,7 +279,31 @@ public final class AaptExecLoopTask extends Task {
final boolean generateRClass = mRFolder != null && new File(mRFolder).isDirectory();
+ // Get whether we have libraries
+ Object libResRef = taskProject.getReference(AntConstants.PROP_PROJECT_LIBS_RES_REF);
+
if (generateRClass) {
+ // If the only reason we're here is to generate R.java and that doesn't need updating
+ // we can skip what comes next. First we grab the dependency file.
+ // Then query to see if an update is needed.
+ ArrayList<File> watchPaths = new ArrayList<File>();
+ // We need to watch for changes in the main project res folder
+ for (Path pathList : mResources) {
+ for (String path : pathList.list()) {
+ watchPaths.add(new File(path));
+ }
+ }
+ // and if libraries exist, in their res folders
+ if (libResRef instanceof Path) {
+ for (String path : ((Path)libResRef).list()) {
+ watchPaths.add(new File(path));
+ }
+ }
+ if (initDependencies(mRFolder + File.separator + "R.d", watchPaths)
+ && dependenciesHaveChanged() == false) {
+ System.out.println("No changed resources. R.java and Manifest.java untouched.");
+ return;
+ }
} else if (mResourceFilter == null) {
System.out.println("Creating full resource package...");
} else {
@@ -347,7 +372,6 @@ public final class AaptExecLoopTask extends Task {
}
// if the project contains libraries, force auto-add-overlay
- Object libResRef = taskProject.getReference(AntConstants.PROP_PROJECT_LIBS_RES_REF);
if (libResRef != null) {
task.createArg().setValue("--auto-add-overlay");
}
@@ -417,6 +441,8 @@ public final class AaptExecLoopTask extends Task {
if (generateRClass) {
task.createArg().setValue("-J");
task.createArg().setValue(mRFolder);
+ // Use dependency generation
+ task.createArg().setValue("--generate-dependencies");
}
// final setup of the task
diff --git a/anttasks/src/com/android/ant/BaseTask.java b/anttasks/src/com/android/ant/BaseTask.java
new file mode 100644
index 0000000..2126d3f
--- /dev/null
+++ b/anttasks/src/com/android/ant/BaseTask.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.ant;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Task;
+
+import java.io.File;
+import java.util.ArrayList;
+
+/**
+ * A base class for the ant task that contains logic for handling dependency files
+ */
+public class BaseTask extends Task {
+
+ private DependencyGraph mDependencies;
+
+ /*
+ * (non-Javadoc)
+ *
+ * Executes the loop. Based on the values inside default.properties, this will
+ * create alternate temporary ap_ files.
+ *
+ * @see org.apache.tools.ant.Task#execute()
+ */
+ @Override
+ public void execute() throws BuildException {
+
+ }
+
+ /**
+ * Set up the dependency graph by passing it the location of the ".d" file
+ * @param dependencyFile path to the dependency file to use
+ * @param watchPaths a list of folders to watch for new files
+ * @return true if the dependency graph was sucessfully initialized
+ */
+ protected boolean initDependencies(String dependencyFile, ArrayList<File> watchPaths) {
+ File depFile = new File(dependencyFile);
+ if (depFile.exists()) {
+ mDependencies = new DependencyGraph(dependencyFile, watchPaths);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Wrapper check to see if we need to execute this task at all
+ * @return true if the DependencyGraph reports that our prereqs or targets
+ * have changed since the last run
+ */
+ protected boolean dependenciesHaveChanged() {
+ assert mDependencies != null : "Dependencies have not been initialized";
+ return mDependencies.dependenciesHaveChanged();
+ }
+}
diff --git a/anttasks/src/com/android/ant/DependencyGraph.java b/anttasks/src/com/android/ant/DependencyGraph.java
new file mode 100644
index 0000000..5939a92
--- /dev/null
+++ b/anttasks/src/com/android/ant/DependencyGraph.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.ant;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * This class takes care of dependency tracking for all targets and prerequisites listed in
+ * a single dependency file. A dependency graph always has a dependency file associated with it
+ * for the duration of its lifetime
+ */
+public class DependencyGraph {
+
+ // Files that we know about from the dependency file
+ private Set<File> mTargets = Collections.emptySet();
+ private Set<File> mPrereqs = mTargets;
+ private ArrayList<File> mWatchPaths;
+
+ public DependencyGraph(String dependencyFilePath, ArrayList<File> watchPaths) {
+ mWatchPaths = watchPaths;
+ parseDependencyFile(dependencyFilePath);
+ }
+
+ /**
+ * Check all the dependencies to see if anything has changed.
+ * @return true if new prerequisites have appeared, target files are missing or if
+ * prerequisite files have been modified since the last target generation.
+ */
+ public boolean dependenciesHaveChanged() {
+ boolean noFile = (mTargets.size() == 0);
+ boolean missingPrereq = missingPrereqFile();
+ boolean newPrereq = newPrereqFile();
+ boolean missingTarget = missingTargetFile();
+ boolean modPrereq = modifiedPrereq();
+
+ if (noFile) {
+ System.out.println("No Dependency File Found");
+ }
+ if (missingPrereq) {
+ System.out.println("Found Deleted Prereq File");
+ }
+ if (newPrereq) {
+ System.out.println("Found New Prereq File");
+ }
+ if (missingTarget) {
+ System.out.println("Found Deleted Target File");
+ }
+ if (modPrereq) {
+ System.out.println("Found Modified Prereq File");
+ }
+ // If no dependency file has been set up, then we'll just return true
+ // if we have a dependency file, we'll check to see what's been changed
+ return noFile || missingPrereq || newPrereq || missingTarget || modPrereq;
+ }
+
+ /**
+ * Parses the given dependency file and stores the file paths
+ *
+ * @param dependencyFilePath the dependency file
+ */
+ private void parseDependencyFile(String dependencyFilePath) {
+ // Read in our dependency file
+ String content = readFile(dependencyFilePath);
+ if (content == null) {
+ System.err.println("ERROR: Couldn't read " + dependencyFilePath);
+ return;
+ }
+
+ // The format is something like:
+ // output1 output2 [...]: dep1 dep2 [...]
+ // expect it's likely split on several lines. So let's move it back on a single line
+ // first
+ String[] lines = content.toString().split("\n");
+ StringBuilder sb = new StringBuilder(content.length());
+ for (String line : lines) {
+ line = line.trim();
+ if (line.endsWith("\\")) {
+ line = line.substring(0, line.length() - 1);
+ }
+ sb.append(line);
+ }
+
+ // split the left and right part
+ String[] files = sb.toString().split(":");
+
+ // get the target files:
+ String[] targets = files[0].trim().split(" ");
+
+ String[] prereqs = {};
+ // Check to make sure our dependency file is okay
+ if (files.length < 1) {
+ System.err.println(
+ "Warning! Dependency file does not list any prerequisites after ':' ");
+ } else {
+ // and the prerequisite files:
+ prereqs = files[1].trim().split(" ");
+ }
+
+ mTargets = new HashSet<File>(targets.length);
+ for (String path : targets) {
+ mTargets.add(new File(path));
+ }
+ mPrereqs = new HashSet<File>(prereqs.length);
+ for (String path : prereqs) {
+ mPrereqs.add(new File(path));
+ }
+ }
+
+ /**
+ * Check all the folders we know about to see if there have been new
+ * files added to them.
+ * @return true if a new file is encountered in the dependency folders
+ */
+ private boolean newPrereqFile() {
+ for (File dir : mWatchPaths) {
+ if (newFileInTree(dir)) {
+ return true;
+ }
+ }
+ // If we make it all the way through our directories we're good.
+ return false;
+ }
+
+ /**
+ * Check all the files in the tree under root and check to see if the files are
+ * listed under the dependencies. Recurses into subdirs.
+ * @param root the root of the file tree to search through
+ * @return true if a file is encountered in the tree that is not in our list of prereqs
+ */
+ private boolean newFileInTree(File root) {
+ File[] files = root.listFiles();
+ if (files == null) {
+ System.err.println("ERROR " + root.toString() + " is not a dir or can't be read");
+ return false;
+ }
+ // Loop through files in this folder
+ for (File file : files) {
+ // If this is a directory, recurse into it
+ if (file.isDirectory()) {
+ if (newFileInTree(file)) {
+ return true;
+ }
+ } else if (file.isFile() && mPrereqs.contains(file) == false) {
+ return true;
+ }
+ }
+ // If we got to here then we didn't find anything interesting
+ return false;
+ }
+
+ /**
+ * Check all the prereq files we know about to make sure they're still there
+ * @return true if any of the prereq files are missing.
+ */
+ private boolean missingPrereqFile() {
+ // Loop through our prereq files and make sure they still exist
+ for (File prereq : mPrereqs) {
+ if (prereq.exists() == false) {
+ return true;
+ }
+ }
+ // If we get this far, then all our targets are okay
+ return false;
+ }
+
+ /**
+ * Check all the target files we know about to make sure they're still there
+ * @return true if any of the target files are missing.
+ */
+ private boolean missingTargetFile() {
+ // Loop through our target files and make sure they still exist
+ for (File target : mTargets) {
+ if (target.exists() == false) {
+ return true;
+ }
+ }
+ // If we get this far, then all our targets are okay
+ return false;
+ }
+
+ /**
+ * Check to see if any of the prerequisite files have been modified since
+ * the targets were last updated.
+ * @return true if the latest prerequisite modification is after the oldest
+ * target modification.
+ */
+ private boolean modifiedPrereq() {
+ // Find the oldest target
+ long oldestTarget = Long.MAX_VALUE;
+ for (File target : mTargets) {
+ if (target.lastModified() < oldestTarget) {
+ oldestTarget = target.lastModified();
+ }
+ }
+
+ // Find the newest prerequisite
+ long newestPrereq = 0;
+ for (File prereq : mPrereqs) {
+ if (prereq.lastModified() > newestPrereq) {
+ newestPrereq = prereq.lastModified();
+ }
+ }
+
+ // And return the comparison
+ return newestPrereq > oldestTarget;
+ }
+
+ /**
+ * Reads and returns the content of a text file.
+ * @param filepath the file path to the text file
+ * @return null if the file could not be read
+ */
+ private static String readFile(String filepath) {
+ try {
+ FileInputStream fStream = new FileInputStream(filepath);
+ if (fStream != null) {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(fStream));
+
+ String line;
+ StringBuilder total = new StringBuilder(reader.readLine());
+ while ((line = reader.readLine()) != null) {
+ total.append('\n');
+ total.append(line);
+ }
+ return total.toString();
+ }
+ } catch (IOException e) {
+ // we'll just return null
+ }
+ return null;
+ }
+}