diff options
Diffstat (limited to 'src/net/sf/antcontrib/cpptasks/TargetHistoryTable.java')
-rw-r--r-- | src/net/sf/antcontrib/cpptasks/TargetHistoryTable.java | 430 |
1 files changed, 430 insertions, 0 deletions
diff --git a/src/net/sf/antcontrib/cpptasks/TargetHistoryTable.java b/src/net/sf/antcontrib/cpptasks/TargetHistoryTable.java new file mode 100644 index 0000000..bdab94c --- /dev/null +++ b/src/net/sf/antcontrib/cpptasks/TargetHistoryTable.java @@ -0,0 +1,430 @@ +/* + * + * Copyright 2002-2004 The Ant-Contrib 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 net.sf.antcontrib.cpptasks; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; + +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import net.sf.antcontrib.cpptasks.compiler.ProcessorConfiguration; + +import org.apache.tools.ant.BuildException; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; +/** + * A history of the compiler and linker settings used to build the files in the + * same directory as the history. + * + * @author Curt Arnold + */ +public final class TargetHistoryTable { + /** + * This class handles populates the TargetHistory hashtable in response to + * SAX parse events + */ + private class TargetHistoryTableHandler extends DefaultHandler { + private final File baseDir; + private String config; + private final Hashtable history; + private String output; + private long outputLastModified; + private final Vector sources = new Vector(); + /** + * Constructor + * + * @param history + * hashtable of TargetHistory keyed by output name + * @param outputFiles + * existing files in output directory + */ + private TargetHistoryTableHandler(Hashtable history, File baseDir) { + this.history = history; + config = null; + output = null; + this.baseDir = baseDir; + } + public void endElement(String namespaceURI, String localName, + String qName) throws SAXException { + // + // if </target> then + // create TargetHistory object and add to hashtable + // if corresponding output file exists and + // has the same timestamp + // + if (qName.equals("target")) { + if (config != null && output != null) { + File existingFile = new File(baseDir, output); + // + // if the corresponding files doesn't exist or has a + // different + // modification time, then discard this record + if (existingFile.exists()) { + // + // would have expected exact time stamps + // but have observed slight differences + // in return value for multiple evaluations of + // lastModified(). Check if times are within + // a second + long existingLastModified = existingFile.lastModified(); + if (!CUtil.isSignificantlyBefore(existingLastModified, outputLastModified) + && !CUtil.isSignificantlyAfter(existingLastModified, outputLastModified)) { + SourceHistory[] sourcesArray = new SourceHistory[sources + .size()]; + sources.copyInto(sourcesArray); + TargetHistory targetHistory = new TargetHistory( + config, output, outputLastModified, + sourcesArray); + history.put(output, targetHistory); + } + } + } + output = null; + sources.setSize(0); + } else { + // + // reset config so targets not within a processor element + // don't pick up a previous processors signature + // + if (qName.equals("processor")) { + config = null; + } + } + } + /** + * startElement handler + */ + public void startElement(String namespaceURI, String localName, + String qName, Attributes atts) throws SAXException { + // + // if sourceElement + // + if (qName.equals("source")) { + String sourceFile = atts.getValue("file"); + long sourceLastModified = Long.parseLong(atts + .getValue("lastModified"), 16); + sources.addElement(new SourceHistory(sourceFile, + sourceLastModified)); + } else { + // + // if <target> element, + // grab file name and lastModified values + // TargetHistory object will be created in endElement + // + if (qName.equals("target")) { + sources.setSize(0); + output = atts.getValue("file"); + outputLastModified = Long.parseLong(atts + .getValue("lastModified"), 16); + } else { + // + // if <processor> element, + // grab signature attribute + // + if (qName.equals("processor")) { + config = atts.getValue("signature"); + } + } + } + } + } + /** Flag indicating whether the cache should be written back to file. */ + private boolean dirty; + /** + * a hashtable of TargetHistory's keyed by output file name + */ + private final Hashtable history = new Hashtable(); + /** The file the cache was loaded from. */ + private/* final */File historyFile; + private/* final */File outputDir; + private String outputDirPath; + /** + * Creates a target history table from history.xml in the output directory, + * if it exists. Otherwise, initializes the history table empty. + * + * @param task + * task used for logging history load errors + * @param outputDir + * output directory for task + */ + public TargetHistoryTable(CCTask task, File outputDir) + throws BuildException { + if (outputDir == null) { + throw new NullPointerException("outputDir"); + } + if (!outputDir.isDirectory()) { + throw new BuildException("Output directory is not a directory"); + } + if (!outputDir.exists()) { + throw new BuildException("Output directory does not exist"); + } + this.outputDir = outputDir; + try { + outputDirPath = outputDir.getCanonicalPath(); + } catch (IOException ex) { + outputDirPath = outputDir.toString(); + } + // + // load any existing history from file + // suppressing any records whose corresponding + // file does not exist, is zero-length or + // last modified dates differ + historyFile = new File(outputDir, "history.xml"); + if (historyFile.exists()) { + SAXParserFactory factory = SAXParserFactory.newInstance(); + factory.setValidating(false); + try { + SAXParser parser = factory.newSAXParser(); + parser.parse(historyFile, new TargetHistoryTableHandler( + history, outputDir)); + } catch (Exception ex) { + // + // a failure on loading this history is not critical + // but should be logged + task.log("Error reading history.xml: " + ex.toString()); + } + } else { + // + // create empty history file for identifying new files by last + // modified + // timestamp comperation (to compare with + // System.currentTimeMillis() don't work on Unix, because it + // maesure timestamps only in seconds). + // + try { + FileOutputStream outputStream = new FileOutputStream( + historyFile); + byte[] historyElement = new byte[]{0x3C, 0x68, 0x69, 0x73, + 0x74, 0x6F, 0x72, 0x79, 0x2F, 0x3E}; + outputStream.write(historyElement); + outputStream.close(); + } catch (IOException ex) { + throw new BuildException("Can't create history file", ex); + } + } + } + public void commit() throws IOException { + // + // if not dirty, no need to update file + // + if (dirty) { + // + // build (small) hashtable of config id's in history + // + Hashtable configs = new Hashtable(20); + Enumeration elements = history.elements(); + while (elements.hasMoreElements()) { + TargetHistory targetHistory = (TargetHistory) elements + .nextElement(); + String configId = targetHistory.getProcessorConfiguration(); + if (configs.get(configId) == null) { + configs.put(configId, configId); + } + } + FileOutputStream outStream = new FileOutputStream(historyFile); + OutputStreamWriter outWriter; + // + // early VM's don't support UTF-8 encoding + // try and fallback to the default encoding + // otherwise + String encodingName = "UTF-8"; + try { + outWriter = new OutputStreamWriter(outStream, "UTF-8"); + } catch (UnsupportedEncodingException ex) { + outWriter = new OutputStreamWriter(outStream); + encodingName = outWriter.getEncoding(); + } + BufferedWriter writer = new BufferedWriter(outWriter); + writer.write("<?xml version='1.0' encoding='"); + writer.write(encodingName); + writer.write("'?>\n"); + writer.write("<history>\n"); + StringBuffer buf = new StringBuffer(200); + Enumeration configEnum = configs.elements(); + while (configEnum.hasMoreElements()) { + String configId = (String) configEnum.nextElement(); + buf.setLength(0); + buf.append(" <processor signature=\""); + buf.append(CUtil.xmlAttribEncode(configId)); + buf.append("\">\n"); + writer.write(buf.toString()); + elements = history.elements(); + while (elements.hasMoreElements()) { + TargetHistory targetHistory = (TargetHistory) elements + .nextElement(); + if (targetHistory.getProcessorConfiguration().equals( + configId)) { + buf.setLength(0); + buf.append(" <target file=\""); + buf.append(CUtil.xmlAttribEncode(targetHistory + .getOutput())); + buf.append("\" lastModified=\""); + buf.append(Long.toHexString(targetHistory + .getOutputLastModified())); + buf.append("\">\n"); + writer.write(buf.toString()); + SourceHistory[] sourceHistories = targetHistory + .getSources(); + for (int i = 0; i < sourceHistories.length; i++) { + buf.setLength(0); + buf.append(" <source file=\""); + buf.append(CUtil.xmlAttribEncode(sourceHistories[i] + .getRelativePath())); + buf.append("\" lastModified=\""); + buf.append(Long.toHexString(sourceHistories[i] + .getLastModified())); + buf.append("\"/>\n"); + writer.write(buf.toString()); + } + writer.write(" </target>\n"); + } + } + writer.write(" </processor>\n"); + } + writer.write("</history>\n"); + writer.close(); + dirty = false; + } + } + public TargetHistory get(String configId, String outputName) { + TargetHistory targetHistory = (TargetHistory) history.get(outputName); + if (targetHistory != null) { + if (!targetHistory.getProcessorConfiguration().equals(configId)) { + targetHistory = null; + } + } + return targetHistory; + } + public void markForRebuild(Hashtable targetInfos) { + Enumeration targetInfoEnum = targetInfos.elements(); + while (targetInfoEnum.hasMoreElements()) { + markForRebuild((TargetInfo) targetInfoEnum.nextElement()); + } + } + public void markForRebuild(TargetInfo targetInfo) { + // + // if it must already be rebuilt, no need to check further + // + if (!targetInfo.getRebuild()) { + TargetHistory history = get(targetInfo.getConfiguration() + .toString(), targetInfo.getOutput().getName()); + if (history == null) { + targetInfo.mustRebuild(); + } else { + SourceHistory[] sourceHistories = history.getSources(); + File[] sources = targetInfo.getSources(); + if (sourceHistories.length != sources.length) { + targetInfo.mustRebuild(); + } else { + Hashtable sourceMap = new Hashtable(sources.length); + for (int i = 0; i < sources.length; i++) { + try { + sourceMap.put(sources[i].getCanonicalPath(), sources[i]); + } catch(IOException ex) { + sourceMap.put(sources[i].getAbsolutePath(), sources[i]); + } + } + for (int i = 0; i < sourceHistories.length; i++) { + // + // relative file name, must absolutize it on output + // directory + // + String absPath = sourceHistories[i].getAbsolutePath(outputDir); + File match = (File) sourceMap.get(absPath); + if (match != null) { + try { + match = (File) sourceMap.get(new File(absPath).getCanonicalPath()); + } catch(IOException ex) { + targetInfo.mustRebuild(); + break; + } + } + if (match == null || match.lastModified() != sourceHistories[i].getLastModified()) { + targetInfo.mustRebuild(); + break; + } + } + } + } + } + } + public void update(ProcessorConfiguration config, String[] sources, VersionInfo versionInfo) { + String configId = config.getIdentifier(); + String[] onesource = new String[1]; + String[] outputNames; + for (int i = 0; i < sources.length; i++) { + onesource[0] = sources[i]; + outputNames = config.getOutputFileNames(sources[i], versionInfo); + for (int j = 0; j < outputNames.length; j++) { + update(configId, outputNames[j], onesource); + } + } + } + private void update(String configId, String outputName, String[] sources) { + File outputFile = new File(outputDir, outputName); + // + // if output file doesn't exist or predates the start of the + // compile step (most likely a compilation error) then + // do not write add a history entry + // + if (outputFile.exists() && + !CUtil.isSignificantlyBefore(outputFile.lastModified(), historyFile.lastModified())) { + dirty = true; + history.remove(outputName); + SourceHistory[] sourceHistories = new SourceHistory[sources.length]; + for (int i = 0; i < sources.length; i++) { + File sourceFile = new File(sources[i]); + long lastModified = sourceFile.lastModified(); + String relativePath = CUtil.getRelativePath(outputDirPath, + sourceFile); + sourceHistories[i] = new SourceHistory(relativePath, + lastModified); + } + TargetHistory newHistory = new TargetHistory(configId, outputName, + outputFile.lastModified(), sourceHistories); + history.put(outputName, newHistory); + } + } + public void update(TargetInfo linkTarget) { + File outputFile = linkTarget.getOutput(); + String outputName = outputFile.getName(); + // + // if output file doesn't exist or predates the start of the + // compile or link step (most likely a compilation error) then + // do not write add a history entry + // + if (outputFile.exists() + && !CUtil.isSignificantlyBefore(outputFile.lastModified(),historyFile.lastModified())) { + dirty = true; + history.remove(outputName); + SourceHistory[] sourceHistories = linkTarget + .getSourceHistories(outputDirPath); + TargetHistory newHistory = new TargetHistory(linkTarget + .getConfiguration().getIdentifier(), outputName, outputFile + .lastModified(), sourceHistories); + history.put(outputName, newHistory); + } + } +} |