1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.github.maven_nar.cpptasks;
21
22 import java.io.BufferedWriter;
23 import java.io.File;
24 import java.io.FileOutputStream;
25 import java.io.IOException;
26 import java.io.OutputStreamWriter;
27 import java.io.UnsupportedEncodingException;
28 import java.util.Enumeration;
29 import java.util.Hashtable;
30 import java.util.Vector;
31
32 import javax.xml.parsers.ParserConfigurationException;
33 import javax.xml.parsers.SAXParser;
34 import javax.xml.parsers.SAXParserFactory;
35
36 import org.apache.tools.ant.BuildException;
37 import org.apache.tools.ant.Project;
38 import org.xml.sax.Attributes;
39 import org.xml.sax.SAXException;
40 import org.xml.sax.helpers.DefaultHandler;
41
42 import com.github.maven_nar.cpptasks.compiler.CompilerConfiguration;
43
44
45
46
47 public final class DependencyTable {
48
49
50
51
52 private class DependencyTableHandler extends DefaultHandler {
53 private final File baseDir;
54 private final DependencyTable dependencyTable;
55 private String includePath;
56 private final Vector includes;
57 private String source;
58 private long sourceLastModified;
59 private final Vector sysIncludes;
60
61
62
63
64
65
66
67
68
69 private DependencyTableHandler(final DependencyTable dependencyTable, final File baseDir) {
70 this.dependencyTable = dependencyTable;
71 this.baseDir = baseDir;
72 this.includes = new Vector();
73 this.sysIncludes = new Vector();
74 this.source = null;
75 }
76
77 @Override
78 public void endElement(final String namespaceURI, final String localName, final String qName) throws SAXException {
79
80
81
82
83
84
85 if (qName.equals("source")) {
86 if (this.source != null && this.includePath != null) {
87 final File existingFile = new File(this.baseDir, this.source);
88
89
90
91 if (existingFile.exists()) {
92
93
94
95
96
97 final long existingLastModified = existingFile.lastModified();
98 if (!CUtil.isSignificantlyAfter(existingLastModified, this.sourceLastModified)
99 && !CUtil.isSignificantlyBefore(existingLastModified, this.sourceLastModified)) {
100 final DependencyInfo dependInfo = new DependencyInfo(this.includePath, this.source,
101 this.sourceLastModified, this.includes, this.sysIncludes);
102 this.dependencyTable.putDependencyInfo(this.source, dependInfo);
103 }
104 }
105 this.source = null;
106 this.includes.setSize(0);
107 }
108 } else {
109
110
111
112
113 if (qName.equals("includePath")) {
114 this.includePath = null;
115 }
116 }
117 }
118
119
120
121
122 @Override
123 public void startElement(final String namespaceURI, final String localName, final String qName,
124 final Attributes atts) throws SAXException {
125
126
127
128 if (qName.equals("include")) {
129 this.includes.addElement(atts.getValue("file"));
130 } else {
131 if (qName.equals("sysinclude")) {
132 this.sysIncludes.addElement(atts.getValue("file"));
133 } else {
134
135
136
137
138
139 if (qName.equals("source")) {
140 this.source = atts.getValue("file");
141 this.sourceLastModified = Long.parseLong(atts.getValue("lastModified"), 16);
142 this.includes.setSize(0);
143 this.sysIncludes.setSize(0);
144 } else {
145 if (qName.equals("includePath")) {
146 this.includePath = atts.getValue("signature");
147 }
148 }
149 }
150 }
151 }
152 }
153
154 public abstract class DependencyVisitor {
155
156
157
158
159
160
161
162
163 public abstract boolean preview(DependencyInfo parent, DependencyInfo[] children);
164
165
166
167
168 public abstract void stackExhausted();
169
170
171
172
173
174
175 public abstract boolean visit(DependencyInfo dependInfo);
176 }
177
178 public class TimestampChecker extends DependencyVisitor {
179 private boolean noNeedToRebuild;
180 private final long outputLastModified;
181 private final boolean rebuildOnStackExhaustion;
182
183 public TimestampChecker(final long outputLastModified, final boolean rebuildOnStackExhaustion) {
184 this.outputLastModified = outputLastModified;
185 this.noNeedToRebuild = true;
186 this.rebuildOnStackExhaustion = rebuildOnStackExhaustion;
187 }
188
189 public boolean getMustRebuild() {
190 return !this.noNeedToRebuild;
191 }
192
193 @Override
194 public boolean preview(final DependencyInfo parent, final DependencyInfo[] children) {
195
196
197
198
199 for (final DependencyInfo element : children) {
200 if (element != null) {
201
202
203
204
205 visit(element);
206
207
208
209
210
211
212
213
214
215
216 }
217 }
218
219
220
221
222
223
224
225
226 return this.noNeedToRebuild;
227 }
228
229 @Override
230 public void stackExhausted() {
231 if (this.rebuildOnStackExhaustion) {
232 this.noNeedToRebuild = false;
233 }
234 }
235
236 @Override
237 public boolean visit(final DependencyInfo dependInfo) {
238 if (this.noNeedToRebuild) {
239 if (CUtil.isSignificantlyAfter(dependInfo.getSourceLastModified(), this.outputLastModified)) {
240
241
242
243
244 this.noNeedToRebuild = false;
245 }
246 }
247
248
249
250
251
252 return this.noNeedToRebuild;
253
254
255 }
256 }
257
258 private final
259 private String baseDirPath;
260
261
262
263 private final Hashtable dependencies = new Hashtable();
264
265 private final
266
267 private boolean dirty;
268
269
270
271
272
273
274
275
276 public DependencyTable(final File baseDir) {
277 if (baseDir == null) {
278 throw new NullPointerException("baseDir");
279 }
280 this.baseDir = baseDir;
281 try {
282 this.baseDirPath = baseDir.getCanonicalPath();
283 } catch (final IOException ex) {
284 this.baseDirPath = baseDir.toString();
285 }
286 this.dirty = false;
287
288
289 this.dependenciesFile = new File(baseDir, "dependencies.xml");
290 }
291
292 public void commit(final CCTask task) {
293
294
295
296 if (this.dirty) {
297
298
299
300
301 final Vector includePaths = getIncludePaths();
302
303
304
305
306 try {
307 final FileOutputStream outStream = new FileOutputStream(this.dependenciesFile);
308 OutputStreamWriter streamWriter;
309
310
311
312
313
314 String encodingName = "UTF-8";
315 try {
316 streamWriter = new OutputStreamWriter(outStream, "UTF-8");
317 } catch (final UnsupportedEncodingException ex) {
318 streamWriter = new OutputStreamWriter(outStream);
319 encodingName = streamWriter.getEncoding();
320 }
321 final BufferedWriter writer = new BufferedWriter(streamWriter);
322 writer.write("<?xml version='1.0' encoding='");
323 writer.write(encodingName);
324 writer.write("'?>\n");
325 writer.write("<dependencies>\n");
326 final StringBuffer buf = new StringBuffer();
327 final Enumeration includePathEnum = includePaths.elements();
328 while (includePathEnum.hasMoreElements()) {
329 writeIncludePathDependencies((String) includePathEnum.nextElement(), writer, buf);
330 }
331 writer.write("</dependencies>\n");
332 writer.close();
333 this.dirty = false;
334 } catch (final IOException ex) {
335 task.log("Error writing " + this.dependenciesFile.toString() + ":" + ex.toString());
336 }
337 }
338 }
339
340
341
342
343 public Enumeration elements() {
344 return this.dependencies.elements();
345 }
346
347
348
349
350
351
352 public DependencyInfo getDependencyInfo(final String sourceRelativeName, final String includePathIdentifier) {
353 DependencyInfo dependInfo = null;
354 final DependencyInfo[] dependInfos = (DependencyInfo[]) this.dependencies.get(sourceRelativeName);
355 if (dependInfos != null) {
356 for (final DependencyInfo dependInfo2 : dependInfos) {
357 dependInfo = dependInfo2;
358 if (dependInfo.getIncludePathIdentifier().equals(includePathIdentifier)) {
359 return dependInfo;
360 }
361 }
362 }
363 return null;
364 }
365
366 private Vector getIncludePaths() {
367 final Vector includePaths = new Vector();
368 DependencyInfo[] dependInfos;
369 final Enumeration dependenciesEnum = this.dependencies.elements();
370 while (dependenciesEnum.hasMoreElements()) {
371 dependInfos = (DependencyInfo[]) dependenciesEnum.nextElement();
372 for (final DependencyInfo dependInfo : dependInfos) {
373 boolean matchesExisting = false;
374 final String dependIncludePath = dependInfo.getIncludePathIdentifier();
375 final Enumeration includePathEnum = includePaths.elements();
376 while (includePathEnum.hasMoreElements()) {
377 if (dependIncludePath.equals(includePathEnum.nextElement())) {
378 matchesExisting = true;
379 break;
380 }
381 }
382 if (!matchesExisting) {
383 includePaths.addElement(dependIncludePath);
384 }
385 }
386 }
387 return includePaths;
388 }
389
390 public void load() throws IOException, ParserConfigurationException, SAXException {
391 this.dependencies.clear();
392 if (this.dependenciesFile.exists()) {
393 final SAXParserFactory factory = SAXParserFactory.newInstance();
394 factory.setValidating(false);
395 final SAXParser parser = factory.newSAXParser();
396 parser.parse(this.dependenciesFile, new DependencyTableHandler(this, this.baseDir));
397 this.dirty = false;
398 }
399 }
400
401
402
403
404
405
406
407 public boolean needsRebuild(final CCTask task, final TargetInfo target, final int dependencyDepth) {
408
409
410
411 boolean mustRebuild = false;
412 final CompilerConfiguration compiler = (CompilerConfiguration) target.getConfiguration();
413 final String includePathIdentifier = compiler.getIncludePathIdentifier();
414 final File[] sources = target.getSources();
415 final DependencyInfo[] dependInfos = new DependencyInfo[sources.length];
416 final long outputLastModified = target.getOutput().lastModified();
417
418
419
420
421 DependencyInfo[] stack = new DependencyInfo[50];
422 boolean rebuildOnStackExhaustion = true;
423 if (dependencyDepth >= 0) {
424 if (dependencyDepth < 50) {
425 stack = new DependencyInfo[dependencyDepth];
426 }
427 rebuildOnStackExhaustion = false;
428 }
429 final TimestampChecker checker = new TimestampChecker(outputLastModified, rebuildOnStackExhaustion);
430 for (int i = 0; i < sources.length && !mustRebuild; i++) {
431 final File source = sources[i];
432 final String relative = CUtil.getRelativePath(this.baseDirPath, source);
433 DependencyInfo dependInfo = getDependencyInfo(relative, includePathIdentifier);
434 if (dependInfo == null) {
435 task.log("Parsing " + relative, Project.MSG_VERBOSE);
436 dependInfo = parseIncludes(task, compiler, source);
437 }
438 walkDependencies(task, dependInfo, compiler, stack, checker);
439 mustRebuild = checker.getMustRebuild();
440 }
441 return mustRebuild;
442 }
443
444 public DependencyInfo parseIncludes(final CCTask task, final CompilerConfiguration compiler, final File source) {
445 final DependencyInfo dependInfo = compiler.parseIncludes(task, this.baseDir, source);
446 final String relativeSource = CUtil.getRelativePath(this.baseDirPath, source);
447 putDependencyInfo(relativeSource, dependInfo);
448 return dependInfo;
449 }
450
451 private void putDependencyInfo(final String key, final DependencyInfo dependInfo) {
452
453
454
455 final DependencyInfo[] old = (DependencyInfo[]) this.dependencies.put(key, new DependencyInfo[] {
456 dependInfo
457 });
458 this.dirty = true;
459
460
461
462 if (old != null) {
463
464
465
466 final String includePathIdentifier = dependInfo.getIncludePathIdentifier();
467 for (int i = 0; i < old.length; i++) {
468 final DependencyInfo oldDepend = old[i];
469 if (oldDepend.getIncludePathIdentifier().equals(includePathIdentifier)) {
470 old[i] = dependInfo;
471 this.dependencies.put(key, old);
472 return;
473 }
474 }
475
476
477
478 final DependencyInfo[] combined = new DependencyInfo[old.length + 1];
479 combined[0] = dependInfo;
480 System.arraycopy(old, 0, combined, 1, old.length);
481 this.dependencies.put(key, combined);
482 }
483 return;
484 }
485
486 public void walkDependencies(final CCTask task, final DependencyInfo dependInfo,
487 final CompilerConfiguration compiler, final DependencyInfo[] stack, final DependencyVisitor visitor)
488 throws BuildException {
489
490 if (dependInfo.hasTag(visitor)) {
491 return;
492 }
493 dependInfo.setTag(visitor);
494
495
496
497
498
499
500 if (visitor.visit(dependInfo)) {
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528 final String[] includes = dependInfo.getIncludes();
529 final String includePathIdentifier = compiler.getIncludePathIdentifier();
530 final DependencyInfo[] includeInfos = new DependencyInfo[includes.length];
531 for (int i = 0; i < includes.length; i++) {
532 final DependencyInfo includeInfo = getDependencyInfo(includes[i], includePathIdentifier);
533 includeInfos[i] = includeInfo;
534 }
535
536
537
538 if (visitor.preview(dependInfo, includeInfos)) {
539
540
541
542 int missingCount = 0;
543 for (int i = 0; i < includes.length; i++) {
544 if (includeInfos[i] == null) {
545 missingCount++;
546 task.log("Parsing " + includes[i], Project.MSG_VERBOSE);
547
548
549
550 File src = new File(includes[i]);
551 if (!src.isAbsolute()) {
552 src = new File(this.baseDir, includes[i]);
553 }
554 final DependencyInfo includeInfo = parseIncludes(task, compiler, src);
555 includeInfos[i] = includeInfo;
556 }
557 }
558
559
560
561 if (missingCount == 0 || visitor.preview(dependInfo, includeInfos)) {
562
563
564
565 for (final DependencyInfo includeInfo : includeInfos) {
566
567
568
569 if (includeInfo.getSource().contains(File.separatorChar + "src" + File.separatorChar + "main")
570 || includeInfo.getSource().contains(File.separatorChar + "src" + File.separatorChar + "test")) {
571 task.log("Walking dependencies for " + includeInfo.getSource(), Project.MSG_VERBOSE);
572 walkDependencies(task, includeInfo, compiler, stack, visitor);
573 }
574 }
575 }
576 }
577
578
579 }
580 }
581
582 private void
583 writeDependencyInfo(final BufferedWriter writer, final StringBuffer buf, final DependencyInfo dependInfo)
584 throws IOException {
585 final String[] includes = dependInfo.getIncludes();
586 final String[] sysIncludes = dependInfo.getSysIncludes();
587
588
589
590
591
592 buf.setLength(0);
593 buf.append(" <source file=\"");
594 buf.append(CUtil.xmlAttribEncode(dependInfo.getSource()));
595 buf.append("\" lastModified=\"");
596 buf.append(Long.toHexString(dependInfo.getSourceLastModified()));
597 buf.append("\">\n");
598 writer.write(buf.toString());
599 for (final String include : includes) {
600 buf.setLength(0);
601 buf.append(" <include file=\"");
602 buf.append(CUtil.xmlAttribEncode(include));
603 buf.append("\"/>\n");
604 writer.write(buf.toString());
605 }
606 for (final String sysInclude : sysIncludes) {
607 buf.setLength(0);
608 buf.append(" <sysinclude file=\"");
609 buf.append(CUtil.xmlAttribEncode(sysInclude));
610 buf.append("\"/>\n");
611 writer.write(buf.toString());
612 }
613 writer.write(" </source>\n");
614 return;
615 }
616
617 private void writeIncludePathDependencies(final String includePathIdentifier, final BufferedWriter writer,
618 final StringBuffer buf) throws IOException {
619
620
621
622 buf.setLength(0);
623 buf.append(" <includePath signature=\"");
624 buf.append(CUtil.xmlAttribEncode(includePathIdentifier));
625 buf.append("\">\n");
626 writer.write(buf.toString());
627 final Enumeration dependenciesEnum = this.dependencies.elements();
628 while (dependenciesEnum.hasMoreElements()) {
629 final DependencyInfo[] dependInfos = (DependencyInfo[]) dependenciesEnum.nextElement();
630 for (final DependencyInfo dependInfo : dependInfos) {
631
632
633
634 if (dependInfo.getIncludePathIdentifier().equals(includePathIdentifier)) {
635 writeDependencyInfo(writer, buf, dependInfo);
636 }
637 }
638 }
639 writer.write(" </includePath>\n");
640 }
641 }