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