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
;
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
;
39 public final class DependencyTable
{
41 * This class handles populates the TargetHistory hashtable in response to
44 private class DependencyTableHandler
extends DefaultHandler
{
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
;
56 * hashtable of TargetHistory keyed by output name
58 * existing files in output directory
60 private DependencyTableHandler(DependencyTable dependencyTable
,
62 this.dependencyTable
= dependencyTable
;
63 this.baseDir
= baseDir
;
64 includes
= new Vector();
65 sysIncludes
= new Vector();
68 public void endElement(String namespaceURI
, String localName
,
69 String qName
) throws SAXException
{
72 // create Dependency object and add to hashtable
73 // if corresponding source file exists and
74 // has the same timestamp
76 if (qName
.equals("source")) {
77 if (source
!= null && includePath
!= null) {
78 File existingFile
= new File(baseDir
, source
);
80 // if the file exists and the time stamp is right
81 // preserve the dependency info
82 if (existingFile
.exists()) {
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
,
103 // this causes any <source> elements outside the
104 // scope of an <includePath> to be discarded
106 if (qName
.equals("includePath")) {
112 * startElement handler
114 public void startElement(String namespaceURI
, String localName
,
115 String qName
, Attributes atts
) throws SAXException
{
117 // if includes, then add relative file name to vector
119 if (qName
.equals("include")) {
120 includes
.addElement(atts
.getValue("file"));
122 if (qName
.equals("sysinclude")) {
123 sysIncludes
.addElement(atts
.getValue("file"));
127 // capture source file name,
128 // modification time and reset includes vector
130 if (qName
.equals("source")) {
131 source
= atts
.getValue("file");
132 sourceLastModified
= Long
.parseLong(atts
133 .getValue("lastModified"), 16);
135 sysIncludes
.setSize(0);
137 if (qName
.equals("includePath")) {
138 includePath
= atts
.getValue("signature");
145 public abstract class DependencyVisitor
{
147 * Previews all the children of this source file.
149 * May be called multiple times as DependencyInfo's for children are
152 * @return true to continue towards recursion into included files
154 public abstract boolean preview(DependencyInfo parent
,
155 DependencyInfo
[] children
);
157 * Called if the dependency depth exhausted the stack.
159 public abstract void stackExhausted();
161 * Visits the dependency info.
163 * @returns true to continue towards recursion into included files
165 public abstract boolean visit(DependencyInfo dependInfo
);
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
;
177 public boolean getMustRebuild() {
178 return !noNeedToRebuild
;
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) {
186 // expedient way to determine if a child forces us to
190 long childCompositeLastModified
= children
[i
]
191 .getCompositeLastModified();
192 if (childCompositeLastModified
!= Long
.MIN_VALUE
) {
193 withCompositeTimes
++;
194 if (childCompositeLastModified
> parentCompositeLastModified
) {
195 parentCompositeLastModified
= childCompositeLastModified
;
200 if (withCompositeTimes
== children
.length
) {
201 parent
.setCompositeLastModified(parentCompositeLastModified
);
204 // may have been changed by an earlier call to visit()
206 return noNeedToRebuild
;
208 public void stackExhausted() {
209 if (rebuildOnStackExhaustion
) {
210 noNeedToRebuild
= false;
213 public boolean visit(DependencyInfo dependInfo
) {
214 if (noNeedToRebuild
) {
215 if (dependInfo
.getSourceLastModified() > outputLastModified
216 || dependInfo
.getCompositeLastModified() > outputLastModified
) {
217 noNeedToRebuild
= false;
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
;
229 private/* final */File baseDir
;
230 private String baseDirPath
;
232 * a hashtable of DependencyInfo[] keyed by output file name
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
;
240 * Creates a target history table from dependencies.xml in the prject
241 * directory, if it exists. Otherwise, initializes the dependencies empty.
244 * task used for logging history load errors
246 * output directory for task
248 public DependencyTable(File baseDir
) {
249 if (baseDir
== null) {
250 throw new NullPointerException("baseDir");
252 this.baseDir
= baseDir
;
254 baseDirPath
= baseDir
.getCanonicalPath();
255 } catch (IOException ex
) {
256 baseDirPath
= baseDir
.toString();
260 // load any existing dependencies from file
261 dependenciesFile
= new File(baseDir
, "dependencies.xml");
263 public void commit(CCTask task
) {
265 // if not dirty, no need to update file
269 // walk through dependencies to get vector of include paths
272 Vector includePaths
= getIncludePaths();
275 // write dependency file
278 FileOutputStream outStream
= new FileOutputStream(
280 OutputStreamWriter streamWriter
;
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";
288 streamWriter
= new OutputStreamWriter(outStream
, "UTF-8");
289 } catch (UnsupportedEncodingException ex
) {
290 streamWriter
= new OutputStreamWriter(outStream
);
291 encodingName
= streamWriter
.getEncoding();
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
);
304 writer
.write("</dependencies>\n");
307 } catch (IOException ex
) {
308 task
.log("Error writing " + dependenciesFile
.toString() + ":"
314 * Returns an enumerator of DependencyInfo's
316 public Enumeration
elements() {
317 return dependencies
.elements();
320 * This method returns a DependencyInfo for the specific source file and
321 * include path identifier
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
)) {
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;
358 if (!matchesExisting
) {
359 includePaths
.addElement(dependIncludePath
);
365 public void load() throws IOException
, ParserConfigurationException
,
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,
378 * Determines if the specified target needs to be rebuilt.
380 * This task may result in substantial IO as files are parsed to determine
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
388 boolean mustRebuild
= false;
389 CompilerConfiguration compiler
= (CompilerConfiguration
) target
391 String includePathIdentifier
= compiler
.getIncludePathIdentifier();
392 File
[] sources
= target
.getSources();
393 DependencyInfo
[] dependInfos
= new DependencyInfo
[sources
.length
];
394 long outputLastModified
= target
.getOutput().lastModified();
396 // try to solve problem using existing dependency info
397 // (not parsing any new files)
399 DependencyInfo
[] stack
= new DependencyInfo
[50];
400 boolean rebuildOnStackExhaustion
= true;
401 if (dependencyDepth
>= 0) {
402 if (dependencyDepth
< 50) {
403 stack
= new DependencyInfo
[dependencyDepth
];
405 rebuildOnStackExhaustion
= false;
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
);
418 walkDependencies(task
, dependInfo
, compiler
, stack
, checker
);
419 mustRebuild
= checker
.getMustRebuild();
423 public DependencyInfo
parseIncludes(CCTask task
,
424 CompilerConfiguration compiler
, File source
) {
425 DependencyInfo dependInfo
= compiler
.parseIncludes(task
, baseDir
,
427 String relativeSource
= CUtil
.getRelativePath(baseDirPath
, source
);
428 putDependencyInfo(relativeSource
, dependInfo
);
431 private void putDependencyInfo(String key
, DependencyInfo dependInfo
) {
433 // optimistic, add new value
435 DependencyInfo
[] old
= (DependencyInfo
[]) dependencies
.put(key
,
436 new DependencyInfo
[]{dependInfo
});
439 // something was already there
443 // see if the include path matches a previous entry
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
)) {
452 dependencies
.put(key
, old
);
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
];
464 dependencies
.put(key
, combined
);
468 public void walkDependencies(CCTask task
, DependencyInfo dependInfo
,
469 CompilerConfiguration compiler
, DependencyInfo
[] stack
,
470 DependencyVisitor visitor
) throws BuildException
{
473 // if visit returns true then
474 // visit the referenced include and sysInclude dependencies
476 if (visitor
.visit(dependInfo
)) {
478 // find first null entry on stack
480 int stackPosition
= -1;
481 for (int i
= 0; i
< stack
.length
; i
++) {
482 if (stack
[i
] == null) {
484 stack
[i
] = dependInfo
;
488 // if we have appeared early in the calling history
489 // then we didn't exceed the criteria
490 if (stack
[i
] == dependInfo
) {
495 if (stackPosition
== -1) {
496 visitor
.stackExhausted();
500 // locate dependency infos
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
;
511 // preview with only the already available dependency infos
513 if (visitor
.preview(dependInfo
, includeInfos
)) {
515 // now need to fill in the missing DependencyInfos
517 int missingCount
= 0;
518 for (int i
= 0; i
< includes
.length
; i
++) {
519 if (includeInfos
[i
] == null) {
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
,
528 includeInfos
[i
] = includeInfo
;
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
)) {
539 for (int i
= 0; i
< includeInfos
.length
; i
++) {
540 DependencyInfo includeInfo
= includeInfos
[i
];
541 walkDependencies(task
, includeInfo
, compiler
, stack
,
546 stack
[stackPosition
] = null;
549 private void writeDependencyInfo(BufferedWriter writer
, StringBuffer buf
,
550 DependencyInfo dependInfo
) throws IOException
{
551 String
[] includes
= dependInfo
.getIncludes();
552 String
[] sysIncludes
= dependInfo
.getSysIncludes();
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
559 buf
.append(" <source file=\"");
560 buf
.append(CUtil
.xmlAttribEncode(dependInfo
.getSource()));
561 buf
.append("\" lastModified=\"");
562 buf
.append(Long
.toHexString(dependInfo
.getSourceLastModified()));
564 writer
.write(buf
.toString());
565 for (int i
= 0; i
< includes
.length
; i
++) {
567 buf
.append(" <include file=\"");
568 buf
.append(CUtil
.xmlAttribEncode(includes
[i
]));
569 buf
.append("\"/>\n");
570 writer
.write(buf
.toString());
572 for (int i
= 0; i
< sysIncludes
.length
; i
++) {
574 buf
.append(" <sysinclude file=\"");
575 buf
.append(CUtil
.xmlAttribEncode(sysIncludes
[i
]));
576 buf
.append("\"/>\n");
577 writer
.write(buf
.toString());
579 writer
.write(" </source>\n");
582 private void writeIncludePathDependencies(String includePathIdentifier
,
583 BufferedWriter writer
, StringBuffer buf
) throws IOException
{
585 // include path element
588 buf
.append(" <includePath signature=\"");
589 buf
.append(CUtil
.xmlAttribEncode(includePathIdentifier
));
591 writer
.write(buf
.toString());
592 Enumeration dependenciesEnum
= dependencies
.elements();
593 while (dependenciesEnum
.hasMoreElements()) {
594 DependencyInfo
[] dependInfos
= (DependencyInfo
[]) dependenciesEnum
596 for (int i
= 0; i
< dependInfos
.length
; i
++) {
597 DependencyInfo dependInfo
= dependInfos
[i
];
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
);
607 writer
.write(" </includePath>\n");