src/jdk.compiler/share/classes/com/sun/tools/sjavac/JavacState.java

Print this page
rev 2819 : imported patch my-classpath-deps-00

*** 23,46 **** * questions. */ package com.sun.tools.sjavac; ! import java.io.*; import java.net.URI; import java.nio.file.NoSuchFileException; import java.text.SimpleDateFormat; ! import java.util.*; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; - import java.util.List; import java.util.Map; import java.util.Set; import com.sun.tools.sjavac.options.Options; import com.sun.tools.sjavac.server.Sjavac; /** * The javac state class maintains the previous (prev) and the current (now) * build states and everything else that goes into the javac_state file. --- 23,53 ---- * questions. */ package com.sun.tools.sjavac; ! import java.io.BufferedReader; ! import java.io.File; ! import java.io.FileNotFoundException; ! import java.io.FileReader; ! import java.io.FileWriter; ! import java.io.IOException; ! import java.io.PrintStream; import java.net.URI; import java.nio.file.NoSuchFileException; import java.text.SimpleDateFormat; ! import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; + import java.util.stream.Collectors; import com.sun.tools.sjavac.options.Options; + import com.sun.tools.sjavac.pubapi.PubApi; import com.sun.tools.sjavac.server.Sjavac; /** * The javac state class maintains the previous (prev) and the current (now) * build states and everything else that goes into the javac_state file.
*** 266,293 **** /** * Save the javac_state file. */ public void save() throws IOException { ! if (!needsSaving) return; try (FileWriter out = new FileWriter(javacState)) { StringBuilder b = new StringBuilder(); long millisNow = System.currentTimeMillis(); Date d = new Date(millisNow); ! SimpleDateFormat df = ! new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS"); b.append("# javac_state ver 0.3 generated "+millisNow+" "+df.format(d)+"\n"); b.append("# This format might change at any time. Please do not depend on it.\n"); b.append("# M module\n"); b.append("# P package\n"); b.append("# S C source_tobe_compiled timestamp\n"); b.append("# S L link_only_source timestamp\n"); b.append("# G C generated_source timestamp\n"); b.append("# A artifact timestamp\n"); ! b.append("# D dependency\n"); b.append("# I pubapi\n"); - b.append("# R arguments\n"); b.append("R ").append(theArgs).append("\n"); // Copy over the javac_state for the packages that did not need recompilation. now.copyPackagesExcept(prev, recompiledPackages, new HashSet<String>()); // Save the packages, ie package names, dependencies, pubapis and artifacts! --- 273,301 ---- /** * Save the javac_state file. */ public void save() throws IOException { ! if (!needsSaving) ! return; try (FileWriter out = new FileWriter(javacState)) { StringBuilder b = new StringBuilder(); long millisNow = System.currentTimeMillis(); Date d = new Date(millisNow); ! SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS"); b.append("# javac_state ver 0.3 generated "+millisNow+" "+df.format(d)+"\n"); b.append("# This format might change at any time. Please do not depend on it.\n"); + b.append("# R arguments\n"); b.append("# M module\n"); b.append("# P package\n"); b.append("# S C source_tobe_compiled timestamp\n"); b.append("# S L link_only_source timestamp\n"); b.append("# G C generated_source timestamp\n"); b.append("# A artifact timestamp\n"); ! b.append("# D S dependant -> source dependency\n"); ! b.append("# D C dependant -> classpath dependency\n"); b.append("# I pubapi\n"); b.append("R ").append(theArgs).append("\n"); // Copy over the javac_state for the packages that did not need recompilation. now.copyPackagesExcept(prev, recompiledPackages, new HashSet<String>()); // Save the packages, ie package names, dependencies, pubapis and artifacts!
*** 310,319 **** --- 318,329 ---- boolean noFileFound = false; boolean foundCorrectVerNr = false; boolean newCommandLine = false; boolean syntaxError = false; + Log.debug("Loading javac state file: " + db.javacState); + try (BufferedReader in = new BufferedReader(new FileReader(db.javacState))) { for (;;) { String l = in.readLine(); if (l==null) break; if (l.length()>=3 && l.charAt(1) == ' ') {
*** 325,339 **** if (lastModule == null) { syntaxError = true; break; } lastPackage = db.prev.loadPackage(lastModule, l); } else if (c == 'D') { if (lastModule == null || lastPackage == null) { syntaxError = true; break; } ! lastPackage.loadDependency(l); } else if (c == 'I') { if (lastModule == null || lastPackage == null) { syntaxError = true; break; } ! lastPackage.loadPubapi(l); } else if (c == 'A') { if (lastModule == null || lastPackage == null) { syntaxError = true; break; } lastPackage.loadArtifact(l); } else --- 335,352 ---- if (lastModule == null) { syntaxError = true; break; } lastPackage = db.prev.loadPackage(lastModule, l); } else if (c == 'D') { if (lastModule == null || lastPackage == null) { syntaxError = true; break; } ! char depType = l.charAt(2); ! if (depType != 'S' && depType != 'C') ! throw new RuntimeException("Bad dependency string: " + l); ! lastPackage.parseAndAddDependency(l.substring(4), depType == 'C'); } else if (c == 'I') { if (lastModule == null || lastPackage == null) { syntaxError = true; break; } ! lastPackage.getPubApi().appendItem(l.substring(2)); // Strip "I " } else if (c == 'A') { if (lastModule == null || lastPackage == null) { syntaxError = true; break; } lastPackage.loadArtifact(l); } else
*** 486,500 **** /** * Propagate recompilation through the dependency chains. * Avoid re-tainting packages that have already been compiled. */ ! public void taintPackagesDependingOnChangedPackages(Set<String> pkgs, Set<String> recentlyCompiled) { for (Package pkg : prev.packages().values()) { ! for (String dep : pkg.dependencies()) { ! if (pkgs.contains(dep) && !recentlyCompiled.contains(pkg.name())) { ! taintPackage(pkg.name(), " its depending on "+dep); } } } } --- 499,592 ---- /** * Propagate recompilation through the dependency chains. * Avoid re-tainting packages that have already been compiled. */ ! public void taintPackagesDependingOnChangedPackages(Set<String> pkgsWithChangedPubApi, Set<String> recentlyCompiled) { ! // For each to-be-recompiled-candidates... ! for (Package pkg : new HashSet<>(prev.packages().values())) { ! // Find out what it depends upon... ! Set<String> deps = pkg.typeDependencies() ! .values() ! .stream() ! .flatMap(s -> s.stream()) ! .collect(Collectors.toSet()); ! for (String dep : deps) { ! String depPkg = ":" + dep.substring(0, dep.lastIndexOf('.')); ! if (depPkg.equals(pkg.name())) ! continue; ! // Checking if that dependency has changed ! if (pkgsWithChangedPubApi.contains(depPkg) && !recentlyCompiled.contains(pkg.name())) { ! taintPackage(pkg.name(), "its depending on " + depPkg); ! } ! } ! } ! } ! ! /** ! * Compare the javac_state recorded public apis of packages on the classpath ! * with the actual public apis on the classpath. ! */ ! public void taintPackagesDependingOnChangedClasspathPackages() { ! ! // 1. Collect fully qualified names of all interesting classpath dependencies ! Set<String> fqDependencies = new HashSet<>(); for (Package pkg : prev.packages().values()) { ! // Check if this package was compiled. If it's presence is recorded ! // because it was on the class path and we needed to save it's ! // public api, it's not a candidate for tainting. ! if (pkg.sources().isEmpty()) ! continue; ! ! pkg.typeClasspathDependencies().values().forEach(fqDependencies::addAll); ! } ! ! // 2. Extract the public APIs from the on disk .class files ! // (Reason for doing step 1 in a separate phase is to avoid extracting ! // public APIs of the same class twice.) ! PubApiExtractor pubApiExtractor = new PubApiExtractor(options); ! Map<String, PubApi> onDiskPubApi = new HashMap<>(); ! for (String cpDep : fqDependencies) ! onDiskPubApi.put(cpDep, pubApiExtractor.getPubApi(cpDep)); ! ! // 3. Compare them with the public APIs as of last compilation (loaded from javac_state) ! nextPkg: ! for (Package pkg : prev.packages().values()) { ! // Check if this package was compiled. If it's presence is recorded ! // because it was on the class path and we needed to save it's ! // public api, it's not a candidate for tainting. ! if (pkg.sources().isEmpty()) ! continue; ! ! Set<String> depsOfThisPkg = pkg.typeClasspathDependencies() ! .values() ! .stream() ! .reduce(new HashSet<>(), Util::union); ! for (String fqDep : depsOfThisPkg) { ! ! String depPkg = ":" + fqDep.substring(0, fqDep.lastIndexOf('.')); ! PubApi prevPkgApi = prev.packages().get(depPkg).getPubApi(); ! ! // This PubApi directly lists the members of the class, i.e. [ MEMBER1, MEMBER2, ... ] ! PubApi prevDepApi = prevPkgApi.types.get(fqDep).pubApi; ! ! // In order to dive *into* the class, we need to add ! // .types.get(fqDep).pubApi below. ! PubApi currentDepApi = onDiskPubApi.get(fqDep).types.get(fqDep).pubApi; ! ! if (!currentDepApi.isBackwardCompatibleWith(prevDepApi)) { ! String apiDiff = currentDepApi.diff(prevDepApi); ! taintPackage(pkg.name(), "depends on classpath " ! + "package which has an updated package api (" ! + apiDiff + ")"); ! //Log.debug("========================================"); ! //Log.debug("------ PREV API ------------------------"); ! //prevDepApi.asListOfStrings().forEach(Log::debug); ! //Log.debug("------ CURRENT API ---------------------"); ! //currentDepApi.asListOfStrings().forEach(Log::debug); ! //Log.debug("========================================"); ! continue nextPkg; } } } }
*** 658,675 **** Set<String> recentlyCompiled, boolean[] rcValue) { Map<String,Transformer> suffixRules = new HashMap<>(); suffixRules.put(".java", compileJavaPackages); compileJavaPackages.setExtra(args); - rcValue[0] = perform(sjavac, binDir, suffixRules); recentlyCompiled.addAll(taintedPackages()); clearTaintedPackages(); boolean again = !packagesWithChangedPublicApis.isEmpty(); taintPackagesDependingOnChangedPackages(packagesWithChangedPublicApis, recentlyCompiled); packagesWithChangedPublicApis = new HashSet<>(); return again && rcValue[0]; } /** * Store the source into the set of sources belonging to the given transform. */ --- 750,771 ---- Set<String> recentlyCompiled, boolean[] rcValue) { Map<String,Transformer> suffixRules = new HashMap<>(); suffixRules.put(".java", compileJavaPackages); compileJavaPackages.setExtra(args); rcValue[0] = perform(sjavac, binDir, suffixRules); recentlyCompiled.addAll(taintedPackages()); clearTaintedPackages(); boolean again = !packagesWithChangedPublicApis.isEmpty(); taintPackagesDependingOnChangedPackages(packagesWithChangedPublicApis, recentlyCompiled); packagesWithChangedPublicApis = new HashSet<>(); return again && rcValue[0]; + + // TODO: Figure out why 'again' checks packagesWithChangedPublicAPis. + // (It shouldn't matter if packages had changed pub apis as long as no + // one depends on them. Wouldn't it make more sense to let 'again' + // depend on taintedPackages?) } /** * Store the source into the set of sources belonging to the given transform. */
*** 704,768 **** addFileToTransform(groupedSources, t, src); } } } // Go through the transforms and transform them. ! for (Map.Entry<Transformer,Map<String,Set<URI>>> e : groupedSources.entrySet()) { Transformer t = e.getKey(); ! Map<String,Set<URI>> srcs = e.getValue(); ! // These maps need to be synchronized since multiple threads will be writing results into them. ! Map<String,Set<URI>> packageArtifacts = ! Collections.synchronizedMap(new HashMap<String,Set<URI>>()); ! Map<String,Set<String>> packageDependencies = ! Collections.synchronizedMap(new HashMap<String,Set<String>>()); ! Map<String,String> packagePublicApis = ! Collections.synchronizedMap(new HashMap<String, String>()); boolean r = t.transform(sjavac, srcs, visibleSrcs, visibleClasses, prev.dependents(), outputDir.toURI(), packageArtifacts, packageDependencies, packagePublicApis, 0, isIncremental(), numCores, out, err); ! if (!r) rc = false; for (String p : srcs.keySet()) { recompiledPackages.add(p); } // The transform is done! Extract all the artifacts and store the info into the Package objects. ! for (Map.Entry<String,Set<URI>> a : packageArtifacts.entrySet()) { Module mnow = now.findModuleFromPackageName(a.getKey()); mnow.addArtifacts(a.getKey(), a.getValue()); } // Extract all the dependencies and store the info into the Package objects. ! for (Map.Entry<String,Set<String>> a : packageDependencies.entrySet()) { ! Set<String> deps = a.getValue(); Module mnow = now.findModuleFromPackageName(a.getKey()); ! mnow.setDependencies(a.getKey(), deps); } ! // Extract all the pubapis and store the info into the Package objects. ! for (Map.Entry<String,String> a : packagePublicApis.entrySet()) { ! Module mprev = prev.findModuleFromPackageName(a.getKey()); ! List<String> pubapi = Package.pubapiToList(a.getValue()); Module mnow = now.findModuleFromPackageName(a.getKey()); ! mnow.setPubapi(a.getKey(), pubapi); ! if (mprev.hasPubapiChanged(a.getKey(), pubapi)) { // Aha! The pubapi of this package has changed! // It can also be a new compile from scratch. ! if (mprev.lookupPackage(a.getKey()).existsInJavacState()) { // This is an incremental compile! The pubapi // did change. Trigger recompilation of dependents. ! packagesWithChangedPublicApis.add(a.getKey()); ! Log.info("The pubapi of "+Util.justPackageName(a.getKey())+" has changed!"); } } } } return rc; --- 800,897 ---- addFileToTransform(groupedSources, t, src); } } } // Go through the transforms and transform them. ! for (Map.Entry<Transformer, Map<String, Set<URI>>> e : groupedSources.entrySet()) { Transformer t = e.getKey(); ! Map<String, Set<URI>> srcs = e.getValue(); ! // These maps need to be synchronized since multiple threads will be ! // writing results into them. ! Map<String, Set<URI>> packageArtifacts = Collections.synchronizedMap(new HashMap<>()); ! Map<String, Map<String, Set<String>>> packageDependencies = Collections.synchronizedMap(new HashMap<>()); ! Map<String, Map<String, Set<String>>> packageCpDependencies = Collections.synchronizedMap(new HashMap<>()); ! Map<String, PubApi> packagePublicApis = Collections.synchronizedMap(new HashMap<>()); ! Map<String, PubApi> dependencyPublicApis = Collections.synchronizedMap(new HashMap<>()); boolean r = t.transform(sjavac, srcs, visibleSrcs, visibleClasses, prev.dependents(), outputDir.toURI(), packageArtifacts, packageDependencies, + packageCpDependencies, packagePublicApis, + dependencyPublicApis, 0, isIncremental(), numCores, out, err); ! if (!r) ! rc = false; for (String p : srcs.keySet()) { recompiledPackages.add(p); } // The transform is done! Extract all the artifacts and store the info into the Package objects. ! for (Map.Entry<String, Set<URI>> a : packageArtifacts.entrySet()) { Module mnow = now.findModuleFromPackageName(a.getKey()); mnow.addArtifacts(a.getKey(), a.getValue()); } // Extract all the dependencies and store the info into the Package objects. ! for (Map.Entry<String, Map<String, Set<String>>> a : packageDependencies.entrySet()) { ! Map<String, Set<String>> deps = a.getValue(); Module mnow = now.findModuleFromPackageName(a.getKey()); ! mnow.setDependencies(a.getKey(), deps, false); } ! for (Map.Entry<String, Map<String, Set<String>>> a : packageCpDependencies.entrySet()) { ! Map<String, Set<String>> deps = a.getValue(); Module mnow = now.findModuleFromPackageName(a.getKey()); ! mnow.setDependencies(a.getKey(), deps, true); ! } ! ! // This map contains the public api of the types that this ! // compilation depended upon. This means that it may not contain ! // full packages. In other words, we shouldn't remove knowledge of ! // public apis but merge these with what we already have. ! for (Map.Entry<String, PubApi> a : dependencyPublicApis.entrySet()) { ! String pkg = a.getKey(); ! PubApi packagePartialPubApi = a.getValue(); ! Package pkgNow = now.findModuleFromPackageName(pkg).lookupPackage(pkg); ! PubApi currentPubApi = pkgNow.getPubApi(); ! PubApi newPubApi = PubApi.mergeTypes(currentPubApi, packagePartialPubApi); ! pkgNow.setPubapi(newPubApi); ! ! // See JDK-8071904 ! if (now.packages().containsKey(pkg)) ! now.packages().get(pkg).setPubapi(newPubApi); ! else ! now.packages().put(pkg, pkgNow); ! } ! ! // The packagePublicApis cover entire packages (since sjavac compiles ! // stuff on package level). This means that if a type is missing ! // in the public api of a given package, it means that it has been ! // removed. In other words, we should *set* the pubapi to whatever ! // this map contains, and not merge it with what we already have. ! for (Map.Entry<String, PubApi> a : packagePublicApis.entrySet()) { ! String pkg = a.getKey(); ! PubApi newPubApi = a.getValue(); ! Module mprev = prev.findModuleFromPackageName(pkg); ! Module mnow = now.findModuleFromPackageName(pkg); ! mnow.setPubapi(pkg, newPubApi); ! if (mprev.hasPubapiChanged(pkg, newPubApi)) { // Aha! The pubapi of this package has changed! // It can also be a new compile from scratch. ! if (mprev.lookupPackage(pkg).existsInJavacState()) { // This is an incremental compile! The pubapi // did change. Trigger recompilation of dependents. ! packagesWithChangedPublicApis.add(pkg); ! Log.info("The API of " + Util.justPackageName(pkg) + " has changed!"); } } } } return rc;
*** 789,809 **** } } } /** ! * Compare the calculate source list, with an explicit list, usually supplied from the makefile. ! * Used to detect bugs where the makefile and sjavac have different opinions on which files ! * should be compiled. ! */ ! public void compareWithMakefileList(File makefileSourceList) throws ProblemException { ! // If we are building on win32 using for example cygwin the paths in the makefile source list // might be /cygdrive/c/.... which does not match c:\.... ! // We need to adjust our calculated sources to be identical, if necessary. boolean mightNeedRewriting = File.pathSeparatorChar == ';'; ! if (makefileSourceList == null) return; Set<String> calculatedSources = new HashSet<>(); Set<String> listedSources = new HashSet<>(); // Create a set of filenames with full paths. --- 918,942 ---- } } } /** ! * Compare the calculate source list, with an explicit list, usually ! * supplied from the makefile. Used to detect bugs where the makefile and ! * sjavac have different opinions on which files should be compiled. ! */ ! public void compareWithMakefileList(File makefileSourceList) ! throws ProblemException { ! // If we are building on win32 using for example cygwin the paths in the ! // makefile source list // might be /cygdrive/c/.... which does not match c:\.... ! // We need to adjust our calculated sources to be identical, if ! // necessary. boolean mightNeedRewriting = File.pathSeparatorChar == ';'; ! if (makefileSourceList == null) ! return; Set<String> calculatedSources = new HashSet<>(); Set<String> listedSources = new HashSet<>(); // Create a set of filenames with full paths.