JavacState.java revision 2674:e284f560acf6
1/* 2 * Copyright (c) 2012, 2014, 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 26package com.sun.tools.sjavac; 27 28import java.io.*; 29import java.net.URI; 30import java.nio.file.NoSuchFileException; 31import java.text.SimpleDateFormat; 32import java.util.*; 33import java.util.Collections; 34import java.util.Date; 35import java.util.HashMap; 36import java.util.HashSet; 37import java.util.List; 38import java.util.Map; 39import java.util.Set; 40 41import com.sun.tools.sjavac.options.Options; 42import com.sun.tools.sjavac.server.Sjavac; 43 44/** 45 * The javac state class maintains the previous (prev) and the current (now) 46 * build states and everything else that goes into the javac_state file. 47 * 48 * <p><b>This is NOT part of any supported API. 49 * If you write code that depends on this, you do so at your own risk. 50 * This code and its internal interfaces are subject to change or 51 * deletion without notice.</b> 52 */ 53public class JavacState { 54 // The arguments to the compile. If not identical, then it cannot 55 // be an incremental build! 56 String theArgs; 57 // The number of cores limits how many threads are used for heavy concurrent work. 58 int numCores; 59 60 // The bin_dir/javac_state 61 private File javacState; 62 63 // The previous build state is loaded from javac_state 64 private BuildState prev; 65 // The current build state is constructed during the build, 66 // then saved as the new javac_state. 67 private BuildState now; 68 69 // Something has changed in the javac_state. It needs to be saved! 70 private boolean needsSaving; 71 // If this is a new javac_state file, then do not print unnecessary messages. 72 private boolean newJavacState; 73 74 // These are packages where something has changed and the package 75 // needs to be recompiled. Actions that trigger recompilation: 76 // * source belonging to the package has changed 77 // * artifact belonging to the package is lost, or its timestamp has been changed. 78 // * an unknown artifact has appeared, we simply delete it, but we also trigger a recompilation. 79 // * a package that is tainted, taints all packages that depend on it. 80 private Set<String> taintedPackages; 81 // After a compile, the pubapis are compared with the pubapis stored in the javac state file. 82 // Any packages where the pubapi differ are added to this set. 83 // Later we use this set and the dependency information to taint dependent packages. 84 private Set<String> packagesWithChangedPublicApis; 85 // When a module-info.java file is changed, taint the module, 86 // then taint all modules that depend on that that module. 87 // A module dependency can occur directly through a require, or 88 // indirectly through a module that does a public export for the first tainted module. 89 // When all modules are tainted, then taint all packages belonging to these modules. 90 // Then rebuild. It is perhaps possible (and valuable?) to do a more finegrained examination of the 91 // change in module-info.java, but that will have to wait. 92 private Set<String> taintedModules; 93 // The set of all packages that has been recompiled. 94 // Copy over the javac_state for the packages that did not need recompilation, 95 // verbatim from the previous (prev) to the new (now) build state. 96 private Set<String> recompiledPackages; 97 98 // The output directories filled with tasty artifacts. 99 private File binDir, gensrcDir, headerDir, stateDir; 100 101 // The current status of the file system. 102 private Set<File> binArtifacts; 103 private Set<File> gensrcArtifacts; 104 private Set<File> headerArtifacts; 105 106 // The status of the sources. 107 Set<Source> removedSources = null; 108 Set<Source> addedSources = null; 109 Set<Source> modifiedSources = null; 110 111 // Visible sources for linking. These are the only 112 // ones that -sourcepath is allowed to see. 113 Set<URI> visibleSrcs; 114 115 // Visible classes for linking. These are the only 116 // ones that -classpath is allowed to see. 117 // It maps from a classpath root to the set of visible classes for that root. 118 // If the set is empty, then all classes are visible for that root. 119 // It can also map from a jar file to the set of visible classes for that jar file. 120 Map<URI,Set<String>> visibleClasses; 121 122 // Setup transform that always exist. 123 private CompileJavaPackages compileJavaPackages = new CompileJavaPackages(); 124 125 // Where to send stdout and stderr. 126 private PrintStream out, err; 127 128 // Command line options. 129 private Options options; 130 131 JavacState(Options op, boolean removeJavacState, PrintStream o, PrintStream e) { 132 options = op; 133 out = o; 134 err = e; 135 numCores = options.getNumCores(); 136 theArgs = options.getStateArgsString(); 137 binDir = Util.pathToFile(options.getDestDir()); 138 gensrcDir = Util.pathToFile(options.getGenSrcDir()); 139 headerDir = Util.pathToFile(options.getHeaderDir()); 140 stateDir = Util.pathToFile(options.getStateDir()); 141 javacState = new File(stateDir, "javac_state"); 142 if (removeJavacState && javacState.exists()) { 143 javacState.delete(); 144 } 145 newJavacState = false; 146 if (!javacState.exists()) { 147 newJavacState = true; 148 // If there is no javac_state then delete the contents of all the artifact dirs! 149 // We do not want to risk building a broken incremental build. 150 // BUT since the makefiles still copy things straight into the bin_dir et al, 151 // we avoid deleting files here, if the option --permit-unidentified-classes was supplied. 152 if (!options.areUnidentifiedArtifactsPermitted()) { 153 deleteContents(binDir); 154 deleteContents(gensrcDir); 155 deleteContents(headerDir); 156 } 157 needsSaving = true; 158 } 159 prev = new BuildState(); 160 now = new BuildState(); 161 taintedPackages = new HashSet<>(); 162 recompiledPackages = new HashSet<>(); 163 packagesWithChangedPublicApis = new HashSet<>(); 164 } 165 166 public BuildState prev() { return prev; } 167 public BuildState now() { return now; } 168 169 /** 170 * Remove args not affecting the state. 171 */ 172 static String[] removeArgsNotAffectingState(String[] args) { 173 String[] out = new String[args.length]; 174 int j = 0; 175 for (int i = 0; i<args.length; ++i) { 176 if (args[i].equals("-j")) { 177 // Just skip it and skip following value 178 i++; 179 } else if (args[i].startsWith("--server:")) { 180 // Just skip it. 181 } else if (args[i].startsWith("--log=")) { 182 // Just skip it. 183 } else if (args[i].equals("--compare-found-sources")) { 184 // Just skip it and skip verify file name 185 i++; 186 } else { 187 // Copy argument. 188 out[j] = args[i]; 189 j++; 190 } 191 } 192 String[] ret = new String[j]; 193 System.arraycopy(out, 0, ret, 0, j); 194 return ret; 195 } 196 197 /** 198 * Specify which sources are visible to the compiler through -sourcepath. 199 */ 200 public void setVisibleSources(Map<String,Source> vs) { 201 visibleSrcs = new HashSet<>(); 202 for (String s : vs.keySet()) { 203 Source src = vs.get(s); 204 visibleSrcs.add(src.file().toURI()); 205 } 206 } 207 208 /** 209 * Specify which classes are visible to the compiler through -classpath. 210 */ 211 public void setVisibleClasses(Map<String,Source> vs) { 212 visibleSrcs = new HashSet<>(); 213 for (String s : vs.keySet()) { 214 Source src = vs.get(s); 215 visibleSrcs.add(src.file().toURI()); 216 } 217 } 218 /** 219 * Returns true if this is an incremental build. 220 */ 221 public boolean isIncremental() { 222 return !prev.sources().isEmpty(); 223 } 224 225 /** 226 * Find all artifacts that exists on disk. 227 */ 228 public void findAllArtifacts() { 229 binArtifacts = findAllFiles(binDir); 230 gensrcArtifacts = findAllFiles(gensrcDir); 231 headerArtifacts = findAllFiles(headerDir); 232 } 233 234 /** 235 * Lookup the artifacts generated for this package in the previous build. 236 */ 237 private Map<String,File> fetchPrevArtifacts(String pkg) { 238 Package p = prev.packages().get(pkg); 239 if (p != null) { 240 return p.artifacts(); 241 } 242 return new HashMap<>(); 243 } 244 245 /** 246 * Delete all prev artifacts in the currently tainted packages. 247 */ 248 public void deleteClassArtifactsInTaintedPackages() { 249 for (String pkg : taintedPackages) { 250 Map<String,File> arts = fetchPrevArtifacts(pkg); 251 for (File f : arts.values()) { 252 if (f.exists() && f.getName().endsWith(".class")) { 253 f.delete(); 254 } 255 } 256 } 257 } 258 259 /** 260 * Mark the javac_state file to be in need of saving and as a side effect, 261 * it gets a new timestamp. 262 */ 263 private void needsSaving() { 264 needsSaving = true; 265 } 266 267 /** 268 * Save the javac_state file. 269 */ 270 public void save() throws IOException { 271 if (!needsSaving) return; 272 try (FileWriter out = new FileWriter(javacState)) { 273 StringBuilder b = new StringBuilder(); 274 long millisNow = System.currentTimeMillis(); 275 Date d = new Date(millisNow); 276 SimpleDateFormat df = 277 new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS"); 278 b.append("# javac_state ver 0.3 generated "+millisNow+" "+df.format(d)+"\n"); 279 b.append("# This format might change at any time. Please do not depend on it.\n"); 280 b.append("# M module\n"); 281 b.append("# P package\n"); 282 b.append("# S C source_tobe_compiled timestamp\n"); 283 b.append("# S L link_only_source timestamp\n"); 284 b.append("# G C generated_source timestamp\n"); 285 b.append("# A artifact timestamp\n"); 286 b.append("# D dependency\n"); 287 b.append("# I pubapi\n"); 288 b.append("# R arguments\n"); 289 b.append("R ").append(theArgs).append("\n"); 290 291 // Copy over the javac_state for the packages that did not need recompilation. 292 now.copyPackagesExcept(prev, recompiledPackages, new HashSet<String>()); 293 // Save the packages, ie package names, dependencies, pubapis and artifacts! 294 // I.e. the lot. 295 Module.saveModules(now.modules(), b); 296 297 String s = b.toString(); 298 out.write(s, 0, s.length()); 299 } 300 } 301 302 /** 303 * Load a javac_state file. 304 */ 305 public static JavacState load(Options options, PrintStream out, PrintStream err) { 306 JavacState db = new JavacState(options, 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.javacState))) { 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 | NoSuchFileException 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(options, 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(options, 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(options, 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(options, 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<>(); 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<>(); 466 sr.put(".java", compileJavaPackages); 467 return sr; 468 } 469 470 471 /** 472 * If artifacts have gone missing, force a recompile of the packages 473 * they belong to. 474 */ 475 public void taintPackagesThatMissArtifacts() { 476 for (Package pkg : prev.packages().values()) { 477 for (File f : pkg.artifacts().values()) { 478 if (!f.exists()) { 479 // Hmm, the artifact on disk does not exist! Someone has removed it.... 480 // Lets rebuild the package. 481 taintPackage(pkg.name(), ""+f+" is missing."); 482 } 483 } 484 } 485 } 486 487 /** 488 * Propagate recompilation through the dependency chains. 489 * Avoid re-tainting packages that have already been compiled. 490 */ 491 public void taintPackagesDependingOnChangedPackages(Set<String> pkgs, Set<String> recentlyCompiled) { 492 for (Package pkg : prev.packages().values()) { 493 for (String dep : pkg.dependencies()) { 494 if (pkgs.contains(dep) && !recentlyCompiled.contains(pkg.name())) { 495 taintPackage(pkg.name(), " its depending on "+dep); 496 } 497 } 498 } 499 } 500 501 /** 502 * Scan all output dirs for artifacts and remove those files (artifacts?) 503 * that are not recognized as such, in the javac_state file. 504 */ 505 public void removeUnidentifiedArtifacts() { 506 Set<File> allKnownArtifacts = new HashSet<>(); 507 for (Package pkg : prev.packages().values()) { 508 for (File f : pkg.artifacts().values()) { 509 allKnownArtifacts.add(f); 510 } 511 } 512 // Do not forget about javac_state.... 513 allKnownArtifacts.add(javacState); 514 515 for (File f : binArtifacts) { 516 if (!allKnownArtifacts.contains(f) && 517 !options.isUnidentifiedArtifactPermitted(f.getAbsolutePath())) { 518 Log.debug("Removing "+f.getPath()+" since it is unknown to the javac_state."); 519 f.delete(); 520 } 521 } 522 for (File f : headerArtifacts) { 523 if (!allKnownArtifacts.contains(f)) { 524 Log.debug("Removing "+f.getPath()+" since it is unknown to the javac_state."); 525 f.delete(); 526 } 527 } 528 for (File f : gensrcArtifacts) { 529 if (!allKnownArtifacts.contains(f)) { 530 Log.debug("Removing "+f.getPath()+" since it is unknown to the javac_state."); 531 f.delete(); 532 } 533 } 534 } 535 536 /** 537 * Remove artifacts that are no longer produced when compiling! 538 */ 539 public void removeSuperfluousArtifacts(Set<String> recentlyCompiled) { 540 // Nothing to do, if nothing was recompiled. 541 if (recentlyCompiled.size() == 0) return; 542 543 for (String pkg : now.packages().keySet()) { 544 // If this package has not been recompiled, skip the check. 545 if (!recentlyCompiled.contains(pkg)) continue; 546 Collection<File> arts = now.artifacts().values(); 547 for (File f : fetchPrevArtifacts(pkg).values()) { 548 if (!arts.contains(f)) { 549 Log.debug("Removing "+f.getPath()+" since it is now superfluous!"); 550 if (f.exists()) f.delete(); 551 } 552 } 553 } 554 } 555 556 /** 557 * Return those files belonging to prev, but not now. 558 */ 559 private Set<Source> calculateRemovedSources() { 560 Set<Source> removed = new HashSet<>(); 561 for (String src : prev.sources().keySet()) { 562 if (now.sources().get(src) == null) { 563 removed.add(prev.sources().get(src)); 564 } 565 } 566 return removed; 567 } 568 569 /** 570 * Return those files belonging to now, but not prev. 571 */ 572 private Set<Source> calculateAddedSources() { 573 Set<Source> added = new HashSet<>(); 574 for (String src : now.sources().keySet()) { 575 if (prev.sources().get(src) == null) { 576 added.add(now.sources().get(src)); 577 } 578 } 579 return added; 580 } 581 582 /** 583 * Return those files where the timestamp is newer. 584 * If a source file timestamp suddenly is older than what is known 585 * about it in javac_state, then consider it modified, but print 586 * a warning! 587 */ 588 private Set<Source> calculateModifiedSources() { 589 Set<Source> modified = new HashSet<>(); 590 for (String src : now.sources().keySet()) { 591 Source n = now.sources().get(src); 592 Source t = prev.sources().get(src); 593 if (prev.sources().get(src) != null) { 594 if (t != null) { 595 if (n.lastModified() > t.lastModified()) { 596 modified.add(n); 597 } else if (n.lastModified() < t.lastModified()) { 598 modified.add(n); 599 Log.warn("The source file "+n.name()+" timestamp has moved backwards in time."); 600 } 601 } 602 } 603 } 604 return modified; 605 } 606 607 /** 608 * Recursively delete a directory and all its contents. 609 */ 610 private void deleteContents(File dir) { 611 if (dir != null && dir.exists()) { 612 for (File f : dir.listFiles()) { 613 if (f.isDirectory()) { 614 deleteContents(f); 615 } 616 if (!options.isUnidentifiedArtifactPermitted(f.getAbsolutePath())) { 617 Log.debug("Removing "+f.getAbsolutePath()); 618 f.delete(); 619 } 620 } 621 } 622 } 623 624 /** 625 * Run the copy translator only. 626 */ 627 public void performCopying(File binDir, Map<String,Transformer> suffixRules) { 628 Map<String,Transformer> sr = new HashMap<>(); 629 for (Map.Entry<String,Transformer> e : suffixRules.entrySet()) { 630 if (e.getValue().getClass().equals(CopyFile.class)) { 631 sr.put(e.getKey(), e.getValue()); 632 } 633 } 634 perform(null, binDir, sr); 635 } 636 637 /** 638 * Run all the translators that translate into java source code. 639 * I.e. all translators that are not copy nor compile_java_source. 640 */ 641 public void performTranslation(File gensrcDir, Map<String,Transformer> suffixRules) { 642 Map<String,Transformer> sr = new HashMap<>(); 643 for (Map.Entry<String,Transformer> e : suffixRules.entrySet()) { 644 Class<?> trClass = e.getValue().getClass(); 645 if (trClass == CompileJavaPackages.class || trClass == CopyFile.class) 646 continue; 647 648 sr.put(e.getKey(), e.getValue()); 649 } 650 perform(null, gensrcDir, sr); 651 } 652 653 /** 654 * Compile all the java sources. Return true, if it needs to be called again! 655 */ 656 public boolean performJavaCompilations(Sjavac sjavac, 657 Options args, 658 Set<String> recentlyCompiled, 659 boolean[] rcValue) { 660 Map<String,Transformer> suffixRules = new HashMap<>(); 661 suffixRules.put(".java", compileJavaPackages); 662 compileJavaPackages.setExtra(args); 663 664 rcValue[0] = perform(sjavac, binDir, suffixRules); 665 recentlyCompiled.addAll(taintedPackages()); 666 clearTaintedPackages(); 667 boolean again = !packagesWithChangedPublicApis.isEmpty(); 668 taintPackagesDependingOnChangedPackages(packagesWithChangedPublicApis, recentlyCompiled); 669 packagesWithChangedPublicApis = new HashSet<>(); 670 return again && rcValue[0]; 671 } 672 673 /** 674 * Store the source into the set of sources belonging to the given transform. 675 */ 676 private void addFileToTransform(Map<Transformer,Map<String,Set<URI>>> gs, Transformer t, Source s) { 677 Map<String,Set<URI>> fs = gs.get(t); 678 if (fs == null) { 679 fs = new HashMap<>(); 680 gs.put(t, fs); 681 } 682 Set<URI> ss = fs.get(s.pkg().name()); 683 if (ss == null) { 684 ss = new HashSet<>(); 685 fs.put(s.pkg().name(), ss); 686 } 687 ss.add(s.file().toURI()); 688 } 689 690 /** 691 * For all packages, find all sources belonging to the package, group the sources 692 * based on their transformers and apply the transformers on each source code group. 693 */ 694 private boolean perform(Sjavac sjavac, 695 File outputDir, 696 Map<String,Transformer> suffixRules) { 697 boolean rc = true; 698 // Group sources based on transforms. A source file can only belong to a single transform. 699 Map<Transformer,Map<String,Set<URI>>> groupedSources = new HashMap<>(); 700 for (Source src : now.sources().values()) { 701 Transformer t = suffixRules.get(src.suffix()); 702 if (t != null) { 703 if (taintedPackages.contains(src.pkg().name()) && !src.isLinkedOnly()) { 704 addFileToTransform(groupedSources, t, src); 705 } 706 } 707 } 708 // Go through the transforms and transform them. 709 for (Map.Entry<Transformer,Map<String,Set<URI>>> e : groupedSources.entrySet()) { 710 Transformer t = e.getKey(); 711 Map<String,Set<URI>> srcs = e.getValue(); 712 // These maps need to be synchronized since multiple threads will be writing results into them. 713 Map<String,Set<URI>> packageArtifacts = 714 Collections.synchronizedMap(new HashMap<String,Set<URI>>()); 715 Map<String,Set<String>> packageDependencies = 716 Collections.synchronizedMap(new HashMap<String,Set<String>>()); 717 Map<String,String> packagePublicApis = 718 Collections.synchronizedMap(new HashMap<String, String>()); 719 720 boolean r = t.transform(sjavac, 721 srcs, 722 visibleSrcs, 723 visibleClasses, 724 prev.dependents(), 725 outputDir.toURI(), 726 packageArtifacts, 727 packageDependencies, 728 packagePublicApis, 729 0, 730 isIncremental(), 731 numCores, 732 out, 733 err); 734 if (!r) rc = false; 735 736 for (String p : srcs.keySet()) { 737 recompiledPackages.add(p); 738 } 739 // The transform is done! Extract all the artifacts and store the info into the Package objects. 740 for (Map.Entry<String,Set<URI>> a : packageArtifacts.entrySet()) { 741 Module mnow = now.findModuleFromPackageName(a.getKey()); 742 mnow.addArtifacts(a.getKey(), a.getValue()); 743 } 744 // Extract all the dependencies and store the info into the Package objects. 745 for (Map.Entry<String,Set<String>> a : packageDependencies.entrySet()) { 746 Set<String> deps = a.getValue(); 747 Module mnow = now.findModuleFromPackageName(a.getKey()); 748 mnow.setDependencies(a.getKey(), deps); 749 } 750 // Extract all the pubapis and store the info into the Package objects. 751 for (Map.Entry<String,String> a : packagePublicApis.entrySet()) { 752 Module mprev = prev.findModuleFromPackageName(a.getKey()); 753 List<String> pubapi = Package.pubapiToList(a.getValue()); 754 Module mnow = now.findModuleFromPackageName(a.getKey()); 755 mnow.setPubapi(a.getKey(), pubapi); 756 if (mprev.hasPubapiChanged(a.getKey(), pubapi)) { 757 // Aha! The pubapi of this package has changed! 758 // It can also be a new compile from scratch. 759 if (mprev.lookupPackage(a.getKey()).existsInJavacState()) { 760 // This is an incremental compile! The pubapi 761 // did change. Trigger recompilation of dependents. 762 packagesWithChangedPublicApis.add(a.getKey()); 763 Log.info("The pubapi of "+Util.justPackageName(a.getKey())+" has changed!"); 764 } 765 } 766 } 767 } 768 return rc; 769 } 770 771 /** 772 * Utility method to recursively find all files below a directory. 773 */ 774 private static Set<File> findAllFiles(File dir) { 775 Set<File> foundFiles = new HashSet<>(); 776 if (dir == null) { 777 return foundFiles; 778 } 779 recurse(dir, foundFiles); 780 return foundFiles; 781 } 782 783 private static void recurse(File dir, Set<File> foundFiles) { 784 for (File f : dir.listFiles()) { 785 if (f.isFile()) { 786 foundFiles.add(f); 787 } else if (f.isDirectory()) { 788 recurse(f, foundFiles); 789 } 790 } 791 } 792 793 /** 794 * Compare the calculate source list, with an explicit list, usually supplied from the makefile. 795 * Used to detect bugs where the makefile and sjavac have different opinions on which files 796 * should be compiled. 797 */ 798 public void compareWithMakefileList(File makefileSourceList) throws ProblemException { 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<>(); 807 Set<String> listedSources = new HashSet<>(); 808 809 // Create a set of filenames with full paths. 810 for (Source s : now.sources().values()) { 811 // Don't include link only sources when comparing sources to compile 812 if (!s.isLinkedOnly()) { 813 String path = s.file().getPath(); 814 if (mightNeedRewriting) 815 path = Util.normalizeDriveLetter(path); 816 calculatedSources.add(path); 817 } 818 } 819 // Read in the file and create another set of filenames with full paths. 820 try { 821 BufferedReader in = new BufferedReader(new FileReader(makefileSourceList)); 822 for (;;) { 823 String l = in.readLine(); 824 if (l==null) break; 825 l = l.trim(); 826 if (mightNeedRewriting) { 827 if (l.indexOf(":") == 1 && l.indexOf("\\") == 2) { 828 // Everything a-ok, the format is already C:\foo\bar 829 } else if (l.indexOf(":") == 1 && l.indexOf("/") == 2) { 830 // The format is C:/foo/bar, rewrite into the above format. 831 l = l.replaceAll("/","\\\\"); 832 } else if (l.charAt(0) == '/' && l.indexOf("/",1) != -1) { 833 // The format might be: /cygdrive/c/foo/bar, rewrite into the above format. 834 // Do not hardcode the name cygdrive here. 835 int slash = l.indexOf("/",1); 836 l = l.replaceAll("/","\\\\"); 837 l = ""+l.charAt(slash+1)+":"+l.substring(slash+2); 838 } 839 if (Character.isLowerCase(l.charAt(0))) { 840 l = Character.toUpperCase(l.charAt(0))+l.substring(1); 841 } 842 } 843 listedSources.add(l); 844 } 845 } catch (FileNotFoundException | NoSuchFileException e) { 846 throw new ProblemException("Could not open "+makefileSourceList.getPath()+" since it does not exist!"); 847 } catch (IOException e) { 848 throw new ProblemException("Could not read "+makefileSourceList.getPath()); 849 } 850 851 for (String s : listedSources) { 852 if (!calculatedSources.contains(s)) { 853 throw new ProblemException("The makefile listed source "+s+" was not calculated by the smart javac wrapper!"); 854 } 855 } 856 857 for (String s : calculatedSources) { 858 if (!listedSources.contains(s)) { 859 throw new ProblemException("The smart javac wrapper calculated source "+s+" was not listed by the makefiles!"); 860 } 861 } 862 } 863} 864