Main.java revision 2593:035b01d356ee
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 26package com.sun.tools.sjavac; 27 28import java.io.IOException; 29import java.io.PrintStream; 30import java.util.*; 31import java.nio.file.Path; 32import java.nio.file.Files; 33 34import com.sun.tools.sjavac.client.SjavacClient; 35import com.sun.tools.sjavac.comp.SjavacImpl; 36import com.sun.tools.sjavac.comp.PooledSjavac; 37import com.sun.tools.sjavac.options.Options; 38import com.sun.tools.sjavac.options.SourceLocation; 39import com.sun.tools.sjavac.server.Sjavac; 40import com.sun.tools.sjavac.server.SjavacServer; 41 42/** 43 * The main class of the smart javac wrapper tool. 44 * 45 * <p><b>This is NOT part of any supported API. 46 * If you write code that depends on this, you do so at your own risk. 47 * This code and its internal interfaces are subject to change or 48 * deletion without notice.</b> 49 */ 50public class Main { 51 52 /* This is a smart javac wrapper primarily used when building the OpenJDK, 53 though other projects are welcome to use it too. But please be aware 54 that it is not an official api and will change in the future. 55 (We really mean it!) 56 57 Goals: 58 59 ** Create a state file, containing information about the build, so 60 that incremental builds only rebuild what is necessary. Also the 61 state file can be used by make/ant to detect when to trigger 62 a call to the smart javac wrapper. 63 64 This file is called bin/javac_state (assuming that you specified "-d bin") 65 Thus the simplest makefile is: 66 67 SJAVAC=java -cp .../tools.jar com.sun.tools.sjavac.Main 68 SRCS=$(shell find src -name "*.java") 69 bin/javac_state : $(SRCS) 70 $(SJAVAC) src -d bin 71 72 This makefile will run very fast and detect properly when Java code needs to 73 be recompiled. The smart javac wrapper will then use the information in java_state 74 to do an efficient incremental compile. 75 76 Previously it was near enough impossible to write an efficient makefile for Java 77 with support for incremental builds and dependency tracking. 78 79 ** Separate java sources to be compiled from java 80 sources used >only< for linking. The options: 81 82 "dir" points to root dir with sources to be compiled 83 "-sourcepath dir" points to root dir with sources used only for linking 84 "-classpath dir" points to dir with classes used only for linking (as before) 85 86 ** Use all cores for compilation by default. 87 "-j 4" limit the number of cores to 4. 88 For the moment, the sjavac server additionally limits the number of cores to three. 89 This will improve in the future when more sharing is performed between concurrent JavaCompilers. 90 91 ** Basic translation support from other sources to java, and then compilation of the generated java. 92 This functionality might be moved into annotation processors instead. 93 Again this is driven by the OpenJDK sources where properties and a few other types of files 94 are converted into Java sources regularily. The javac_state embraces copy and tr, and perform 95 incremental recompiles and copying for these as well. META-INF will be a special copy rule 96 that will copy any files found below any META-INF dir in src to the bin/META-INF dir. 97 "-copy .gif" 98 "-copy META-INF" 99 "-tr .prop=com.sun.tools.javac.smart.CompileProperties 100 "-tr .propp=com.sun.tools.javac.smart.CompileProperties,java.util.ListResourceBundle 101 "-tr .proppp=com.sun.tools.javac.smart.CompileProperties,sun.util.resources.LocaleNamesBundle 102 103 ** Control which classes in the src,sourcepath and classpath that javac is allowed to see. 104 Again, this is necessary to deal with the source code structure of the OpenJDK which is 105 intricate (read messy). 106 107 "-i tools.*" to include the tools package and all its subpackages in the build. 108 "-x tools.net.aviancarrier.*" to exclude the aviancarrier package and all its sources and subpackages. 109 "-x tools.net.drums" to exclude the drums package only, keep its subpackages. 110 "-xf tools/net/Bar.java" // Do not compile this file... 111 "-xf *Bor.java" // Do not compile Bor.java wherever it is found, BUT do compile ABor.java! 112 "-if tools/net/Bor.java" // Only compile this file...odd, but sometimes used. 113 114 ** The smart javac wrapper is driven by the modification time on the source files and compared 115 to the modification times written into the javac_state file. 116 117 It does not compare the modification time of the source with the modification time of the artifact. 118 However it will detect if the modification time of an artifact has changed compared to the java_state, 119 and this will trigger a delete of the artifact and a subsequent recompile of the source. 120 121 The smart javac wrapper is not a generic makefile/ant system. Its purpose is to compile java source 122 as the final step before the output dir is finalized and immediately jared, or jmodded. The output 123 dir should be considered opaque. Do not write into the outputdir yourself! 124 Any artifacts found in the outputdir that javac_state does not know of, will be deleted! 125 This can however be prevented, using the switch --permit-unidentified-artifacts 126 This switch is necessary when build the OpenJDK because its makefiles still write directly to 127 the output classes dirs. 128 129 Any makefile/ant rules that want to put contents into the outputdir should put the content 130 in one of several source roots. Static content that is under version control, can be put in the same source 131 code tree as the Java sources. Dynamic content that is generated by make/ant on the fly, should 132 be put in a separate gensrc_stuff root. The smart javac wrapper call will then take the arguments: 133 "gensrc_stuff src -d bin" 134 135 The command line: 136 java -cp tools.jar com.sun.tools.sjavac.Main \ 137 -i "com.bar.*" -x "com.bar.foo.*" \ 138 first_root \ 139 -i "com.bar.foo.*" \ 140 second_root \ 141 -x "org.net.*" \ 142 -sourcepath link_root_sources \ 143 -classpath link_root_classes \ 144 -d bin 145 146 Will compile all sources for package com.bar and its subpackages, found below first_root, 147 except the package com.bar.foo (and its subpackages), for which the sources are picked 148 from second_root instead. It will link against classes in link_root_classes and against 149 sources in link_root_sources, but will not see (try to link against) sources matching org.net.* 150 but will link against org.net* classes (if they exist) in link_root_classes. 151 152 (If you want a set of complex filter rules to be applied to several source directories, without 153 having to repeat the the filter rules for each root. You can use the explicit -src option. For example: 154 sjavac -x "com.foo.*" -src root1:root2:root3 ) 155 156 The resulting classes are written into bin. 157 */ 158 159 private JavacState javac_state; 160 161 public static void main(String... args) { 162 if (args.length > 0 && args[0].startsWith("--startserver:")) { 163 if (args.length>1) { 164 Log.error("When spawning a background server, only a single --startserver argument is allowed."); 165 return; 166 } 167 // Spawn a background server. 168 try { 169 SjavacServer server = new SjavacServer(args[0], System.err); 170 int rc = server.startServer(); 171 System.exit(rc); 172 } catch (IOException ioex) { 173 Log.error("IOException caught: " + ioex); 174 System.exit(-1); 175 } 176 } 177 Main main = new Main(); 178 int rc = main.go(args, System.out, System.err); 179 // Remove the portfile, but only if this background=false was used. 180 SjavacServer.cleanup(args); 181 System.exit(rc); 182 } 183 184 private void printHelp() { 185 System.out.println("Usage: sjavac <options>\n"+ 186 "where required options are:\n"+ 187 "dir Compile all sources in dir recursively\n"+ 188 "-d dir Store generated classes here and the javac_state file\n"+ 189 "--server:portfile=/tmp/abc Use a background sjavac server\n\n"+ 190 "All other arguments as javac, except -implicit:none which is forced by default.\n"+ 191 "No java source files can be supplied on the command line, nor can an @file be supplied.\n\n"+ 192 "Warning!\n"+ 193 "This tool might disappear at any time, and its command line options might change at any time!"); 194 } 195 196 public int go(String[] args, PrintStream out, PrintStream err) { 197 198 Log.initializeLog(out, err); 199 200 Options options; 201 try { 202 options = Options.parseArgs(args); 203 } catch (IllegalArgumentException e) { 204 Log.error(e.getMessage()); 205 return -1; 206 } 207 208 Log.setLogLevel(options.getLogLevel()); 209 210 if (!validateOptions(options)) 211 return -1; 212 213 if (!createIfMissing(options.getDestDir())) 214 return -1; 215 216 if (!createIfMissing(options.getStateDir())) 217 return -1; 218 219 Path gensrc = options.getGenSrcDir(); 220 if (gensrc != null && !createIfMissing(gensrc)) 221 return -1; 222 223 Path hdrdir = options.getHeaderDir(); 224 if (hdrdir != null && !createIfMissing(hdrdir)) 225 return -1; 226 227 // Load the prev build state database. 228 javac_state = JavacState.load(options, out, err); 229 230 // Setup the suffix rules from the command line. 231 Map<String, Transformer> suffixRules = new HashMap<>(); 232 233 // Handling of .java-compilation 234 suffixRules.putAll(javac_state.getJavaSuffixRule()); 235 236 // Handling of -copy and -tr 237 suffixRules.putAll(options.getTranslationRules()); 238 239 // All found modules are put here. 240 Map<String,Module> modules = new HashMap<>(); 241 // We start out in the legacy empty no-name module. 242 // As soon as we stumble on a module-info.java file we change to that module. 243 Module current_module = new Module("", ""); 244 modules.put("", current_module); 245 246 // Find all sources, use the suffix rules to know which files are sources. 247 Map<String,Source> sources = new HashMap<>(); 248 249 // Find the files, this will automatically populate the found modules 250 // with found packages where the sources are found! 251 findSourceFiles(options.getSources(), 252 suffixRules.keySet(), 253 sources, 254 modules, 255 current_module, 256 options.isDefaultPackagePermitted(), 257 false); 258 259 if (sources.isEmpty()) { 260 Log.error("Found nothing to compile!"); 261 return -1; 262 } 263 264 // Create a map of all source files that are available for linking. Both -src and 265 // -sourcepath point to such files. It is possible to specify multiple 266 // -sourcepath options to enable different filtering rules. If the 267 // filters are the same for multiple sourcepaths, they may be concatenated 268 // using :(;). Before sending the list of sourcepaths to javac, they are 269 // all concatenated. The list created here is used by the SmartFileWrapper to 270 // make sure only the correct sources are actually available. 271 // We might find more modules here as well. 272 Map<String,Source> sources_to_link_to = new HashMap<>(); 273 274 List<SourceLocation> sourceResolutionLocations = new ArrayList<>(); 275 sourceResolutionLocations.addAll(options.getSources()); 276 sourceResolutionLocations.addAll(options.getSourceSearchPaths()); 277 findSourceFiles(sourceResolutionLocations, 278 Collections.singleton(".java"), 279 sources_to_link_to, 280 modules, 281 current_module, 282 options.isDefaultPackagePermitted(), 283 true); 284 285 // Find all class files allowable for linking. 286 // And pickup knowledge of all modules found here. 287 // This cannot currently filter classes inside jar files. 288// Map<String,Source> classes_to_link_to = new HashMap<String,Source>(); 289// findFiles(args, "-classpath", Util.set(".class"), classes_to_link_to, modules, current_module, true); 290 291 // Find all module sources allowable for linking. 292// Map<String,Source> modules_to_link_to = new HashMap<String,Source>(); 293// findFiles(args, "-modulepath", Util.set(".class"), modules_to_link_to, modules, current_module, true); 294 295 // Add the set of sources to the build database. 296 javac_state.now().flattenPackagesSourcesAndArtifacts(modules); 297 javac_state.now().checkInternalState("checking sources", false, sources); 298 javac_state.now().checkInternalState("checking linked sources", true, sources_to_link_to); 299 javac_state.setVisibleSources(sources_to_link_to); 300 301 // If there is any change in the source files, taint packages 302 // and mark the database in need of saving. 303 javac_state.checkSourceStatus(false); 304 305 // Find all existing artifacts. Their timestamp will match the last modified timestamps stored 306 // in javac_state, simply because loading of the JavacState will clean out all artifacts 307 // that do not match the javac_state database. 308 javac_state.findAllArtifacts(); 309 310 // Remove unidentified artifacts from the bin, gensrc and header dirs. 311 // (Unless we allow them to be there.) 312 // I.e. artifacts that are not known according to the build database (javac_state). 313 // For examples, files that have been manually copied into these dirs. 314 // Artifacts with bad timestamps (ie the on disk timestamp does not match the timestamp 315 // in javac_state) have already been removed when the javac_state was loaded. 316 if (!options.areUnidentifiedArtifactsPermitted()) { 317 javac_state.removeUnidentifiedArtifacts(); 318 } 319 // Go through all sources and taint all packages that miss artifacts. 320 javac_state.taintPackagesThatMissArtifacts(); 321 322 // Now clean out all known artifacts belonging to tainted packages. 323 javac_state.deleteClassArtifactsInTaintedPackages(); 324 // Copy files, for example property files, images files, xml files etc etc. 325 javac_state.performCopying(Util.pathToFile(options.getDestDir()), suffixRules); 326 // Translate files, for example compile properties or compile idls. 327 javac_state.performTranslation(Util.pathToFile(gensrc), suffixRules); 328 // Add any potentially generated java sources to the tobe compiled list. 329 // (Generated sources must always have a package.) 330 Map<String,Source> generated_sources = new HashMap<>(); 331 332 try { 333 334 Source.scanRoot(Util.pathToFile(options.getGenSrcDir()), Util.set(".java"), null, null, null, null, 335 generated_sources, modules, current_module, false, true, false); 336 javac_state.now().flattenPackagesSourcesAndArtifacts(modules); 337 // Recheck the the source files and their timestamps again. 338 javac_state.checkSourceStatus(true); 339 340 // Now do a safety check that the list of source files is identical 341 // to the list Make believes we are compiling. If we do not get this 342 // right, then incremental builds will fail with subtility. 343 // If any difference is detected, then we will fail hard here. 344 // This is an important safety net. 345 javac_state.compareWithMakefileList(Util.pathToFile(options.getSourceReferenceList())); 346 347 // Do the compilations, repeatedly until no tainted packages exist. 348 boolean again; 349 // Collect the name of all compiled packages. 350 Set<String> recently_compiled = new HashSet<>(); 351 boolean[] rc = new boolean[1]; 352 Sjavac sjavac; 353 boolean background = Util.extractBooleanOption("background", options.getServerConf(), true); 354 do { 355 // Clean out artifacts in tainted packages. 356 javac_state.deleteClassArtifactsInTaintedPackages(); 357 // Create an sjavac implementation to be used for compilation 358 if (background) { 359 sjavac = new SjavacClient(options); 360 } else { 361 int poolsize = Util.extractIntOption("poolsize", options.getServerConf()); 362 if (poolsize <= 0) 363 poolsize = Runtime.getRuntime().availableProcessors(); 364 sjavac = new PooledSjavac(new SjavacImpl(), poolsize); 365 } 366 367 again = javac_state.performJavaCompilations(sjavac, options, recently_compiled, rc); 368 if (!rc[0]) break; 369 } while (again); 370 // Only update the state if the compile went well. 371 if (rc[0]) { 372 javac_state.save(); 373 // Reflatten only the artifacts. 374 javac_state.now().flattenArtifacts(modules); 375 // Remove artifacts that were generated during the last compile, but not this one. 376 javac_state.removeSuperfluousArtifacts(recently_compiled); 377 } 378 if (!background) 379 sjavac.shutdown(); 380 return rc[0] ? 0 : -1; 381 } catch (ProblemException e) { 382 Log.error(e.getMessage()); 383 return -1; 384 } catch (Exception e) { 385 e.printStackTrace(err); 386 return -1; 387 } 388 } 389 390 private static boolean validateOptions(Options options) { 391 392 String err = null; 393 394 if (options.getDestDir() == null) { 395 err = "Please specify output directory."; 396 } else if (options.isJavaFilesAmongJavacArgs()) { 397 err = "Sjavac does not handle explicit compilation of single .java files."; 398 } else if (options.getServerConf() == null) { 399 err = "No server configuration provided."; 400 } else if (!options.getImplicitPolicy().equals("none")) { 401 err = "The only allowed setting for sjavac is -implicit:none"; 402 } else if (options.getSources().isEmpty()) { 403 err = "You have to specify -src."; 404 } else if (options.getTranslationRules().size() > 1 405 && options.getGenSrcDir() == null) { 406 err = "You have translators but no gensrc dir (-s) specified!"; 407 } 408 409 if (err != null) 410 Log.error(err); 411 412 return err == null; 413 414 } 415 416 private static boolean createIfMissing(Path dir) { 417 418 if (Files.isDirectory(dir)) 419 return true; 420 421 if (Files.exists(dir)) { 422 Log.error(dir + " is not a directory."); 423 return false; 424 } 425 426 try { 427 Files.createDirectories(dir); 428 } catch (IOException e) { 429 Log.error("Could not create directory: " + e.getMessage()); 430 return false; 431 } 432 433 return true; 434 } 435 436 437 /** Find source files in the given source locations. */ 438 public static void findSourceFiles(List<SourceLocation> sourceLocations, 439 Set<String> sourceTypes, 440 Map<String,Source> foundFiles, 441 Map<String, Module> foundModules, 442 Module currentModule, 443 boolean permitSourcesInDefaultPackage, 444 boolean inLinksrc) { 445 446 for (SourceLocation source : sourceLocations) { 447 source.findSourceFiles(sourceTypes, 448 foundFiles, 449 foundModules, 450 currentModule, 451 permitSourcesInDefaultPackage, 452 inLinksrc); 453 } 454 } 455} 456