]> git.proxmox.com Git - mirror_edk2.git/blame - Tools/Source/Cpptasks/net/sf/antcontrib/cpptasks/DependencyTable.java
Initial import.
[mirror_edk2.git] / Tools / Source / Cpptasks / net / sf / antcontrib / cpptasks / DependencyTable.java
CommitLineData
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
17package net.sf.antcontrib.cpptasks;\r
18import java.io.BufferedWriter;\r
19import java.io.File;\r
20import java.io.FileOutputStream;\r
21import java.io.IOException;\r
22import java.io.OutputStreamWriter;\r
23import java.io.UnsupportedEncodingException;\r
24import java.util.Enumeration;\r
25import java.util.Hashtable;\r
26import java.util.Vector;\r
27import javax.xml.parsers.ParserConfigurationException;\r
28import javax.xml.parsers.SAXParser;\r
29import javax.xml.parsers.SAXParserFactory;\r
30import net.sf.antcontrib.cpptasks.compiler.CompilerConfiguration;\r
31import org.apache.tools.ant.BuildException;\r
32import org.apache.tools.ant.Project;\r
33import org.xml.sax.Attributes;\r
34import org.xml.sax.SAXException;\r
35import org.xml.sax.helpers.DefaultHandler;\r
36/**\r
37 * @author Curt Arnold\r
38 */\r
39public final class DependencyTable {\r
40 /**\r
41 * This class handles populates the TargetHistory hashtable in response to\r
42 * SAX parse events\r
43 */\r
44 private class DependencyTableHandler extends DefaultHandler {\r
45 private File baseDir;\r
46 private final DependencyTable dependencyTable;\r
47 private String includePath;\r
48 private Vector includes;\r
49 private String source;\r
50 private long sourceLastModified;\r
51 private Vector sysIncludes;\r
52 /**\r
53 * Constructor\r
54 * \r
55 * @param history\r
56 * hashtable of TargetHistory keyed by output name\r
57 * @param outputFiles\r
58 * existing files in output directory\r
59 */\r
60 private DependencyTableHandler(DependencyTable dependencyTable,\r
61 File baseDir) {\r
62 this.dependencyTable = dependencyTable;\r
63 this.baseDir = baseDir;\r
64 includes = new Vector();\r
65 sysIncludes = new Vector();\r
66 source = null;\r
67 }\r
68 public void endElement(String namespaceURI, String localName,\r
69 String qName) throws SAXException {\r
70 //\r
71 // if </source> then\r
72 // create Dependency object and add to hashtable\r
73 // if corresponding source file exists and\r
74 // has the same timestamp\r
75 //\r
76 if (qName.equals("source")) {\r
77 if (source != null && includePath != null) {\r
78 File existingFile = new File(baseDir, source);\r
79 //\r
80 // if the file exists and the time stamp is right\r
81 // preserve the dependency info\r
82 if (existingFile.exists()) {\r
83 //\r
84 // would have expected exact matches\r
85 // but was seeing some unexpected difference by\r
86 // a few tens of milliseconds, as long\r
87 // as the times are within a second\r
88 long existingLastModified = existingFile.lastModified();\r
89 long diff = existingLastModified - sourceLastModified;\r
90 if (diff >= -500 && diff <= 500) {\r
91 DependencyInfo dependInfo = new DependencyInfo(\r
92 includePath, source, sourceLastModified,\r
93 includes, sysIncludes);\r
94 dependencyTable.putDependencyInfo(source,\r
95 dependInfo);\r
96 }\r
97 }\r
98 source = null;\r
99 includes.setSize(0);\r
100 }\r
101 } else {\r
102 //\r
103 // this causes any <source> elements outside the\r
104 // scope of an <includePath> to be discarded\r
105 //\r
106 if (qName.equals("includePath")) {\r
107 includePath = null;\r
108 }\r
109 }\r
110 }\r
111 /**\r
112 * startElement handler\r
113 */\r
114 public void startElement(String namespaceURI, String localName,\r
115 String qName, Attributes atts) throws SAXException {\r
116 //\r
117 // if includes, then add relative file name to vector\r
118 //\r
119 if (qName.equals("include")) {\r
120 includes.addElement(atts.getValue("file"));\r
121 } else {\r
122 if (qName.equals("sysinclude")) {\r
123 sysIncludes.addElement(atts.getValue("file"));\r
124 } else {\r
125 //\r
126 // if source then\r
127 // capture source file name,\r
128 // modification time and reset includes vector\r
129 //\r
130 if (qName.equals("source")) {\r
131 source = atts.getValue("file");\r
132 sourceLastModified = Long.parseLong(atts\r
133 .getValue("lastModified"), 16);\r
134 includes.setSize(0);\r
135 sysIncludes.setSize(0);\r
136 } else {\r
137 if (qName.equals("includePath")) {\r
138 includePath = atts.getValue("signature");\r
139 }\r
140 }\r
141 }\r
142 }\r
143 }\r
144 }\r
145 public abstract class DependencyVisitor {\r
146 /**\r
147 * Previews all the children of this source file.\r
148 * \r
149 * May be called multiple times as DependencyInfo's for children are\r
150 * filled in.\r
151 * \r
152 * @return true to continue towards recursion into included files\r
153 */\r
154 public abstract boolean preview(DependencyInfo parent,\r
155 DependencyInfo[] children);\r
156 /**\r
157 * Called if the dependency depth exhausted the stack.\r
158 */\r
159 public abstract void stackExhausted();\r
160 /**\r
161 * Visits the dependency info.\r
162 * \r
163 * @returns true to continue towards recursion into included files\r
164 */\r
165 public abstract boolean visit(DependencyInfo dependInfo);\r
166 }\r
167 public class TimestampChecker extends DependencyVisitor {\r
168 private boolean noNeedToRebuild;\r
169 private long outputLastModified;\r
170 private boolean rebuildOnStackExhaustion;\r
171 public TimestampChecker(final long outputLastModified,\r
172 boolean rebuildOnStackExhaustion) {\r
173 this.outputLastModified = outputLastModified;\r
174 noNeedToRebuild = true;\r
175 this.rebuildOnStackExhaustion = rebuildOnStackExhaustion;\r
176 }\r
177 public boolean getMustRebuild() {\r
178 return !noNeedToRebuild;\r
179 }\r
180 public boolean preview(DependencyInfo parent, DependencyInfo[] children) {\r
181 int withCompositeTimes = 0;\r
182 long parentCompositeLastModified = parent.getSourceLastModified();\r
183 for (int i = 0; i < children.length; i++) {\r
184 if (children[i] != null) {\r
185 //\r
186 // expedient way to determine if a child forces us to\r
187 // rebuild\r
188 //\r
189 visit(children[i]);\r
190 long childCompositeLastModified = children[i]\r
191 .getCompositeLastModified();\r
192 if (childCompositeLastModified != Long.MIN_VALUE) {\r
193 withCompositeTimes++;\r
194 if (childCompositeLastModified > parentCompositeLastModified) {\r
195 parentCompositeLastModified = childCompositeLastModified;\r
196 }\r
197 }\r
198 }\r
199 }\r
200 if (withCompositeTimes == children.length) {\r
201 parent.setCompositeLastModified(parentCompositeLastModified);\r
202 }\r
203 //\r
204 // may have been changed by an earlier call to visit()\r
205 //\r
206 return noNeedToRebuild;\r
207 }\r
208 public void stackExhausted() {\r
209 if (rebuildOnStackExhaustion) {\r
210 noNeedToRebuild = false;\r
211 }\r
212 }\r
213 public boolean visit(DependencyInfo dependInfo) {\r
214 if (noNeedToRebuild) {\r
215 if (dependInfo.getSourceLastModified() > outputLastModified\r
216 || dependInfo.getCompositeLastModified() > outputLastModified) {\r
217 noNeedToRebuild = false;\r
218 }\r
219 }\r
220 //\r
221 // only need to process the children if\r
222 // it has not yet been determined whether\r
223 // we need to rebuild and the composite modified time\r
224 // has not been determined for this file\r
225 return noNeedToRebuild\r
226 && dependInfo.getCompositeLastModified() == Long.MIN_VALUE;\r
227 }\r
228 }\r
229 private/* final */File baseDir;\r
230 private String baseDirPath;\r
231 /**\r
232 * a hashtable of DependencyInfo[] keyed by output file name\r
233 */\r
234 private final Hashtable dependencies = new Hashtable();\r
235 /** The file the cache was loaded from. */\r
236 private/* final */File dependenciesFile;\r
237 /** Flag indicating whether the cache should be written back to file. */\r
238 private boolean dirty;\r
239 /**\r
240 * Creates a target history table from dependencies.xml in the prject\r
241 * directory, if it exists. Otherwise, initializes the dependencies empty.\r
242 * \r
243 * @param task\r
244 * task used for logging history load errors\r
245 * @param baseDir\r
246 * output directory for task\r
247 */\r
248 public DependencyTable(File baseDir) {\r
249 if (baseDir == null) {\r
250 throw new NullPointerException("baseDir");\r
251 }\r
252 this.baseDir = baseDir;\r
253 try {\r
254 baseDirPath = baseDir.getCanonicalPath();\r
255 } catch (IOException ex) {\r
256 baseDirPath = baseDir.toString();\r
257 }\r
258 dirty = false;\r
259 //\r
260 // load any existing dependencies from file\r
261 dependenciesFile = new File(baseDir, "dependencies.xml");\r
262 }\r
263 public void commit(CCTask task) {\r
264 //\r
265 // if not dirty, no need to update file\r
266 //\r
267 if (dirty) {\r
268 //\r
269 // walk through dependencies to get vector of include paths\r
270 // identifiers\r
271 //\r
272 Vector includePaths = getIncludePaths();\r
273 //\r
274 //\r
275 // write dependency file\r
276 //\r
277 try {\r
278 FileOutputStream outStream = new FileOutputStream(\r
279 dependenciesFile);\r
280 OutputStreamWriter streamWriter;\r
281 //\r
282 // Early VM's may not have UTF-8 support\r
283 // fallback to default code page which\r
284 // "should" be okay unless there are\r
285 // non ASCII file names\r
286 String encodingName = "UTF-8";\r
287 try {\r
288 streamWriter = new OutputStreamWriter(outStream, "UTF-8");\r
289 } catch (UnsupportedEncodingException ex) {\r
290 streamWriter = new OutputStreamWriter(outStream);\r
291 encodingName = streamWriter.getEncoding();\r
292 }\r
293 BufferedWriter writer = new BufferedWriter(streamWriter);\r
294 writer.write("<?xml version='1.0' encoding='");\r
295 writer.write(encodingName);\r
296 writer.write("'?>\n");\r
297 writer.write("<dependencies>\n");\r
298 StringBuffer buf = new StringBuffer();\r
299 Enumeration includePathEnum = includePaths.elements();\r
300 while (includePathEnum.hasMoreElements()) {\r
301 writeIncludePathDependencies((String) includePathEnum\r
302 .nextElement(), writer, buf);\r
303 }\r
304 writer.write("</dependencies>\n");\r
305 writer.close();\r
306 dirty = false;\r
307 } catch (IOException ex) {\r
308 task.log("Error writing " + dependenciesFile.toString() + ":"\r
309 + ex.toString());\r
310 }\r
311 }\r
312 }\r
313 /**\r
314 * Returns an enumerator of DependencyInfo's\r
315 */\r
316 public Enumeration elements() {\r
317 return dependencies.elements();\r
318 }\r
319 /**\r
320 * This method returns a DependencyInfo for the specific source file and\r
321 * include path identifier\r
322 * \r
323 */\r
324 public DependencyInfo getDependencyInfo(String sourceRelativeName,\r
325 String includePathIdentifier) {\r
326 DependencyInfo dependInfo = null;\r
327 DependencyInfo[] dependInfos = (DependencyInfo[]) dependencies\r
328 .get(sourceRelativeName);\r
329 if (dependInfos != null) {\r
330 for (int i = 0; i < dependInfos.length; i++) {\r
331 dependInfo = dependInfos[i];\r
332 if (dependInfo.getIncludePathIdentifier().equals(\r
333 includePathIdentifier)) {\r
334 return dependInfo;\r
335 }\r
336 }\r
337 }\r
338 return null;\r
339 }\r
340 private Vector getIncludePaths() {\r
341 Vector includePaths = new Vector();\r
342 DependencyInfo[] dependInfos;\r
343 Enumeration dependenciesEnum = dependencies.elements();\r
344 while (dependenciesEnum.hasMoreElements()) {\r
345 dependInfos = (DependencyInfo[]) dependenciesEnum.nextElement();\r
346 for (int i = 0; i < dependInfos.length; i++) {\r
347 DependencyInfo dependInfo = dependInfos[i];\r
348 boolean matchesExisting = false;\r
349 final String dependIncludePath = dependInfo\r
350 .getIncludePathIdentifier();\r
351 Enumeration includePathEnum = includePaths.elements();\r
352 while (includePathEnum.hasMoreElements()) {\r
353 if (dependIncludePath.equals(includePathEnum.nextElement())) {\r
354 matchesExisting = true;\r
355 break;\r
356 }\r
357 }\r
358 if (!matchesExisting) {\r
359 includePaths.addElement(dependIncludePath);\r
360 }\r
361 }\r
362 }\r
363 return includePaths;\r
364 }\r
365 public void load() throws IOException, ParserConfigurationException,\r
366 SAXException {\r
367 dependencies.clear();\r
368 if (dependenciesFile.exists()) {\r
369 SAXParserFactory factory = SAXParserFactory.newInstance();\r
370 factory.setValidating(false);\r
371 SAXParser parser = factory.newSAXParser();\r
372 parser.parse(dependenciesFile, new DependencyTableHandler(this,\r
373 baseDir));\r
374 dirty = false;\r
375 }\r
376 }\r
377 /**\r
378 * Determines if the specified target needs to be rebuilt.\r
379 * \r
380 * This task may result in substantial IO as files are parsed to determine\r
381 * their dependencies\r
382 */\r
383 public boolean needsRebuild(CCTask task, TargetInfo target,\r
384 int dependencyDepth) {\r
385 // look at any files where the compositeLastModified\r
386 // is not known, but the includes are known\r
387 //\r
388 boolean mustRebuild = false;\r
389 CompilerConfiguration compiler = (CompilerConfiguration) target\r
390 .getConfiguration();\r
391 String includePathIdentifier = compiler.getIncludePathIdentifier();\r
392 File[] sources = target.getSources();\r
393 DependencyInfo[] dependInfos = new DependencyInfo[sources.length];\r
394 long outputLastModified = target.getOutput().lastModified();\r
395 //\r
396 // try to solve problem using existing dependency info\r
397 // (not parsing any new files)\r
398 //\r
399 DependencyInfo[] stack = new DependencyInfo[50];\r
400 boolean rebuildOnStackExhaustion = true;\r
401 if (dependencyDepth >= 0) {\r
402 if (dependencyDepth < 50) {\r
403 stack = new DependencyInfo[dependencyDepth];\r
404 }\r
405 rebuildOnStackExhaustion = false;\r
406 }\r
407 TimestampChecker checker = new TimestampChecker(outputLastModified,\r
408 rebuildOnStackExhaustion);\r
409 for (int i = 0; i < sources.length && !mustRebuild; i++) {\r
410 File source = sources[i];\r
411 String relative = CUtil.getRelativePath(baseDirPath, source);\r
412 DependencyInfo dependInfo = getDependencyInfo(relative,\r
413 includePathIdentifier);\r
414 if (dependInfo == null) {\r
415 task.log("Parsing " + relative, Project.MSG_VERBOSE);\r
416 dependInfo = parseIncludes(task, compiler, source);\r
417 }\r
418 walkDependencies(task, dependInfo, compiler, stack, checker);\r
419 mustRebuild = checker.getMustRebuild();\r
420 }\r
421 return mustRebuild;\r
422 }\r
423 public DependencyInfo parseIncludes(CCTask task,\r
424 CompilerConfiguration compiler, File source) {\r
425 DependencyInfo dependInfo = compiler.parseIncludes(task, baseDir,\r
426 source);\r
427 String relativeSource = CUtil.getRelativePath(baseDirPath, source);\r
428 putDependencyInfo(relativeSource, dependInfo);\r
429 return dependInfo;\r
430 }\r
431 private void putDependencyInfo(String key, DependencyInfo dependInfo) {\r
432 //\r
433 // optimistic, add new value\r
434 //\r
435 DependencyInfo[] old = (DependencyInfo[]) dependencies.put(key,\r
436 new DependencyInfo[]{dependInfo});\r
437 dirty = true;\r
438 //\r
439 // something was already there\r
440 //\r
441 if (old != null) {\r
442 //\r
443 // see if the include path matches a previous entry\r
444 // if so replace it\r
445 String includePathIdentifier = dependInfo\r
446 .getIncludePathIdentifier();\r
447 for (int i = 0; i < old.length; i++) {\r
448 DependencyInfo oldDepend = old[i];\r
449 if (oldDepend.getIncludePathIdentifier().equals(\r
450 includePathIdentifier)) {\r
451 old[i] = dependInfo;\r
452 dependencies.put(key, old);\r
453 return;\r
454 }\r
455 }\r
456 //\r
457 // no match prepend the new entry to the array\r
458 // of dependencies for the file\r
459 DependencyInfo[] combined = new DependencyInfo[old.length + 1];\r
460 combined[0] = dependInfo;\r
461 for (int i = 0; i < old.length; i++) {\r
462 combined[i + 1] = old[i];\r
463 }\r
464 dependencies.put(key, combined);\r
465 }\r
466 return;\r
467 }\r
468 public void walkDependencies(CCTask task, DependencyInfo dependInfo,\r
469 CompilerConfiguration compiler, DependencyInfo[] stack,\r
470 DependencyVisitor visitor) throws BuildException {\r
471 //\r
472 // visit this node\r
473 // if visit returns true then\r
474 // visit the referenced include and sysInclude dependencies\r
475 //\r
476 if (visitor.visit(dependInfo)) {\r
477 //\r
478 // find first null entry on stack\r
479 //\r
480 int stackPosition = -1;\r
481 for (int i = 0; i < stack.length; i++) {\r
482 if (stack[i] == null) {\r
483 stackPosition = i;\r
484 stack[i] = dependInfo;\r
485 break;\r
486 } else {\r
487 //\r
488 // if we have appeared early in the calling history\r
489 // then we didn't exceed the criteria\r
490 if (stack[i] == dependInfo) {\r
491 return;\r
492 }\r
493 }\r
494 }\r
495 if (stackPosition == -1) {\r
496 visitor.stackExhausted();\r
497 return;\r
498 }\r
499 //\r
500 // locate dependency infos\r
501 //\r
502 String[] includes = dependInfo.getIncludes();\r
503 String includePathIdentifier = compiler.getIncludePathIdentifier();\r
504 DependencyInfo[] includeInfos = new DependencyInfo[includes.length];\r
505 for (int i = 0; i < includes.length; i++) {\r
506 DependencyInfo includeInfo = getDependencyInfo(includes[i],\r
507 includePathIdentifier);\r
508 includeInfos[i] = includeInfo;\r
509 }\r
510 //\r
511 // preview with only the already available dependency infos\r
512 //\r
513 if (visitor.preview(dependInfo, includeInfos)) {\r
514 //\r
515 // now need to fill in the missing DependencyInfos\r
516 //\r
517 int missingCount = 0;\r
518 for (int i = 0; i < includes.length; i++) {\r
519 if (includeInfos[i] == null) {\r
520 missingCount++;\r
521 task.log("Parsing " + includes[i], Project.MSG_VERBOSE);\r
522 // If the include is part of a UNC don't go building a\r
523 // relative file name.\r
524 File src = includes[i].startsWith("\\\\") ? new File(\r
525 includes[i]) : new File(baseDir, includes[i]);\r
526 DependencyInfo includeInfo = parseIncludes(task,\r
527 compiler, src);\r
528 includeInfos[i] = includeInfo;\r
529 }\r
530 }\r
531 //\r
532 // if it passes a review the second time\r
533 // then recurse into all the children\r
534 if (missingCount == 0\r
535 || visitor.preview(dependInfo, includeInfos)) {\r
536 //\r
537 // recurse into\r
538 //\r
539 for (int i = 0; i < includeInfos.length; i++) {\r
540 DependencyInfo includeInfo = includeInfos[i];\r
541 walkDependencies(task, includeInfo, compiler, stack,\r
542 visitor);\r
543 }\r
544 }\r
545 }\r
546 stack[stackPosition] = null;\r
547 }\r
548 }\r
549 private void writeDependencyInfo(BufferedWriter writer, StringBuffer buf,\r
550 DependencyInfo dependInfo) throws IOException {\r
551 String[] includes = dependInfo.getIncludes();\r
552 String[] sysIncludes = dependInfo.getSysIncludes();\r
553 //\r
554 // if the includes have not been evaluted then\r
555 // it is not worth our time saving it\r
556 // and trying to distiguish between files with\r
557 // no dependencies and those with undetermined dependencies\r
558 buf.setLength(0);\r
559 buf.append(" <source file=\"");\r
560 buf.append(CUtil.xmlAttribEncode(dependInfo.getSource()));\r
561 buf.append("\" lastModified=\"");\r
562 buf.append(Long.toHexString(dependInfo.getSourceLastModified()));\r
563 buf.append("\">\n");\r
564 writer.write(buf.toString());\r
565 for (int i = 0; i < includes.length; i++) {\r
566 buf.setLength(0);\r
567 buf.append(" <include file=\"");\r
568 buf.append(CUtil.xmlAttribEncode(includes[i]));\r
569 buf.append("\"/>\n");\r
570 writer.write(buf.toString());\r
571 }\r
572 for (int i = 0; i < sysIncludes.length; i++) {\r
573 buf.setLength(0);\r
574 buf.append(" <sysinclude file=\"");\r
575 buf.append(CUtil.xmlAttribEncode(sysIncludes[i]));\r
576 buf.append("\"/>\n");\r
577 writer.write(buf.toString());\r
578 }\r
579 writer.write(" </source>\n");\r
580 return;\r
581 }\r
582 private void writeIncludePathDependencies(String includePathIdentifier,\r
583 BufferedWriter writer, StringBuffer buf) throws IOException {\r
584 //\r
585 // include path element\r
586 //\r
587 buf.setLength(0);\r
588 buf.append(" <includePath signature=\"");\r
589 buf.append(CUtil.xmlAttribEncode(includePathIdentifier));\r
590 buf.append("\">\n");\r
591 writer.write(buf.toString());\r
592 Enumeration dependenciesEnum = dependencies.elements();\r
593 while (dependenciesEnum.hasMoreElements()) {\r
594 DependencyInfo[] dependInfos = (DependencyInfo[]) dependenciesEnum\r
595 .nextElement();\r
596 for (int i = 0; i < dependInfos.length; i++) {\r
597 DependencyInfo dependInfo = dependInfos[i];\r
598 //\r
599 // if this is for the same include path\r
600 // then output the info\r
601 if (dependInfo.getIncludePathIdentifier().equals(\r
602 includePathIdentifier)) {\r
603 writeDependencyInfo(writer, buf, dependInfo);\r
604 }\r
605 }\r
606 }\r
607 writer.write(" </includePath>\n");\r
608 }\r
609}\r