Mon, 04 Feb 2013 18:08:53 -0500
Merge
1 /*
2 * Copyright (c) 2012, 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.util.HashMap;
30 import java.util.HashSet;
31 import java.util.LinkedList;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Set;
35 import java.util.regex.Matcher;
36 import java.util.regex.Pattern;
37 import com.sun.tools.sjavac.server.JavacServer;
38 import java.io.IOException;
39 import java.io.PrintStream;
40 import java.util.*;
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
47 * risk. This code and its internal interfaces are subject to change
48 * or deletion without notice.</b></p>
49 */
50 public class Main {
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!)
57 Goals:
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.
64 This file is called bin/javac_state (assuming that you specified "-d bin")
65 Thus the simplest makefile is:
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
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.
76 Previously it was near enough impossible to write an efficient makefile for Java
77 with support for incremental builds and dependency tracking.
79 ** Separate java sources to be compiled from java
80 sources used >only< for linking. The options:
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)
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.
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
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).
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.
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.
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.
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.
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"
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
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.
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 )
156 The resulting classes are written into bin.
157 */
159 // This is the final destination for classes and copied files.
160 private File bin_dir;
161 // This is where the annotation process will put generated sources.
162 private File gensrc_dir;
163 // This is where javac -h puts the generated c-header files.
164 private File header_dir;
166 // This file contains the list of sources genereated by the makefile.
167 // We double check that our calculated list of sources matches this list,
168 // if not, then we terminate with an error!
169 private File makefile_source_list;
170 // The challenging task to manage an incremental build is done by javac_state.
171 private JavacState javac_state;
173 // The suffix rules tells you for example, that .java files should be compiled,
174 // and .html files should be copied and .properties files be translated.
175 Map<String,Transformer> suffix_rules;
177 public static void main(String... args) {
178 if (args.length > 0 && args[0].startsWith("--startserver:")) {
179 if (args.length>1) {
180 Log.error("When spawning a background server, only a single --startserver argument is allowed.");
181 return;
182 }
183 // Spawn a background server.
184 int rc = JavacServer.startServer(args[0], System.err);
185 System.exit(rc);
186 }
187 Main main = new Main();
188 int rc = main.go(args, System.out, System.err);
189 // Remove the portfile, but only if this background=false was used.
190 JavacServer.cleanup(args);
191 System.exit(rc);
192 }
194 private void printHelp() {
195 System.out.println("Usage: sjavac <options>\n"+
196 "where required options are:\n"+
197 "dir Compile all sources in dir recursively\n"+
198 "-d dir Store generated classes here and the javac_state file\n"+
199 "--server:portfile=/tmp/abc Use a background sjavac server\n\n"+
200 "All other arguments as javac, except -implicit:none which is forced by default.\n"+
201 "No java source files can be supplied on the command line, nor can an @file be supplied.\n\n"+
202 "Warning!\n"+
203 "This tool might disappear at any time, and its command line options might change at any time!");
204 }
206 public int go(String[] args, PrintStream out, PrintStream err) {
207 try {
208 if (args.length == 0 || findJavaSourceFiles(args) || findAtFile(args) || null==Util.findServerSettings(args)) {
209 printHelp();
210 return 0;
211 }
213 Log.setLogLevel(findLogLevel(args), out, err);
214 String server_settings = Util.findServerSettings(args);
215 args = verifyImplicitOption(args);
216 // Find the source root directories, and add the -src option before these, if not there already.
217 args = addSrcBeforeDirectories(args);
218 // Check that there is at least one -src supplied.
219 checkSrcOption(args);
220 // Check that there is one -d supplied.
221 bin_dir = findDirectoryOption(args,"-d","output", true, false, true);
222 gensrc_dir = findDirectoryOption(args,"-s","gensrc", false, false, true);
223 header_dir = findDirectoryOption(args,"-h","headers", false, false, true);
224 makefile_source_list = findFileOption(args,"--compare-found-sources","makefile source list", false);
226 // Load the prev build state database.
227 javac_state = JavacState.load(args, bin_dir, gensrc_dir, header_dir,
228 findBooleanOption(args, "--permit-unidentified-artifacts"), out, err);
230 // Setup the suffix rules from the command line.
231 suffix_rules = javac_state.getJavaSuffixRule();
232 findTranslateOptions(args, suffix_rules);
233 if (suffix_rules.keySet().size() > 1 && gensrc_dir == null) {
234 Log.error("You have translators but no gensrc dir (-s) specified!");
235 return -1;
236 }
237 findCopyOptions(args, suffix_rules);
239 // All found modules are put here.
240 Map<String,Module> modules = new HashMap<String,Module>();
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);
246 // Find all sources, use the suffix rules to know which files are sources.
247 Map<String,Source> sources = new HashMap<String,Source>();
248 // Find the files, this will automatically populate the found modules
249 // with found packages where the sources are found!
250 findFiles(args, "-src", suffix_rules.keySet(), sources, modules, current_module, false);
252 if (sources.isEmpty()) {
253 Log.error("Found nothing to compile!");
254 return -1;
255 }
257 // Find all source files allowable for linking.
258 // We might find more modules here as well.
259 Map<String,Source> sources_to_link_to = new HashMap<String,Source>();
260 // Always reuse -src for linking as well! This means that we might
261 // get two -sourcepath on the commandline after the rewrite, which is
262 // fine. We can have as many as we like. You need to have separate -src/-sourcepath/-classpath
263 // if you need different filtering rules for different roots. If you have the same filtering
264 // rules for all sourcepath roots, you can concatenate them using :(;) as before.
265 rewriteOptions(args, "-src", "-sourcepath");
266 findFiles(args, "-sourcepath", Util.set(".java"), sources_to_link_to, modules, current_module, true);
268 // Find all class files allowable for linking.
269 // And pickup knowledge of all modules found here.
270 // This cannot currently filter classes inside jar files.
271 Map<String,Source> classes_to_link_to = new HashMap<String,Source>();
272 // findFiles(args, "-classpath", Util.set(".class"), classes_to_link_to, modules, current_module, true);
274 // Find all module sources allowable for linking.
275 Map<String,Source> modules_to_link_to = new HashMap<String,Source>();
276 // findFiles(args, "-modulepath", Util.set(".class"), modules_to_link_to, modules, current_module, true);
278 // Add the set of sources to the build database.
279 javac_state.now().collectPackagesSourcesAndArtifacts(modules);
280 javac_state.now().checkInternalState("checking sources", false, sources);
281 javac_state.now().checkInternalState("checking linked sources", true, sources_to_link_to);
282 javac_state.setVisibleSources(sources_to_link_to);
284 // If there is any change in the source files, taint packages
285 // and mark the database in need of saving.
286 javac_state.checkSourceStatus(false);
288 // Find all existing artifacts. Their timestamp will match the last modified timestamps stored
289 // in javac_state, simply because loading of the JavacState will clean out all artifacts
290 // that do not match the javac_state database.
291 javac_state.findAllArtifacts();
293 // Remove unidentified artifacts from the bin, gensrc and header dirs.
294 // (Unless we allow them to be there.)
295 // I.e. artifacts that are not known according to the build database (javac_state).
296 // For examples, files that have been manually copied into these dirs.
297 // Artifacts with bad timestamps (ie the on disk timestamp does not match the timestamp
298 // in javac_state) have already been removed when the javac_state was loaded.
299 if (!findBooleanOption(args, "--permit-unidentified-artifacts")) {
300 javac_state.removeUnidentifiedArtifacts();
301 }
302 // Go through all sources and taint all packages that miss artifacts.
303 javac_state.taintPackagesThatMissArtifacts();
305 // Now clean out all known artifacts belonging to tainted packages.
306 javac_state.deleteClassArtifactsInTaintedPackages();
307 // Copy files, for example property files, images files, xml files etc etc.
308 javac_state.performCopying(bin_dir, suffix_rules);
309 // Translate files, for example compile properties or compile idls.
310 javac_state.performTranslation(gensrc_dir, suffix_rules);
311 // Add any potentially generated java sources to the tobe compiled list.
312 // (Generated sources must always have a package.)
313 Map<String,Source> generated_sources = new HashMap<String,Source>();
314 Source.scanRoot(gensrc_dir, Util.set(".java"), null, null, null, null,
315 generated_sources, modules, current_module, false, true, false);
316 javac_state.now().collectPackagesSourcesAndArtifacts(modules);
317 // Recheck the the source files and their timestamps again.
318 javac_state.checkSourceStatus(true);
320 // Now do a safety check that the list of source files is identical
321 // to the list Make believes we are compiling. If we do not get this
322 // right, then incremental builds will fail with subtility.
323 // If any difference is detected, then we will fail hard here.
324 // This is an important safety net.
325 javac_state.compareWithMakefileList(makefile_source_list);
327 // Do the compilations, repeatedly until no tainted packages exist.
328 boolean again;
329 // Collect the name of all compiled packages.
330 Set<String> recently_compiled = new HashSet<String>();
331 boolean[] rc = new boolean[1];
332 do {
333 // Clean out artifacts in tainted packages.
334 javac_state.deleteClassArtifactsInTaintedPackages();
335 again = javac_state.performJavaCompilations(bin_dir, server_settings, args, recently_compiled, rc);
336 if (!rc[0]) break;
337 } while (again);
338 // Only update the state if the compile went well.
339 if (rc[0]) {
340 javac_state.save();
341 // Collect all the artifacts.
342 javac_state.now().collectArtifacts(modules);
343 // Remove artifacts that were generated during the last compile, but not this one.
344 javac_state.removeSuperfluousArtifacts(recently_compiled);
345 }
346 return rc[0] ? 0 : -1;
347 } catch (ProblemException e) {
348 Log.error(e.getMessage());
349 return -1;
350 } catch (Exception e) {
351 e.printStackTrace(err);
352 return -1;
353 }
354 }
356 /**
357 * Are java source files passed on the command line?
358 */
359 private boolean findJavaSourceFiles(String[] args) {
360 String prev = "";
361 for (String s : args) {
362 if (s.endsWith(".java") && !prev.equals("-xf") && !prev.equals("-if")) {
363 return true;
364 }
365 prev = s;
366 }
367 return false;
368 }
370 /**
371 * Is an at file passed on the command line?
372 */
373 private boolean findAtFile(String[] args) {
374 for (String s : args) {
375 if (s.startsWith("@")) {
376 return true;
377 }
378 }
379 return false;
380 }
382 /**
383 * Find the log level setting.
384 */
385 private String findLogLevel(String[] args) {
386 for (String s : args) {
387 if (s.startsWith("--log=") && s.length()>6) {
388 return s.substring(6);
389 }
390 if (s.equals("-verbose")) {
391 return "info";
392 }
393 }
394 return "info";
395 }
397 /**
398 * Remove smart javac wrapper arguments, before feeding
399 * the args to the plain javac.
400 */
401 static String[] removeWrapperArgs(String[] args) {
402 String[] out = new String[args.length];
403 // The first source path index is remembered
404 // here. So that all following can be concatenated to it.
405 int source_path = -1;
406 // The same for class path.
407 int class_path = -1;
408 // And module path.
409 int module_path = -1;
410 int j = 0;
411 for (int i = 0; i<args.length; ++i) {
412 if (args[i].equals("-src") ||
413 args[i].equals("-x") ||
414 args[i].equals("-i") ||
415 args[i].equals("-xf") ||
416 args[i].equals("-if") ||
417 args[i].equals("-copy") ||
418 args[i].equals("-tr") ||
419 args[i].equals("-j")) {
420 // Just skip it and skip following value
421 i++;
422 } else if (args[i].startsWith("--server:")) {
423 // Just skip it.
424 } else if (args[i].startsWith("--log=")) {
425 // Just skip it.
426 } else if (args[i].equals("--permit-unidentified-artifacts")) {
427 // Just skip it.
428 } else if (args[i].equals("--permit-sources-without-package")) {
429 // Just skip it.
430 } else if (args[i].equals("--compare-found-sources")) {
431 // Just skip it and skip verify file name
432 i++;
433 } else if (args[i].equals("-sourcepath")) {
434 if (source_path == -1) {
435 source_path = j;
436 out[j] = args[i];
437 out[j+1] = args[i+1];
438 j+=2;
439 i++;
440 } else {
441 // Skip this and its argument, but
442 // append argument to found sourcepath.
443 out[source_path+1] = out[source_path+1]+File.pathSeparatorChar+args[i+1];
444 i++;
445 }
446 } else if (args[i].equals("-classpath")) {
447 if (class_path == -1) {
448 class_path = j;
449 out[j] = args[i];
450 out[j+1] = args[i+1];
451 j+=2;
452 i++;
453 } else {
454 // Skip this and its argument, but
455 // append argument to found sourcepath.
456 out[class_path+1] = out[class_path+1]+File.pathSeparatorChar+args[i+1];
457 i++;
458 }
459 } else if (args[i].equals("-modulepath")) {
460 if (module_path == -1) {
461 module_path = j;
462 out[j] = args[i];
463 out[j+1] = args[i+1];
464 j+=2;
465 i++;
466 } else {
467 // Skip this and its argument, but
468 // append argument to found sourcepath.
469 out[module_path+1] = out[module_path+1]+File.pathSeparatorChar+args[i+1];
470 i++;
471 }
472 } else {
473 // Copy argument.
474 out[j] = args[i];
475 j++;
476 }
477 }
478 String[] ret = new String[j];
479 System.arraycopy(out, 0, ret, 0, j);
480 return ret;
481 }
483 /**
484 * Make sure directory exist, create it if not.
485 */
486 private static boolean makeSureExists(File dir) {
487 // Make sure the dest directories exist.
488 if (!dir.exists()) {
489 if (!dir.mkdirs()) {
490 Log.error("Could not create the directory "+dir.getPath());
491 return false;
492 }
493 }
494 return true;
495 }
497 /**
498 * Verify that a package pattern is valid.
499 */
500 private static void checkPattern(String s) throws ProblemException {
501 // Package names like foo.bar.gamma are allowed, and
502 // package names suffixed with .* like foo.bar.* are
503 // also allowed.
504 Pattern p = Pattern.compile("[a-zA-Z_]{1}[a-zA-Z0-9_]*(\\.[a-zA-Z_]{1}[a-zA-Z0-9_]*)*(\\.\\*)?+");
505 Matcher m = p.matcher(s);
506 if (!m.matches()) {
507 throw new ProblemException("The string \""+s+"\" is not a proper package name pattern.");
508 }
509 }
511 /**
512 * Verify that a translate pattern is valid.
513 */
514 private static void checkTranslatePattern(String s) throws ProblemException {
515 // .prop=com.sun.tools.javac.smart.CompileProperties
516 // .idl=com.sun.corba.CompileIdl
517 // .g3=antlr.CompileGrammar,debug=true
518 Pattern p = Pattern.compile(
519 "\\.[a-zA-Z_]{1}[a-zA-Z0-9_]*=[a-z_]{1}[a-z0-9_]*(\\.[a-z_]{1}[a-z0-9_]*)*"+
520 "(\\.[a-zA-Z_]{1}[a-zA-Z0-9_]*)(,.*)?");
521 Matcher m = p.matcher(s);
522 if (!m.matches()) {
523 throw new ProblemException("The string \""+s+"\" is not a proper translate pattern.");
524 }
525 }
527 /**
528 * Verify that a copy pattern is valid.
529 */
530 private static void checkCopyPattern(String s) throws ProblemException {
531 // .gif
532 // .html
533 Pattern p = Pattern.compile(
534 "\\.[a-zA-Z_]{1}[a-zA-Z0-9_]*");
535 Matcher m = p.matcher(s);
536 if (!m.matches()) {
537 throw new ProblemException("The string \""+s+"\" is not a proper suffix.");
538 }
539 }
541 /**
542 * Verify that a source file name is valid.
543 */
544 private static void checkFilePattern(String s) throws ProblemException {
545 // File names like foo/bar/gamma/Bar.java are allowed,
546 // as well as /bar/jndi.properties as well as,
547 // */bar/Foo.java
548 Pattern p = null;
549 if (File.separatorChar == '\\') {
550 p = Pattern.compile("\\*?(.+\\\\)*.+");
551 }
552 else if (File.separatorChar == '/') {
553 p = Pattern.compile("\\*?(.+/)*.+");
554 } else {
555 throw new ProblemException("This platform uses the unsupported "+File.separatorChar+
556 " as file separator character. Please add support for it!");
557 }
558 Matcher m = p.matcher(s);
559 if (!m.matches()) {
560 throw new ProblemException("The string \""+s+"\" is not a proper file name.");
561 }
562 }
564 /**
565 * Scan the arguments to find an option is used.
566 */
567 private static boolean hasOption(String[] args, String option) {
568 for (String a : args) {
569 if (a.equals(option)) return true;
570 }
571 return false;
572 }
574 /**
575 * Check if -implicit is supplied, if so check that it is none.
576 * If -implicit is not supplied, supply -implicit:none
577 * Only implicit:none is allowed because otherwise the multicore compilations
578 * and dependency tracking will be tangled up.
579 */
580 private static String[] verifyImplicitOption(String[] args)
581 throws ProblemException {
583 boolean foundImplicit = false;
584 for (String a : args) {
585 if (a.startsWith("-implicit:")) {
586 foundImplicit = true;
587 if (!a.equals("-implicit:none")) {
588 throw new ProblemException("The only allowed setting for sjavac is -implicit:none, it is also the default.");
589 }
590 }
591 }
592 if (foundImplicit) {
593 return args;
594 }
595 // -implicit:none not found lets add it.
596 String[] newargs = new String[args.length+1];
597 System.arraycopy(args,0, newargs, 0, args.length);
598 newargs[args.length] = "-implicit:none";
599 return newargs;
600 }
602 /**
603 * Rewrite a single option into something else.
604 */
605 private static void rewriteOptions(String[] args, String option, String new_option) {
606 for (int i=0; i<args.length; ++i) {
607 if (args[i].equals(option)) {
608 args[i] = new_option;
609 }
610 }
611 }
613 /**
614 * Scan the arguments to find an option that specifies a directory.
615 * Create the directory if necessary.
616 */
617 private static File findDirectoryOption(String[] args, String option, String name, boolean needed, boolean allow_dups, boolean create)
618 throws ProblemException, ProblemException {
619 File dir = null;
620 for (int i = 0; i<args.length; ++i) {
621 if (args[i].equals(option)) {
622 if (dir != null) {
623 throw new ProblemException("You have already specified the "+name+" dir!");
624 }
625 if (i+1 >= args.length) {
626 throw new ProblemException("You have to specify a directory following "+option+".");
627 }
628 if (args[i+1].indexOf(File.pathSeparatorChar) != -1) {
629 throw new ProblemException("You must only specify a single directory for "+option+".");
630 }
631 dir = new File(args[i+1]);
632 if (!dir.exists()) {
633 if (!create) {
634 throw new ProblemException("This directory does not exist: "+dir.getPath());
635 } else
636 if (!makeSureExists(dir)) {
637 throw new ProblemException("Cannot create directory "+dir.getPath());
638 }
639 }
640 if (!dir.isDirectory()) {
641 throw new ProblemException("\""+args[i+1]+"\" is not a directory.");
642 }
643 }
644 }
645 if (dir == null && needed) {
646 throw new ProblemException("You have to specify "+option);
647 }
648 try {
649 if (dir != null)
650 return dir.getCanonicalFile();
651 } catch (IOException e) {
652 throw new ProblemException(""+e);
653 }
654 return null;
655 }
657 /**
658 * Option is followed by path.
659 */
660 private static boolean shouldBeFollowedByPath(String o) {
661 return o.equals("-s") ||
662 o.equals("-h") ||
663 o.equals("-d") ||
664 o.equals("-sourcepath") ||
665 o.equals("-classpath") ||
666 o.equals("-bootclasspath") ||
667 o.equals("-src");
668 }
670 /**
671 * Add -src before source root directories if not already there.
672 */
673 private static String[] addSrcBeforeDirectories(String[] args) {
674 List<String> newargs = new ArrayList<String>();
675 for (int i = 0; i<args.length; ++i) {
676 File dir = new File(args[i]);
677 if (dir.exists() && dir.isDirectory()) {
678 if (i == 0 || !shouldBeFollowedByPath(args[i-1])) {
679 newargs.add("-src");
680 }
681 }
682 newargs.add(args[i]);
683 }
684 return newargs.toArray(new String[0]);
685 }
687 /**
688 * Check the -src options.
689 */
690 private static void checkSrcOption(String[] args)
691 throws ProblemException {
692 Set<File> dirs = new HashSet<File>();
693 for (int i = 0; i<args.length; ++i) {
694 if (args[i].equals("-src")) {
695 if (i+1 >= args.length) {
696 throw new ProblemException("You have to specify a directory following -src.");
697 }
698 StringTokenizer st = new StringTokenizer(args[i+1], File.pathSeparator);
699 while (st.hasMoreElements()) {
700 File dir = new File(st.nextToken());
701 if (!dir.exists()) {
702 throw new ProblemException("This directory does not exist: "+dir.getPath());
703 }
704 if (!dir.isDirectory()) {
705 throw new ProblemException("\""+dir.getPath()+"\" is not a directory.");
706 }
707 if (dirs.contains(dir)) {
708 throw new ProblemException("The src directory \""+dir.getPath()+"\" is specified more than once!");
709 }
710 dirs.add(dir);
711 }
712 }
713 }
714 if (dirs.isEmpty()) {
715 throw new ProblemException("You have to specify -src.");
716 }
717 }
719 /**
720 * Scan the arguments to find an option that specifies a file.
721 */
722 private static File findFileOption(String[] args, String option, String name, boolean needed)
723 throws ProblemException, ProblemException {
724 File file = null;
725 for (int i = 0; i<args.length; ++i) {
726 if (args[i].equals(option)) {
727 if (file != null) {
728 throw new ProblemException("You have already specified the "+name+" file!");
729 }
730 if (i+1 >= args.length) {
731 throw new ProblemException("You have to specify a file following "+option+".");
732 }
733 file = new File(args[i+1]);
734 if (file.isDirectory()) {
735 throw new ProblemException("\""+args[i+1]+"\" is not a file.");
736 }
737 if (!file.exists() && needed) {
738 throw new ProblemException("The file \""+args[i+1]+"\" does not exist.");
739 }
741 }
742 }
743 if (file == null && needed) {
744 throw new ProblemException("You have to specify "+option);
745 }
746 return file;
747 }
749 /**
750 * Look for a specific switch, return true if found.
751 */
752 public static boolean findBooleanOption(String[] args, String option) {
753 for (int i = 0; i<args.length; ++i) {
754 if (args[i].equals(option)) return true;
755 }
756 return false;
757 }
759 /**
760 * Scan the arguments to find an option that specifies a number.
761 */
762 public static int findNumberOption(String[] args, String option) {
763 int rc = 0;
764 for (int i = 0; i<args.length; ++i) {
765 if (args[i].equals(option)) {
766 if (args.length > i+1) {
767 rc = Integer.parseInt(args[i+1]);
768 }
769 }
770 }
771 return rc;
772 }
774 /**
775 * Scan the arguments to find the option (-tr) that setup translation rules to java source
776 * from different sources. For example: .properties are translated using CompileProperties
777 * The found translators are stored as suffix rules.
778 */
779 private static void findTranslateOptions(String[] args, Map<String,Transformer> suffix_rules)
780 throws ProblemException, ProblemException {
782 for (int i = 0; i<args.length; ++i) {
783 if (args[i].equals("-tr")) {
784 if (i+1 >= args.length) {
785 throw new ProblemException("You have to specify a translate rule following -tr.");
786 }
787 String s = args[i+1];
788 checkTranslatePattern(s);
789 int ep = s.indexOf("=");
790 String suffix = s.substring(0,ep);
791 String classname = s.substring(ep+1);
792 if (suffix_rules.get(suffix) != null) {
793 throw new ProblemException("You have already specified a "+
794 "rule for the suffix "+suffix);
795 }
796 if (s.equals(".class")) {
797 throw new ProblemException("You cannot have a translator for .class files!");
798 }
799 if (s.equals(".java")) {
800 throw new ProblemException("You cannot have a translator for .java files!");
801 }
802 String extra = null;
803 int exp = classname.indexOf(",");
804 if (exp != -1) {
805 extra = classname.substring(exp+1);
806 classname = classname.substring(0,exp);
807 }
808 try {
809 Class<?> cl = Class.forName(classname);
810 Transformer t = (Transformer)cl.newInstance();
811 t.setExtra(extra);
812 suffix_rules.put(suffix, t);
813 }
814 catch (Exception e) {
815 throw new ProblemException("Cannot use "+classname+" as a translator!");
816 }
817 }
818 }
819 }
821 /**
822 * Scan the arguments to find the option (-copy) that setup copying rules into the bin dir.
823 * For example: -copy .html
824 * The found copiers are stored as suffix rules as well. No translation is done, just copying.
825 */
826 private void findCopyOptions(String[] args, Map<String,Transformer> suffix_rules)
827 throws ProblemException, ProblemException {
829 for (int i = 0; i<args.length; ++i) {
830 if (args[i].equals("-copy")) {
831 if (i+1 >= args.length) {
832 throw new ProblemException("You have to specify a translate rule following -tr.");
833 }
834 String s = args[i+1];
835 checkCopyPattern(s);
836 if (suffix_rules.get(s) != null) {
837 throw new ProblemException("You have already specified a "+
838 "rule for the suffix "+s);
839 }
840 if (s.equals(".class")) {
841 throw new ProblemException("You cannot have a copy rule for .class files!");
842 }
843 if (s.equals(".java")) {
844 throw new ProblemException("You cannot have a copy rule for .java files!");
845 }
846 suffix_rules.put(s, javac_state.getCopier());
847 }
848 }
849 }
851 /**
852 * Rewrite a / separated path into \ separated, but only
853 * if we are running on a platform were File.separatorChar=='\', ie winapi.
854 */
855 private String fixupSeparator(String p) {
856 if (File.separatorChar == '/') return p;
857 return p.replaceAll("/", "\\\\");
858 }
860 /**
861 * Scan the arguments for -i -x -xf -if followed by the option
862 * -src, -sourcepath, -modulepath or -classpath and produce a map of all the
863 * files to referenced for that particular option.
864 *
865 * Store the found sources and the found modules in the supplied maps.
866 */
867 private boolean findFiles(String[] args, String option, Set<String> suffixes,
868 Map<String,Source> found_files, Map<String, Module> found_modules,
869 Module current_module, boolean inLinksrc)
870 throws ProblemException, ProblemException
871 {
872 // Track which source roots, source path roots and class path roots have been added.
873 Set<File> roots = new HashSet<File>();
874 // Track the current set of package includes,excludes as well as excluded source files,
875 // to be used in the next -src/-sourcepath/-classpath
876 List<String> includes = new LinkedList<String>();
877 List<String> excludes = new LinkedList<String>();
878 List<String> excludefiles = new LinkedList<String>();
879 List<String> includefiles = new LinkedList<String>();
880 // This include is used to find all modules in the source.
881 List<String> moduleinfo = new LinkedList<String>();
882 moduleinfo.add("module-info.java");
884 for (int i = 0; i<args.length; ++i) {
885 if (args[i].equals("-i")) {
886 if (i+1 >= args.length) {
887 throw new ProblemException("You have to specify a package pattern following -i");
888 }
889 String incl = args[i+1];
890 checkPattern(incl);
891 includes.add(incl);
892 }
893 if (args[i].equals("-x")) {
894 if (i+1 >= args.length) {
895 throw new ProblemException("You have to specify a package pattern following -x");
896 }
897 String excl = args[i+1];
898 checkPattern(excl);
899 excludes.add(excl);
900 }
901 if (args[i].equals("-xf")) {
902 if (i+1 >= args.length) {
903 throw new ProblemException("You have to specify a file following -xf");
904 }
905 String exclf = args[i+1];
906 checkFilePattern(exclf);
907 exclf = Util.normalizeDriveLetter(exclf);
908 excludefiles.add(fixupSeparator(exclf));
909 }
910 if (args[i].equals("-if")) {
911 if (i+1 >= args.length) {
912 throw new ProblemException("You have to specify a file following -xf");
913 }
914 String inclf = args[i+1];
915 checkFilePattern(inclf);
916 inclf = Util.normalizeDriveLetter(inclf);
917 includefiles.add(fixupSeparator(inclf));
918 }
919 if (args[i].equals(option)) {
920 if (i+1 >= args.length) {
921 throw new ProblemException("You have to specify a directory following "+option);
922 }
923 String[] root_dirs = args[i+1].split(File.pathSeparator);
924 for (String r : root_dirs) {
925 File root = new File(r);
926 if (!root.isDirectory()) {
927 throw new ProblemException("\""+r+"\" is not a directory.");
928 }
929 try {
930 root = root.getCanonicalFile();
931 } catch (IOException e) {
932 throw new ProblemException(""+e);
933 }
934 if (roots.contains(root)) {
935 throw new ProblemException("\""+r+"\" has already been used for "+option);
936 }
937 if (roots.equals(bin_dir)) {
938 throw new ProblemException("\""+r+"\" cannot be used both for "+option+" and -d");
939 }
940 if (roots.equals(gensrc_dir)) {
941 throw new ProblemException("\""+r+"\" cannot be used both for "+option+" and -s");
942 }
943 if (roots.equals(header_dir)) {
944 throw new ProblemException("\""+r+"\" cannot be used both for "+option+" and -h");
945 }
946 roots.add(root);
947 Source.scanRoot(root, suffixes, excludes, includes, excludefiles, includefiles,
948 found_files, found_modules, current_module,
949 findBooleanOption(args, "--permit-sources-without-package"),
950 false, inLinksrc);
951 }
952 }
953 if (args[i].equals("-src") ||
954 args[i].equals("-sourcepath") ||
955 args[i].equals("-modulepath") ||
956 args[i].equals("-classpath"))
957 {
958 // Reset the includes,excludes and excludefiles after they have been used.
959 includes = new LinkedList<String>();
960 excludes = new LinkedList<String>();
961 excludefiles = new LinkedList<String>();
962 includefiles = new LinkedList<String>();
963 }
964 }
965 return true;
966 }
968 }