SjavacImpl.java revision 3271:01b00ac6bc18
1/* 2 * Copyright (c) 2014, 2016, 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.comp; 27 28import java.io.IOException; 29import java.io.PrintWriter; 30import java.io.StringWriter; 31import java.nio.file.Files; 32import java.nio.file.Path; 33import java.util.ArrayList; 34import java.util.Collections; 35import java.util.HashMap; 36import java.util.HashSet; 37import java.util.List; 38import java.util.Map; 39import java.util.Set; 40import java.util.stream.Stream; 41 42import com.sun.tools.javac.file.JavacFileManager; 43import com.sun.tools.javac.main.Main; 44import com.sun.tools.javac.util.Context; 45import com.sun.tools.sjavac.JavacState; 46import com.sun.tools.sjavac.Log; 47import com.sun.tools.sjavac.Module; 48import com.sun.tools.sjavac.ProblemException; 49import com.sun.tools.sjavac.Source; 50import com.sun.tools.sjavac.Transformer; 51import com.sun.tools.sjavac.Util; 52import com.sun.tools.sjavac.options.Option; 53import com.sun.tools.sjavac.options.Options; 54import com.sun.tools.sjavac.options.SourceLocation; 55import com.sun.tools.sjavac.server.Sjavac; 56 57import javax.tools.JavaFileManager; 58 59/** 60 * The sjavac implementation that interacts with javac and performs the actual 61 * compilation. 62 * 63 * <p><b>This is NOT part of any supported API. 64 * If you write code that depends on this, you do so at your own risk. 65 * This code and its internal interfaces are subject to change or 66 * deletion without notice.</b> 67 */ 68public class SjavacImpl implements Sjavac { 69 70 @Override 71 public int compile(String[] args) { 72 Options options; 73 try { 74 options = Options.parseArgs(args); 75 } catch (IllegalArgumentException e) { 76 Log.error(e.getMessage()); 77 return RC_FATAL; 78 } 79 80 if (!validateOptions(options)) 81 return RC_FATAL; 82 83 if (srcDstOverlap(options.getSources(), options.getDestDir())) { 84 return RC_FATAL; 85 } 86 87 if (!createIfMissing(options.getDestDir())) 88 return RC_FATAL; 89 90 Path stateDir = options.getStateDir(); 91 if (stateDir != null && !createIfMissing(options.getStateDir())) 92 return RC_FATAL; 93 94 Path gensrc = options.getGenSrcDir(); 95 if (gensrc != null && !createIfMissing(gensrc)) 96 return RC_FATAL; 97 98 Path hdrdir = options.getHeaderDir(); 99 if (hdrdir != null && !createIfMissing(hdrdir)) 100 return RC_FATAL; 101 102 if (stateDir == null) { 103 // Prepare context. Direct logging to our byte array stream. 104 Context context = new Context(); 105 StringWriter strWriter = new StringWriter(); 106 PrintWriter printWriter = new PrintWriter(strWriter); 107 com.sun.tools.javac.util.Log.preRegister(context, printWriter); 108 JavacFileManager.preRegister(context); 109 110 // Prepare arguments 111 String[] passThroughArgs = Stream.of(args) 112 .filter(arg -> !arg.startsWith(Option.SERVER.arg)) 113 .toArray(String[]::new); 114 // Compile 115 Main.Result result = new Main("javac", printWriter).compile(passThroughArgs, context); 116 117 // Process compiler output (which is always errors) 118 printWriter.flush(); 119 Util.getLines(strWriter.toString()).forEach(Log::error); 120 121 // Clean up 122 JavaFileManager fileManager = context.get(JavaFileManager.class); 123 if (fileManager instanceof JavacFileManager) { 124 try { 125 ((JavacFileManager) fileManager).close(); 126 } catch (IOException e) { 127 return RC_FATAL; 128 } 129 } 130 return result.exitCode; 131 132 } else { 133 // Load the prev build state database. 134 JavacState javac_state = JavacState.load(options); 135 136 // Setup the suffix rules from the command line. 137 Map<String, Transformer> suffixRules = new HashMap<>(); 138 139 // Handling of .java-compilation 140 suffixRules.putAll(javac_state.getJavaSuffixRule()); 141 142 // Handling of -copy and -tr 143 suffixRules.putAll(options.getTranslationRules()); 144 145 // All found modules are put here. 146 Map<String,Module> modules = new HashMap<>(); 147 // We start out in the legacy empty no-name module. 148 // As soon as we stumble on a module-info.java file we change to that module. 149 Module current_module = new Module("", ""); 150 modules.put("", current_module); 151 152 try { 153 // Find all sources, use the suffix rules to know which files are sources. 154 Map<String,Source> sources = new HashMap<>(); 155 156 // Find the files, this will automatically populate the found modules 157 // with found packages where the sources are found! 158 findSourceFiles(options.getSources(), 159 suffixRules.keySet(), 160 sources, 161 modules, 162 current_module, 163 options.isDefaultPackagePermitted(), 164 false); 165 166 if (sources.isEmpty()) { 167 Log.error("Found nothing to compile!"); 168 return RC_FATAL; 169 } 170 171 172 // Create a map of all source files that are available for linking. Both -src and 173 // -sourcepath point to such files. It is possible to specify multiple 174 // -sourcepath options to enable different filtering rules. If the 175 // filters are the same for multiple sourcepaths, they may be concatenated 176 // using :(;). Before sending the list of sourcepaths to javac, they are 177 // all concatenated. The list created here is used by the SmartFileWrapper to 178 // make sure only the correct sources are actually available. 179 // We might find more modules here as well. 180 Map<String,Source> sources_to_link_to = new HashMap<>(); 181 182 List<SourceLocation> sourceResolutionLocations = new ArrayList<>(); 183 sourceResolutionLocations.addAll(options.getSources()); 184 sourceResolutionLocations.addAll(options.getSourceSearchPaths()); 185 findSourceFiles(sourceResolutionLocations, 186 Collections.singleton(".java"), 187 sources_to_link_to, 188 modules, 189 current_module, 190 options.isDefaultPackagePermitted(), 191 true); 192 193 // Add the set of sources to the build database. 194 javac_state.now().flattenPackagesSourcesAndArtifacts(modules); 195 javac_state.now().checkInternalState("checking sources", false, sources); 196 javac_state.now().checkInternalState("checking linked sources", true, sources_to_link_to); 197 javac_state.setVisibleSources(sources_to_link_to); 198 199 int round = 0; 200 printRound(round); 201 202 // If there is any change in the source files, taint packages 203 // and mark the database in need of saving. 204 javac_state.checkSourceStatus(false); 205 206 // Find all existing artifacts. Their timestamp will match the last modified timestamps stored 207 // in javac_state, simply because loading of the JavacState will clean out all artifacts 208 // that do not match the javac_state database. 209 javac_state.findAllArtifacts(); 210 211 // Remove unidentified artifacts from the bin, gensrc and header dirs. 212 // (Unless we allow them to be there.) 213 // I.e. artifacts that are not known according to the build database (javac_state). 214 // For examples, files that have been manually copied into these dirs. 215 // Artifacts with bad timestamps (ie the on disk timestamp does not match the timestamp 216 // in javac_state) have already been removed when the javac_state was loaded. 217 if (!options.areUnidentifiedArtifactsPermitted()) { 218 javac_state.removeUnidentifiedArtifacts(); 219 } 220 // Go through all sources and taint all packages that miss artifacts. 221 javac_state.taintPackagesThatMissArtifacts(); 222 223 // Check recorded classpath public apis. Taint packages that depend on 224 // classpath classes whose public apis have changed. 225 javac_state.taintPackagesDependingOnChangedClasspathPackages(); 226 227 // Now clean out all known artifacts belonging to tainted packages. 228 javac_state.deleteClassArtifactsInTaintedPackages(); 229 // Copy files, for example property files, images files, xml files etc etc. 230 javac_state.performCopying(Util.pathToFile(options.getDestDir()), suffixRules); 231 // Translate files, for example compile properties or compile idls. 232 javac_state.performTranslation(Util.pathToFile(gensrc), suffixRules); 233 // Add any potentially generated java sources to the tobe compiled list. 234 // (Generated sources must always have a package.) 235 Map<String,Source> generated_sources = new HashMap<>(); 236 237 Source.scanRoot(Util.pathToFile(options.getGenSrcDir()), 238 Util.set(".java"), 239 Collections.emptyList(), 240 Collections.emptyList(), 241 generated_sources, 242 modules, 243 current_module, 244 false, 245 true, 246 false); 247 javac_state.now().flattenPackagesSourcesAndArtifacts(modules); 248 // Recheck the the source files and their timestamps again. 249 javac_state.checkSourceStatus(true); 250 251 // Now do a safety check that the list of source files is identical 252 // to the list Make believes we are compiling. If we do not get this 253 // right, then incremental builds will fail with subtility. 254 // If any difference is detected, then we will fail hard here. 255 // This is an important safety net. 256 javac_state.compareWithMakefileList(Util.pathToFile(options.getSourceReferenceList())); 257 258 // Do the compilations, repeatedly until no tainted packages exist. 259 boolean again; 260 // Collect the name of all compiled packages. 261 Set<String> recently_compiled = new HashSet<>(); 262 boolean[] rc = new boolean[1]; 263 264 CompilationService compilationService = new CompilationService(); 265 do { 266 if (round > 0) 267 printRound(round); 268 // Clean out artifacts in tainted packages. 269 javac_state.deleteClassArtifactsInTaintedPackages(); 270 again = javac_state.performJavaCompilations(compilationService, 271 options, 272 recently_compiled, 273 rc); 274 if (!rc[0]) { 275 Log.debug("Compilation failed."); 276 break; 277 } 278 if (!again) { 279 Log.debug("Nothing left to do."); 280 } 281 round++; 282 } while (again); 283 Log.debug("No need to do another round."); 284 285 // Only update the state if the compile went well. 286 if (rc[0]) { 287 javac_state.save(); 288 // Reflatten only the artifacts. 289 javac_state.now().flattenArtifacts(modules); 290 // Remove artifacts that were generated during the last compile, but not this one. 291 javac_state.removeSuperfluousArtifacts(recently_compiled); 292 } 293 294 return rc[0] ? RC_OK : RC_FATAL; 295 } catch (ProblemException e) { 296 // For instance make file list mismatch. 297 Log.error(e.getMessage()); 298 Log.debug(e); 299 return RC_FATAL; 300 } catch (Exception e) { 301 Log.error(e); 302 return RC_FATAL; 303 } 304 } 305 } 306 307 @Override 308 public void shutdown() { 309 // Nothing to clean up 310 } 311 312 private static boolean validateOptions(Options options) { 313 314 String err = null; 315 316 if (options.getDestDir() == null) { 317 err = "Please specify output directory."; 318 } else if (options.isJavaFilesAmongJavacArgs()) { 319 err = "Sjavac does not handle explicit compilation of single .java files."; 320 } else if (!options.getImplicitPolicy().equals("none")) { 321 err = "The only allowed setting for sjavac is -implicit:none"; 322 } else if (options.getSources().isEmpty() && options.getStateDir() != null) { 323 err = "You have to specify -src when using --state-dir."; 324 } else if (options.getTranslationRules().size() > 1 325 && options.getGenSrcDir() == null) { 326 err = "You have translators but no gensrc dir (-s) specified!"; 327 } 328 329 if (err != null) 330 Log.error(err); 331 332 return err == null; 333 334 } 335 336 private static boolean srcDstOverlap(List<SourceLocation> locs, Path dest) { 337 for (SourceLocation loc : locs) { 338 if (isOverlapping(loc.getPath(), dest)) { 339 Log.error("Source location " + loc.getPath() + " overlaps with destination " + dest); 340 return true; 341 } 342 } 343 return false; 344 } 345 346 private static boolean isOverlapping(Path p1, Path p2) { 347 p1 = p1.toAbsolutePath().normalize(); 348 p2 = p2.toAbsolutePath().normalize(); 349 return p1.startsWith(p2) || p2.startsWith(p1); 350 } 351 352 private static boolean createIfMissing(Path dir) { 353 354 if (Files.isDirectory(dir)) 355 return true; 356 357 if (Files.exists(dir)) { 358 Log.error(dir + " is not a directory."); 359 return false; 360 } 361 362 try { 363 Files.createDirectories(dir); 364 } catch (IOException e) { 365 Log.error("Could not create directory: " + e.getMessage()); 366 return false; 367 } 368 369 return true; 370 } 371 372 /** Find source files in the given source locations. */ 373 public static void findSourceFiles(List<SourceLocation> sourceLocations, 374 Set<String> sourceTypes, 375 Map<String,Source> foundFiles, 376 Map<String, Module> foundModules, 377 Module currentModule, 378 boolean permitSourcesInDefaultPackage, 379 boolean inLinksrc) 380 throws IOException { 381 382 for (SourceLocation source : sourceLocations) { 383 source.findSourceFiles(sourceTypes, 384 foundFiles, 385 foundModules, 386 currentModule, 387 permitSourcesInDefaultPackage, 388 inLinksrc); 389 } 390 } 391 392 private static void printRound(int round) { 393 Log.debug("****************************************"); 394 Log.debug("* Round " + round + " *"); 395 Log.debug("****************************************"); 396 } 397} 398