src/share/classes/com/sun/tools/sjavac/Main.java

Mon, 04 Feb 2013 18:08:53 -0500

author
dholmes
date
Mon, 04 Feb 2013 18:08:53 -0500
changeset 1570
f91144b7da75
parent 1504
22e417cdddee
child 1625
fbb6e470079d
permissions
-rw-r--r--

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 }

mercurial