|
1 /* |
|
2 * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. |
|
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
4 * |
|
5 * This code is free software; you can redistribute it and/or modify it |
|
6 * under the terms of the GNU General Public License version 2 only, as |
|
7 * published by the Free Software Foundation. Oracle designates this |
|
8 * particular file as subject to the "Classpath" exception as provided |
|
9 * by Oracle in the LICENSE file that accompanied this code. |
|
10 * |
|
11 * This code is distributed in the hope that it will be useful, but WITHOUT |
|
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
14 * version 2 for more details (a copy is included in the LICENSE file that |
|
15 * accompanied this code). |
|
16 * |
|
17 * You should have received a copy of the GNU General Public License version |
|
18 * 2 along with this work; if not, write to the Free Software Foundation, |
|
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
20 * |
|
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
22 * or visit www.oracle.com if you need additional information or have any |
|
23 * questions. |
|
24 */ |
|
25 |
|
26 package com.sun.tools.sjavac; |
|
27 |
|
28 import java.io.*; |
|
29 import java.util.Collections; |
|
30 import java.util.Date; |
|
31 import java.util.Set; |
|
32 import java.util.HashSet; |
|
33 import java.util.List; |
|
34 import java.util.Map; |
|
35 import java.util.HashMap; |
|
36 import java.text.SimpleDateFormat; |
|
37 import java.net.URI; |
|
38 import java.util.*; |
|
39 |
|
40 /** |
|
41 * The javac state class maintains the previous (prev) and the current (now) |
|
42 * build states and everything else that goes into the javac_state file. |
|
43 * |
|
44 * <p><b>This is NOT part of any supported API. |
|
45 * If you write code that depends on this, you do so at your own |
|
46 * risk. This code and its internal interfaces are subject to change |
|
47 * or deletion without notice.</b></p> |
|
48 */ |
|
49 public class JavacState |
|
50 { |
|
51 // The arguments to the compile. If not identical, then it cannot |
|
52 // be an incremental build! |
|
53 String theArgs; |
|
54 // The number of cores limits how many threads are used for heavy concurrent work. |
|
55 int numCores; |
|
56 |
|
57 // The bin_dir/javac_state |
|
58 private String javacStateFilename; |
|
59 private File javacState; |
|
60 |
|
61 // The previous build state is loaded from javac_state |
|
62 private BuildState prev; |
|
63 // The current build state is constructed during the build, |
|
64 // then saved as the new javac_state. |
|
65 private BuildState now; |
|
66 |
|
67 // Something has changed in the javac_state. It needs to be saved! |
|
68 private boolean needsSaving; |
|
69 // If this is a new javac_state file, then do not print unnecessary messages. |
|
70 private boolean newJavacState; |
|
71 |
|
72 // These are packages where something has changed and the package |
|
73 // needs to be recompiled. Actions that trigger recompilation: |
|
74 // * source belonging to the package has changed |
|
75 // * artifact belonging to the package is lost, or its timestamp has been changed. |
|
76 // * an unknown artifact has appeared, we simply delete it, but we also trigger a recompilation. |
|
77 // * a package that is tainted, taints all packages that depend on it. |
|
78 private Set<String> taintedPackages; |
|
79 // After a compile, the pubapis are compared with the pubapis stored in the javac state file. |
|
80 // Any packages where the pubapi differ are added to this set. |
|
81 // Later we use this set and the dependency information to taint dependent packages. |
|
82 private Set<String> packagesWithChangedPublicApis; |
|
83 // When a module-info.java file is changed, taint the module, |
|
84 // then taint all modules that depend on that that module. |
|
85 // A module dependency can occur directly through a require, or |
|
86 // indirectly through a module that does a public export for the first tainted module. |
|
87 // When all modules are tainted, then taint all packages belonging to these modules. |
|
88 // Then rebuild. It is perhaps possible (and valuable?) to do a more finegrained examination of the |
|
89 // change in module-info.java, but that will have to wait. |
|
90 private Set<String> taintedModules; |
|
91 // The set of all packages that has been recompiled. |
|
92 // Copy over the javac_state for the packages that did not need recompilation, |
|
93 // verbatim from the previous (prev) to the new (now) build state. |
|
94 private Set<String> recompiledPackages; |
|
95 |
|
96 // The output directories filled with tasty artifacts. |
|
97 private File binDir, gensrcDir, headerDir; |
|
98 |
|
99 // The current status of the file system. |
|
100 private Set<File> binArtifacts; |
|
101 private Set<File> gensrcArtifacts; |
|
102 private Set<File> headerArtifacts; |
|
103 |
|
104 // The status of the sources. |
|
105 Set<Source> removedSources = null; |
|
106 Set<Source> addedSources = null; |
|
107 Set<Source> modifiedSources = null; |
|
108 |
|
109 // Visible sources for linking. These are the only |
|
110 // ones that -sourcepath is allowed to see. |
|
111 Set<URI> visibleSrcs; |
|
112 |
|
113 // Visible classes for linking. These are the only |
|
114 // ones that -classpath is allowed to see. |
|
115 // It maps from a classpath root to the set of visible classes for that root. |
|
116 // If the set is empty, then all classes are visible for that root. |
|
117 // It can also map from a jar file to the set of visible classes for that jar file. |
|
118 Map<URI,Set<String>> visibleClasses; |
|
119 |
|
120 // Setup two transforms that always exist. |
|
121 private CopyFile copyFiles = new CopyFile(); |
|
122 private CompileJavaPackages compileJavaPackages = new CompileJavaPackages(); |
|
123 |
|
124 // Where to send stdout and stderr. |
|
125 private PrintStream out, err; |
|
126 |
|
127 JavacState(String[] args, File bd, File gd, File hd, boolean permitUnidentifiedArtifacts, boolean removeJavacState, |
|
128 PrintStream o, PrintStream e) { |
|
129 out = o; |
|
130 err = e; |
|
131 numCores = Main.findNumberOption(args, "-j"); |
|
132 theArgs = ""; |
|
133 for (String a : removeArgsNotAffectingState(args)) { |
|
134 theArgs = theArgs+a+" "; |
|
135 } |
|
136 binDir = bd; |
|
137 gensrcDir = gd; |
|
138 headerDir = hd; |
|
139 javacStateFilename = binDir.getPath()+File.separator+"javac_state"; |
|
140 javacState = new File(javacStateFilename); |
|
141 if (removeJavacState && javacState.exists()) { |
|
142 javacState.delete(); |
|
143 } |
|
144 newJavacState = false; |
|
145 if (!javacState.exists()) { |
|
146 newJavacState = true; |
|
147 // If there is no javac_state then delete the contents of all the artifact dirs! |
|
148 // We do not want to risk building a broken incremental build. |
|
149 // BUT since the makefiles still copy things straight into the bin_dir et al, |
|
150 // we avoid deleting files here, if the option --permit-unidentified-classes was supplied. |
|
151 if (!permitUnidentifiedArtifacts) { |
|
152 deleteContents(binDir); |
|
153 deleteContents(gensrcDir); |
|
154 deleteContents(headerDir); |
|
155 } |
|
156 needsSaving = true; |
|
157 } |
|
158 prev = new BuildState(); |
|
159 now = new BuildState(); |
|
160 taintedPackages = new HashSet<String>(); |
|
161 recompiledPackages = new HashSet<String>(); |
|
162 packagesWithChangedPublicApis = new HashSet<String>(); |
|
163 } |
|
164 |
|
165 public BuildState prev() { return prev; } |
|
166 public BuildState now() { return now; } |
|
167 |
|
168 /** |
|
169 * Remove args not affecting the state. |
|
170 */ |
|
171 static String[] removeArgsNotAffectingState(String[] args) { |
|
172 String[] out = new String[args.length]; |
|
173 int j = 0; |
|
174 for (int i = 0; i<args.length; ++i) { |
|
175 if (args[i].equals("-j")) { |
|
176 // Just skip it and skip following value |
|
177 i++; |
|
178 } else if (args[i].startsWith("--server:")) { |
|
179 // Just skip it. |
|
180 } else if (args[i].startsWith("--log=")) { |
|
181 // Just skip it. |
|
182 } else if (args[i].equals("--compare-found-sources")) { |
|
183 // Just skip it and skip verify file name |
|
184 i++; |
|
185 } else { |
|
186 // Copy argument. |
|
187 out[j] = args[i]; |
|
188 j++; |
|
189 } |
|
190 } |
|
191 String[] ret = new String[j]; |
|
192 System.arraycopy(out, 0, ret, 0, j); |
|
193 return ret; |
|
194 } |
|
195 |
|
196 /** |
|
197 * Specify which sources are visible to the compiler through -sourcepath. |
|
198 */ |
|
199 public void setVisibleSources(Map<String,Source> vs) { |
|
200 visibleSrcs = new HashSet<URI>(); |
|
201 for (String s : vs.keySet()) { |
|
202 Source src = vs.get(s); |
|
203 visibleSrcs.add(src.file().toURI()); |
|
204 } |
|
205 } |
|
206 |
|
207 /** |
|
208 * Specify which classes are visible to the compiler through -classpath. |
|
209 */ |
|
210 public void setVisibleClasses(Map<String,Source> vs) { |
|
211 visibleSrcs = new HashSet<URI>(); |
|
212 for (String s : vs.keySet()) { |
|
213 Source src = vs.get(s); |
|
214 visibleSrcs.add(src.file().toURI()); |
|
215 } |
|
216 } |
|
217 /** |
|
218 * Returns true if this is an incremental build. |
|
219 */ |
|
220 public boolean isIncremental() { |
|
221 return !prev.sources().isEmpty(); |
|
222 } |
|
223 |
|
224 /** |
|
225 * Find all artifacts that exists on disk. |
|
226 */ |
|
227 public void findAllArtifacts() { |
|
228 binArtifacts = findAllFiles(binDir); |
|
229 gensrcArtifacts = findAllFiles(gensrcDir); |
|
230 headerArtifacts = findAllFiles(headerDir); |
|
231 } |
|
232 |
|
233 /** |
|
234 * Lookup the artifacts generated for this package in the previous build. |
|
235 */ |
|
236 private Map<String,File> fetchPrevArtifacts(String pkg) { |
|
237 Package p = prev.packages().get(pkg); |
|
238 if (p != null) { |
|
239 return p.artifacts(); |
|
240 } |
|
241 return new HashMap<String,File>(); |
|
242 } |
|
243 |
|
244 /** |
|
245 * Delete all prev artifacts in the currently tainted packages. |
|
246 */ |
|
247 public void deleteClassArtifactsInTaintedPackages() { |
|
248 for (String pkg : taintedPackages) { |
|
249 Map<String,File> arts = fetchPrevArtifacts(pkg); |
|
250 for (File f : arts.values()) { |
|
251 if (f.exists() && f.getName().endsWith(".class")) { |
|
252 f.delete(); |
|
253 } |
|
254 } |
|
255 } |
|
256 } |
|
257 |
|
258 /** |
|
259 * Mark the javac_state file to be in need of saving and as a side effect, |
|
260 * it gets a new timestamp. |
|
261 */ |
|
262 private void needsSaving() { |
|
263 needsSaving = true; |
|
264 } |
|
265 |
|
266 /** |
|
267 * Save the javac_state file. |
|
268 */ |
|
269 public void save() throws IOException { |
|
270 if (!needsSaving) return; |
|
271 try (FileWriter out = new FileWriter(javacStateFilename)) { |
|
272 StringBuilder b = new StringBuilder(); |
|
273 long millisNow = System.currentTimeMillis(); |
|
274 Date d = new Date(millisNow); |
|
275 SimpleDateFormat df = |
|
276 new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS"); |
|
277 b.append("# javac_state ver 0.3 generated "+millisNow+" "+df.format(d)+"\n"); |
|
278 b.append("# This format might change at any time. Please do not depend on it.\n"); |
|
279 b.append("# M module\n"); |
|
280 b.append("# P package\n"); |
|
281 b.append("# S C source_tobe_compiled timestamp\n"); |
|
282 b.append("# S L link_only_source timestamp\n"); |
|
283 b.append("# G C generated_source timestamp\n"); |
|
284 b.append("# A artifact timestamp\n"); |
|
285 b.append("# D dependency\n"); |
|
286 b.append("# I pubapi\n"); |
|
287 b.append("# R arguments\n"); |
|
288 b.append("R ").append(theArgs).append("\n"); |
|
289 |
|
290 // Copy over the javac_state for the packages that did not need recompilation. |
|
291 now.copyPackagesExcept(prev, recompiledPackages, new HashSet<String>()); |
|
292 // Save the packages, ie package names, dependencies, pubapis and artifacts! |
|
293 // I.e. the lot. |
|
294 Module.saveModules(now.modules(), b); |
|
295 |
|
296 String s = b.toString(); |
|
297 out.write(s, 0, s.length()); |
|
298 } |
|
299 } |
|
300 |
|
301 /** |
|
302 * Load a javac_state file. |
|
303 */ |
|
304 public static JavacState load(String[] args, File binDir, File gensrcDir, File headerDir, |
|
305 boolean permitUnidentifiedArtifacts, PrintStream out, PrintStream err) { |
|
306 JavacState db = new JavacState(args, binDir, gensrcDir, headerDir, permitUnidentifiedArtifacts, false, out, err); |
|
307 Module lastModule = null; |
|
308 Package lastPackage = null; |
|
309 Source lastSource = null; |
|
310 boolean noFileFound = false; |
|
311 boolean foundCorrectVerNr = false; |
|
312 boolean newCommandLine = false; |
|
313 boolean syntaxError = false; |
|
314 |
|
315 try (BufferedReader in = new BufferedReader(new FileReader(db.javacStateFilename))) { |
|
316 for (;;) { |
|
317 String l = in.readLine(); |
|
318 if (l==null) break; |
|
319 if (l.length()>=3 && l.charAt(1) == ' ') { |
|
320 char c = l.charAt(0); |
|
321 if (c == 'M') { |
|
322 lastModule = db.prev.loadModule(l); |
|
323 } else |
|
324 if (c == 'P') { |
|
325 if (lastModule == null) { syntaxError = true; break; } |
|
326 lastPackage = db.prev.loadPackage(lastModule, l); |
|
327 } else |
|
328 if (c == 'D') { |
|
329 if (lastModule == null || lastPackage == null) { syntaxError = true; break; } |
|
330 lastPackage.loadDependency(l); |
|
331 } else |
|
332 if (c == 'I') { |
|
333 if (lastModule == null || lastPackage == null) { syntaxError = true; break; } |
|
334 lastPackage.loadPubapi(l); |
|
335 } else |
|
336 if (c == 'A') { |
|
337 if (lastModule == null || lastPackage == null) { syntaxError = true; break; } |
|
338 lastPackage.loadArtifact(l); |
|
339 } else |
|
340 if (c == 'S') { |
|
341 if (lastModule == null || lastPackage == null) { syntaxError = true; break; } |
|
342 lastSource = db.prev.loadSource(lastPackage, l, false); |
|
343 } else |
|
344 if (c == 'G') { |
|
345 if (lastModule == null || lastPackage == null) { syntaxError = true; break; } |
|
346 lastSource = db.prev.loadSource(lastPackage, l, true); |
|
347 } else |
|
348 if (c == 'R') { |
|
349 String ncmdl = "R "+db.theArgs; |
|
350 if (!l.equals(ncmdl)) { |
|
351 newCommandLine = true; |
|
352 } |
|
353 } else |
|
354 if (c == '#') { |
|
355 if (l.startsWith("# javac_state ver ")) { |
|
356 int sp = l.indexOf(" ", 18); |
|
357 if (sp != -1) { |
|
358 String ver = l.substring(18,sp); |
|
359 if (!ver.equals("0.3")) { |
|
360 break; |
|
361 } |
|
362 foundCorrectVerNr = true; |
|
363 } |
|
364 } |
|
365 } |
|
366 } |
|
367 } |
|
368 } catch (FileNotFoundException e) { |
|
369 // Silently create a new javac_state file. |
|
370 noFileFound = true; |
|
371 } catch (IOException e) { |
|
372 Log.info("Dropping old javac_state because of errors when reading it."); |
|
373 db = new JavacState(args, binDir, gensrcDir, headerDir, permitUnidentifiedArtifacts, true, out, err); |
|
374 foundCorrectVerNr = true; |
|
375 newCommandLine = false; |
|
376 syntaxError = false; |
|
377 } |
|
378 if (foundCorrectVerNr == false && !noFileFound) { |
|
379 Log.info("Dropping old javac_state since it is of an old version."); |
|
380 db = new JavacState(args, binDir, gensrcDir, headerDir, permitUnidentifiedArtifacts, true, out, err); |
|
381 } else |
|
382 if (newCommandLine == true && !noFileFound) { |
|
383 Log.info("Dropping old javac_state since a new command line is used!"); |
|
384 db = new JavacState(args, binDir, gensrcDir, headerDir, permitUnidentifiedArtifacts, true, out, err); |
|
385 } else |
|
386 if (syntaxError == true) { |
|
387 Log.info("Dropping old javac_state since it contains syntax errors."); |
|
388 db = new JavacState(args, binDir, gensrcDir, headerDir, permitUnidentifiedArtifacts, true, out, err); |
|
389 } |
|
390 db.prev.calculateDependents(); |
|
391 return db; |
|
392 } |
|
393 |
|
394 /** |
|
395 * Mark a java package as tainted, ie it needs recompilation. |
|
396 */ |
|
397 public void taintPackage(String name, String because) { |
|
398 if (!taintedPackages.contains(name)) { |
|
399 if (because != null) Log.debug("Tainting "+Util.justPackageName(name)+" because "+because); |
|
400 // It has not been tainted before. |
|
401 taintedPackages.add(name); |
|
402 needsSaving(); |
|
403 Package nowp = now.packages().get(name); |
|
404 if (nowp != null) { |
|
405 for (String d : nowp.dependents()) { |
|
406 taintPackage(d, because); |
|
407 } |
|
408 } |
|
409 } |
|
410 } |
|
411 |
|
412 /** |
|
413 * This packages need recompilation. |
|
414 */ |
|
415 public Set<String> taintedPackages() { |
|
416 return taintedPackages; |
|
417 } |
|
418 |
|
419 /** |
|
420 * Clean out the tainted package set, used after the first round of compiles, |
|
421 * prior to propagating dependencies. |
|
422 */ |
|
423 public void clearTaintedPackages() { |
|
424 taintedPackages = new HashSet<String>(); |
|
425 } |
|
426 |
|
427 /** |
|
428 * Go through all sources and check which have been removed, added or modified |
|
429 * and taint the corresponding packages. |
|
430 */ |
|
431 public void checkSourceStatus(boolean check_gensrc) { |
|
432 removedSources = calculateRemovedSources(); |
|
433 for (Source s : removedSources) { |
|
434 if (!s.isGenerated() || check_gensrc) { |
|
435 taintPackage(s.pkg().name(), "source "+s.name()+" was removed"); |
|
436 } |
|
437 } |
|
438 |
|
439 addedSources = calculateAddedSources(); |
|
440 for (Source s : addedSources) { |
|
441 String msg = null; |
|
442 if (isIncremental()) { |
|
443 // When building from scratch, there is no point |
|
444 // printing "was added" for every file since all files are added. |
|
445 // However for an incremental build it makes sense. |
|
446 msg = "source "+s.name()+" was added"; |
|
447 } |
|
448 if (!s.isGenerated() || check_gensrc) { |
|
449 taintPackage(s.pkg().name(), msg); |
|
450 } |
|
451 } |
|
452 |
|
453 modifiedSources = calculateModifiedSources(); |
|
454 for (Source s : modifiedSources) { |
|
455 if (!s.isGenerated() || check_gensrc) { |
|
456 taintPackage(s.pkg().name(), "source "+s.name()+" was modified"); |
|
457 } |
|
458 } |
|
459 } |
|
460 |
|
461 /** |
|
462 * Acquire the compile_java_packages suffix rule for .java files. |
|
463 */ |
|
464 public Map<String,Transformer> getJavaSuffixRule() { |
|
465 Map<String,Transformer> sr = new HashMap<String,Transformer>(); |
|
466 sr.put(".java", compileJavaPackages); |
|
467 return sr; |
|
468 } |
|
469 |
|
470 /** |
|
471 * Acquire the copying transform. |
|
472 */ |
|
473 public Transformer getCopier() { |
|
474 return copyFiles; |
|
475 } |
|
476 |
|
477 /** |
|
478 * If artifacts have gone missing, force a recompile of the packages |
|
479 * they belong to. |
|
480 */ |
|
481 public void taintPackagesThatMissArtifacts() { |
|
482 for (Package pkg : prev.packages().values()) { |
|
483 for (File f : pkg.artifacts().values()) { |
|
484 if (!f.exists()) { |
|
485 // Hmm, the artifact on disk does not exist! Someone has removed it.... |
|
486 // Lets rebuild the package. |
|
487 taintPackage(pkg.name(), ""+f+" is missing."); |
|
488 } |
|
489 } |
|
490 } |
|
491 } |
|
492 |
|
493 /** |
|
494 * Propagate recompilation through the dependency chains. |
|
495 * Avoid re-tainting packages that have already been compiled. |
|
496 */ |
|
497 public void taintPackagesDependingOnChangedPackages(Set<String> pkgs, Set<String> recentlyCompiled) { |
|
498 for (Package pkg : prev.packages().values()) { |
|
499 for (String dep : pkg.dependencies()) { |
|
500 if (pkgs.contains(dep) && !recentlyCompiled.contains(pkg.name())) { |
|
501 taintPackage(pkg.name(), " its depending on "+dep); |
|
502 } |
|
503 } |
|
504 } |
|
505 } |
|
506 |
|
507 /** |
|
508 * Scan all output dirs for artifacts and remove those files (artifacts?) |
|
509 * that are not recognized as such, in the javac_state file. |
|
510 */ |
|
511 public void removeUnidentifiedArtifacts() { |
|
512 Set<File> allKnownArtifacts = new HashSet<File>(); |
|
513 for (Package pkg : prev.packages().values()) { |
|
514 for (File f : pkg.artifacts().values()) { |
|
515 allKnownArtifacts.add(f); |
|
516 } |
|
517 } |
|
518 // Do not forget about javac_state.... |
|
519 allKnownArtifacts.add(javacState); |
|
520 |
|
521 for (File f : binArtifacts) { |
|
522 if (!allKnownArtifacts.contains(f)) { |
|
523 Log.debug("Removing "+f.getPath()+" since it is unknown to the javac_state."); |
|
524 f.delete(); |
|
525 } |
|
526 } |
|
527 for (File f : headerArtifacts) { |
|
528 if (!allKnownArtifacts.contains(f)) { |
|
529 Log.debug("Removing "+f.getPath()+" since it is unknown to the javac_state."); |
|
530 f.delete(); |
|
531 } |
|
532 } |
|
533 for (File f : gensrcArtifacts) { |
|
534 if (!allKnownArtifacts.contains(f)) { |
|
535 Log.debug("Removing "+f.getPath()+" since it is unknown to the javac_state."); |
|
536 f.delete(); |
|
537 } |
|
538 } |
|
539 } |
|
540 |
|
541 /** |
|
542 * Remove artifacts that are no longer produced when compiling! |
|
543 */ |
|
544 public void removeSuperfluousArtifacts(Set<String> recentlyCompiled) { |
|
545 // Nothing to do, if nothing was recompiled. |
|
546 if (recentlyCompiled.size() == 0) return; |
|
547 |
|
548 for (String pkg : now.packages().keySet()) { |
|
549 // If this package has not been recompiled, skip the check. |
|
550 if (!recentlyCompiled.contains(pkg)) continue; |
|
551 Collection<File> arts = now.artifacts().values(); |
|
552 for (File f : fetchPrevArtifacts(pkg).values()) { |
|
553 if (!arts.contains(f)) { |
|
554 Log.debug("Removing "+f.getPath()+" since it is now superfluous!"); |
|
555 if (f.exists()) f.delete(); |
|
556 } |
|
557 } |
|
558 } |
|
559 } |
|
560 |
|
561 /** |
|
562 * Return those files belonging to prev, but not now. |
|
563 */ |
|
564 private Set<Source> calculateRemovedSources() { |
|
565 Set<Source> removed = new HashSet<Source>(); |
|
566 for (String src : prev.sources().keySet()) { |
|
567 if (now.sources().get(src) == null) { |
|
568 removed.add(prev.sources().get(src)); |
|
569 } |
|
570 } |
|
571 return removed; |
|
572 } |
|
573 |
|
574 /** |
|
575 * Return those files belonging to now, but not prev. |
|
576 */ |
|
577 private Set<Source> calculateAddedSources() { |
|
578 Set<Source> added = new HashSet<Source>(); |
|
579 for (String src : now.sources().keySet()) { |
|
580 if (prev.sources().get(src) == null) { |
|
581 added.add(now.sources().get(src)); |
|
582 } |
|
583 } |
|
584 return added; |
|
585 } |
|
586 |
|
587 /** |
|
588 * Return those files where the timestamp is newer. |
|
589 * If a source file timestamp suddenly is older than what is known |
|
590 * about it in javac_state, then consider it modified, but print |
|
591 * a warning! |
|
592 */ |
|
593 private Set<Source> calculateModifiedSources() { |
|
594 Set<Source> modified = new HashSet<Source>(); |
|
595 for (String src : now.sources().keySet()) { |
|
596 Source n = now.sources().get(src); |
|
597 Source t = prev.sources().get(src); |
|
598 if (prev.sources().get(src) != null) { |
|
599 if (t != null) { |
|
600 if (n.lastModified() > t.lastModified()) { |
|
601 modified.add(n); |
|
602 } else if (n.lastModified() < t.lastModified()) { |
|
603 modified.add(n); |
|
604 Log.warn("The source file "+n.name()+" timestamp has moved backwards in time."); |
|
605 } |
|
606 } |
|
607 } |
|
608 } |
|
609 return modified; |
|
610 } |
|
611 |
|
612 /** |
|
613 * Recursively delete a directory and all its contents. |
|
614 */ |
|
615 private static void deleteContents(File dir) { |
|
616 if (dir != null && dir.exists()) { |
|
617 for (File f : dir.listFiles()) { |
|
618 if (f.isDirectory()) { |
|
619 deleteContents(f); |
|
620 } |
|
621 f.delete(); |
|
622 } |
|
623 } |
|
624 } |
|
625 |
|
626 /** |
|
627 * Run the copy translator only. |
|
628 */ |
|
629 public void performCopying(File binDir, Map<String,Transformer> suffixRules) { |
|
630 Map<String,Transformer> sr = new HashMap<String,Transformer>(); |
|
631 for (Map.Entry<String,Transformer> e : suffixRules.entrySet()) { |
|
632 if (e.getValue() == copyFiles) { |
|
633 sr.put(e.getKey(), e.getValue()); |
|
634 } |
|
635 } |
|
636 perform(binDir, sr); |
|
637 } |
|
638 |
|
639 /** |
|
640 * Run all the translators that translate into java source code. |
|
641 * I.e. all translators that are not copy nor compile_java_source. |
|
642 */ |
|
643 public void performTranslation(File gensrcDir, Map<String,Transformer> suffixRules) { |
|
644 Map<String,Transformer> sr = new HashMap<String,Transformer>(); |
|
645 for (Map.Entry<String,Transformer> e : suffixRules.entrySet()) { |
|
646 if (e.getValue() != copyFiles && |
|
647 e.getValue() != compileJavaPackages) { |
|
648 sr.put(e.getKey(), e.getValue()); |
|
649 } |
|
650 } |
|
651 perform(gensrcDir, sr); |
|
652 } |
|
653 |
|
654 /** |
|
655 * Compile all the java sources. Return true, if it needs to be called again! |
|
656 */ |
|
657 public boolean performJavaCompilations(File binDir, |
|
658 String serverSettings, |
|
659 String[] args, |
|
660 Set<String> recentlyCompiled, |
|
661 boolean[] rcValue) { |
|
662 Map<String,Transformer> suffixRules = new HashMap<String,Transformer>(); |
|
663 suffixRules.put(".java", compileJavaPackages); |
|
664 compileJavaPackages.setExtra(serverSettings); |
|
665 compileJavaPackages.setExtra(args); |
|
666 |
|
667 rcValue[0] = perform(binDir, suffixRules); |
|
668 recentlyCompiled.addAll(taintedPackages()); |
|
669 clearTaintedPackages(); |
|
670 boolean again = !packagesWithChangedPublicApis.isEmpty(); |
|
671 taintPackagesDependingOnChangedPackages(packagesWithChangedPublicApis, recentlyCompiled); |
|
672 packagesWithChangedPublicApis = new HashSet<String>(); |
|
673 return again && rcValue[0]; |
|
674 } |
|
675 |
|
676 /** |
|
677 * Store the source into the set of sources belonging to the given transform. |
|
678 */ |
|
679 private void addFileToTransform(Map<Transformer,Map<String,Set<URI>>> gs, Transformer t, Source s) { |
|
680 Map<String,Set<URI>> fs = gs.get(t); |
|
681 if (fs == null) { |
|
682 fs = new HashMap<String,Set<URI>>(); |
|
683 gs.put(t, fs); |
|
684 } |
|
685 Set<URI> ss = fs.get(s.pkg().name()); |
|
686 if (ss == null) { |
|
687 ss = new HashSet<URI>(); |
|
688 fs.put(s.pkg().name(), ss); |
|
689 } |
|
690 ss.add(s.file().toURI()); |
|
691 } |
|
692 |
|
693 /** |
|
694 * For all packages, find all sources belonging to the package, group the sources |
|
695 * based on their transformers and apply the transformers on each source code group. |
|
696 */ |
|
697 private boolean perform(File outputDir, Map<String,Transformer> suffixRules) |
|
698 { |
|
699 boolean rc = true; |
|
700 // Group sources based on transforms. A source file can only belong to a single transform. |
|
701 Map<Transformer,Map<String,Set<URI>>> groupedSources = new HashMap<Transformer,Map<String,Set<URI>>>(); |
|
702 for (Source src : now.sources().values()) { |
|
703 Transformer t = suffixRules.get(src.suffix()); |
|
704 if (t != null) { |
|
705 if (taintedPackages.contains(src.pkg().name()) && !src.isLinkedOnly()) { |
|
706 addFileToTransform(groupedSources, t, src); |
|
707 } |
|
708 } |
|
709 } |
|
710 // Go through the transforms and transform them. |
|
711 for (Map.Entry<Transformer,Map<String,Set<URI>>> e : groupedSources.entrySet()) { |
|
712 Transformer t = e.getKey(); |
|
713 Map<String,Set<URI>> srcs = e.getValue(); |
|
714 // These maps need to be synchronized since multiple threads will be writing results into them. |
|
715 Map<String,Set<URI>> packageArtifacts = Collections.synchronizedMap(new HashMap<String,Set<URI>>()); |
|
716 Map<String,Set<String>> packageDependencies = Collections.synchronizedMap(new HashMap<String,Set<String>>()); |
|
717 Map<String,String> packagePublicApis = Collections.synchronizedMap(new HashMap<String,String>()); |
|
718 |
|
719 boolean r = t.transform(srcs, |
|
720 visibleSrcs, |
|
721 visibleClasses, |
|
722 prev.dependents(), |
|
723 outputDir.toURI(), |
|
724 packageArtifacts, |
|
725 packageDependencies, |
|
726 packagePublicApis, |
|
727 0, |
|
728 isIncremental(), |
|
729 numCores, |
|
730 out, |
|
731 err); |
|
732 if (!r) rc = false; |
|
733 |
|
734 for (String p : srcs.keySet()) { |
|
735 recompiledPackages.add(p); |
|
736 } |
|
737 // The transform is done! Extract all the artifacts and store the info into the Package objects. |
|
738 for (Map.Entry<String,Set<URI>> a : packageArtifacts.entrySet()) { |
|
739 Module mnow = now.findModuleFromPackageName(a.getKey()); |
|
740 mnow.addArtifacts(a.getKey(), a.getValue()); |
|
741 } |
|
742 // Extract all the dependencies and store the info into the Package objects. |
|
743 for (Map.Entry<String,Set<String>> a : packageDependencies.entrySet()) { |
|
744 Set<String> deps = a.getValue(); |
|
745 Module mnow = now.findModuleFromPackageName(a.getKey()); |
|
746 mnow.setDependencies(a.getKey(), deps); |
|
747 } |
|
748 // Extract all the pubapis and store the info into the Package objects. |
|
749 for (Map.Entry<String,String> a : packagePublicApis.entrySet()) { |
|
750 Module mprev = prev.findModuleFromPackageName(a.getKey()); |
|
751 List<String> pubapi = Package.pubapiToList(a.getValue()); |
|
752 Module mnow = now.findModuleFromPackageName(a.getKey()); |
|
753 mnow.setPubapi(a.getKey(), pubapi); |
|
754 if (mprev.hasPubapiChanged(a.getKey(), pubapi)) { |
|
755 // Aha! The pubapi of this package has changed! |
|
756 // It can also be a new compile from scratch. |
|
757 if (mprev.lookupPackage(a.getKey()).existsInJavacState()) { |
|
758 // This is an incremental compile! The pubapi |
|
759 // did change. Trigger recompilation of dependents. |
|
760 packagesWithChangedPublicApis.add(a.getKey()); |
|
761 Log.info("The pubapi of "+Util.justPackageName(a.getKey())+" has changed!"); |
|
762 } |
|
763 } |
|
764 } |
|
765 } |
|
766 return rc; |
|
767 } |
|
768 |
|
769 /** |
|
770 * Utility method to recursively find all files below a directory. |
|
771 */ |
|
772 private static Set<File> findAllFiles(File dir) { |
|
773 Set<File> foundFiles = new HashSet<File>(); |
|
774 if (dir == null) { |
|
775 return foundFiles; |
|
776 } |
|
777 recurse(dir, foundFiles); |
|
778 return foundFiles; |
|
779 } |
|
780 |
|
781 private static void recurse(File dir, Set<File> foundFiles) { |
|
782 for (File f : dir.listFiles()) { |
|
783 if (f.isFile()) { |
|
784 foundFiles.add(f); |
|
785 } else if (f.isDirectory()) { |
|
786 recurse(f, foundFiles); |
|
787 } |
|
788 } |
|
789 } |
|
790 |
|
791 /** |
|
792 * Compare the calculate source list, with an explicit list, usually supplied from the makefile. |
|
793 * Used to detect bugs where the makefile and sjavac have different opinions on which files |
|
794 * should be compiled. |
|
795 */ |
|
796 public void compareWithMakefileList(File makefileSourceList) |
|
797 throws ProblemException |
|
798 { |
|
799 // If we are building on win32 using for example cygwin the paths in the makefile source list |
|
800 // might be /cygdrive/c/.... which does not match c:\.... |
|
801 // We need to adjust our calculated sources to be identical, if necessary. |
|
802 boolean mightNeedRewriting = File.pathSeparatorChar == ';'; |
|
803 |
|
804 if (makefileSourceList == null) return; |
|
805 |
|
806 Set<String> calculatedSources = new HashSet<String>(); |
|
807 Set<String> listedSources = new HashSet<String>(); |
|
808 |
|
809 // Create a set of filenames with full paths. |
|
810 for (Source s : now.sources().values()) { |
|
811 calculatedSources.add(s.file().getPath()); |
|
812 } |
|
813 // Read in the file and create another set of filenames with full paths. |
|
814 try { |
|
815 BufferedReader in = new BufferedReader(new FileReader(makefileSourceList)); |
|
816 for (;;) { |
|
817 String l = in.readLine(); |
|
818 if (l==null) break; |
|
819 l = l.trim(); |
|
820 if (mightNeedRewriting) { |
|
821 if (l.indexOf(":") == 1 && l.indexOf("\\") == 2) { |
|
822 // Everything a-ok, the format is already C:\foo\bar |
|
823 } else if (l.indexOf(":") == 1 && l.indexOf("/") == 2) { |
|
824 // The format is C:/foo/bar, rewrite into the above format. |
|
825 l = l.replaceAll("/","\\\\"); |
|
826 } else if (l.charAt(0) == '/' && l.indexOf("/",1) != -1) { |
|
827 // The format might be: /cygdrive/c/foo/bar, rewrite into the above format. |
|
828 // Do not hardcode the name cygdrive here. |
|
829 int slash = l.indexOf("/",1); |
|
830 l = l.replaceAll("/","\\\\"); |
|
831 l = ""+l.charAt(slash+1)+":"+l.substring(slash+2); |
|
832 } |
|
833 if (Character.isLowerCase(l.charAt(0))) { |
|
834 l = Character.toUpperCase(l.charAt(0))+l.substring(1); |
|
835 } |
|
836 } |
|
837 listedSources.add(l); |
|
838 } |
|
839 } catch (FileNotFoundException e) { |
|
840 throw new ProblemException("Could not open "+makefileSourceList.getPath()+" since it does not exist!"); |
|
841 } catch (IOException e) { |
|
842 throw new ProblemException("Could not read "+makefileSourceList.getPath()); |
|
843 } |
|
844 |
|
845 for (String s : listedSources) { |
|
846 if (!calculatedSources.contains(s)) { |
|
847 throw new ProblemException("The makefile listed source "+s+" was not calculated by the smart javac wrapper!"); |
|
848 } |
|
849 } |
|
850 |
|
851 for (String s : calculatedSources) { |
|
852 if (!listedSources.contains(s)) { |
|
853 throw new ProblemException("The smart javac wrapper calculated source "+s+" was not listed by the makefiles!"); |
|
854 } |
|
855 } |
|
856 } |
|
857 } |