Thu, 20 Jun 2013 08:45:43 +0100
8016613: javac should avoid source 8 only analysis when compiling for source 7
Reviewed-by: jjg
Contributed-by: maurizio.cimadamore@oracle.com
1 /*
2 * Copyright (c) 2012, 2013, 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 */
26 package com.sun.tools.sjavac;
28 import java.io.File;
29 import java.io.IOException;
30 import java.io.PrintStream;
31 import java.util.*;
32 import java.util.regex.Matcher;
33 import java.util.regex.Pattern;
35 import com.sun.tools.sjavac.server.JavacServer;
37 /**
38 * The main class of the smart javac wrapper tool.
39 *
40 * <p><b>This is NOT part of any supported API.
41 * If you write code that depends on this, you do so at your own
42 * risk. This code and its internal interfaces are subject to change
43 * or deletion without notice.</b></p>
44 */
45 public class Main {
47 /* This is a smart javac wrapper primarily used when building the OpenJDK,
48 though other projects are welcome to use it too. But please be aware
49 that it is not an official api and will change in the future.
50 (We really mean it!)
52 Goals:
54 ** Create a state file, containing information about the build, so
55 that incremental builds only rebuild what is necessary. Also the
56 state file can be used by make/ant to detect when to trigger
57 a call to the smart javac wrapper.
59 This file is called bin/javac_state (assuming that you specified "-d bin")
60 Thus the simplest makefile is:
62 SJAVAC=java -cp .../tools.jar com.sun.tools.sjavac.Main
63 SRCS=$(shell find src -name "*.java")
64 bin/javac_state : $(SRCS)
65 $(SJAVAC) src -d bin
67 This makefile will run very fast and detect properly when Java code needs to
68 be recompiled. The smart javac wrapper will then use the information in java_state
69 to do an efficient incremental compile.
71 Previously it was near enough impossible to write an efficient makefile for Java
72 with support for incremental builds and dependency tracking.
74 ** Separate java sources to be compiled from java
75 sources used >only< for linking. The options:
77 "dir" points to root dir with sources to be compiled
78 "-sourcepath dir" points to root dir with sources used only for linking
79 "-classpath dir" points to dir with classes used only for linking (as before)
81 ** Use all cores for compilation by default.
82 "-j 4" limit the number of cores to 4.
83 For the moment, the sjavac server additionally limits the number of cores to three.
84 This will improve in the future when more sharing is performed between concurrent JavaCompilers.
86 ** Basic translation support from other sources to java, and then compilation of the generated java.
87 This functionality might be moved into annotation processors instead.
88 Again this is driven by the OpenJDK sources where properties and a few other types of files
89 are converted into Java sources regularily. The javac_state embraces copy and tr, and perform
90 incremental recompiles and copying for these as well. META-INF will be a special copy rule
91 that will copy any files found below any META-INF dir in src to the bin/META-INF dir.
92 "-copy .gif"
93 "-copy META-INF"
94 "-tr .prop=com.sun.tools.javac.smart.CompileProperties
95 "-tr .propp=com.sun.tools.javac.smart.CompileProperties,java.util.ListResourceBundle
96 "-tr .proppp=com.sun.tools.javac.smart.CompileProperties,sun.util.resources.LocaleNamesBundle
98 ** Control which classes in the src,sourcepath and classpath that javac is allowed to see.
99 Again, this is necessary to deal with the source code structure of the OpenJDK which is
100 intricate (read messy).
102 "-i tools.*" to include the tools package and all its subpackages in the build.
103 "-x tools.net.aviancarrier.*" to exclude the aviancarrier package and all its sources and subpackages.
104 "-x tools.net.drums" to exclude the drums package only, keep its subpackages.
105 "-xf tools/net/Bar.java" // Do not compile this file...
106 "-xf *Bor.java" // Do not compile Bor.java wherever it is found, BUT do compile ABor.java!
107 "-if tools/net/Bor.java" // Only compile this file...odd, but sometimes used.
109 ** The smart javac wrapper is driven by the modification time on the source files and compared
110 to the modification times written into the javac_state file.
112 It does not compare the modification time of the source with the modification time of the artifact.
113 However it will detect if the modification time of an artifact has changed compared to the java_state,
114 and this will trigger a delete of the artifact and a subsequent recompile of the source.
116 The smart javac wrapper is not a generic makefile/ant system. Its purpose is to compile java source
117 as the final step before the output dir is finalized and immediately jared, or jmodded. The output
118 dir should be considered opaque. Do not write into the outputdir yourself!
119 Any artifacts found in the outputdir that javac_state does not know of, will be deleted!
120 This can however be prevented, using the switch --permit-unidentified-artifacts
121 This switch is necessary when build the OpenJDK because its makefiles still write directly to
122 the output classes dirs.
124 Any makefile/ant rules that want to put contents into the outputdir should put the content
125 in one of several source roots. Static content that is under version control, can be put in the same source
126 code tree as the Java sources. Dynamic content that is generated by make/ant on the fly, should
127 be put in a separate gensrc_stuff root. The smart javac wrapper call will then take the arguments:
128 "gensrc_stuff src -d bin"
130 The command line:
131 java -cp tools.jar com.sun.tools.sjavac.Main \
132 -i "com.bar.*" -x "com.bar.foo.*" \
133 first_root \
134 -i "com.bar.foo.*" \
135 second_root \
136 -x "org.net.*" \
137 -sourcepath link_root_sources \
138 -classpath link_root_classes \
139 -d bin
141 Will compile all sources for package com.bar and its subpackages, found below first_root,
142 except the package com.bar.foo (and its subpackages), for which the sources are picked
143 from second_root instead. It will link against classes in link_root_classes and against
144 sources in link_root_sources, but will not see (try to link against) sources matching org.net.*
145 but will link against org.net* classes (if they exist) in link_root_classes.
147 (If you want a set of complex filter rules to be applied to several source directories, without
148 having to repeat the the filter rules for each root. You can use the explicit -src option. For example:
149 sjavac -x "com.foo.*" -src root1:root2:root3 )
151 The resulting classes are written into bin.
152 */
154 // This is the final destination for classes and copied files.
155 private File bin_dir;
156 // This is where the annotation process will put generated sources.
157 private File gensrc_dir;
158 // This is where javac -h puts the generated c-header files.
159 private File header_dir;
161 // This file contains the list of sources genereated by the makefile.
162 // We double check that our calculated list of sources matches this list,
163 // if not, then we terminate with an error!
164 private File makefile_source_list;
165 // The challenging task to manage an incremental build is done by javac_state.
166 private JavacState javac_state;
168 // The suffix rules tells you for example, that .java files should be compiled,
169 // and .html files should be copied and .properties files be translated.
170 Map<String,Transformer> suffix_rules;
172 public static void main(String... args) {
173 if (args.length > 0 && args[0].startsWith("--startserver:")) {
174 if (args.length>1) {
175 Log.error("When spawning a background server, only a single --startserver argument is allowed.");
176 return;
177 }
178 // Spawn a background server.
179 int rc = JavacServer.startServer(args[0], System.err);
180 System.exit(rc);
181 }
182 Main main = new Main();
183 int rc = main.go(args, System.out, System.err);
184 // Remove the portfile, but only if this background=false was used.
185 JavacServer.cleanup(args);
186 System.exit(rc);
187 }
189 private void printHelp() {
190 System.out.println("Usage: sjavac <options>\n"+
191 "where required options are:\n"+
192 "dir Compile all sources in dir recursively\n"+
193 "-d dir Store generated classes here and the javac_state file\n"+
194 "--server:portfile=/tmp/abc Use a background sjavac server\n\n"+
195 "All other arguments as javac, except -implicit:none which is forced by default.\n"+
196 "No java source files can be supplied on the command line, nor can an @file be supplied.\n\n"+
197 "Warning!\n"+
198 "This tool might disappear at any time, and its command line options might change at any time!");
199 }
201 public int go(String[] args, PrintStream out, PrintStream err) {
202 try {
203 if (args.length == 0 || findJavaSourceFiles(args) || findAtFile(args) || null==Util.findServerSettings(args)) {
204 printHelp();
205 return 0;
206 }
208 Log.setLogLevel(findLogLevel(args), out, err);
209 String server_settings = Util.findServerSettings(args);
210 args = verifyImplicitOption(args);
211 // Find the source root directories, and add the -src option before these, if not there already.
212 args = addSrcBeforeDirectories(args);
213 // Check that there is at least one -src supplied.
214 checkSrcOption(args);
215 // Check that there is one -d supplied.
216 bin_dir = findDirectoryOption(args,"-d","output", true, false, true);
217 gensrc_dir = findDirectoryOption(args,"-s","gensrc", false, false, true);
218 header_dir = findDirectoryOption(args,"-h","headers", false, false, true);
219 makefile_source_list = findFileOption(args,"--compare-found-sources","makefile source list", false);
221 // Load the prev build state database.
222 javac_state = JavacState.load(args, bin_dir, gensrc_dir, header_dir,
223 findBooleanOption(args, "--permit-unidentified-artifacts"), out, err);
225 // Setup the suffix rules from the command line.
226 suffix_rules = javac_state.getJavaSuffixRule();
227 findTranslateOptions(args, suffix_rules);
228 if (suffix_rules.keySet().size() > 1 && gensrc_dir == null) {
229 Log.error("You have translators but no gensrc dir (-s) specified!");
230 return -1;
231 }
232 findCopyOptions(args, suffix_rules);
234 // All found modules are put here.
235 Map<String,Module> modules = new HashMap<String,Module>();
236 // We start out in the legacy empty no-name module.
237 // As soon as we stumble on a module-info.java file we change to that module.
238 Module current_module = new Module("", "");
239 modules.put("", current_module);
241 // Find all sources, use the suffix rules to know which files are sources.
242 Map<String,Source> sources = new HashMap<String,Source>();
243 // Find the files, this will automatically populate the found modules
244 // with found packages where the sources are found!
245 findFiles(args, "-src", suffix_rules.keySet(), sources, modules, current_module, false);
247 if (sources.isEmpty()) {
248 Log.error("Found nothing to compile!");
249 return -1;
250 }
252 // Find all source files allowable for linking.
253 // We might find more modules here as well.
254 Map<String,Source> sources_to_link_to = new HashMap<String,Source>();
255 // Always reuse -src for linking as well! This means that we might
256 // get two -sourcepath on the commandline after the rewrite, which is
257 // fine. We can have as many as we like. You need to have separate -src/-sourcepath/-classpath
258 // if you need different filtering rules for different roots. If you have the same filtering
259 // rules for all sourcepath roots, you can concatenate them using :(;) as before.
260 rewriteOptions(args, "-src", "-sourcepath");
261 findFiles(args, "-sourcepath", Util.set(".java"), sources_to_link_to, modules, current_module, true);
263 // Find all class files allowable for linking.
264 // And pickup knowledge of all modules found here.
265 // This cannot currently filter classes inside jar files.
266 // Map<String,Source> classes_to_link_to = new HashMap<String,Source>();
267 // findFiles(args, "-classpath", Util.set(".class"), classes_to_link_to, modules, current_module, true);
269 // Find all module sources allowable for linking.
270 // Map<String,Source> modules_to_link_to = new HashMap<String,Source>();
271 // findFiles(args, "-modulepath", Util.set(".class"), modules_to_link_to, modules, current_module, true);
273 // Add the set of sources to the build database.
274 javac_state.now().collectPackagesSourcesAndArtifacts(modules);
275 javac_state.now().checkInternalState("checking sources", false, sources);
276 javac_state.now().checkInternalState("checking linked sources", true, sources_to_link_to);
277 javac_state.setVisibleSources(sources_to_link_to);
279 // If there is any change in the source files, taint packages
280 // and mark the database in need of saving.
281 javac_state.checkSourceStatus(false);
283 // Find all existing artifacts. Their timestamp will match the last modified timestamps stored
284 // in javac_state, simply because loading of the JavacState will clean out all artifacts
285 // that do not match the javac_state database.
286 javac_state.findAllArtifacts();
288 // Remove unidentified artifacts from the bin, gensrc and header dirs.
289 // (Unless we allow them to be there.)
290 // I.e. artifacts that are not known according to the build database (javac_state).
291 // For examples, files that have been manually copied into these dirs.
292 // Artifacts with bad timestamps (ie the on disk timestamp does not match the timestamp
293 // in javac_state) have already been removed when the javac_state was loaded.
294 if (!findBooleanOption(args, "--permit-unidentified-artifacts")) {
295 javac_state.removeUnidentifiedArtifacts();
296 }
297 // Go through all sources and taint all packages that miss artifacts.
298 javac_state.taintPackagesThatMissArtifacts();
300 // Now clean out all known artifacts belonging to tainted packages.
301 javac_state.deleteClassArtifactsInTaintedPackages();
302 // Copy files, for example property files, images files, xml files etc etc.
303 javac_state.performCopying(bin_dir, suffix_rules);
304 // Translate files, for example compile properties or compile idls.
305 javac_state.performTranslation(gensrc_dir, suffix_rules);
306 // Add any potentially generated java sources to the tobe compiled list.
307 // (Generated sources must always have a package.)
308 Map<String,Source> generated_sources = new HashMap<String,Source>();
309 Source.scanRoot(gensrc_dir, Util.set(".java"), null, null, null, null,
310 generated_sources, modules, current_module, false, true, false);
311 javac_state.now().collectPackagesSourcesAndArtifacts(modules);
312 // Recheck the the source files and their timestamps again.
313 javac_state.checkSourceStatus(true);
315 // Now do a safety check that the list of source files is identical
316 // to the list Make believes we are compiling. If we do not get this
317 // right, then incremental builds will fail with subtility.
318 // If any difference is detected, then we will fail hard here.
319 // This is an important safety net.
320 javac_state.compareWithMakefileList(makefile_source_list);
322 // Do the compilations, repeatedly until no tainted packages exist.
323 boolean again;
324 // Collect the name of all compiled packages.
325 Set<String> recently_compiled = new HashSet<String>();
326 boolean[] rc = new boolean[1];
327 do {
328 // Clean out artifacts in tainted packages.
329 javac_state.deleteClassArtifactsInTaintedPackages();
330 again = javac_state.performJavaCompilations(bin_dir, server_settings, args, recently_compiled, rc);
331 if (!rc[0]) break;
332 } while (again);
333 // Only update the state if the compile went well.
334 if (rc[0]) {
335 javac_state.save();
336 // Collect all the artifacts.
337 javac_state.now().collectArtifacts(modules);
338 // Remove artifacts that were generated during the last compile, but not this one.
339 javac_state.removeSuperfluousArtifacts(recently_compiled);
340 }
341 return rc[0] ? 0 : -1;
342 } catch (ProblemException e) {
343 Log.error(e.getMessage());
344 return -1;
345 } catch (Exception e) {
346 e.printStackTrace(err);
347 return -1;
348 }
349 }
351 /**
352 * Are java source files passed on the command line?
353 */
354 private boolean findJavaSourceFiles(String[] args) {
355 String prev = "";
356 for (String s : args) {
357 if (s.endsWith(".java") && !prev.equals("-xf") && !prev.equals("-if")) {
358 return true;
359 }
360 prev = s;
361 }
362 return false;
363 }
365 /**
366 * Is an at file passed on the command line?
367 */
368 private boolean findAtFile(String[] args) {
369 for (String s : args) {
370 if (s.startsWith("@")) {
371 return true;
372 }
373 }
374 return false;
375 }
377 /**
378 * Find the log level setting.
379 */
380 private String findLogLevel(String[] args) {
381 for (String s : args) {
382 if (s.startsWith("--log=") && s.length()>6) {
383 return s.substring(6);
384 }
385 if (s.equals("-verbose")) {
386 return "info";
387 }
388 }
389 return "info";
390 }
392 /**
393 * Remove smart javac wrapper arguments, before feeding
394 * the args to the plain javac.
395 */
396 static String[] removeWrapperArgs(String[] args) {
397 String[] out = new String[args.length];
398 // The first source path index is remembered
399 // here. So that all following can be concatenated to it.
400 int source_path = -1;
401 // The same for class path.
402 int class_path = -1;
403 // And module path.
404 int module_path = -1;
405 int j = 0;
406 for (int i = 0; i<args.length; ++i) {
407 if (args[i].equals("-src") ||
408 args[i].equals("-x") ||
409 args[i].equals("-i") ||
410 args[i].equals("-xf") ||
411 args[i].equals("-if") ||
412 args[i].equals("-copy") ||
413 args[i].equals("-tr") ||
414 args[i].equals("-j")) {
415 // Just skip it and skip following value
416 i++;
417 } else if (args[i].startsWith("--server:")) {
418 // Just skip it.
419 } else if (args[i].startsWith("--log=")) {
420 // Just skip it.
421 } else if (args[i].equals("--permit-unidentified-artifacts")) {
422 // Just skip it.
423 } else if (args[i].equals("--permit-sources-without-package")) {
424 // Just skip it.
425 } else if (args[i].equals("--compare-found-sources")) {
426 // Just skip it and skip verify file name
427 i++;
428 } else if (args[i].equals("-sourcepath")) {
429 if (source_path == -1) {
430 source_path = j;
431 out[j] = args[i];
432 out[j+1] = args[i+1];
433 j+=2;
434 i++;
435 } else {
436 // Skip this and its argument, but
437 // append argument to found sourcepath.
438 out[source_path+1] = out[source_path+1]+File.pathSeparatorChar+args[i+1];
439 i++;
440 }
441 } else if (args[i].equals("-classpath") || args[i].equals("-cp")) {
442 if (class_path == -1) {
443 class_path = j;
444 out[j] = args[i];
445 out[j+1] = args[i+1];
446 j+=2;
447 i++;
448 } else {
449 // Skip this and its argument, but
450 // append argument to found sourcepath.
451 out[class_path+1] = out[class_path+1]+File.pathSeparatorChar+args[i+1];
452 i++;
453 }
454 } else if (args[i].equals("-modulepath")) {
455 if (module_path == -1) {
456 module_path = j;
457 out[j] = args[i];
458 out[j+1] = args[i+1];
459 j+=2;
460 i++;
461 } else {
462 // Skip this and its argument, but
463 // append argument to found sourcepath.
464 out[module_path+1] = out[module_path+1]+File.pathSeparatorChar+args[i+1];
465 i++;
466 }
467 } else {
468 // Copy argument.
469 out[j] = args[i];
470 j++;
471 }
472 }
473 String[] ret = new String[j];
474 System.arraycopy(out, 0, ret, 0, j);
475 return ret;
476 }
478 /**
479 * Make sure directory exist, create it if not.
480 */
481 private static boolean makeSureExists(File dir) {
482 // Make sure the dest directories exist.
483 if (!dir.exists()) {
484 if (!dir.mkdirs()) {
485 Log.error("Could not create the directory "+dir.getPath());
486 return false;
487 }
488 }
489 return true;
490 }
492 /**
493 * Verify that a package pattern is valid.
494 */
495 private static void checkPattern(String s) throws ProblemException {
496 // Package names like foo.bar.gamma are allowed, and
497 // package names suffixed with .* like foo.bar.* are
498 // also allowed.
499 Pattern p = Pattern.compile("[a-zA-Z_]{1}[a-zA-Z0-9_]*(\\.[a-zA-Z_]{1}[a-zA-Z0-9_]*)*(\\.\\*)?+");
500 Matcher m = p.matcher(s);
501 if (!m.matches()) {
502 throw new ProblemException("The string \""+s+"\" is not a proper package name pattern.");
503 }
504 }
506 /**
507 * Verify that a translate pattern is valid.
508 */
509 private static void checkTranslatePattern(String s) throws ProblemException {
510 // .prop=com.sun.tools.javac.smart.CompileProperties
511 // .idl=com.sun.corba.CompileIdl
512 // .g3=antlr.CompileGrammar,debug=true
513 Pattern p = Pattern.compile(
514 "\\.[a-zA-Z_]{1}[a-zA-Z0-9_]*=[a-z_]{1}[a-z0-9_]*(\\.[a-z_]{1}[a-z0-9_]*)*"+
515 "(\\.[a-zA-Z_]{1}[a-zA-Z0-9_]*)(,.*)?");
516 Matcher m = p.matcher(s);
517 if (!m.matches()) {
518 throw new ProblemException("The string \""+s+"\" is not a proper translate pattern.");
519 }
520 }
522 /**
523 * Verify that a copy pattern is valid.
524 */
525 private static void checkCopyPattern(String s) throws ProblemException {
526 // .gif
527 // .html
528 Pattern p = Pattern.compile(
529 "\\.[a-zA-Z_]{1}[a-zA-Z0-9_]*");
530 Matcher m = p.matcher(s);
531 if (!m.matches()) {
532 throw new ProblemException("The string \""+s+"\" is not a proper suffix.");
533 }
534 }
536 /**
537 * Verify that a source file name is valid.
538 */
539 private static void checkFilePattern(String s) throws ProblemException {
540 // File names like foo/bar/gamma/Bar.java are allowed,
541 // as well as /bar/jndi.properties as well as,
542 // */bar/Foo.java
543 Pattern p = null;
544 if (File.separatorChar == '\\') {
545 p = Pattern.compile("\\*?(.+\\\\)*.+");
546 }
547 else if (File.separatorChar == '/') {
548 p = Pattern.compile("\\*?(.+/)*.+");
549 } else {
550 throw new ProblemException("This platform uses the unsupported "+File.separatorChar+
551 " as file separator character. Please add support for it!");
552 }
553 Matcher m = p.matcher(s);
554 if (!m.matches()) {
555 throw new ProblemException("The string \""+s+"\" is not a proper file name.");
556 }
557 }
559 /**
560 * Scan the arguments to find an option is used.
561 */
562 private static boolean hasOption(String[] args, String option) {
563 for (String a : args) {
564 if (a.equals(option)) return true;
565 }
566 return false;
567 }
569 /**
570 * Check if -implicit is supplied, if so check that it is none.
571 * If -implicit is not supplied, supply -implicit:none
572 * Only implicit:none is allowed because otherwise the multicore compilations
573 * and dependency tracking will be tangled up.
574 */
575 private static String[] verifyImplicitOption(String[] args)
576 throws ProblemException {
578 boolean foundImplicit = false;
579 for (String a : args) {
580 if (a.startsWith("-implicit:")) {
581 foundImplicit = true;
582 if (!a.equals("-implicit:none")) {
583 throw new ProblemException("The only allowed setting for sjavac is -implicit:none, it is also the default.");
584 }
585 }
586 }
587 if (foundImplicit) {
588 return args;
589 }
590 // -implicit:none not found lets add it.
591 String[] newargs = new String[args.length+1];
592 System.arraycopy(args,0, newargs, 0, args.length);
593 newargs[args.length] = "-implicit:none";
594 return newargs;
595 }
597 /**
598 * Rewrite a single option into something else.
599 */
600 private static void rewriteOptions(String[] args, String option, String new_option) {
601 for (int i=0; i<args.length; ++i) {
602 if (args[i].equals(option)) {
603 args[i] = new_option;
604 }
605 }
606 }
608 /**
609 * Scan the arguments to find an option that specifies a directory.
610 * Create the directory if necessary.
611 */
612 private static File findDirectoryOption(String[] args, String option, String name, boolean needed, boolean allow_dups, boolean create)
613 throws ProblemException, ProblemException {
614 File dir = null;
615 for (int i = 0; i<args.length; ++i) {
616 if (args[i].equals(option)) {
617 if (dir != null) {
618 throw new ProblemException("You have already specified the "+name+" dir!");
619 }
620 if (i+1 >= args.length) {
621 throw new ProblemException("You have to specify a directory following "+option+".");
622 }
623 if (args[i+1].indexOf(File.pathSeparatorChar) != -1) {
624 throw new ProblemException("You must only specify a single directory for "+option+".");
625 }
626 dir = new File(args[i+1]);
627 if (!dir.exists()) {
628 if (!create) {
629 throw new ProblemException("This directory does not exist: "+dir.getPath());
630 } else
631 if (!makeSureExists(dir)) {
632 throw new ProblemException("Cannot create directory "+dir.getPath());
633 }
634 }
635 if (!dir.isDirectory()) {
636 throw new ProblemException("\""+args[i+1]+"\" is not a directory.");
637 }
638 }
639 }
640 if (dir == null && needed) {
641 throw new ProblemException("You have to specify "+option);
642 }
643 try {
644 if (dir != null)
645 return dir.getCanonicalFile();
646 } catch (IOException e) {
647 throw new ProblemException(""+e);
648 }
649 return null;
650 }
652 /**
653 * Option is followed by path.
654 */
655 private static boolean shouldBeFollowedByPath(String o) {
656 return o.equals("-s") ||
657 o.equals("-h") ||
658 o.equals("-d") ||
659 o.equals("-sourcepath") ||
660 o.equals("-classpath") ||
661 o.equals("-cp") ||
662 o.equals("-bootclasspath") ||
663 o.equals("-src");
664 }
666 /**
667 * Add -src before source root directories if not already there.
668 */
669 private static String[] addSrcBeforeDirectories(String[] args) {
670 List<String> newargs = new ArrayList<String>();
671 for (int i = 0; i<args.length; ++i) {
672 File dir = new File(args[i]);
673 if (dir.exists() && dir.isDirectory()) {
674 if (i == 0 || !shouldBeFollowedByPath(args[i-1])) {
675 newargs.add("-src");
676 }
677 }
678 newargs.add(args[i]);
679 }
680 return newargs.toArray(new String[0]);
681 }
683 /**
684 * Check the -src options.
685 */
686 private static void checkSrcOption(String[] args)
687 throws ProblemException {
688 Set<File> dirs = new HashSet<File>();
689 for (int i = 0; i<args.length; ++i) {
690 if (args[i].equals("-src")) {
691 if (i+1 >= args.length) {
692 throw new ProblemException("You have to specify a directory following -src.");
693 }
694 StringTokenizer st = new StringTokenizer(args[i+1], File.pathSeparator);
695 while (st.hasMoreElements()) {
696 File dir = new File(st.nextToken());
697 if (!dir.exists()) {
698 throw new ProblemException("This directory does not exist: "+dir.getPath());
699 }
700 if (!dir.isDirectory()) {
701 throw new ProblemException("\""+dir.getPath()+"\" is not a directory.");
702 }
703 if (dirs.contains(dir)) {
704 throw new ProblemException("The src directory \""+dir.getPath()+"\" is specified more than once!");
705 }
706 dirs.add(dir);
707 }
708 }
709 }
710 if (dirs.isEmpty()) {
711 throw new ProblemException("You have to specify -src.");
712 }
713 }
715 /**
716 * Scan the arguments to find an option that specifies a file.
717 */
718 private static File findFileOption(String[] args, String option, String name, boolean needed)
719 throws ProblemException, ProblemException {
720 File file = null;
721 for (int i = 0; i<args.length; ++i) {
722 if (args[i].equals(option)) {
723 if (file != null) {
724 throw new ProblemException("You have already specified the "+name+" file!");
725 }
726 if (i+1 >= args.length) {
727 throw new ProblemException("You have to specify a file following "+option+".");
728 }
729 file = new File(args[i+1]);
730 if (file.isDirectory()) {
731 throw new ProblemException("\""+args[i+1]+"\" is not a file.");
732 }
733 if (!file.exists() && needed) {
734 throw new ProblemException("The file \""+args[i+1]+"\" does not exist.");
735 }
737 }
738 }
739 if (file == null && needed) {
740 throw new ProblemException("You have to specify "+option);
741 }
742 return file;
743 }
745 /**
746 * Look for a specific switch, return true if found.
747 */
748 public static boolean findBooleanOption(String[] args, String option) {
749 for (int i = 0; i<args.length; ++i) {
750 if (args[i].equals(option)) return true;
751 }
752 return false;
753 }
755 /**
756 * Scan the arguments to find an option that specifies a number.
757 */
758 public static int findNumberOption(String[] args, String option) {
759 int rc = 0;
760 for (int i = 0; i<args.length; ++i) {
761 if (args[i].equals(option)) {
762 if (args.length > i+1) {
763 rc = Integer.parseInt(args[i+1]);
764 }
765 }
766 }
767 return rc;
768 }
770 /**
771 * Scan the arguments to find the option (-tr) that setup translation rules to java source
772 * from different sources. For example: .properties are translated using CompileProperties
773 * The found translators are stored as suffix rules.
774 */
775 private static void findTranslateOptions(String[] args, Map<String,Transformer> suffix_rules)
776 throws ProblemException, ProblemException {
778 for (int i = 0; i<args.length; ++i) {
779 if (args[i].equals("-tr")) {
780 if (i+1 >= args.length) {
781 throw new ProblemException("You have to specify a translate rule following -tr.");
782 }
783 String s = args[i+1];
784 checkTranslatePattern(s);
785 int ep = s.indexOf("=");
786 String suffix = s.substring(0,ep);
787 String classname = s.substring(ep+1);
788 if (suffix_rules.get(suffix) != null) {
789 throw new ProblemException("You have already specified a "+
790 "rule for the suffix "+suffix);
791 }
792 if (s.equals(".class")) {
793 throw new ProblemException("You cannot have a translator for .class files!");
794 }
795 if (s.equals(".java")) {
796 throw new ProblemException("You cannot have a translator for .java files!");
797 }
798 String extra = null;
799 int exp = classname.indexOf(",");
800 if (exp != -1) {
801 extra = classname.substring(exp+1);
802 classname = classname.substring(0,exp);
803 }
804 try {
805 Class<?> cl = Class.forName(classname);
806 Transformer t = (Transformer)cl.newInstance();
807 t.setExtra(extra);
808 suffix_rules.put(suffix, t);
809 }
810 catch (Exception e) {
811 throw new ProblemException("Cannot use "+classname+" as a translator!");
812 }
813 }
814 }
815 }
817 /**
818 * Scan the arguments to find the option (-copy) that setup copying rules into the bin dir.
819 * For example: -copy .html
820 * The found copiers are stored as suffix rules as well. No translation is done, just copying.
821 */
822 private void findCopyOptions(String[] args, Map<String,Transformer> suffix_rules)
823 throws ProblemException, ProblemException {
825 for (int i = 0; i<args.length; ++i) {
826 if (args[i].equals("-copy")) {
827 if (i+1 >= args.length) {
828 throw new ProblemException("You have to specify a translate rule following -tr.");
829 }
830 String s = args[i+1];
831 checkCopyPattern(s);
832 if (suffix_rules.get(s) != null) {
833 throw new ProblemException("You have already specified a "+
834 "rule for the suffix "+s);
835 }
836 if (s.equals(".class")) {
837 throw new ProblemException("You cannot have a copy rule for .class files!");
838 }
839 if (s.equals(".java")) {
840 throw new ProblemException("You cannot have a copy rule for .java files!");
841 }
842 suffix_rules.put(s, javac_state.getCopier());
843 }
844 }
845 }
847 /**
848 * Rewrite a / separated path into \ separated, but only
849 * if we are running on a platform were File.separatorChar=='\', ie winapi.
850 */
851 private String fixupSeparator(String p) {
852 if (File.separatorChar == '/') return p;
853 return p.replaceAll("/", "\\\\");
854 }
856 /**
857 * Scan the arguments for -i -x -xf -if followed by the option
858 * -src, -sourcepath, -modulepath or -classpath and produce a map of all the
859 * files to referenced for that particular option.
860 *
861 * Store the found sources and the found modules in the supplied maps.
862 */
863 private boolean findFiles(String[] args, String option, Set<String> suffixes,
864 Map<String,Source> found_files, Map<String, Module> found_modules,
865 Module current_module, boolean inLinksrc)
866 throws ProblemException, ProblemException
867 {
868 // Track which source roots, source path roots and class path roots have been added.
869 Set<File> roots = new HashSet<File>();
870 // Track the current set of package includes,excludes as well as excluded source files,
871 // to be used in the next -src/-sourcepath/-classpath
872 List<String> includes = new LinkedList<String>();
873 List<String> excludes = new LinkedList<String>();
874 List<String> excludefiles = new LinkedList<String>();
875 List<String> includefiles = new LinkedList<String>();
876 // This include is used to find all modules in the source.
877 List<String> moduleinfo = new LinkedList<String>();
878 moduleinfo.add("module-info.java");
880 for (int i = 0; i<args.length; ++i) {
881 if (args[i].equals("-i")) {
882 if (i+1 >= args.length) {
883 throw new ProblemException("You have to specify a package pattern following -i");
884 }
885 String incl = args[i+1];
886 checkPattern(incl);
887 includes.add(incl);
888 }
889 if (args[i].equals("-x")) {
890 if (i+1 >= args.length) {
891 throw new ProblemException("You have to specify a package pattern following -x");
892 }
893 String excl = args[i+1];
894 checkPattern(excl);
895 excludes.add(excl);
896 }
897 if (args[i].equals("-xf")) {
898 if (i+1 >= args.length) {
899 throw new ProblemException("You have to specify a file following -xf");
900 }
901 String exclf = args[i+1];
902 checkFilePattern(exclf);
903 exclf = Util.normalizeDriveLetter(exclf);
904 excludefiles.add(fixupSeparator(exclf));
905 }
906 if (args[i].equals("-if")) {
907 if (i+1 >= args.length) {
908 throw new ProblemException("You have to specify a file following -xf");
909 }
910 String inclf = args[i+1];
911 checkFilePattern(inclf);
912 inclf = Util.normalizeDriveLetter(inclf);
913 includefiles.add(fixupSeparator(inclf));
914 }
915 if (args[i].equals(option)) {
916 if (i+1 >= args.length) {
917 throw new ProblemException("You have to specify a directory following "+option);
918 }
919 String[] root_dirs = args[i+1].split(File.pathSeparator);
920 for (String r : root_dirs) {
921 File root = new File(r);
922 if (!root.isDirectory()) {
923 throw new ProblemException("\""+r+"\" is not a directory.");
924 }
925 try {
926 root = root.getCanonicalFile();
927 } catch (IOException e) {
928 throw new ProblemException(""+e);
929 }
930 if (roots.contains(root)) {
931 throw new ProblemException("\""+r+"\" has already been used for "+option);
932 }
933 if (root.equals(bin_dir)) {
934 throw new ProblemException("\""+r+"\" cannot be used both for "+option+" and -d");
935 }
936 if (root.equals(gensrc_dir)) {
937 throw new ProblemException("\""+r+"\" cannot be used both for "+option+" and -s");
938 }
939 if (root.equals(header_dir)) {
940 throw new ProblemException("\""+r+"\" cannot be used both for "+option+" and -h");
941 }
942 roots.add(root);
943 Source.scanRoot(root, suffixes, excludes, includes, excludefiles, includefiles,
944 found_files, found_modules, current_module,
945 findBooleanOption(args, "--permit-sources-without-package"),
946 false, inLinksrc);
947 }
948 }
949 if (args[i].equals("-src") ||
950 args[i].equals("-sourcepath") ||
951 args[i].equals("-modulepath") ||
952 args[i].equals("-classpath") ||
953 args[i].equals("-cp"))
954 {
955 // Reset the includes,excludes and excludefiles after they have been used.
956 includes = new LinkedList<String>();
957 excludes = new LinkedList<String>();
958 excludefiles = new LinkedList<String>();
959 includefiles = new LinkedList<String>();
960 }
961 }
962 return true;
963 }
965 }