/* * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.tools.sjavac.comp; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Stream; import com.sun.tools.javac.file.JavacFileManager; import com.sun.tools.javac.main.Main; import com.sun.tools.javac.main.Main.Result; import com.sun.tools.javac.util.Context; import com.sun.tools.sjavac.JavacState; import com.sun.tools.sjavac.Log; import com.sun.tools.sjavac.Module; import com.sun.tools.sjavac.ProblemException; import com.sun.tools.sjavac.Source; import com.sun.tools.sjavac.Transformer; import com.sun.tools.sjavac.Util; import com.sun.tools.sjavac.options.Option; import com.sun.tools.sjavac.options.Options; import com.sun.tools.sjavac.options.SourceLocation; import com.sun.tools.sjavac.server.Sjavac; import java.io.UncheckedIOException; import javax.tools.JavaFileManager; /** * The sjavac implementation that interacts with javac and performs the actual * compilation. * * <p><b>This is NOT part of any supported API. * If you write code that depends on this, you do so at your own risk. * This code and its internal interfaces are subject to change or * deletion without notice.</b> */ public class SjavacImpl implements Sjavac { @Override public Result compile(String[] args) { Options options; try { options = Options.parseArgs(args); } catch (IllegalArgumentException e) { Log.error(e.getMessage()); return Result.CMDERR; } if (!validateOptions(options)) return Result.CMDERR; if (srcDstOverlap(options.getSources(), options.getDestDir())) { return Result.CMDERR; } if (!createIfMissing(options.getDestDir())) return Result.ERROR; Path stateDir = options.getStateDir(); if (stateDir != null && !createIfMissing(options.getStateDir())) return Result.ERROR; Path gensrc = options.getGenSrcDir(); if (gensrc != null && !createIfMissing(gensrc)) return Result.ERROR; Path hdrdir = options.getHeaderDir(); if (hdrdir != null && !createIfMissing(hdrdir)) return Result.ERROR; if (stateDir == null) { // Prepare context. Direct logging to our byte array stream. Context context = new Context(); StringWriter strWriter = new StringWriter(); PrintWriter printWriter = new PrintWriter(strWriter); com.sun.tools.javac.util.Log.preRegister(context, printWriter); JavacFileManager.preRegister(context); // Prepare arguments String[] passThroughArgs = Stream.of(args) .filter(arg -> !arg.startsWith(Option.SERVER.arg)) .toArray(String[]::new); // Compile Result result = new Main("javac", printWriter).compile(passThroughArgs, context); // Process compiler output (which is always errors) printWriter.flush(); Util.getLines(strWriter.toString()).forEach(Log::error); // Clean up JavaFileManager fileManager = context.get(JavaFileManager.class); if (fileManager instanceof JavacFileManager) { try { ((JavacFileManager) fileManager).close(); } catch (IOException es) { throw new UncheckedIOException(es); } } return result; } else { // Load the prev build state database. JavacState javac_state = JavacState.load(options); // Setup the suffix rules from the command line. Map<String, Transformer> suffixRules = new HashMap<>(); // Handling of .java-compilation suffixRules.putAll(javac_state.getJavaSuffixRule()); // Handling of -copy and -tr suffixRules.putAll(options.getTranslationRules()); // All found modules are put here. Map<String,Module> modules = new HashMap<>(); // We start out in the legacy empty no-name module. // As soon as we stumble on a module-info.java file we change to that module. Module current_module = new Module("", ""); modules.put("", current_module); try { // Find all sources, use the suffix rules to know which files are sources. Map<String,Source> sources = new HashMap<>(); // Find the files, this will automatically populate the found modules // with found packages where the sources are found! findSourceFiles(options.getSources(), suffixRules.keySet(), sources, modules, current_module, options.isDefaultPackagePermitted(), false); if (sources.isEmpty()) { Log.error("Found nothing to compile!"); return Result.ERROR; } // Create a map of all source files that are available for linking. Both -src and // -sourcepath point to such files. It is possible to specify multiple // -sourcepath options to enable different filtering rules. If the // filters are the same for multiple sourcepaths, they may be concatenated // using :(;). Before sending the list of sourcepaths to javac, they are // all concatenated. The list created here is used by the SmartFileWrapper to // make sure only the correct sources are actually available. // We might find more modules here as well. Map<String,Source> sources_to_link_to = new HashMap<>(); List<SourceLocation> sourceResolutionLocations = new ArrayList<>(); sourceResolutionLocations.addAll(options.getSources()); sourceResolutionLocations.addAll(options.getSourceSearchPaths()); findSourceFiles(sourceResolutionLocations, Collections.singleton(".java"), sources_to_link_to, modules, current_module, options.isDefaultPackagePermitted(), true); // Add the set of sources to the build database. javac_state.now().flattenPackagesSourcesAndArtifacts(modules); javac_state.now().checkInternalState("checking sources", false, sources); javac_state.now().checkInternalState("checking linked sources", true, sources_to_link_to); javac_state.setVisibleSources(sources_to_link_to); int round = 0; printRound(round); // If there is any change in the source files, taint packages // and mark the database in need of saving. javac_state.checkSourceStatus(false); // Find all existing artifacts. Their timestamp will match the last modified timestamps stored // in javac_state, simply because loading of the JavacState will clean out all artifacts // that do not match the javac_state database. javac_state.findAllArtifacts(); // Remove unidentified artifacts from the bin, gensrc and header dirs. // (Unless we allow them to be there.) // I.e. artifacts that are not known according to the build database (javac_state). // For examples, files that have been manually copied into these dirs. // Artifacts with bad timestamps (ie the on disk timestamp does not match the timestamp // in javac_state) have already been removed when the javac_state was loaded. if (!options.areUnidentifiedArtifactsPermitted()) { javac_state.removeUnidentifiedArtifacts(); } // Go through all sources and taint all packages that miss artifacts. javac_state.taintPackagesThatMissArtifacts(); // Check recorded classpath public apis. Taint packages that depend on // classpath classes whose public apis have changed. javac_state.taintPackagesDependingOnChangedClasspathPackages(); // Now clean out all known artifacts belonging to tainted packages. javac_state.deleteClassArtifactsInTaintedPackages(); // Copy files, for example property files, images files, xml files etc etc. javac_state.performCopying(Util.pathToFile(options.getDestDir()), suffixRules); // Translate files, for example compile properties or compile idls. javac_state.performTranslation(Util.pathToFile(gensrc), suffixRules); // Add any potentially generated java sources to the tobe compiled list. // (Generated sources must always have a package.) Map<String,Source> generated_sources = new HashMap<>(); Source.scanRoot(Util.pathToFile(options.getGenSrcDir()), Util.set(".java"), Collections.emptyList(), Collections.emptyList(), generated_sources, modules, current_module, false, true, false); javac_state.now().flattenPackagesSourcesAndArtifacts(modules); // Recheck the the source files and their timestamps again. javac_state.checkSourceStatus(true); // Now do a safety check that the list of source files is identical // to the list Make believes we are compiling. If we do not get this // right, then incremental builds will fail with subtility. // If any difference is detected, then we will fail hard here. // This is an important safety net. javac_state.compareWithMakefileList(Util.pathToFile(options.getSourceReferenceList())); // Do the compilations, repeatedly until no tainted packages exist. boolean again; // Collect the name of all compiled packages. Set<String> recently_compiled = new HashSet<>(); boolean[] rc = new boolean[1]; CompilationService compilationService = new CompilationService(); do { if (round > 0) printRound(round); // Clean out artifacts in tainted packages. javac_state.deleteClassArtifactsInTaintedPackages(); again = javac_state.performJavaCompilations(compilationService, options, recently_compiled, rc); if (!rc[0]) { Log.debug("Compilation failed."); break; } if (!again) { Log.debug("Nothing left to do."); } round++; } while (again); Log.debug("No need to do another round."); // Only update the state if the compile went well. if (rc[0]) { javac_state.save(); // Reflatten only the artifacts. javac_state.now().flattenArtifacts(modules); // Remove artifacts that were generated during the last compile, but not this one. javac_state.removeSuperfluousArtifacts(recently_compiled); } return rc[0] ? Result.OK : Result.ERROR; } catch (ProblemException e) { // For instance make file list mismatch. Log.error(e.getMessage()); Log.debug(e); return Result.ERROR; } catch (Exception e) { Log.error(e); return Result.ERROR; } } } @Override public void shutdown() { // Nothing to clean up } private static boolean validateOptions(Options options) { String err = null; if (options.getDestDir() == null) { err = "Please specify output directory."; } else if (options.isJavaFilesAmongJavacArgs()) { err = "Sjavac does not handle explicit compilation of single .java files."; } else if (!options.getImplicitPolicy().equals("none")) { err = "The only allowed setting for sjavac is -implicit:none"; } else if (options.getSources().isEmpty() && options.getStateDir() != null) { err = "You have to specify -src when using --state-dir."; } else if (options.getTranslationRules().size() > 1 && options.getGenSrcDir() == null) { err = "You have translators but no gensrc dir (-s) specified!"; } if (err != null) Log.error(err); return err == null; } private static boolean srcDstOverlap(List<SourceLocation> locs, Path dest) { for (SourceLocation loc : locs) { if (isOverlapping(loc.getPath(), dest)) { Log.error("Source location " + loc.getPath() + " overlaps with destination " + dest); return true; } } return false; } private static boolean isOverlapping(Path p1, Path p2) { p1 = p1.toAbsolutePath().normalize(); p2 = p2.toAbsolutePath().normalize(); return p1.startsWith(p2) || p2.startsWith(p1); } private static boolean createIfMissing(Path dir) { if (Files.isDirectory(dir)) return true; if (Files.exists(dir)) { Log.error(dir + " is not a directory."); return false; } try { Files.createDirectories(dir); } catch (IOException e) { Log.error("Could not create directory: " + e.getMessage()); return false; } return true; } /** Find source files in the given source locations. */ public static void findSourceFiles(List<SourceLocation> sourceLocations, Set<String> sourceTypes, Map<String,Source> foundFiles, Map<String, Module> foundModules, Module currentModule, boolean permitSourcesInDefaultPackage, boolean inLinksrc) throws IOException { for (SourceLocation source : sourceLocations) { source.findSourceFiles(sourceTypes, foundFiles, foundModules, currentModule, permitSourcesInDefaultPackage, inLinksrc); } } private static void printRound(int round) { Log.debug("****************************************"); Log.debug("* Round " + round + " *"); Log.debug("****************************************"); } }