+++ /dev/null
-/*\r
- * \r
- * Copyright 2002-2004 The Ant-Contrib project\r
- *\r
- * Licensed under the Apache License, Version 2.0 (the "License");\r
- * you may not use this file except in compliance with the License.\r
- * You may obtain a copy of the License at\r
- *\r
- * http://www.apache.org/licenses/LICENSE-2.0\r
- *\r
- * Unless required by applicable law or agreed to in writing, software\r
- * distributed under the License is distributed on an "AS IS" BASIS,\r
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- * See the License for the specific language governing permissions and\r
- * limitations under the License.\r
- */\r
-package net.sf.antcontrib.cpptasks;\r
-import java.io.BufferedWriter;\r
-import java.io.File;\r
-import java.io.FileOutputStream;\r
-import java.io.IOException;\r
-import java.io.OutputStreamWriter;\r
-import java.io.UnsupportedEncodingException;\r
-import java.util.Enumeration;\r
-import java.util.Hashtable;\r
-import java.util.Vector;\r
-import javax.xml.parsers.ParserConfigurationException;\r
-import javax.xml.parsers.SAXParser;\r
-import javax.xml.parsers.SAXParserFactory;\r
-import net.sf.antcontrib.cpptasks.compiler.CompilerConfiguration;\r
-import org.apache.tools.ant.BuildException;\r
-import org.apache.tools.ant.Project;\r
-import org.xml.sax.Attributes;\r
-import org.xml.sax.SAXException;\r
-import org.xml.sax.helpers.DefaultHandler;\r
-/**\r
- * @author Curt Arnold\r
- */\r
-public final class DependencyTable {\r
- /**\r
- * This class handles populates the TargetHistory hashtable in response to\r
- * SAX parse events\r
- */\r
- private class DependencyTableHandler extends DefaultHandler {\r
- private File baseDir;\r
- private final DependencyTable dependencyTable;\r
- private String includePath;\r
- private Vector includes;\r
- private String source;\r
- private long sourceLastModified;\r
- private Vector sysIncludes;\r
- /**\r
- * Constructor\r
- * \r
- * @param history\r
- * hashtable of TargetHistory keyed by output name\r
- * @param outputFiles\r
- * existing files in output directory\r
- */\r
- private DependencyTableHandler(DependencyTable dependencyTable,\r
- File baseDir) {\r
- this.dependencyTable = dependencyTable;\r
- this.baseDir = baseDir;\r
- includes = new Vector();\r
- sysIncludes = new Vector();\r
- source = null;\r
- }\r
- public void endElement(String namespaceURI, String localName,\r
- String qName) throws SAXException {\r
- //\r
- // if </source> then\r
- // create Dependency object and add to hashtable\r
- // if corresponding source file exists and\r
- // has the same timestamp\r
- //\r
- if (qName.equals("source")) {\r
- if (source != null && includePath != null) {\r
- File existingFile = new File(baseDir, source);\r
- //\r
- // if the file exists and the time stamp is right\r
- // preserve the dependency info\r
- if (existingFile.exists()) {\r
- //\r
- // would have expected exact matches\r
- // but was seeing some unexpected difference by\r
- // a few tens of milliseconds, as long\r
- // as the times are within a second\r
- long existingLastModified = existingFile.lastModified();\r
- long diff = existingLastModified - sourceLastModified;\r
- if (diff >= -500 && diff <= 500) {\r
- DependencyInfo dependInfo = new DependencyInfo(\r
- includePath, source, sourceLastModified,\r
- includes, sysIncludes);\r
- dependencyTable.putDependencyInfo(source,\r
- dependInfo);\r
- }\r
- }\r
- source = null;\r
- includes.setSize(0);\r
- }\r
- } else {\r
- //\r
- // this causes any <source> elements outside the\r
- // scope of an <includePath> to be discarded\r
- //\r
- if (qName.equals("includePath")) {\r
- includePath = null;\r
- }\r
- }\r
- }\r
- /**\r
- * startElement handler\r
- */\r
- public void startElement(String namespaceURI, String localName,\r
- String qName, Attributes atts) throws SAXException {\r
- //\r
- // if includes, then add relative file name to vector\r
- //\r
- if (qName.equals("include")) {\r
- includes.addElement(atts.getValue("file"));\r
- } else {\r
- if (qName.equals("sysinclude")) {\r
- sysIncludes.addElement(atts.getValue("file"));\r
- } else {\r
- //\r
- // if source then\r
- // capture source file name,\r
- // modification time and reset includes vector\r
- //\r
- if (qName.equals("source")) {\r
- source = atts.getValue("file");\r
- sourceLastModified = Long.parseLong(atts\r
- .getValue("lastModified"), 16);\r
- includes.setSize(0);\r
- sysIncludes.setSize(0);\r
- } else {\r
- if (qName.equals("includePath")) {\r
- includePath = atts.getValue("signature");\r
- }\r
- }\r
- }\r
- }\r
- }\r
- }\r
- public abstract class DependencyVisitor {\r
- /**\r
- * Previews all the children of this source file.\r
- * \r
- * May be called multiple times as DependencyInfo's for children are\r
- * filled in.\r
- * \r
- * @return true to continue towards recursion into included files\r
- */\r
- public abstract boolean preview(DependencyInfo parent,\r
- DependencyInfo[] children);\r
- /**\r
- * Called if the dependency depth exhausted the stack.\r
- */\r
- public abstract void stackExhausted();\r
- /**\r
- * Visits the dependency info.\r
- * \r
- * @returns true to continue towards recursion into included files\r
- */\r
- public abstract boolean visit(DependencyInfo dependInfo);\r
- }\r
- public class TimestampChecker extends DependencyVisitor {\r
- private boolean noNeedToRebuild;\r
- private long outputLastModified;\r
- private boolean rebuildOnStackExhaustion;\r
- public TimestampChecker(final long outputLastModified,\r
- boolean rebuildOnStackExhaustion) {\r
- this.outputLastModified = outputLastModified;\r
- noNeedToRebuild = true;\r
- this.rebuildOnStackExhaustion = rebuildOnStackExhaustion;\r
- }\r
- public boolean getMustRebuild() {\r
- return !noNeedToRebuild;\r
- }\r
- public boolean preview(DependencyInfo parent, DependencyInfo[] children) {\r
- int withCompositeTimes = 0;\r
- long parentCompositeLastModified = parent.getSourceLastModified();\r
- for (int i = 0; i < children.length; i++) {\r
- if (children[i] != null) {\r
- //\r
- // expedient way to determine if a child forces us to\r
- // rebuild\r
- //\r
- visit(children[i]);\r
- long childCompositeLastModified = children[i]\r
- .getCompositeLastModified();\r
- if (childCompositeLastModified != Long.MIN_VALUE) {\r
- withCompositeTimes++;\r
- if (childCompositeLastModified > parentCompositeLastModified) {\r
- parentCompositeLastModified = childCompositeLastModified;\r
- }\r
- }\r
- }\r
- }\r
- if (withCompositeTimes == children.length) {\r
- parent.setCompositeLastModified(parentCompositeLastModified);\r
- }\r
- //\r
- // may have been changed by an earlier call to visit()\r
- //\r
- return noNeedToRebuild;\r
- }\r
- public void stackExhausted() {\r
- if (rebuildOnStackExhaustion) {\r
- noNeedToRebuild = false;\r
- }\r
- }\r
- public boolean visit(DependencyInfo dependInfo) {\r
- if (noNeedToRebuild) {\r
- if (dependInfo.getSourceLastModified() > outputLastModified\r
- || dependInfo.getCompositeLastModified() > outputLastModified) {\r
- noNeedToRebuild = false;\r
- }\r
- }\r
- //\r
- // only need to process the children if\r
- // it has not yet been determined whether\r
- // we need to rebuild and the composite modified time\r
- // has not been determined for this file\r
- return noNeedToRebuild\r
- && dependInfo.getCompositeLastModified() == Long.MIN_VALUE;\r
- }\r
- }\r
- private/* final */File baseDir;\r
- private String baseDirPath;\r
- /**\r
- * a hashtable of DependencyInfo[] keyed by output file name\r
- */\r
- private final Hashtable dependencies = new Hashtable();\r
- /** The file the cache was loaded from. */\r
- private/* final */File dependenciesFile;\r
- /** Flag indicating whether the cache should be written back to file. */\r
- private boolean dirty;\r
- /**\r
- * Creates a target history table from dependencies.xml in the prject\r
- * directory, if it exists. Otherwise, initializes the dependencies empty.\r
- * \r
- * @param task\r
- * task used for logging history load errors\r
- * @param baseDir\r
- * output directory for task\r
- */\r
- public DependencyTable(File baseDir) {\r
- if (baseDir == null) {\r
- throw new NullPointerException("baseDir");\r
- }\r
- this.baseDir = baseDir;\r
- try {\r
- baseDirPath = baseDir.getCanonicalPath();\r
- } catch (IOException ex) {\r
- baseDirPath = baseDir.toString();\r
- }\r
- dirty = false;\r
- //\r
- // load any existing dependencies from file\r
- dependenciesFile = new File(baseDir, "dependencies.xml");\r
- }\r
- public void commit(CCTask task) {\r
- //\r
- // if not dirty, no need to update file\r
- //\r
- if (dirty) {\r
- //\r
- // walk through dependencies to get vector of include paths\r
- // identifiers\r
- //\r
- Vector includePaths = getIncludePaths();\r
- //\r
- //\r
- // write dependency file\r
- //\r
- try {\r
- FileOutputStream outStream = new FileOutputStream(\r
- dependenciesFile);\r
- OutputStreamWriter streamWriter;\r
- //\r
- // Early VM's may not have UTF-8 support\r
- // fallback to default code page which\r
- // "should" be okay unless there are\r
- // non ASCII file names\r
- String encodingName = "UTF-8";\r
- try {\r
- streamWriter = new OutputStreamWriter(outStream, "UTF-8");\r
- } catch (UnsupportedEncodingException ex) {\r
- streamWriter = new OutputStreamWriter(outStream);\r
- encodingName = streamWriter.getEncoding();\r
- }\r
- BufferedWriter writer = new BufferedWriter(streamWriter);\r
- writer.write("<?xml version='1.0' encoding='");\r
- writer.write(encodingName);\r
- writer.write("'?>\n");\r
- writer.write("<dependencies>\n");\r
- StringBuffer buf = new StringBuffer();\r
- Enumeration includePathEnum = includePaths.elements();\r
- while (includePathEnum.hasMoreElements()) {\r
- writeIncludePathDependencies((String) includePathEnum\r
- .nextElement(), writer, buf);\r
- }\r
- writer.write("</dependencies>\n");\r
- writer.close();\r
- dirty = false;\r
- } catch (IOException ex) {\r
- task.log("Error writing " + dependenciesFile.toString() + ":"\r
- + ex.toString());\r
- }\r
- }\r
- }\r
- /**\r
- * Returns an enumerator of DependencyInfo's\r
- */\r
- public Enumeration elements() {\r
- return dependencies.elements();\r
- }\r
- /**\r
- * This method returns a DependencyInfo for the specific source file and\r
- * include path identifier\r
- * \r
- */\r
- public DependencyInfo getDependencyInfo(String sourceRelativeName,\r
- String includePathIdentifier) {\r
- DependencyInfo dependInfo = null;\r
- DependencyInfo[] dependInfos = (DependencyInfo[]) dependencies\r
- .get(sourceRelativeName);\r
- if (dependInfos != null) {\r
- for (int i = 0; i < dependInfos.length; i++) {\r
- dependInfo = dependInfos[i];\r
- if (dependInfo.getIncludePathIdentifier().equals(\r
- includePathIdentifier)) {\r
- return dependInfo;\r
- }\r
- }\r
- }\r
- return null;\r
- }\r
- private Vector getIncludePaths() {\r
- Vector includePaths = new Vector();\r
- DependencyInfo[] dependInfos;\r
- Enumeration dependenciesEnum = dependencies.elements();\r
- while (dependenciesEnum.hasMoreElements()) {\r
- dependInfos = (DependencyInfo[]) dependenciesEnum.nextElement();\r
- for (int i = 0; i < dependInfos.length; i++) {\r
- DependencyInfo dependInfo = dependInfos[i];\r
- boolean matchesExisting = false;\r
- final String dependIncludePath = dependInfo\r
- .getIncludePathIdentifier();\r
- Enumeration includePathEnum = includePaths.elements();\r
- while (includePathEnum.hasMoreElements()) {\r
- if (dependIncludePath.equals(includePathEnum.nextElement())) {\r
- matchesExisting = true;\r
- break;\r
- }\r
- }\r
- if (!matchesExisting) {\r
- includePaths.addElement(dependIncludePath);\r
- }\r
- }\r
- }\r
- return includePaths;\r
- }\r
- public void load() throws IOException, ParserConfigurationException,\r
- SAXException {\r
- dependencies.clear();\r
- if (dependenciesFile.exists()) {\r
- SAXParserFactory factory = SAXParserFactory.newInstance();\r
- factory.setValidating(false);\r
- SAXParser parser = factory.newSAXParser();\r
- parser.parse(dependenciesFile, new DependencyTableHandler(this,\r
- baseDir));\r
- dirty = false;\r
- }\r
- }\r
- /**\r
- * Determines if the specified target needs to be rebuilt.\r
- * \r
- * This task may result in substantial IO as files are parsed to determine\r
- * their dependencies\r
- */\r
- public boolean needsRebuild(CCTask task, TargetInfo target,\r
- int dependencyDepth) {\r
- // look at any files where the compositeLastModified\r
- // is not known, but the includes are known\r
- //\r
- boolean mustRebuild = false;\r
- CompilerConfiguration compiler = (CompilerConfiguration) target\r
- .getConfiguration();\r
- String includePathIdentifier = compiler.getIncludePathIdentifier();\r
- File[] sources = target.getSources();\r
- DependencyInfo[] dependInfos = new DependencyInfo[sources.length];\r
- long outputLastModified = target.getOutput().lastModified();\r
- //\r
- // try to solve problem using existing dependency info\r
- // (not parsing any new files)\r
- //\r
- DependencyInfo[] stack = new DependencyInfo[50];\r
- boolean rebuildOnStackExhaustion = true;\r
- if (dependencyDepth >= 0) {\r
- if (dependencyDepth < 50) {\r
- stack = new DependencyInfo[dependencyDepth];\r
- }\r
- rebuildOnStackExhaustion = false;\r
- }\r
- TimestampChecker checker = new TimestampChecker(outputLastModified,\r
- rebuildOnStackExhaustion);\r
- for (int i = 0; i < sources.length && !mustRebuild; i++) {\r
- File source = sources[i];\r
- String relative = CUtil.getRelativePath(baseDirPath, source);\r
- DependencyInfo dependInfo = getDependencyInfo(relative,\r
- includePathIdentifier);\r
- if (dependInfo == null) {\r
- task.log("Parsing " + relative, Project.MSG_VERBOSE);\r
- dependInfo = parseIncludes(task, compiler, source);\r
- }\r
- walkDependencies(task, dependInfo, compiler, stack, checker);\r
- mustRebuild = checker.getMustRebuild();\r
- }\r
- return mustRebuild;\r
- }\r
- public DependencyInfo parseIncludes(CCTask task,\r
- CompilerConfiguration compiler, File source) {\r
- DependencyInfo dependInfo = compiler.parseIncludes(task, baseDir,\r
- source);\r
- String relativeSource = CUtil.getRelativePath(baseDirPath, source);\r
- putDependencyInfo(relativeSource, dependInfo);\r
- return dependInfo;\r
- }\r
- private void putDependencyInfo(String key, DependencyInfo dependInfo) {\r
- //\r
- // optimistic, add new value\r
- //\r
- DependencyInfo[] old = (DependencyInfo[]) dependencies.put(key,\r
- new DependencyInfo[]{dependInfo});\r
- dirty = true;\r
- //\r
- // something was already there\r
- //\r
- if (old != null) {\r
- //\r
- // see if the include path matches a previous entry\r
- // if so replace it\r
- String includePathIdentifier = dependInfo\r
- .getIncludePathIdentifier();\r
- for (int i = 0; i < old.length; i++) {\r
- DependencyInfo oldDepend = old[i];\r
- if (oldDepend.getIncludePathIdentifier().equals(\r
- includePathIdentifier)) {\r
- old[i] = dependInfo;\r
- dependencies.put(key, old);\r
- return;\r
- }\r
- }\r
- //\r
- // no match prepend the new entry to the array\r
- // of dependencies for the file\r
- DependencyInfo[] combined = new DependencyInfo[old.length + 1];\r
- combined[0] = dependInfo;\r
- for (int i = 0; i < old.length; i++) {\r
- combined[i + 1] = old[i];\r
- }\r
- dependencies.put(key, combined);\r
- }\r
- return;\r
- }\r
- public void walkDependencies(CCTask task, DependencyInfo dependInfo,\r
- CompilerConfiguration compiler, DependencyInfo[] stack,\r
- DependencyVisitor visitor) throws BuildException {\r
- //\r
- // visit this node\r
- // if visit returns true then\r
- // visit the referenced include and sysInclude dependencies\r
- //\r
- if (visitor.visit(dependInfo)) {\r
- //\r
- // find first null entry on stack\r
- //\r
- int stackPosition = -1;\r
- for (int i = 0; i < stack.length; i++) {\r
- if (stack[i] == null) {\r
- stackPosition = i;\r
- stack[i] = dependInfo;\r
- break;\r
- } else {\r
- //\r
- // if we have appeared early in the calling history\r
- // then we didn't exceed the criteria\r
- if (stack[i] == dependInfo) {\r
- return;\r
- }\r
- }\r
- }\r
- if (stackPosition == -1) {\r
- visitor.stackExhausted();\r
- return;\r
- }\r
- //\r
- // locate dependency infos\r
- //\r
- String[] includes = dependInfo.getIncludes();\r
- String includePathIdentifier = compiler.getIncludePathIdentifier();\r
- DependencyInfo[] includeInfos = new DependencyInfo[includes.length];\r
- for (int i = 0; i < includes.length; i++) {\r
- DependencyInfo includeInfo = getDependencyInfo(includes[i],\r
- includePathIdentifier);\r
- includeInfos[i] = includeInfo;\r
- }\r
- //\r
- // preview with only the already available dependency infos\r
- //\r
- if (visitor.preview(dependInfo, includeInfos)) {\r
- //\r
- // now need to fill in the missing DependencyInfos\r
- //\r
- int missingCount = 0;\r
- for (int i = 0; i < includes.length; i++) {\r
- if (includeInfos[i] == null) {\r
- missingCount++;\r
- task.log("Parsing " + includes[i], Project.MSG_VERBOSE);\r
- // If the include is part of a UNC don't go building a\r
- // relative file name.\r
- File src = includes[i].startsWith("\\\\") ? new File(\r
- includes[i]) : new File(baseDir, includes[i]);\r
- DependencyInfo includeInfo = parseIncludes(task,\r
- compiler, src);\r
- includeInfos[i] = includeInfo;\r
- }\r
- }\r
- //\r
- // if it passes a review the second time\r
- // then recurse into all the children\r
- if (missingCount == 0\r
- || visitor.preview(dependInfo, includeInfos)) {\r
- //\r
- // recurse into\r
- //\r
- for (int i = 0; i < includeInfos.length; i++) {\r
- DependencyInfo includeInfo = includeInfos[i];\r
- walkDependencies(task, includeInfo, compiler, stack,\r
- visitor);\r
- }\r
- }\r
- }\r
- stack[stackPosition] = null;\r
- }\r
- }\r
- private void writeDependencyInfo(BufferedWriter writer, StringBuffer buf,\r
- DependencyInfo dependInfo) throws IOException {\r
- String[] includes = dependInfo.getIncludes();\r
- String[] sysIncludes = dependInfo.getSysIncludes();\r
- //\r
- // if the includes have not been evaluted then\r
- // it is not worth our time saving it\r
- // and trying to distiguish between files with\r
- // no dependencies and those with undetermined dependencies\r
- buf.setLength(0);\r
- buf.append(" <source file=\"");\r
- buf.append(CUtil.xmlAttribEncode(dependInfo.getSource()));\r
- buf.append("\" lastModified=\"");\r
- buf.append(Long.toHexString(dependInfo.getSourceLastModified()));\r
- buf.append("\">\n");\r
- writer.write(buf.toString());\r
- for (int i = 0; i < includes.length; i++) {\r
- buf.setLength(0);\r
- buf.append(" <include file=\"");\r
- buf.append(CUtil.xmlAttribEncode(includes[i]));\r
- buf.append("\"/>\n");\r
- writer.write(buf.toString());\r
- }\r
- for (int i = 0; i < sysIncludes.length; i++) {\r
- buf.setLength(0);\r
- buf.append(" <sysinclude file=\"");\r
- buf.append(CUtil.xmlAttribEncode(sysIncludes[i]));\r
- buf.append("\"/>\n");\r
- writer.write(buf.toString());\r
- }\r
- writer.write(" </source>\n");\r
- return;\r
- }\r
- private void writeIncludePathDependencies(String includePathIdentifier,\r
- BufferedWriter writer, StringBuffer buf) throws IOException {\r
- //\r
- // include path element\r
- //\r
- buf.setLength(0);\r
- buf.append(" <includePath signature=\"");\r
- buf.append(CUtil.xmlAttribEncode(includePathIdentifier));\r
- buf.append("\">\n");\r
- writer.write(buf.toString());\r
- Enumeration dependenciesEnum = dependencies.elements();\r
- while (dependenciesEnum.hasMoreElements()) {\r
- DependencyInfo[] dependInfos = (DependencyInfo[]) dependenciesEnum\r
- .nextElement();\r
- for (int i = 0; i < dependInfos.length; i++) {\r
- DependencyInfo dependInfo = dependInfos[i];\r
- //\r
- // if this is for the same include path\r
- // then output the info\r
- if (dependInfo.getIncludePathIdentifier().equals(\r
- includePathIdentifier)) {\r
- writeDependencyInfo(writer, buf, dependInfo);\r
- }\r
- }\r
- }\r
- writer.write(" </includePath>\n");\r
- }\r
-}\r