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 26 package com.sun.tools.sjavac; 27 28 import java.io.BufferedReader; 29 import java.io.File; 30 import java.io.FileNotFoundException; 31 import java.io.FileReader; 32 import java.io.FileWriter; 33 import java.io.IOException; 34 import java.io.PrintStream; 35 import java.net.URI; 36 import java.nio.file.NoSuchFileException; 37 import java.text.SimpleDateFormat; 38 import java.util.Collection; 39 import java.util.Collections; 40 import java.util.Date; 41 import java.util.HashMap; 42 import java.util.HashSet; 43 import java.util.Map; 44 import java.util.Set; 45 import java.util.stream.Collectors; 46 47 import com.sun.tools.sjavac.options.Options; 48 import com.sun.tools.sjavac.pubapi.PubApi; 49 import com.sun.tools.sjavac.server.Sjavac; 50 51 /** 52 * The javac state class maintains the previous (prev) and the current (now) 53 * build states and everything else that goes into the javac_state file. 54 * 55 * <p><b>This is NOT part of any supported API. 56 * If you write code that depends on this, you do so at your own risk. 57 * This code and its internal interfaces are subject to change or 58 * deletion without notice.</b> 59 */ 60 public class JavacState { 61 // The arguments to the compile. If not identical, then it cannot 62 // be an incremental build! 63 String theArgs; 64 // The number of cores limits how many threads are used for heavy concurrent work. 65 int numCores; 66 67 // The bin_dir/javac_state 68 private File javacState; 69 70 // The previous build state is loaded from javac_state 71 private BuildState prev; 72 // The current build state is constructed during the build, 73 // then saved as the new javac_state. 74 private BuildState now; 75 76 // Something has changed in the javac_state. It needs to be saved! 77 private boolean needsSaving; 78 // If this is a new javac_state file, then do not print unnecessary messages. 79 private boolean newJavacState; 80 81 // These are packages where something has changed and the package 82 // needs to be recompiled. Actions that trigger recompilation: 83 // * source belonging to the package has changed 84 // * artifact belonging to the package is lost, or its timestamp has been changed. 85 // * an unknown artifact has appeared, we simply delete it, but we also trigger a recompilation. 86 // * a package that is tainted, taints all packages that depend on it. 87 private Set<String> taintedPackages; 88 // After a compile, the pubapis are compared with the pubapis stored in the javac state file. 89 // Any packages where the pubapi differ are added to this set. 90 // Later we use this set and the dependency information to taint dependent packages. 91 private Set<String> packagesWithChangedPublicApis; 92 // When a module-info.java file is changed, taint the module, 93 // then taint all modules that depend on that that module. 94 // A module dependency can occur directly through a require, or 95 // indirectly through a module that does a public export for the first tainted module. 96 // When all modules are tainted, then taint all packages belonging to these modules. 97 // Then rebuild. It is perhaps possible (and valuable?) to do a more finegrained examination of the 98 // change in module-info.java, but that will have to wait. 99 private Set<String> taintedModules; 100 // The set of all packages that has been recompiled. 101 // Copy over the javac_state for the packages that did not need recompilation, 102 // verbatim from the previous (prev) to the new (now) build state. 103 private Set<String> recompiledPackages; 104 105 // The output directories filled with tasty artifacts. 106 private File binDir, gensrcDir, headerDir, stateDir; 107 108 // The current status of the file system. 109 private Set<File> binArtifacts; 110 private Set<File> gensrcArtifacts; 111 private Set<File> headerArtifacts; 112 113 // The status of the sources. 114 Set<Source> removedSources = null; 115 Set<Source> addedSources = null; 116 Set<Source> modifiedSources = null; 117 118 // Visible sources for linking. These are the only 119 // ones that -sourcepath is allowed to see. 120 Set<URI> visibleSrcs; 121 122 // Visible classes for linking. These are the only 123 // ones that -classpath is allowed to see. 124 // It maps from a classpath root to the set of visible classes for that root. 125 // If the set is empty, then all classes are visible for that root. 126 // It can also map from a jar file to the set of visible classes for that jar file. 127 Map<URI,Set<String>> visibleClasses; 128 129 // Setup transform that always exist. 130 private CompileJavaPackages compileJavaPackages = new CompileJavaPackages(); 131 132 // Where to send stdout and stderr. 133 private PrintStream out, err; 134 135 // Command line options. 136 private Options options; 137 138 JavacState(Options op, boolean removeJavacState, PrintStream o, PrintStream e) { 139 options = op; 140 out = o; 141 err = e; 142 numCores = options.getNumCores(); 143 theArgs = options.getStateArgsString(); 144 binDir = Util.pathToFile(options.getDestDir()); 145 gensrcDir = Util.pathToFile(options.getGenSrcDir()); 146 headerDir = Util.pathToFile(options.getHeaderDir()); 147 stateDir = Util.pathToFile(options.getStateDir()); 148 javacState = new File(stateDir, "javac_state"); 149 if (removeJavacState && javacState.exists()) { 150 javacState.delete(); 151 } 152 newJavacState = false; 153 if (!javacState.exists()) { 154 newJavacState = true; 155 // If there is no javac_state then delete the contents of all the artifact dirs! 156 // We do not want to risk building a broken incremental build. 157 // BUT since the makefiles still copy things straight into the bin_dir et al, 158 // we avoid deleting files here, if the option --permit-unidentified-classes was supplied. 159 if (!options.areUnidentifiedArtifactsPermitted()) { 160 deleteContents(binDir); 161 deleteContents(gensrcDir); 162 deleteContents(headerDir); 163 } 164 needsSaving = true; 165 } 166 prev = new BuildState(); 167 now = new BuildState(); 168 taintedPackages = new HashSet<>(); 169 recompiledPackages = new HashSet<>(); 170 packagesWithChangedPublicApis = new HashSet<>(); 171 } 172 173 public BuildState prev() { return prev; } 174 public BuildState now() { return now; } 175 176 /** 177 * Remove args not affecting the state. 178 */ 179 static String[] removeArgsNotAffectingState(String[] args) { 180 String[] out = new String[args.length]; 181 int j = 0; 182 for (int i = 0; i<args.length; ++i) { 183 if (args[i].equals("-j")) { 184 // Just skip it and skip following value 185 i++; 186 } else if (args[i].startsWith("--server:")) { 187 // Just skip it. 188 } else if (args[i].startsWith("--log=")) { 189 // Just skip it. 190 } else if (args[i].equals("--compare-found-sources")) { 191 // Just skip it and skip verify file name 192 i++; 193 } else { 194 // Copy argument. 195 out[j] = args[i]; 196 j++; 197 } 198 } 199 String[] ret = new String[j]; 200 System.arraycopy(out, 0, ret, 0, j); 201 return ret; 202 } 203 204 /** 205 * Specify which sources are visible to the compiler through -sourcepath. 206 */ 207 public void setVisibleSources(Map<String,Source> vs) { 208 visibleSrcs = new HashSet<>(); 209 for (String s : vs.keySet()) { 210 Source src = vs.get(s); 211 visibleSrcs.add(src.file().toURI()); 212 } 213 } 214 215 /** 216 * Specify which classes are visible to the compiler through -classpath. 217 */ 218 public void setVisibleClasses(Map<String,Source> vs) { 219 visibleSrcs = new HashSet<>(); 220 for (String s : vs.keySet()) { 221 Source src = vs.get(s); 222 visibleSrcs.add(src.file().toURI()); 223 } 224 } 225 /** 226 * Returns true if this is an incremental build. 227 */ 228 public boolean isIncremental() { 229 return !prev.sources().isEmpty(); 230 } 231 232 /** 233 * Find all artifacts that exists on disk. 234 */ 235 public void findAllArtifacts() { 236 binArtifacts = findAllFiles(binDir); 237 gensrcArtifacts = findAllFiles(gensrcDir); 238 headerArtifacts = findAllFiles(headerDir); 239 } 240 241 /** 242 * Lookup the artifacts generated for this package in the previous build. 243 */ 244 private Map<String,File> fetchPrevArtifacts(String pkg) { 245 Package p = prev.packages().get(pkg); 246 if (p != null) { 247 return p.artifacts(); 248 } 249 return new HashMap<>(); 250 } 251 252 /** 253 * Delete all prev artifacts in the currently tainted packages. 254 */ 255 public void deleteClassArtifactsInTaintedPackages() { 256 for (String pkg : taintedPackages) { 257 Map<String,File> arts = fetchPrevArtifacts(pkg); 258 for (File f : arts.values()) { 259 if (f.exists() && f.getName().endsWith(".class")) { 260 f.delete(); 261 } 262 } 263 } 264 } 265 266 /** 267 * Mark the javac_state file to be in need of saving and as a side effect, 268 * it gets a new timestamp. 269 */ 270 private void needsSaving() { 271 needsSaving = true; 272 } 273 274 /** 275 * Save the javac_state file. 276 */ 277 public void save() throws IOException { 278 if (!needsSaving) 279 return; 280 try (FileWriter out = new FileWriter(javacState)) { 281 StringBuilder b = new StringBuilder(); 282 long millisNow = System.currentTimeMillis(); 283 Date d = new Date(millisNow); 284 SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS"); 285 b.append("# javac_state ver 0.3 generated "+millisNow+" "+df.format(d)+"\n"); 286 b.append("# This format might change at any time. Please do not depend on it.\n"); 287 b.append("# R arguments\n"); 288 b.append("# M module\n"); 289 b.append("# P package\n"); 290 b.append("# S C source_tobe_compiled timestamp\n"); 291 b.append("# S L link_only_source timestamp\n"); 292 b.append("# G C generated_source timestamp\n"); 293 b.append("# A artifact timestamp\n"); 294 b.append("# D S dependant -> source dependency\n"); 295 b.append("# D C dependant -> classpath dependency\n"); 296 b.append("# I pubapi\n"); 297 b.append("R ").append(theArgs).append("\n"); 298 299 // Copy over the javac_state for the packages that did not need recompilation. 300 now.copyPackagesExcept(prev, recompiledPackages, new HashSet<String>()); 301 // Save the packages, ie package names, dependencies, pubapis and artifacts! 302 // I.e. the lot. 303 Module.saveModules(now.modules(), b); 304 305 String s = b.toString(); 306 out.write(s, 0, s.length()); 307 } 308 } 309 310 /** 311 * Load a javac_state file. 312 */ 313 public static JavacState load(Options options, PrintStream out, PrintStream err) { 314 JavacState db = new JavacState(options, false, out, err); 315 Module lastModule = null; 316 Package lastPackage = null; 317 Source lastSource = null; 318 boolean noFileFound = false; 319 boolean foundCorrectVerNr = false; 320 boolean newCommandLine = false; 321 boolean syntaxError = false; 322 323 Log.debug("Loading javac state file: " + db.javacState); 324 325 try (BufferedReader in = new BufferedReader(new FileReader(db.javacState))) { 326 for (;;) { 327 String l = in.readLine(); 328 if (l==null) break; 329 if (l.length()>=3 && l.charAt(1) == ' ') { 330 char c = l.charAt(0); 331 if (c == 'M') { 332 lastModule = db.prev.loadModule(l); 333 } else 334 if (c == 'P') { 335 if (lastModule == null) { syntaxError = true; break; } 336 lastPackage = db.prev.loadPackage(lastModule, l); 337 } else 338 if (c == 'D') { 339 if (lastModule == null || lastPackage == null) { syntaxError = true; break; } 340 char depType = l.charAt(2); 341 if (depType != 'S' && depType != 'C') 342 throw new RuntimeException("Bad dependency string: " + l); 343 lastPackage.parseAndAddDependency(l.substring(4), depType == 'C'); 344 } else 345 if (c == 'I') { 346 if (lastModule == null || lastPackage == null) { syntaxError = true; break; } 347 lastPackage.getPubApi().appendItem(l.substring(2)); // Strip "I " 348 } else 349 if (c == 'A') { 350 if (lastModule == null || lastPackage == null) { syntaxError = true; break; } 351 lastPackage.loadArtifact(l); 352 } else 353 if (c == 'S') { 354 if (lastModule == null || lastPackage == null) { syntaxError = true; break; } 355 lastSource = db.prev.loadSource(lastPackage, l, false); 356 } else 357 if (c == 'G') { 358 if (lastModule == null || lastPackage == null) { syntaxError = true; break; } 359 lastSource = db.prev.loadSource(lastPackage, l, true); 360 } else 361 if (c == 'R') { 362 String ncmdl = "R "+db.theArgs; 363 if (!l.equals(ncmdl)) { 364 newCommandLine = true; 365 } 366 } else 367 if (c == '#') { 368 if (l.startsWith("# javac_state ver ")) { 369 int sp = l.indexOf(" ", 18); 370 if (sp != -1) { 371 String ver = l.substring(18,sp); 372 if (!ver.equals("0.3")) { 373 break; 374 } 375 foundCorrectVerNr = true; 376 } 377 } 378 } 379 } 380 } 381 } catch (FileNotFoundException | NoSuchFileException e) { 382 // Silently create a new javac_state file. 383 noFileFound = true; 384 } catch (IOException e) { 385 Log.info("Dropping old javac_state because of errors when reading it."); 386 db = new JavacState(options, true, out, err); 387 foundCorrectVerNr = true; 388 newCommandLine = false; 389 syntaxError = false; 390 } 391 if (foundCorrectVerNr == false && !noFileFound) { 392 Log.info("Dropping old javac_state since it is of an old version."); 393 db = new JavacState(options, true, out, err); 394 } else 395 if (newCommandLine == true && !noFileFound) { 396 Log.info("Dropping old javac_state since a new command line is used!"); 397 db = new JavacState(options, true, out, err); 398 } else 399 if (syntaxError == true) { 400 Log.info("Dropping old javac_state since it contains syntax errors."); 401 db = new JavacState(options, true, out, err); 402 } 403 db.prev.calculateDependents(); 404 return db; 405 } 406 407 /** 408 * Mark a java package as tainted, ie it needs recompilation. 409 */ 410 public void taintPackage(String name, String because) { 411 if (!taintedPackages.contains(name)) { 412 if (because != null) Log.debug("Tainting "+Util.justPackageName(name)+" because "+because); 413 // It has not been tainted before. 414 taintedPackages.add(name); 415 needsSaving(); 416 Package nowp = now.packages().get(name); 417 if (nowp != null) { 418 for (String d : nowp.dependents()) { 419 taintPackage(d, because); 420 } 421 } 422 } 423 } 424 425 /** 426 * This packages need recompilation. 427 */ 428 public Set<String> taintedPackages() { 429 return taintedPackages; 430 } 431 432 /** 433 * Clean out the tainted package set, used after the first round of compiles, 434 * prior to propagating dependencies. 435 */ 436 public void clearTaintedPackages() { 437 taintedPackages = new HashSet<>(); 438 } 439 440 /** 441 * Go through all sources and check which have been removed, added or modified 442 * and taint the corresponding packages. 443 */ 444 public void checkSourceStatus(boolean check_gensrc) { 445 removedSources = calculateRemovedSources(); 446 for (Source s : removedSources) { 447 if (!s.isGenerated() || check_gensrc) { 448 taintPackage(s.pkg().name(), "source "+s.name()+" was removed"); 449 } 450 } 451 452 addedSources = calculateAddedSources(); 453 for (Source s : addedSources) { 454 String msg = null; 455 if (isIncremental()) { 456 // When building from scratch, there is no point 457 // printing "was added" for every file since all files are added. 458 // However for an incremental build it makes sense. 459 msg = "source "+s.name()+" was added"; 460 } 461 if (!s.isGenerated() || check_gensrc) { 462 taintPackage(s.pkg().name(), msg); 463 } 464 } 465 466 modifiedSources = calculateModifiedSources(); 467 for (Source s : modifiedSources) { 468 if (!s.isGenerated() || check_gensrc) { 469 taintPackage(s.pkg().name(), "source "+s.name()+" was modified"); 470 } 471 } 472 } 473 474 /** 475 * Acquire the compile_java_packages suffix rule for .java files. 476 */ 477 public Map<String,Transformer> getJavaSuffixRule() { 478 Map<String,Transformer> sr = new HashMap<>(); 479 sr.put(".java", compileJavaPackages); 480 return sr; 481 } 482 483 484 /** 485 * If artifacts have gone missing, force a recompile of the packages 486 * they belong to. 487 */ 488 public void taintPackagesThatMissArtifacts() { 489 for (Package pkg : prev.packages().values()) { 490 for (File f : pkg.artifacts().values()) { 491 if (!f.exists()) { 492 // Hmm, the artifact on disk does not exist! Someone has removed it.... 493 // Lets rebuild the package. 494 taintPackage(pkg.name(), ""+f+" is missing."); 495 } 496 } 497 } 498 } 499 500 /** 501 * Propagate recompilation through the dependency chains. 502 * Avoid re-tainting packages that have already been compiled. 503 */ 504 public void taintPackagesDependingOnChangedPackages(Set<String> pkgsWithChangedPubApi, Set<String> recentlyCompiled) { 505 // For each to-be-recompiled-candidates... 506 for (Package pkg : new HashSet<>(prev.packages().values())) { 507 // Find out what it depends upon... 508 Set<String> deps = pkg.typeDependencies() 509 .values() 510 .stream() 511 .flatMap(s -> s.stream()) 512 .collect(Collectors.toSet()); 513 for (String dep : deps) { 514 String depPkg = ":" + dep.substring(0, dep.lastIndexOf('.')); 515 if (depPkg.equals(pkg.name())) 516 continue; 517 // Checking if that dependency has changed 518 if (pkgsWithChangedPubApi.contains(depPkg) && !recentlyCompiled.contains(pkg.name())) { 519 taintPackage(pkg.name(), "its depending on " + depPkg); 520 } 521 } 522 } 523 } 524 525 /** 526 * Compare the javac_state recorded public apis of packages on the classpath 527 * with the actual public apis on the classpath. 528 */ 529 public void taintPackagesDependingOnChangedClasspathPackages() { 530 531 // 1. Collect fully qualified names of all interesting classpath dependencies 532 Set<String> fqDependencies = new HashSet<>(); 533 for (Package pkg : prev.packages().values()) { 534 // Check if this package was compiled. If it's presence is recorded 535 // because it was on the class path and we needed to save it's 536 // public api, it's not a candidate for tainting. 537 if (pkg.sources().isEmpty()) 538 continue; 539 540 pkg.typeClasspathDependencies().values().forEach(fqDependencies::addAll); 541 } 542 543 // 2. Extract the public APIs from the on disk .class files 544 // (Reason for doing step 1 in a separate phase is to avoid extracting 545 // public APIs of the same class twice.) 546 PubApiExtractor pubApiExtractor = new PubApiExtractor(options); 547 Map<String, PubApi> onDiskPubApi = new HashMap<>(); 548 for (String cpDep : fqDependencies) 549 onDiskPubApi.put(cpDep, pubApiExtractor.getPubApi(cpDep)); 550 551 // 3. Compare them with the public APIs as of last compilation (loaded from javac_state) 552 nextPkg: 553 for (Package pkg : prev.packages().values()) { 554 // Check if this package was compiled. If it's presence is recorded 555 // because it was on the class path and we needed to save it's 556 // public api, it's not a candidate for tainting. 557 if (pkg.sources().isEmpty()) 558 continue; 559 560 Set<String> depsOfThisPkg = pkg.typeClasspathDependencies() 561 .values() 562 .stream() 563 .reduce(new HashSet<>(), Util::union); 564 for (String fqDep : depsOfThisPkg) { 565 566 String depPkg = ":" + fqDep.substring(0, fqDep.lastIndexOf('.')); 567 PubApi prevPkgApi = prev.packages().get(depPkg).getPubApi(); 568 569 // This PubApi directly lists the members of the class, i.e. [ MEMBER1, MEMBER2, ... ] 570 PubApi prevDepApi = prevPkgApi.types.get(fqDep).pubApi; 571 572 // In order to dive *into* the class, we need to add 573 // .types.get(fqDep).pubApi below. 574 PubApi currentDepApi = onDiskPubApi.get(fqDep).types.get(fqDep).pubApi; 575 576 if (!currentDepApi.isBackwardCompatibleWith(prevDepApi)) { 577 String apiDiff = currentDepApi.diff(prevDepApi); 578 taintPackage(pkg.name(), "depends on classpath " 579 + "package which has an updated package api (" 580 + apiDiff + ")"); 581 //Log.debug("========================================"); 582 //Log.debug("------ PREV API ------------------------"); 583 //prevDepApi.asListOfStrings().forEach(Log::debug); 584 //Log.debug("------ CURRENT API ---------------------"); 585 //currentDepApi.asListOfStrings().forEach(Log::debug); 586 //Log.debug("========================================"); 587 continue nextPkg; 588 } 589 } 590 } 591 } 592 593 /** 594 * Scan all output dirs for artifacts and remove those files (artifacts?) 595 * that are not recognized as such, in the javac_state file. 596 */ 597 public void removeUnidentifiedArtifacts() { 598 Set<File> allKnownArtifacts = new HashSet<>(); 599 for (Package pkg : prev.packages().values()) { 600 for (File f : pkg.artifacts().values()) { 601 allKnownArtifacts.add(f); 602 } 603 } 604 // Do not forget about javac_state.... 605 allKnownArtifacts.add(javacState); 606 607 for (File f : binArtifacts) { 608 if (!allKnownArtifacts.contains(f) && 609 !options.isUnidentifiedArtifactPermitted(f.getAbsolutePath())) { 610 Log.debug("Removing "+f.getPath()+" since it is unknown to the javac_state."); 611 f.delete(); 612 } 613 } 614 for (File f : headerArtifacts) { 615 if (!allKnownArtifacts.contains(f)) { 616 Log.debug("Removing "+f.getPath()+" since it is unknown to the javac_state."); 617 f.delete(); 618 } 619 } 620 for (File f : gensrcArtifacts) { 621 if (!allKnownArtifacts.contains(f)) { 622 Log.debug("Removing "+f.getPath()+" since it is unknown to the javac_state."); 623 f.delete(); 624 } 625 } 626 } 627 628 /** 629 * Remove artifacts that are no longer produced when compiling! 630 */ 631 public void removeSuperfluousArtifacts(Set<String> recentlyCompiled) { 632 // Nothing to do, if nothing was recompiled. 633 if (recentlyCompiled.size() == 0) return; 634 635 for (String pkg : now.packages().keySet()) { 636 // If this package has not been recompiled, skip the check. 637 if (!recentlyCompiled.contains(pkg)) continue; 638 Collection<File> arts = now.artifacts().values(); 639 for (File f : fetchPrevArtifacts(pkg).values()) { 640 if (!arts.contains(f)) { 641 Log.debug("Removing "+f.getPath()+" since it is now superfluous!"); 642 if (f.exists()) f.delete(); 643 } 644 } 645 } 646 } 647 648 /** 649 * Return those files belonging to prev, but not now. 650 */ 651 private Set<Source> calculateRemovedSources() { 652 Set<Source> removed = new HashSet<>(); 653 for (String src : prev.sources().keySet()) { 654 if (now.sources().get(src) == null) { 655 removed.add(prev.sources().get(src)); 656 } 657 } 658 return removed; 659 } 660 661 /** 662 * Return those files belonging to now, but not prev. 663 */ 664 private Set<Source> calculateAddedSources() { 665 Set<Source> added = new HashSet<>(); 666 for (String src : now.sources().keySet()) { 667 if (prev.sources().get(src) == null) { 668 added.add(now.sources().get(src)); 669 } 670 } 671 return added; 672 } 673 674 /** 675 * Return those files where the timestamp is newer. 676 * If a source file timestamp suddenly is older than what is known 677 * about it in javac_state, then consider it modified, but print 678 * a warning! 679 */ 680 private Set<Source> calculateModifiedSources() { 681 Set<Source> modified = new HashSet<>(); 682 for (String src : now.sources().keySet()) { 683 Source n = now.sources().get(src); 684 Source t = prev.sources().get(src); 685 if (prev.sources().get(src) != null) { 686 if (t != null) { 687 if (n.lastModified() > t.lastModified()) { 688 modified.add(n); 689 } else if (n.lastModified() < t.lastModified()) { 690 modified.add(n); 691 Log.warn("The source file "+n.name()+" timestamp has moved backwards in time."); 692 } 693 } 694 } 695 } 696 return modified; 697 } 698 699 /** 700 * Recursively delete a directory and all its contents. 701 */ 702 private void deleteContents(File dir) { 703 if (dir != null && dir.exists()) { 704 for (File f : dir.listFiles()) { 705 if (f.isDirectory()) { 706 deleteContents(f); 707 } 708 if (!options.isUnidentifiedArtifactPermitted(f.getAbsolutePath())) { 709 Log.debug("Removing "+f.getAbsolutePath()); 710 f.delete(); 711 } 712 } 713 } 714 } 715 716 /** 717 * Run the copy translator only. 718 */ 719 public void performCopying(File binDir, Map<String,Transformer> suffixRules) { 720 Map<String,Transformer> sr = new HashMap<>(); 721 for (Map.Entry<String,Transformer> e : suffixRules.entrySet()) { 722 if (e.getValue().getClass().equals(CopyFile.class)) { 723 sr.put(e.getKey(), e.getValue()); 724 } 725 } 726 perform(null, binDir, sr); 727 } 728 729 /** 730 * Run all the translators that translate into java source code. 731 * I.e. all translators that are not copy nor compile_java_source. 732 */ 733 public void performTranslation(File gensrcDir, Map<String,Transformer> suffixRules) { 734 Map<String,Transformer> sr = new HashMap<>(); 735 for (Map.Entry<String,Transformer> e : suffixRules.entrySet()) { 736 Class<?> trClass = e.getValue().getClass(); 737 if (trClass == CompileJavaPackages.class || trClass == CopyFile.class) 738 continue; 739 740 sr.put(e.getKey(), e.getValue()); 741 } 742 perform(null, gensrcDir, sr); 743 } 744 745 /** 746 * Compile all the java sources. Return true, if it needs to be called again! 747 */ 748 public boolean performJavaCompilations(Sjavac sjavac, 749 Options args, 750 Set<String> recentlyCompiled, 751 boolean[] rcValue) { 752 Map<String,Transformer> suffixRules = new HashMap<>(); 753 suffixRules.put(".java", compileJavaPackages); 754 compileJavaPackages.setExtra(args); 755 rcValue[0] = perform(sjavac, binDir, suffixRules); 756 recentlyCompiled.addAll(taintedPackages()); 757 clearTaintedPackages(); 758 boolean again = !packagesWithChangedPublicApis.isEmpty(); 759 taintPackagesDependingOnChangedPackages(packagesWithChangedPublicApis, recentlyCompiled); 760 packagesWithChangedPublicApis = new HashSet<>(); 761 return again && rcValue[0]; 762 763 // TODO: Figure out why 'again' checks packagesWithChangedPublicAPis. 764 // (It shouldn't matter if packages had changed pub apis as long as no 765 // one depends on them. Wouldn't it make more sense to let 'again' 766 // depend on taintedPackages?) 767 } 768 769 /** 770 * Store the source into the set of sources belonging to the given transform. 771 */ 772 private void addFileToTransform(Map<Transformer,Map<String,Set<URI>>> gs, Transformer t, Source s) { 773 Map<String,Set<URI>> fs = gs.get(t); 774 if (fs == null) { 775 fs = new HashMap<>(); 776 gs.put(t, fs); 777 } 778 Set<URI> ss = fs.get(s.pkg().name()); 779 if (ss == null) { 780 ss = new HashSet<>(); 781 fs.put(s.pkg().name(), ss); 782 } 783 ss.add(s.file().toURI()); 784 } 785 786 /** 787 * For all packages, find all sources belonging to the package, group the sources 788 * based on their transformers and apply the transformers on each source code group. 789 */ 790 private boolean perform(Sjavac sjavac, 791 File outputDir, 792 Map<String,Transformer> suffixRules) { 793 boolean rc = true; 794 // Group sources based on transforms. A source file can only belong to a single transform. 795 Map<Transformer,Map<String,Set<URI>>> groupedSources = new HashMap<>(); 796 for (Source src : now.sources().values()) { 797 Transformer t = suffixRules.get(src.suffix()); 798 if (t != null) { 799 if (taintedPackages.contains(src.pkg().name()) && !src.isLinkedOnly()) { 800 addFileToTransform(groupedSources, t, src); 801 } 802 } 803 } 804 // Go through the transforms and transform them. 805 for (Map.Entry<Transformer, Map<String, Set<URI>>> e : groupedSources.entrySet()) { 806 Transformer t = e.getKey(); 807 Map<String, Set<URI>> srcs = e.getValue(); 808 // These maps need to be synchronized since multiple threads will be 809 // writing results into them. 810 Map<String, Set<URI>> packageArtifacts = Collections.synchronizedMap(new HashMap<>()); 811 Map<String, Map<String, Set<String>>> packageDependencies = Collections.synchronizedMap(new HashMap<>()); 812 Map<String, Map<String, Set<String>>> packageCpDependencies = Collections.synchronizedMap(new HashMap<>()); 813 Map<String, PubApi> packagePublicApis = Collections.synchronizedMap(new HashMap<>()); 814 Map<String, PubApi> dependencyPublicApis = Collections.synchronizedMap(new HashMap<>()); 815 816 boolean r = t.transform(sjavac, 817 srcs, 818 visibleSrcs, 819 visibleClasses, 820 prev.dependents(), 821 outputDir.toURI(), 822 packageArtifacts, 823 packageDependencies, 824 packageCpDependencies, 825 packagePublicApis, 826 dependencyPublicApis, 827 0, 828 isIncremental(), 829 numCores, 830 out, 831 err); 832 if (!r) 833 rc = false; 834 835 for (String p : srcs.keySet()) { 836 recompiledPackages.add(p); 837 } 838 // The transform is done! Extract all the artifacts and store the info into the Package objects. 839 for (Map.Entry<String, Set<URI>> a : packageArtifacts.entrySet()) { 840 Module mnow = now.findModuleFromPackageName(a.getKey()); 841 mnow.addArtifacts(a.getKey(), a.getValue()); 842 } 843 // Extract all the dependencies and store the info into the Package objects. 844 for (Map.Entry<String, Map<String, Set<String>>> a : packageDependencies.entrySet()) { 845 Map<String, Set<String>> deps = a.getValue(); 846 Module mnow = now.findModuleFromPackageName(a.getKey()); 847 mnow.setDependencies(a.getKey(), deps, false); 848 } 849 for (Map.Entry<String, Map<String, Set<String>>> a : packageCpDependencies.entrySet()) { 850 Map<String, Set<String>> deps = a.getValue(); 851 Module mnow = now.findModuleFromPackageName(a.getKey()); 852 mnow.setDependencies(a.getKey(), deps, true); 853 } 854 855 // This map contains the public api of the types that this 856 // compilation depended upon. This means that it may not contain 857 // full packages. In other words, we shouldn't remove knowledge of 858 // public apis but merge these with what we already have. 859 for (Map.Entry<String, PubApi> a : dependencyPublicApis.entrySet()) { 860 String pkg = a.getKey(); 861 PubApi packagePartialPubApi = a.getValue(); 862 Package pkgNow = now.findModuleFromPackageName(pkg).lookupPackage(pkg); 863 PubApi currentPubApi = pkgNow.getPubApi(); 864 PubApi newPubApi = PubApi.mergeTypes(currentPubApi, packagePartialPubApi); 865 pkgNow.setPubapi(newPubApi); 866 867 // See JDK-8071904 868 if (now.packages().containsKey(pkg)) 869 now.packages().get(pkg).setPubapi(newPubApi); 870 else 871 now.packages().put(pkg, pkgNow); 872 } 873 874 // The packagePublicApis cover entire packages (since sjavac compiles 875 // stuff on package level). This means that if a type is missing 876 // in the public api of a given package, it means that it has been 877 // removed. In other words, we should *set* the pubapi to whatever 878 // this map contains, and not merge it with what we already have. 879 for (Map.Entry<String, PubApi> a : packagePublicApis.entrySet()) { 880 String pkg = a.getKey(); 881 PubApi newPubApi = a.getValue(); 882 Module mprev = prev.findModuleFromPackageName(pkg); 883 Module mnow = now.findModuleFromPackageName(pkg); 884 mnow.setPubapi(pkg, newPubApi); 885 if (mprev.hasPubapiChanged(pkg, newPubApi)) { 886 // Aha! The pubapi of this package has changed! 887 // It can also be a new compile from scratch. 888 if (mprev.lookupPackage(pkg).existsInJavacState()) { 889 // This is an incremental compile! The pubapi 890 // did change. Trigger recompilation of dependents. 891 packagesWithChangedPublicApis.add(pkg); 892 Log.info("The API of " + Util.justPackageName(pkg) + " has changed!"); 893 } 894 } 895 } 896 } 897 return rc; 898 } 899 900 /** 901 * Utility method to recursively find all files below a directory. 902 */ 903 private static Set<File> findAllFiles(File dir) { 904 Set<File> foundFiles = new HashSet<>(); 905 if (dir == null) { 906 return foundFiles; 907 } 908 recurse(dir, foundFiles); 909 return foundFiles; 910 } 911 912 private static void recurse(File dir, Set<File> foundFiles) { 913 for (File f : dir.listFiles()) { 914 if (f.isFile()) { 915 foundFiles.add(f); 916 } else if (f.isDirectory()) { 917 recurse(f, foundFiles); 918 } 919 } 920 } 921 922 /** 923 * Compare the calculate source list, with an explicit list, usually 924 * supplied from the makefile. Used to detect bugs where the makefile and 925 * sjavac have different opinions on which files should be compiled. 926 */ 927 public void compareWithMakefileList(File makefileSourceList) 928 throws ProblemException { 929 // If we are building on win32 using for example cygwin the paths in the 930 // makefile source list 931 // might be /cygdrive/c/.... which does not match c:\.... 932 // We need to adjust our calculated sources to be identical, if 933 // necessary. 934 boolean mightNeedRewriting = File.pathSeparatorChar == ';'; 935 936 if (makefileSourceList == null) 937 return; 938 939 Set<String> calculatedSources = new HashSet<>(); 940 Set<String> listedSources = new HashSet<>(); 941 942 // Create a set of filenames with full paths. 943 for (Source s : now.sources().values()) { 944 // Don't include link only sources when comparing sources to compile 945 if (!s.isLinkedOnly()) { 946 String path = s.file().getPath(); 947 if (mightNeedRewriting) 948 path = Util.normalizeDriveLetter(path); 949 calculatedSources.add(path); 950 } 951 } 952 // Read in the file and create another set of filenames with full paths. 953 try { 954 BufferedReader in = new BufferedReader(new FileReader(makefileSourceList)); 955 for (;;) { 956 String l = in.readLine(); 957 if (l==null) break; 958 l = l.trim(); 959 if (mightNeedRewriting) { 960 if (l.indexOf(":") == 1 && l.indexOf("\\") == 2) { 961 // Everything a-ok, the format is already C:\foo\bar 962 } else if (l.indexOf(":") == 1 && l.indexOf("/") == 2) { 963 // The format is C:/foo/bar, rewrite into the above format. 964 l = l.replaceAll("/","\\\\"); 965 } else if (l.charAt(0) == '/' && l.indexOf("/",1) != -1) { 966 // The format might be: /cygdrive/c/foo/bar, rewrite into the above format. 967 // Do not hardcode the name cygdrive here. 968 int slash = l.indexOf("/",1); 969 l = l.replaceAll("/","\\\\"); 970 l = ""+l.charAt(slash+1)+":"+l.substring(slash+2); 971 } 972 if (Character.isLowerCase(l.charAt(0))) { 973 l = Character.toUpperCase(l.charAt(0))+l.substring(1); 974 } 975 } 976 listedSources.add(l); 977 } 978 } catch (FileNotFoundException | NoSuchFileException e) { 979 throw new ProblemException("Could not open "+makefileSourceList.getPath()+" since it does not exist!"); 980 } catch (IOException e) { 981 throw new ProblemException("Could not read "+makefileSourceList.getPath()); 982 } 983 984 for (String s : listedSources) { 985 if (!calculatedSources.contains(s)) { 986 throw new ProblemException("The makefile listed source "+s+" was not calculated by the smart javac wrapper!"); 987 } 988 } 989 990 for (String s : calculatedSources) { 991 if (!listedSources.contains(s)) { 992 throw new ProblemException("The smart javac wrapper calculated source "+s+" was not listed by the makefiles!"); 993 } 994 } 995 } 996 }