3 * Copyright 2002-2004 The Ant-Contrib project
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 package net
.sf
.antcontrib
.cpptasks
;
18 import java
.io
.BufferedWriter
;
20 import java
.io
.FileOutputStream
;
21 import java
.io
.IOException
;
22 import java
.io
.OutputStreamWriter
;
23 import java
.io
.UnsupportedEncodingException
;
24 import java
.util
.Enumeration
;
25 import java
.util
.Hashtable
;
26 import java
.util
.Vector
;
28 import javax
.xml
.parsers
.SAXParser
;
29 import javax
.xml
.parsers
.SAXParserFactory
;
31 import net
.sf
.antcontrib
.cpptasks
.compiler
.ProcessorConfiguration
;
33 import org
.apache
.tools
.ant
.BuildException
;
34 import org
.xml
.sax
.Attributes
;
35 import org
.xml
.sax
.SAXException
;
36 import org
.xml
.sax
.helpers
.DefaultHandler
;
38 * A history of the compiler and linker settings used to build the files in the
39 * same directory as the history.
43 public final class TargetHistoryTable
{
45 * This class handles populates the TargetHistory hashtable in response to
48 private class TargetHistoryTableHandler
extends DefaultHandler
{
49 private final File baseDir
;
50 private String config
;
51 private final Hashtable history
;
52 private String output
;
53 private long outputLastModified
;
54 private final Vector sources
= new Vector();
59 * hashtable of TargetHistory keyed by output name
61 * existing files in output directory
63 private TargetHistoryTableHandler(Hashtable history
, File baseDir
) {
64 this.history
= history
;
67 this.baseDir
= baseDir
;
69 public void endElement(String namespaceURI
, String localName
,
70 String qName
) throws SAXException
{
73 // create TargetHistory object and add to hashtable
74 // if corresponding output file exists and
75 // has the same timestamp
77 if (qName
.equals("target")) {
78 if (config
!= null && output
!= null) {
79 File existingFile
= new File(baseDir
, output
);
81 // if the corresponding files doesn't exist or has a
83 // modification time, then discard this record
84 if (existingFile
.exists()) {
85 long existingLastModified
= existingFile
.lastModified();
87 // would have expected exact time stamps
88 // but have observed slight differences
89 // in return value for multiple evaluations of
90 // lastModified(). Check if times are within
92 long diff
= outputLastModified
- existingLastModified
;
93 if (diff
>= -500 && diff
<= 500) {
94 SourceHistory
[] sourcesArray
= new SourceHistory
[sources
96 sources
.copyInto(sourcesArray
);
97 TargetHistory targetHistory
= new TargetHistory(
98 config
, output
, outputLastModified
,
100 history
.put(output
, targetHistory
);
108 // reset config so targets not within a processor element
109 // don't pick up a previous processors signature
111 if (qName
.equals("processor")) {
117 * startElement handler
119 public void startElement(String namespaceURI
, String localName
,
120 String qName
, Attributes atts
) throws SAXException
{
124 if (qName
.equals("source")) {
125 String sourceFile
= atts
.getValue("file");
126 long sourceLastModified
= Long
.parseLong(atts
127 .getValue("lastModified"), 16);
128 sources
.addElement(new SourceHistory(sourceFile
,
129 sourceLastModified
));
132 // if <target> element,
133 // grab file name and lastModified values
134 // TargetHistory object will be created in endElement
136 if (qName
.equals("target")) {
138 output
= atts
.getValue("file");
139 outputLastModified
= Long
.parseLong(atts
140 .getValue("lastModified"), 16);
143 // if <processor> element,
144 // grab signature attribute
146 if (qName
.equals("processor")) {
147 config
= atts
.getValue("signature");
153 /** Flag indicating whether the cache should be written back to file. */
154 private boolean dirty
;
156 * a hashtable of TargetHistory's keyed by output file name
158 private final Hashtable history
= new Hashtable();
159 /** The file the cache was loaded from. */
160 private/* final */File historyFile
;
161 private/* final */File outputDir
;
162 private String outputDirPath
;
164 * Creates a target history table from history.xml in the output directory,
165 * if it exists. Otherwise, initializes the history table empty.
168 * task used for logging history load errors
170 * output directory for task
172 public TargetHistoryTable(CCTask task
, File outputDir
)
173 throws BuildException
{
174 if (outputDir
== null) {
175 throw new NullPointerException("outputDir");
177 if (!outputDir
.isDirectory()) {
178 throw new BuildException("Output directory is not a directory");
180 if (!outputDir
.exists()) {
181 throw new BuildException("Output directory does not exist");
183 this.outputDir
= outputDir
;
185 outputDirPath
= outputDir
.getCanonicalPath();
186 } catch (IOException ex
) {
187 outputDirPath
= outputDir
.toString();
190 // load any existing history from file
191 // suppressing any records whose corresponding
192 // file does not exist, is zero-length or
193 // last modified dates differ
194 historyFile
= new File(outputDir
, "history.xml");
195 if (historyFile
.exists()) {
196 SAXParserFactory factory
= SAXParserFactory
.newInstance();
197 factory
.setValidating(false);
199 SAXParser parser
= factory
.newSAXParser();
200 parser
.parse(historyFile
, new TargetHistoryTableHandler(
201 history
, outputDir
));
202 } catch (Exception ex
) {
204 // a failure on loading this history is not critical
205 // but should be logged
206 task
.log("Error reading history.xml: " + ex
.toString());
210 // create empty history file for identifying new files by last
212 // timestamp comperation (to compare with
213 // System.currentTimeMillis() don't work on Unix, because it
214 // maesure timestamps only in seconds).
217 FileOutputStream outputStream
= new FileOutputStream(
219 byte[] historyElement
= new byte[]{0x3C, 0x68, 0x69, 0x73,
220 0x74, 0x6F, 0x72, 0x79, 0x2F, 0x3E};
221 outputStream
.write(historyElement
);
222 outputStream
.close();
223 } catch (IOException ex
) {
224 throw new BuildException("Can't create history file", ex
);
228 public void commit() throws IOException
{
230 // if not dirty, no need to update file
234 // build (small) hashtable of config id's in history
236 Hashtable configs
= new Hashtable(20);
237 Enumeration elements
= history
.elements();
238 while (elements
.hasMoreElements()) {
239 TargetHistory targetHistory
= (TargetHistory
) elements
241 String configId
= targetHistory
.getProcessorConfiguration();
242 if (configs
.get(configId
) == null) {
243 configs
.put(configId
, configId
);
246 FileOutputStream outStream
= new FileOutputStream(historyFile
);
247 OutputStreamWriter outWriter
;
249 // early VM's don't support UTF-8 encoding
250 // try and fallback to the default encoding
252 String encodingName
= "UTF-8";
254 outWriter
= new OutputStreamWriter(outStream
, "UTF-8");
255 } catch (UnsupportedEncodingException ex
) {
256 outWriter
= new OutputStreamWriter(outStream
);
257 encodingName
= outWriter
.getEncoding();
259 BufferedWriter writer
= new BufferedWriter(outWriter
);
260 writer
.write("<?xml version='1.0' encoding='");
261 writer
.write(encodingName
);
262 writer
.write("'?>\n");
263 writer
.write("<history>\n");
264 StringBuffer buf
= new StringBuffer(200);
265 Enumeration configEnum
= configs
.elements();
266 while (configEnum
.hasMoreElements()) {
267 String configId
= (String
) configEnum
.nextElement();
269 buf
.append(" <processor signature=\"");
270 buf
.append(CUtil
.xmlAttribEncode(configId
));
272 writer
.write(buf
.toString());
273 elements
= history
.elements();
274 while (elements
.hasMoreElements()) {
275 TargetHistory targetHistory
= (TargetHistory
) elements
277 if (targetHistory
.getProcessorConfiguration().equals(
280 buf
.append(" <target file=\"");
281 buf
.append(CUtil
.xmlAttribEncode(targetHistory
283 buf
.append("\" lastModified=\"");
284 buf
.append(Long
.toHexString(targetHistory
285 .getOutputLastModified()));
287 writer
.write(buf
.toString());
288 SourceHistory
[] sourceHistories
= targetHistory
290 for (int i
= 0; i
< sourceHistories
.length
; i
++) {
292 buf
.append(" <source file=\"");
293 buf
.append(CUtil
.xmlAttribEncode(sourceHistories
[i
]
294 .getRelativePath()));
295 buf
.append("\" lastModified=\"");
296 buf
.append(Long
.toHexString(sourceHistories
[i
]
297 .getLastModified()));
298 buf
.append("\"/>\n");
299 writer
.write(buf
.toString());
301 writer
.write(" </target>\n");
304 writer
.write(" </processor>\n");
306 writer
.write("</history>\n");
311 public TargetHistory
get(String configId
, String outputName
) {
312 TargetHistory targetHistory
= (TargetHistory
) history
.get(outputName
);
313 if (targetHistory
!= null) {
314 if (!targetHistory
.getProcessorConfiguration().equals(configId
)) {
315 targetHistory
= null;
318 return targetHistory
;
320 public void markForRebuild(Hashtable targetInfos
) {
321 Enumeration targetInfoEnum
= targetInfos
.elements();
322 while (targetInfoEnum
.hasMoreElements()) {
323 markForRebuild((TargetInfo
) targetInfoEnum
.nextElement());
326 public void markForRebuild(TargetInfo targetInfo
) {
328 // if it must already be rebuilt, no need to check further
330 if (!targetInfo
.getRebuild()) {
331 TargetHistory history
= get(targetInfo
.getConfiguration()
332 .toString(), targetInfo
.getOutput().getName());
333 if (history
== null) {
334 targetInfo
.mustRebuild();
336 SourceHistory
[] sourceHistories
= history
.getSources();
337 File
[] sources
= targetInfo
.getSources();
338 if (sourceHistories
.length
!= sources
.length
) {
339 targetInfo
.mustRebuild();
341 for (int i
= 0; i
< sourceHistories
.length
342 && !targetInfo
.getRebuild(); i
++) {
344 // relative file name, must absolutize it on output
347 boolean foundMatch
= false;
348 String historySourcePath
= sourceHistories
[i
]
349 .getAbsolutePath(outputDir
);
350 for (int j
= 0; j
< sources
.length
; j
++) {
351 File targetSource
= sources
[j
];
352 String targetSourcePath
= targetSource
354 if (targetSourcePath
.equals(historySourcePath
)) {
356 if (targetSource
.lastModified() != sourceHistories
[i
]
357 .getLastModified()) {
358 targetInfo
.mustRebuild();
364 targetInfo
.mustRebuild();
371 public void update(ProcessorConfiguration config
, String
[] sources
) {
372 String configId
= config
.getIdentifier();
373 String
[] onesource
= new String
[1];
375 for (int i
= 0; i
< sources
.length
; i
++) {
376 onesource
[0] = sources
[i
];
377 outputName
= config
.getOutputFileName(sources
[i
]);
378 update(configId
, outputName
, onesource
);
381 private void update(String configId
, String outputName
, String
[] sources
) {
382 File outputFile
= new File(outputDir
, outputName
);
384 // if output file doesn't exist or predates the start of the
385 // compile step (most likely a compilation error) then
386 // do not write add a history entry
388 if (outputFile
.exists()
389 && outputFile
.lastModified() >= historyFile
.lastModified()) {
391 history
.remove(outputName
);
392 SourceHistory
[] sourceHistories
= new SourceHistory
[sources
.length
];
393 for (int i
= 0; i
< sources
.length
; i
++) {
394 File sourceFile
= new File(sources
[i
]);
395 long lastModified
= sourceFile
.lastModified();
396 String relativePath
= CUtil
.getRelativePath(outputDirPath
,
398 sourceHistories
[i
] = new SourceHistory(relativePath
,
401 TargetHistory newHistory
= new TargetHistory(configId
, outputName
,
402 outputFile
.lastModified(), sourceHistories
);
403 history
.put(outputName
, newHistory
);
406 public void update(TargetInfo linkTarget
) {
407 File outputFile
= linkTarget
.getOutput();
408 String outputName
= outputFile
.getName();
410 // if output file doesn't exist or predates the start of the
411 // compile or link step (most likely a compilation error) then
412 // do not write add a history entry
414 if (outputFile
.exists()
415 && outputFile
.lastModified() >= historyFile
.lastModified()) {
417 history
.remove(outputName
);
418 SourceHistory
[] sourceHistories
= linkTarget
419 .getSourceHistories(outputDirPath
);
420 TargetHistory newHistory
= new TargetHistory(linkTarget
421 .getConfiguration().getIdentifier(), outputName
, outputFile
422 .lastModified(), sourceHistories
);
423 history
.put(outputName
, newHistory
);