/** Save the javac_state file. */ public void save() throws IOException { if (!needsSaving) return; try (FileWriter out = new FileWriter(javacStateFilename)) { 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! // I.e. the lot. Module.saveModules(now.modules(), b); String s = b.toString(); out.write(s, 0, s.length()); } }
/** Return those files belonging to now, but not prev. */ private Set<Source> calculateAddedSources() { Set<Source> added = new HashSet<>(); for (String src : now.sources().keySet()) { if (prev.sources().get(src) == null) { added.add(now.sources().get(src)); } } return added; }
/** Return those files belonging to prev, but not now. */ private Set<Source> calculateRemovedSources() { Set<Source> removed = new HashSet<>(); for (String src : prev.sources().keySet()) { if (now.sources().get(src) == null) { removed.add(prev.sources().get(src)); } } return removed; }
/** Remove artifacts that are no longer produced when compiling! */ public void removeSuperfluousArtifacts(Set<String> recentlyCompiled) { // Nothing to do, if nothing was recompiled. if (recentlyCompiled.size() == 0) return; for (String pkg : now.packages().keySet()) { // If this package has not been recompiled, skip the check. if (!recentlyCompiled.contains(pkg)) continue; Collection<File> arts = now.artifacts().values(); for (File f : fetchPrevArtifacts(pkg).values()) { if (!arts.contains(f)) { Log.debug("Removing " + f.getPath() + " since it is now superfluous!"); if (f.exists()) f.delete(); } } } }
/** * Scan all output dirs for artifacts and remove those files (artifacts?) that are not recognized * as such, in the javac_state file. */ public void removeUnidentifiedArtifacts() { Set<File> allKnownArtifacts = new HashSet<>(); for (Package pkg : prev.packages().values()) { for (File f : pkg.artifacts().values()) { allKnownArtifacts.add(f); } } // Do not forget about javac_state.... allKnownArtifacts.add(javacState); for (File f : binArtifacts) { if (!allKnownArtifacts.contains(f)) { Log.debug("Removing " + f.getPath() + " since it is unknown to the javac_state."); f.delete(); } } for (File f : headerArtifacts) { if (!allKnownArtifacts.contains(f)) { Log.debug("Removing " + f.getPath() + " since it is unknown to the javac_state."); f.delete(); } } for (File f : gensrcArtifacts) { if (!allKnownArtifacts.contains(f)) { Log.debug("Removing " + f.getPath() + " since it is unknown to the javac_state."); f.delete(); } } }
/** Lookup the artifacts generated for this package in the previous build. */ private Map<String, File> fetchPrevArtifacts(String pkg) { Package p = prev.packages().get(pkg); if (p != null) { return p.artifacts(); } return new HashMap<>(); }
/** * Return those files where the timestamp is newer. If a source file timestamp suddenly is older * than what is known about it in javac_state, then consider it modified, but print a warning! */ private Set<Source> calculateModifiedSources() { Set<Source> modified = new HashSet<>(); for (String src : now.sources().keySet()) { Source n = now.sources().get(src); Source t = prev.sources().get(src); if (prev.sources().get(src) != null) { if (t != null) { if (n.lastModified() > t.lastModified()) { modified.add(n); } else if (n.lastModified() < t.lastModified()) { modified.add(n); Log.warn("The source file " + n.name() + " timestamp has moved backwards in time."); } } } } return modified; }
/** * 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); } } } }
/** If artifacts have gone missing, force a recompile of the packages they belong to. */ public void taintPackagesThatMissArtifacts() { for (Package pkg : prev.packages().values()) { for (File f : pkg.artifacts().values()) { if (!f.exists()) { // Hmm, the artifact on disk does not exist! Someone has removed it.... // Lets rebuild the package. taintPackage(pkg.name(), "" + f + " is missing."); } } } }
/** Mark a java package as tainted, ie it needs recompilation. */ public void taintPackage(String name, String because) { if (!taintedPackages.contains(name)) { if (because != null) Log.debug("Tainting " + Util.justPackageName(name) + " because " + because); // It has not been tainted before. taintedPackages.add(name); needsSaving(); Package nowp = now.packages().get(name); if (nowp != null) { for (String d : nowp.dependents()) { taintPackage(d, because); } } } }
/** * 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. for (Source s : now.sources().values()) { // Don't include link only sources when comparing sources to compile if (!s.isLinkedOnly()) { calculatedSources.add(s.file().getPath()); } } // Read in the file and create another set of filenames with full paths. try { BufferedReader in = new BufferedReader(new FileReader(makefileSourceList)); for (; ; ) { String l = in.readLine(); if (l == null) break; l = l.trim(); if (mightNeedRewriting) { if (l.indexOf(":") == 1 && l.indexOf("\\") == 2) { // Everything a-ok, the format is already C:\foo\bar } else if (l.indexOf(":") == 1 && l.indexOf("/") == 2) { // The format is C:/foo/bar, rewrite into the above format. l = l.replaceAll("/", "\\\\"); } else if (l.charAt(0) == '/' && l.indexOf("/", 1) != -1) { // The format might be: /cygdrive/c/foo/bar, rewrite into the above format. // Do not hardcode the name cygdrive here. int slash = l.indexOf("/", 1); l = l.replaceAll("/", "\\\\"); l = "" + l.charAt(slash + 1) + ":" + l.substring(slash + 2); } if (Character.isLowerCase(l.charAt(0))) { l = Character.toUpperCase(l.charAt(0)) + l.substring(1); } } listedSources.add(l); } } catch (FileNotFoundException e) { throw new ProblemException( "Could not open " + makefileSourceList.getPath() + " since it does not exist!"); } catch (IOException e) { throw new ProblemException("Could not read " + makefileSourceList.getPath()); } for (String s : listedSources) { if (!calculatedSources.contains(s)) { throw new ProblemException( "The makefile listed source " + s + " was not calculated by the smart javac wrapper!"); } } for (String s : calculatedSources) { if (!listedSources.contains(s)) { throw new ProblemException( "The smart javac wrapper calculated source " + s + " was not listed by the makefiles!"); } } }
/** * For all packages, find all sources belonging to the package, group the sources based on their * transformers and apply the transformers on each source code group. */ private boolean perform(File outputDir, Map<String, Transformer> suffixRules) { boolean rc = true; // Group sources based on transforms. A source file can only belong to a single transform. Map<Transformer, Map<String, Set<URI>>> groupedSources = new HashMap<>(); for (Source src : now.sources().values()) { Transformer t = suffixRules.get(src.suffix()); if (t != null) { if (taintedPackages.contains(src.pkg().name()) && !src.isLinkedOnly()) { 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( 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; }
/** Returns true if this is an incremental build. */ public boolean isIncremental() { return !prev.sources().isEmpty(); }