]>
Commit | Line | Data |
---|---|---|
878ddf1f | 1 | /*\r |
2 | * \r | |
3 | * Copyright 2002-2004 The Ant-Contrib project\r | |
4 | *\r | |
5 | * Licensed under the Apache License, Version 2.0 (the "License");\r | |
6 | * you may not use this file except in compliance with the License.\r | |
7 | * You may obtain a copy of the License at\r | |
8 | *\r | |
9 | * http://www.apache.org/licenses/LICENSE-2.0\r | |
10 | *\r | |
11 | * Unless required by applicable law or agreed to in writing, software\r | |
12 | * distributed under the License is distributed on an "AS IS" BASIS,\r | |
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r | |
14 | * See the License for the specific language governing permissions and\r | |
15 | * limitations under the License.\r | |
16 | */\r | |
17 | package net.sf.antcontrib.cpptasks;\r | |
18 | import java.io.BufferedWriter;\r | |
19 | import java.io.File;\r | |
20 | import java.io.FileOutputStream;\r | |
21 | import java.io.IOException;\r | |
22 | import java.io.OutputStreamWriter;\r | |
23 | import java.io.UnsupportedEncodingException;\r | |
24 | import java.util.Enumeration;\r | |
25 | import java.util.Hashtable;\r | |
26 | import java.util.Vector;\r | |
27 | \r | |
28 | import javax.xml.parsers.SAXParser;\r | |
29 | import javax.xml.parsers.SAXParserFactory;\r | |
30 | \r | |
31 | import net.sf.antcontrib.cpptasks.compiler.ProcessorConfiguration;\r | |
32 | \r | |
33 | import org.apache.tools.ant.BuildException;\r | |
34 | import org.xml.sax.Attributes;\r | |
35 | import org.xml.sax.SAXException;\r | |
36 | import org.xml.sax.helpers.DefaultHandler;\r | |
37 | /**\r | |
38 | * A history of the compiler and linker settings used to build the files in the\r | |
39 | * same directory as the history.\r | |
40 | * \r | |
41 | * @author Curt Arnold\r | |
42 | */\r | |
43 | public final class TargetHistoryTable {\r | |
44 | /**\r | |
45 | * This class handles populates the TargetHistory hashtable in response to\r | |
46 | * SAX parse events\r | |
47 | */\r | |
48 | private class TargetHistoryTableHandler extends DefaultHandler {\r | |
49 | private final File baseDir;\r | |
50 | private String config;\r | |
51 | private final Hashtable history;\r | |
52 | private String output;\r | |
53 | private long outputLastModified;\r | |
54 | private final Vector sources = new Vector();\r | |
55 | /**\r | |
56 | * Constructor\r | |
57 | * \r | |
58 | * @param history\r | |
59 | * hashtable of TargetHistory keyed by output name\r | |
60 | * @param outputFiles\r | |
61 | * existing files in output directory\r | |
62 | */\r | |
63 | private TargetHistoryTableHandler(Hashtable history, File baseDir) {\r | |
64 | this.history = history;\r | |
65 | config = null;\r | |
66 | output = null;\r | |
67 | this.baseDir = baseDir;\r | |
68 | }\r | |
69 | public void endElement(String namespaceURI, String localName,\r | |
70 | String qName) throws SAXException {\r | |
71 | //\r | |
72 | // if </target> then\r | |
73 | // create TargetHistory object and add to hashtable\r | |
74 | // if corresponding output file exists and\r | |
75 | // has the same timestamp\r | |
76 | //\r | |
77 | if (qName.equals("target")) {\r | |
78 | if (config != null && output != null) {\r | |
79 | File existingFile = new File(baseDir, output);\r | |
80 | //\r | |
81 | // if the corresponding files doesn't exist or has a\r | |
82 | // different\r | |
83 | // modification time, then discard this record\r | |
84 | if (existingFile.exists()) {\r | |
85 | long existingLastModified = existingFile.lastModified();\r | |
86 | //\r | |
87 | // would have expected exact time stamps\r | |
88 | // but have observed slight differences\r | |
89 | // in return value for multiple evaluations of\r | |
90 | // lastModified(). Check if times are within\r | |
91 | // a second\r | |
92 | long diff = outputLastModified - existingLastModified;\r | |
93 | if (diff >= -500 && diff <= 500) {\r | |
94 | SourceHistory[] sourcesArray = new SourceHistory[sources\r | |
95 | .size()];\r | |
96 | sources.copyInto(sourcesArray);\r | |
97 | TargetHistory targetHistory = new TargetHistory(\r | |
98 | config, output, outputLastModified,\r | |
99 | sourcesArray);\r | |
100 | history.put(output, targetHistory);\r | |
101 | }\r | |
102 | }\r | |
103 | }\r | |
104 | output = null;\r | |
105 | sources.setSize(0);\r | |
106 | } else {\r | |
107 | //\r | |
108 | // reset config so targets not within a processor element\r | |
109 | // don't pick up a previous processors signature\r | |
110 | //\r | |
111 | if (qName.equals("processor")) {\r | |
112 | config = null;\r | |
113 | }\r | |
114 | }\r | |
115 | }\r | |
116 | /**\r | |
117 | * startElement handler\r | |
118 | */\r | |
119 | public void startElement(String namespaceURI, String localName,\r | |
120 | String qName, Attributes atts) throws SAXException {\r | |
121 | //\r | |
122 | // if sourceElement\r | |
123 | //\r | |
124 | if (qName.equals("source")) {\r | |
125 | String sourceFile = atts.getValue("file");\r | |
126 | long sourceLastModified = Long.parseLong(atts\r | |
127 | .getValue("lastModified"), 16);\r | |
128 | sources.addElement(new SourceHistory(sourceFile,\r | |
129 | sourceLastModified));\r | |
130 | } else {\r | |
131 | //\r | |
132 | // if <target> element,\r | |
133 | // grab file name and lastModified values\r | |
134 | // TargetHistory object will be created in endElement\r | |
135 | //\r | |
136 | if (qName.equals("target")) {\r | |
137 | sources.setSize(0);\r | |
138 | output = atts.getValue("file");\r | |
139 | outputLastModified = Long.parseLong(atts\r | |
140 | .getValue("lastModified"), 16);\r | |
141 | } else {\r | |
142 | //\r | |
143 | // if <processor> element,\r | |
144 | // grab signature attribute\r | |
145 | //\r | |
146 | if (qName.equals("processor")) {\r | |
147 | config = atts.getValue("signature");\r | |
148 | }\r | |
149 | }\r | |
150 | }\r | |
151 | }\r | |
152 | }\r | |
153 | /** Flag indicating whether the cache should be written back to file. */\r | |
154 | private boolean dirty;\r | |
155 | /**\r | |
156 | * a hashtable of TargetHistory's keyed by output file name\r | |
157 | */\r | |
158 | private final Hashtable history = new Hashtable();\r | |
159 | /** The file the cache was loaded from. */\r | |
160 | private/* final */File historyFile;\r | |
161 | private/* final */File outputDir;\r | |
162 | private String outputDirPath;\r | |
163 | /**\r | |
164 | * Creates a target history table from history.xml in the output directory,\r | |
165 | * if it exists. Otherwise, initializes the history table empty.\r | |
166 | * \r | |
167 | * @param task\r | |
168 | * task used for logging history load errors\r | |
169 | * @param outputDir\r | |
170 | * output directory for task\r | |
171 | */\r | |
172 | public TargetHistoryTable(CCTask task, File outputDir)\r | |
173 | throws BuildException {\r | |
174 | if (outputDir == null) {\r | |
175 | throw new NullPointerException("outputDir");\r | |
176 | }\r | |
177 | if (!outputDir.isDirectory()) {\r | |
178 | throw new BuildException("Output directory is not a directory");\r | |
179 | }\r | |
180 | if (!outputDir.exists()) {\r | |
181 | throw new BuildException("Output directory does not exist");\r | |
182 | }\r | |
183 | this.outputDir = outputDir;\r | |
184 | try {\r | |
185 | outputDirPath = outputDir.getCanonicalPath();\r | |
186 | } catch (IOException ex) {\r | |
187 | outputDirPath = outputDir.toString();\r | |
188 | }\r | |
189 | //\r | |
190 | // load any existing history from file\r | |
191 | // suppressing any records whose corresponding\r | |
192 | // file does not exist, is zero-length or\r | |
193 | // last modified dates differ\r | |
194 | historyFile = new File(outputDir, "history.xml");\r | |
195 | if (historyFile.exists()) {\r | |
196 | SAXParserFactory factory = SAXParserFactory.newInstance();\r | |
197 | factory.setValidating(false);\r | |
198 | try {\r | |
199 | SAXParser parser = factory.newSAXParser();\r | |
200 | parser.parse(historyFile, new TargetHistoryTableHandler(\r | |
201 | history, outputDir));\r | |
202 | } catch (Exception ex) {\r | |
203 | //\r | |
204 | // a failure on loading this history is not critical\r | |
205 | // but should be logged\r | |
206 | task.log("Error reading history.xml: " + ex.toString());\r | |
207 | }\r | |
208 | } else {\r | |
209 | //\r | |
210 | // create empty history file for identifying new files by last\r | |
211 | // modified\r | |
212 | // timestamp comperation (to compare with\r | |
213 | // System.currentTimeMillis() don't work on Unix, because it\r | |
214 | // maesure timestamps only in seconds).\r | |
215 | //\r | |
216 | try {\r | |
217 | FileOutputStream outputStream = new FileOutputStream(\r | |
218 | historyFile);\r | |
219 | byte[] historyElement = new byte[]{0x3C, 0x68, 0x69, 0x73,\r | |
220 | 0x74, 0x6F, 0x72, 0x79, 0x2F, 0x3E};\r | |
221 | outputStream.write(historyElement);\r | |
222 | outputStream.close();\r | |
223 | } catch (IOException ex) {\r | |
224 | throw new BuildException("Can't create history file", ex);\r | |
225 | }\r | |
226 | }\r | |
227 | }\r | |
228 | public void commit() throws IOException {\r | |
229 | //\r | |
230 | // if not dirty, no need to update file\r | |
231 | //\r | |
232 | if (dirty) {\r | |
233 | //\r | |
234 | // build (small) hashtable of config id's in history\r | |
235 | //\r | |
236 | Hashtable configs = new Hashtable(20);\r | |
237 | Enumeration elements = history.elements();\r | |
238 | while (elements.hasMoreElements()) {\r | |
239 | TargetHistory targetHistory = (TargetHistory) elements\r | |
240 | .nextElement();\r | |
241 | String configId = targetHistory.getProcessorConfiguration();\r | |
242 | if (configs.get(configId) == null) {\r | |
243 | configs.put(configId, configId);\r | |
244 | }\r | |
245 | }\r | |
246 | FileOutputStream outStream = new FileOutputStream(historyFile);\r | |
247 | OutputStreamWriter outWriter;\r | |
248 | //\r | |
249 | // early VM's don't support UTF-8 encoding\r | |
250 | // try and fallback to the default encoding\r | |
251 | // otherwise\r | |
252 | String encodingName = "UTF-8";\r | |
253 | try {\r | |
254 | outWriter = new OutputStreamWriter(outStream, "UTF-8");\r | |
255 | } catch (UnsupportedEncodingException ex) {\r | |
256 | outWriter = new OutputStreamWriter(outStream);\r | |
257 | encodingName = outWriter.getEncoding();\r | |
258 | }\r | |
259 | BufferedWriter writer = new BufferedWriter(outWriter);\r | |
260 | writer.write("<?xml version='1.0' encoding='");\r | |
261 | writer.write(encodingName);\r | |
262 | writer.write("'?>\n");\r | |
263 | writer.write("<history>\n");\r | |
264 | StringBuffer buf = new StringBuffer(200);\r | |
265 | Enumeration configEnum = configs.elements();\r | |
266 | while (configEnum.hasMoreElements()) {\r | |
267 | String configId = (String) configEnum.nextElement();\r | |
268 | buf.setLength(0);\r | |
269 | buf.append(" <processor signature=\"");\r | |
270 | buf.append(CUtil.xmlAttribEncode(configId));\r | |
271 | buf.append("\">\n");\r | |
272 | writer.write(buf.toString());\r | |
273 | elements = history.elements();\r | |
274 | while (elements.hasMoreElements()) {\r | |
275 | TargetHistory targetHistory = (TargetHistory) elements\r | |
276 | .nextElement();\r | |
277 | if (targetHistory.getProcessorConfiguration().equals(\r | |
278 | configId)) {\r | |
279 | buf.setLength(0);\r | |
280 | buf.append(" <target file=\"");\r | |
281 | buf.append(CUtil.xmlAttribEncode(targetHistory\r | |
282 | .getOutput()));\r | |
283 | buf.append("\" lastModified=\"");\r | |
284 | buf.append(Long.toHexString(targetHistory\r | |
285 | .getOutputLastModified()));\r | |
286 | buf.append("\">\n");\r | |
287 | writer.write(buf.toString());\r | |
288 | SourceHistory[] sourceHistories = targetHistory\r | |
289 | .getSources();\r | |
290 | for (int i = 0; i < sourceHistories.length; i++) {\r | |
291 | buf.setLength(0);\r | |
292 | buf.append(" <source file=\"");\r | |
293 | buf.append(CUtil.xmlAttribEncode(sourceHistories[i]\r | |
294 | .getRelativePath()));\r | |
295 | buf.append("\" lastModified=\"");\r | |
296 | buf.append(Long.toHexString(sourceHistories[i]\r | |
297 | .getLastModified()));\r | |
298 | buf.append("\"/>\n");\r | |
299 | writer.write(buf.toString());\r | |
300 | }\r | |
301 | writer.write(" </target>\n");\r | |
302 | }\r | |
303 | }\r | |
304 | writer.write(" </processor>\n");\r | |
305 | }\r | |
306 | writer.write("</history>\n");\r | |
307 | writer.close();\r | |
308 | dirty = false;\r | |
309 | }\r | |
310 | }\r | |
311 | public TargetHistory get(String configId, String outputName) {\r | |
312 | TargetHistory targetHistory = (TargetHistory) history.get(outputName);\r | |
313 | if (targetHistory != null) {\r | |
314 | if (!targetHistory.getProcessorConfiguration().equals(configId)) {\r | |
315 | targetHistory = null;\r | |
316 | }\r | |
317 | }\r | |
318 | return targetHistory;\r | |
319 | }\r | |
320 | public void markForRebuild(Hashtable targetInfos) {\r | |
321 | Enumeration targetInfoEnum = targetInfos.elements();\r | |
322 | while (targetInfoEnum.hasMoreElements()) {\r | |
323 | markForRebuild((TargetInfo) targetInfoEnum.nextElement());\r | |
324 | }\r | |
325 | }\r | |
326 | public void markForRebuild(TargetInfo targetInfo) {\r | |
327 | //\r | |
328 | // if it must already be rebuilt, no need to check further\r | |
329 | //\r | |
330 | if (!targetInfo.getRebuild()) {\r | |
331 | TargetHistory history = get(targetInfo.getConfiguration()\r | |
332 | .toString(), targetInfo.getOutput().getName());\r | |
333 | if (history == null) {\r | |
334 | targetInfo.mustRebuild();\r | |
335 | } else {\r | |
336 | SourceHistory[] sourceHistories = history.getSources();\r | |
337 | File[] sources = targetInfo.getSources();\r | |
338 | if (sourceHistories.length != sources.length) {\r | |
339 | targetInfo.mustRebuild();\r | |
340 | } else {\r | |
341 | for (int i = 0; i < sourceHistories.length\r | |
342 | && !targetInfo.getRebuild(); i++) {\r | |
343 | //\r | |
344 | // relative file name, must absolutize it on output\r | |
345 | // directory\r | |
346 | //\r | |
347 | boolean foundMatch = false;\r | |
348 | String historySourcePath = sourceHistories[i]\r | |
349 | .getAbsolutePath(outputDir);\r | |
350 | for (int j = 0; j < sources.length; j++) {\r | |
351 | File targetSource = sources[j];\r | |
352 | String targetSourcePath = targetSource\r | |
353 | .getAbsolutePath();\r | |
354 | if (targetSourcePath.equals(historySourcePath)) {\r | |
355 | foundMatch = true;\r | |
356 | if (targetSource.lastModified() != sourceHistories[i]\r | |
357 | .getLastModified()) {\r | |
358 | targetInfo.mustRebuild();\r | |
359 | break;\r | |
360 | }\r | |
361 | }\r | |
362 | }\r | |
363 | if (!foundMatch) {\r | |
364 | targetInfo.mustRebuild();\r | |
365 | }\r | |
366 | }\r | |
367 | }\r | |
368 | }\r | |
369 | }\r | |
370 | }\r | |
371 | public void update(ProcessorConfiguration config, String[] sources) {\r | |
372 | String configId = config.getIdentifier();\r | |
373 | String[] onesource = new String[1];\r | |
374 | String outputName;\r | |
375 | for (int i = 0; i < sources.length; i++) {\r | |
376 | onesource[0] = sources[i];\r | |
377 | outputName = config.getOutputFileName(sources[i]);\r | |
378 | update(configId, outputName, onesource);\r | |
379 | }\r | |
380 | }\r | |
381 | private void update(String configId, String outputName, String[] sources) {\r | |
382 | File outputFile = new File(outputDir, outputName);\r | |
383 | //\r | |
384 | // if output file doesn't exist or predates the start of the\r | |
385 | // compile step (most likely a compilation error) then\r | |
386 | // do not write add a history entry\r | |
387 | //\r | |
388 | if (outputFile.exists()\r | |
389 | && outputFile.lastModified() >= historyFile.lastModified()) {\r | |
390 | dirty = true;\r | |
391 | history.remove(outputName);\r | |
392 | SourceHistory[] sourceHistories = new SourceHistory[sources.length];\r | |
393 | for (int i = 0; i < sources.length; i++) {\r | |
394 | File sourceFile = new File(sources[i]);\r | |
395 | long lastModified = sourceFile.lastModified();\r | |
396 | String relativePath = CUtil.getRelativePath(outputDirPath,\r | |
397 | sourceFile);\r | |
398 | sourceHistories[i] = new SourceHistory(relativePath,\r | |
399 | lastModified);\r | |
400 | }\r | |
401 | TargetHistory newHistory = new TargetHistory(configId, outputName,\r | |
402 | outputFile.lastModified(), sourceHistories);\r | |
403 | history.put(outputName, newHistory);\r | |
404 | }\r | |
405 | }\r | |
406 | public void update(TargetInfo linkTarget) {\r | |
407 | File outputFile = linkTarget.getOutput();\r | |
408 | String outputName = outputFile.getName();\r | |
409 | //\r | |
410 | // if output file doesn't exist or predates the start of the\r | |
411 | // compile or link step (most likely a compilation error) then\r | |
412 | // do not write add a history entry\r | |
413 | //\r | |
414 | if (outputFile.exists()\r | |
415 | && outputFile.lastModified() >= historyFile.lastModified()) {\r | |
416 | dirty = true;\r | |
417 | history.remove(outputName);\r | |
418 | SourceHistory[] sourceHistories = linkTarget\r | |
419 | .getSourceHistories(outputDirPath);\r | |
420 | TargetHistory newHistory = new TargetHistory(linkTarget\r | |
421 | .getConfiguration().getIdentifier(), outputName, outputFile\r | |
422 | .lastModified(), sourceHistories);\r | |
423 | history.put(outputName, newHistory);\r | |
424 | }\r | |
425 | }\r | |
426 | }\r |