blob: 0e8055489fba32becf0778f1dbd5bcbc2555f80c [file] [log] [blame]
/* Copyright (c) 2002-2008 Sun Microsystems, Inc. All rights reserved
*
* This program is distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/
package org.pantsbuild.jmake;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintStream;
import java.io.Reader;
import java.io.StreamTokenizer;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* The main class of the <b>jmake</b> tool.<p>
*
* Has several entrypoints: <code>main</code>, <code>mainExternal</code>, <code>mainProgrammatic</code>,
* <code>mainExternalControlled</code> and <code>mainProgrammaticControlled</code>.
* The first is not intended to be used by applications other than <b>jmake</b> itself, whereas the
* rest can be used to call <b>jmake</b> externally with various degrees of control over its behaviour.
* See method comments for more details.
*
* @author Misha Dmitriev
* 12 October 2004
*/
public class Main {
static final String DEFAULT_STORE_NAME = "jmake.pdb";
static final String VERSION = "1.3.8-11";
private String pdbFileName = null;
private List<String> allProjectJavaFileNamesList =
new ArrayList<String>(100);
private String allProjectJavaFileNames[];
private String addedJavaFileNames[], removedJavaFileNames[], updatedJavaFileNames[];
private String destDir = "";
private List<String> javacAddArgs = new ArrayList<String>();
private String jcPath, jcMainClass, jcMethod;
private String jcExecApp;
boolean controlledExecution = false;
Object externalApp = null;
Method externalCompileSourceFilesMethod = null;
private boolean failOnDependentJar = false, noWarnOnDependentJar = false;
private boolean pdbTextFormat = false;
private PCDManager pcdm = null;
private String dependencyFile;
private static final String optNames[] = {"-h", "-help", "-d", "-pdb", "-C", "-jcpath", "-jcmainclass", "-jcmethod", "-jcexec", "-Xtiming", "-version",
"-warnlimit", "-failondependentjar", "-nowarnondependentjar", "-classpath", "-projclasspath", "-bootclasspath", "-extdirs", "-vpath", "-pdb-text-format",
"-depfile"};
private static final int optArgs[] = {0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1};
private static final int OPT_H = 0;
private static final int OPT_HELP = 1;
private static final int OPT_D = 2;
private static final int OPT_STORE = 3;
private static final int OPT_JAVAC_OPT = 4;
private static final int OPT_JCPATH = 5;
private static final int OPT_JCMAINCLASS = 6;
private static final int OPT_JCMETHOD = 7;
private static final int OPT_JCEXEC = 8;
private static final int OPT_TIMING = 9;
private static final int OPT_VERSION = 10;
private static final int OPT_WARNLIMIT = 11;
private static final int OPT_FAILONDEPJAR = 12;
private static final int OPT_NOWARNONDEPJAR = 13;
private static final int OPT_CLASSPATH = 14;
private static final int OPT_PROJECTCLASSPATH = 15;
private static final int OPT_BOOTCLASSPATH = 16;
private static final int OPT_EXTDIRS = 17;
private static final int OPT_VPATH = 18;
private static final int OPT_PDB_TEXT_FORMAT = 19;
private static final int OPT_DEPENDENCY_FILE = 20;
/** Construct a new instance of Main. */
public Main() {
}
/**
* Checks whether the argument is a legal jmake option. Returns its number or
* -1 if not found.
*/
private static int inOptions(String option) {
if (option.startsWith(optNames[OPT_JAVAC_OPT])) {
return OPT_JAVAC_OPT;
}
for (int i = 0; i < optNames.length; i++) {
if (option.equals(optNames[i])) {
return i;
}
}
return -1;
}
private void processCommandLine(String args[]) {
if ((args.length == 0) || (args.length == 1 && args[0].equals(optNames[OPT_HELP]))) {
printUsage();
throw new PrivateException(new PublicExceptions.NoActionRequestedException());
}
List<String> argsV = new ArrayList<String>();
for (int i = 0; i < args.length; i++) {
argsV.add(args[i]);
}
int argsSt = 0;
String arg;
while (argsSt < argsV.size()) {
arg = argsV.get(argsSt++);
if (arg.charAt(0) == '-') {
int argN = inOptions(arg);
if (argN != -1) {
if (argsSt + optArgs[argN] > argsV.size()) {
optRequiresArg("\"" + optNames[argN] + "\"");
}
} else {
bailOut(arg + ERR_IS_INVALID_OPTION);
}
switch (argN) {
case OPT_H:
case OPT_HELP:
printUsage();
throw new PrivateException(new PublicExceptions.NoActionRequestedException());
case OPT_D:
destDir = argsV.get(argsSt);
javacAddArgs.add("-d");
javacAddArgs.add(argsV.get(argsSt));
argsSt++;
break;
case OPT_CLASSPATH:
try {
setClassPath(argsV.get(argsSt++));
} catch (PublicExceptions.InvalidCmdOptionException ex) {
bailOut(ex.getMessage());
}
break;
case OPT_PROJECTCLASSPATH:
try {
setProjectClassPath(argsV.get(argsSt++));
} catch (PublicExceptions.InvalidCmdOptionException ex) {
bailOut(ex.getMessage());
}
break;
case OPT_STORE:
pdbFileName = argsV.get(argsSt++);
break;
case OPT_JAVAC_OPT:
String javacArg =
(argsV.get(argsSt - 1)).substring(2);
if (javacArg.equals("-d") ||
javacArg.equals("-classpath") ||
javacArg.equals("-bootclasspath") ||
javacArg.equals("-extdirs")) {
bailOut(javacArg + ERR_SHOULD_BE_EXPLICIT);
}
javacAddArgs.add(javacArg);
break;
case OPT_JCPATH:
if (jcExecApp != null) {
bailOut(ERR_NO_TWO_COMPILER_OPTIONS);
}
jcPath = argsV.get(argsSt++);
break;
case OPT_JCMAINCLASS:
if (jcExecApp != null) {
bailOut(ERR_NO_TWO_COMPILER_OPTIONS);
}
jcMainClass = argsV.get(argsSt++);
break;
case OPT_JCMETHOD:
if (jcExecApp != null) {
bailOut(ERR_NO_TWO_COMPILER_OPTIONS);
}
jcMethod = argsV.get(argsSt++);
break;
case OPT_JCEXEC:
if (jcPath != null || jcMainClass != null || jcMethod != null) {
bailOut(ERR_NO_TWO_COMPILER_OPTIONS);
}
jcExecApp = argsV.get(argsSt++);
break;
case OPT_TIMING:
Utils.setTimingOn();
break;
case OPT_VERSION:
// Utils.printInfoMessage("jmake version " + VERSION); // Printed anyway at present...
throw new PrivateException(new PublicExceptions.NoActionRequestedException());
case OPT_WARNLIMIT:
try {
Utils.warningLimit =
Integer.parseInt(argsV.get(argsSt++));
} catch (NumberFormatException e) {
Utils.ignore(e);
bailOut(argsV.get(argsSt) + ERR_IS_INVALID_OPTION);
}
break;
case OPT_FAILONDEPJAR:
if (noWarnOnDependentJar) {
bailOut("it is not allowed to use -nowarnondependentjar and -failondependentjar together");
}
failOnDependentJar = true;
break;
case OPT_NOWARNONDEPJAR:
if (failOnDependentJar) {
bailOut("it is not allowed to use -failondependentjar and -nowarnondependentjar together");
}
noWarnOnDependentJar = true;
break;
case OPT_BOOTCLASSPATH:
try {
setBootClassPath(argsV.get(argsSt++));
} catch (PublicExceptions.InvalidCmdOptionException ex) {
bailOut(ex.getMessage());
}
break;
case OPT_EXTDIRS:
try {
setExtDirs(argsV.get(argsSt++));
} catch (PublicExceptions.InvalidCmdOptionException ex) {
bailOut(ex.getMessage());
}
break;
case OPT_VPATH:
try {
setVirtualPath(argsV.get(argsSt++));
} catch (PublicExceptions.InvalidCmdOptionException ex) {
bailOut(ex.getMessage());
}
break;
case OPT_PDB_TEXT_FORMAT:
pdbTextFormat = true;
break;
case OPT_DEPENDENCY_FILE:
dependencyFile = argsV.get(argsSt++);
break;
default:
bailOut(arg + ERR_IS_INVALID_OPTION);
}
} else { // Not an option, at least does not start with "-". Treat it as a command line file name or source name.
if (arg.length() > 1 && arg.charAt(0) == '@') {
arg = arg.substring(1);
loadCmdFile(arg, argsV);
} else {
if (!arg.endsWith(".java")) {
bailOut(arg + ERR_IS_INVALID_OPTION);
}
allProjectJavaFileNamesList.add(arg);
}
}
}
}
/** Load @-file that can contain anything that command line can contain. */
private void loadCmdFile(String name, List<String> argsV) {
try {
Reader r = new BufferedReader(new FileReader(name));
StreamTokenizer st = new StreamTokenizer(r);
st.resetSyntax();
st.wordChars(' ', 255);
st.whitespaceChars(0, ' ');
st.commentChar('#');
st.quoteChar('"');
st.quoteChar('\'');
while (st.nextToken() != StreamTokenizer.TT_EOF) {
argsV.add(st.sval);
}
r.close();
} catch (IOException e) {
throw new PrivateException(new PublicExceptions.CommandFileReadException(name + ":\n" + e.getMessage()));
}
}
/**
* Main entrypoint for applications that want to call <b>jmake</b> externally and are willing
* to handle exceptions that it may throw.
*
* @param args command line arguments passed to <b>jmake</b>.
*
* @throws PublicExceptions.NoActionRequestedException if <b>jmake</b> was not requested to do any real work;
* @throws PublicExceptions.InvalidCmdOptionException if invalid command line option was detected;
* @throws PublicExceptions.PDBCorruptedException if project database is corrupted;
* @throws PublicExceptions.CommandFileReadException if there was error reading a command file;
* @throws PublicExceptions.CompilerInteractionException if there was a problem initializing or calling the compiler,
* or compilation errors were detected;
* @throws PublicExceptions.ClassFileParseException if there was error parsing a class file;
* @throws PublicExceptions.ClassNameMismatchException if there is a mismatch between the deduced and the actual class name;
* @throws PublicExceptions.InvalidSourceFileExtensionException if a supplied source file has an invalid extension (not .java);
* @throws PublicExceptions.JarDependsOnSourceException if a class in a <code>JAR</code> is found dependent on a class with the .java source;
* @throws PublicExceptions.DoubleEntryException if more than one entry for the same class is found in the project
* @throws FileNotFoundException if a <code>.java</code> or a <code>.class</code> file was not found;
* @throws IOException if there was an I/O problem of any kind;
* @throws PublicExceptions.InternalException if an internal problem that should never happen was detected.
*/
public void mainProgrammatic(String args[]) throws
PublicExceptions.NoActionRequestedException,
PublicExceptions.InvalidCmdOptionException,
PublicExceptions.PDBCorruptedException,
PublicExceptions.CommandFileReadException,
PublicExceptions.CompilerInteractionException,
PublicExceptions.ClassFileParseException,
PublicExceptions.ClassNameMismatchException,
PublicExceptions.InvalidSourceFileExtensionException,
PublicExceptions.JarDependsOnSourceException,
PublicExceptions.DoubleEntryException,
FileNotFoundException,
IOException,
PublicExceptions.InternalException {
try {
Utils.printInfoMessage("Jmake version " + VERSION);
if (!controlledExecution) {
processCommandLine(args);
String[] projectJars = ClassPath.getProjectJars();
if (projectJars != null) {
for (int i = 0; i < projectJars.length; i++) {
allProjectJavaFileNamesList.add(projectJars[i]);
}
}
allProjectJavaFileNames =
allProjectJavaFileNamesList.toArray(new String[allProjectJavaFileNamesList.size()]);
} else {
String[] projectJars = ClassPath.getProjectJars();
if (projectJars != null) {
String newNames[] =
new String[allProjectJavaFileNames.length + projectJars.length];
System.arraycopy(allProjectJavaFileNames, 0, newNames, 0, allProjectJavaFileNames.length);
System.arraycopy(projectJars, 0, newNames, allProjectJavaFileNames.length, projectJars.length);
allProjectJavaFileNames = newNames;
}
}
Utils.startTiming(Utils.TIMING_PDBREAD);
PCDContainer pcdc;
pcdc = PCDContainer.load(pdbFileName, pdbTextFormat);
Utils.stopAndPrintTiming("DB read", Utils.TIMING_PDBREAD);
pcdm = new PCDManager(pcdc, allProjectJavaFileNames,
addedJavaFileNames, removedJavaFileNames, updatedJavaFileNames,
destDir, javacAddArgs, failOnDependentJar, noWarnOnDependentJar,
dependencyFile);
pcdm.initializeCompiler(jcExecApp, jcPath, jcMainClass, jcMethod, externalApp, externalCompileSourceFilesMethod);
pcdm.run();
} catch (PrivateException e) {
Throwable origException = e.getOriginalException();
if (origException instanceof PublicExceptions.NoActionRequestedException) {
throw (PublicExceptions.NoActionRequestedException) origException;
} else if (origException instanceof PublicExceptions.InvalidCmdOptionException) {
throw (PublicExceptions.InvalidCmdOptionException) origException;
} else if (origException instanceof PublicExceptions.CommandFileReadException) {
throw (PublicExceptions.CommandFileReadException) origException;
} else if (origException instanceof PublicExceptions.PDBCorruptedException) {
throw (PublicExceptions.PDBCorruptedException) origException;
} else if (origException instanceof PublicExceptions.CompilerInteractionException) {
throw (PublicExceptions.CompilerInteractionException) origException;
} else if (origException instanceof PublicExceptions.ClassFileParseException) {
throw (PublicExceptions.ClassFileParseException) origException;
} else if (origException instanceof PublicExceptions.ClassNameMismatchException) {
throw (PublicExceptions.ClassNameMismatchException) origException;
} else if (origException instanceof PublicExceptions.InvalidSourceFileExtensionException) {
throw (PublicExceptions.InvalidSourceFileExtensionException) origException;
} else if (origException instanceof PublicExceptions.JarDependsOnSourceException) {
throw (PublicExceptions.JarDependsOnSourceException) origException;
} else if (origException instanceof PublicExceptions.DoubleEntryException) {
throw (PublicExceptions.DoubleEntryException) origException;
} else if (origException instanceof FileNotFoundException) {
throw (FileNotFoundException) origException;
} else if (origException instanceof IOException) {
throw (IOException) origException;
} else if (origException instanceof PublicExceptions.InternalException) {
throw (PublicExceptions.InternalException) origException;
}
} finally {
ClassPath.resetOnFinish();
}
}
/**
* Main entrypoint for applications that want to call <b>jmake</b> externally and would prefer
* receiving an error code instead of an exception in case something goes wrong.<p>
*
* @param args command line arguments passed to <b>jmake</b>.
*
* @return <dl>
* <dt><code> 0</code> if everything was successful;
* <dt><code> -1</code> invalid command line option detected;
* <dt><code> -2</code> error reading command file;
* <dt><code> -3</code> project database corrupted;
* <dt><code> -4</code> error initializing or calling the compiler;
* <dt><code> -5</code> compilation error;
* <dt><code> -6</code> error parsing a class file;
* <dt><code> -7</code> file not found;
* <dt><code> -8</code> I/O exception;
* <dt><code> -9</code> internal jmake exception;
* <dt><code>-10</code> deduced and actual class name mismatch;
* <dt><code>-11</code> invalid source file extension;
* <dt><code>-12</code> a class in a <code>JAR</code> is found dependent on a class with the .java source;
* <dt><code>-13</code> more than one entry for the same class is found in the project
* <dt><code>-20</code> internal Java error (caused by <code>java.lang.InternalError</code>);
* <dt><code>-30</code> internal Java error (caused by <code>java.lang.RuntimeException</code>).
* </dl>
*/
public int mainExternal(String args[]) {
try {
mainProgrammatic(args);
} catch (PublicExceptions.NoActionRequestedException e0) {
// Nothing to do
} catch (PublicExceptions.InvalidCmdOptionException e1) {
Utils.printErrorMessage(e1.getMessage());
return -1;
} catch (PublicExceptions.CommandFileReadException e2) {
Utils.printErrorMessage("error parsing command file:");
Utils.printErrorMessage(e2.getMessage());
return -2;
} catch (PublicExceptions.PDBCorruptedException e3) {
Utils.printErrorMessage("project database corrupted: " + e3.getMessage());
return -3;
} catch (PublicExceptions.CompilerInteractionException e4) {
if (e4.getOriginalException() != null) {
Utils.printErrorMessage("error interacting with the compiler: ");
Utils.printErrorMessage(e4.getMessage());
Utils.printErrorMessage("original exception:");
Utils.printErrorMessage(e4.getOriginalException().getMessage());
return -4;
} else { // Otherwise there is a compilation error, and the compiler has already printed a lot...
return -5;
}
} catch (PublicExceptions.ClassFileParseException e6) {
Utils.printErrorMessage(e6.getMessage());
return -6;
} catch (FileNotFoundException e7) {
Utils.printErrorMessage(e7.getMessage());
return -7;
} catch (IOException e8) {
Utils.printErrorMessage(e8.getMessage());
return -8;
} catch (PublicExceptions.InternalException e9) {
Utils.printErrorMessage("internal jmake exception detected:");
Utils.printErrorMessage(e9.getMessage());
Utils.printErrorMessage(Utils.REPORT_PROBLEM);
Utils.printErrorMessage("the stack trace is as follows:");
e9.printStackTrace();
return -9;
} catch (PublicExceptions.ClassNameMismatchException e10) {
Utils.printErrorMessage(e10.getMessage());
return -10;
} catch (PublicExceptions.InvalidSourceFileExtensionException e11) {
Utils.printErrorMessage(e11.getMessage());
return -11;
} catch (PublicExceptions.JarDependsOnSourceException e12) {
Utils.printErrorMessage(e12.getMessage());
return -12;
} catch (PublicExceptions.DoubleEntryException e13) {
Utils.printErrorMessage(e13.getMessage());
return -13;
} catch (InternalError e20) {
Utils.printErrorMessage("internal Java error: " + e20);
Utils.printErrorMessage("Consult the following stack trace for more info:");
e20.printStackTrace();
return -20;
} catch (RuntimeException e30) {
Utils.printErrorMessage("internal Java exception: " + e30);
Utils.printErrorMessage("Consult the following stack trace for more info:");
e30.printStackTrace();
return -30;
}
return 0;
}
/**
* Main entrypoint for applications such as Ant, that want to have full control over
* compilations that <b>jmake</b> invokes, and are willing to handle exceptions
* that it may throw.
*
* @param javaFileNames array of strings that specify <code>.java</code> file names.
* @param destDirName name of the destination directory (<b>jmake</b> will look up binary classes
* in there, it should be the same as the one used by the Java compiler method).
* If <code>null</code> is passed, classes will be looked up in the same directories
* as their sources, in agreement with the default Java compiler behaviour.
* @param pdbFileName project database file name (if <code>null</code> is passed,
* a file with the default name placed in the current directory will be used).
* @param externalApp an object on which to invoke <code>externalCompileSourceFilesMethod</code> method.
* @param externalCompileSourceFilesMethod a method of the form <code>int x(String[] args)</code>. It
* should return <code>0</code> if compilation is successful and any non-zero value
* otherwise. <b>jmake</b> passes it a list of the <code>.java</code> files to
* recompile in the form of canonical full path file names.
*
* @throws PublicExceptions.NoActionRequestedException if <b>jmake</b> was not requested to do any real work;
* @throws PublicExceptions.InvalidCmdOptionException if invalid command line option was detected;
* @throws PublicExceptions.PDBCorruptedException if project database is corrupted;
* @throws PublicExceptions.CommandFileReadException if there was error reading a command file;
* @throws PublicExceptions.CompilerInteractionException if there was a problem initializing or calling the compiler,
* or compilation errors were detected;
* @throws PublicExceptions.ClassFileParseException if there was error parsing a class file;
* @throws PublicExceptions.ClassNameMismatchException if there is a mismatch between the deduced and the actual class name;
* @throws PublicExceptions.InvalidSourceFileExtensionException if a specified source file has an invalid extension (not .java);
* @throws PublicExceptions.JarDependsOnSourceException if a class in a <code>JAR</code> is found dependent on a class with the .java source;
* @throws PublicExceptions.DoubleEntryException if more than one entry for the same class is found in the project
* @throws PublicExceptions.InternalException if an internal problem that should never happen was detected.
* @throws FileNotFoundException if a <code>.java</code> or a <code>.class</code> file was not found;
* @throws IOException if there was an I/O problem of any kind;
*/
public void mainProgrammaticControlled(String javaFileNames[], String destDirName, String pdbFileName,
Object externalApp, Method externalCompileSourceFilesMethod) throws
PublicExceptions.NoActionRequestedException,
PublicExceptions.InvalidCmdOptionException,
PublicExceptions.PDBCorruptedException,
PublicExceptions.CommandFileReadException,
PublicExceptions.CompilerInteractionException,
PublicExceptions.ClassFileParseException,
PublicExceptions.ClassNameMismatchException,
PublicExceptions.InvalidSourceFileExtensionException,
PublicExceptions.JarDependsOnSourceException,
PublicExceptions.DoubleEntryException,
PublicExceptions.InternalException,
FileNotFoundException,
IOException {
controlledExecution = true;
this.pdbFileName = pdbFileName;
this.destDir = destDirName;
this.allProjectJavaFileNames = javaFileNames;
this.externalApp = externalApp;
this.externalCompileSourceFilesMethod = externalCompileSourceFilesMethod;
mainProgrammatic(null);
}
/**
* Main entrypoint for applications such as Ant, that want to have full control over
* compilations that <b>jmake</b> invokes, and do not want to handle exceptions that it
* may throw. Error codes returned are the same as <code>mainExternal(String[])</code> returns.
*
* @param javaFileNames array of strings that specify <code>.java</code> file names.
* @param destDirName name of the destination directory (<b>jmake</b> will look up binary classes
* in there, it should be the same as the one used by the Java compiler method).
* If <code>null</code> is passed, classes will be looked up in the same directories
* as their sources, in agreement with the default Java compiler behaviour.
* @param pdbFileName project database file name (if <code>null</code> is passed,
* a file with the default name placed in the current directory will be used).
* @param externalApp an object on which to invoke <code>externalCompileSourceFilesMethod</code> method.
* @param externalCompileSourceFilesMethod a method of the form <code>int x(String[] args)</code>. It
* should return <code>0</code> if compilation is successful and any non-zero value
* otherwise. <b>jmake</b> passes it a list of the <code>.java</code> files to
* recompile in the form of canonical full path file names.
*
* @see #mainExternal(String[])
*/
public int mainExternalControlled(String javaFileNames[], String destDirName, String pdbFileName,
Object externalApp, Method externalCompileSourceFilesMethod) {
controlledExecution = true;
this.pdbFileName = pdbFileName;
this.destDir = destDirName;
this.allProjectJavaFileNames = javaFileNames;
this.externalApp = externalApp;
this.externalCompileSourceFilesMethod = externalCompileSourceFilesMethod;
return mainExternal(null);
}
/**
* Main entrypoint for applications such as IDEs, that themselves keep track of updated/added/removed sources,
* want to have full control over compilations that <b>jmake</b> invokes, and are willing to handle exceptions
* that it may throw.
*
* @param addedJavaFileNames names of <code>.java</code> files just added to the project
* @param removedJavaFileNames names of <code>.java</code> files just removed from the project
* @param updatedJavaFileNames names of updated project <code>.java</code> files
* @param destDirName name of the destination directory (<b>jmake</b> will look up binary classes
* in there, it should be the same as the one used by the Java compiler method).
* If <code>null</code> is passed, classes will be looked up in the same directories
* as their sources, in agreement with the default Java compiler behaviour.
* @param pdbFileName project database file name (if <code>null</code> is passed,
* a file with the default name placed in the current directory will be used).
* @param externalApp an object on which to invoke <code>externalCompileSourceFilesMethod</code> method.
* @param externalCompileSourceFilesMethod a method of the form <code>int x(String[] args)</code>. It
* should return <code>0</code> if compilation is successful and any non-zero value
* otherwise. <b>jmake</b> passes it a list of the <code>.java</code> files to
* recompile in the form of canonical full path file names.
*
* @throws PublicExceptions.NoActionRequestedException if <b>jmake</b> was not requested to do any real work;
* @throws PublicExceptions.InvalidCmdOptionException if invalid command line option was detected;
* @throws PublicExceptions.PDBCorruptedException if project database is corrupted;
* @throws PublicExceptions.CommandFileReadException if there was error reading a command file;
* @throws PublicExceptions.CompilerInteractionException if there was a problem initializing or calling the compiler,
* or compilation errors were detected;
* @throws PublicExceptions.ClassFileParseException if there was error parsing a class file;
* @throws PublicExceptions.ClassNameMismatchException if there is a mismatch between the deduced and the actual class name;
* @throws PublicExceptions.InvalidSourceFileExtensionException if a specified source file has an invalid extension (not .java);
* @throws PublicExceptions.JarDependsOnSourceException if a class in a <code>JAR</code> is found dependent on a class with the .java source;
* @throws PublicExceptions.DoubleEntryException if more than one entry for the same class is found in the project
* @throws PublicExceptions.InternalException if an internal problem that should never happen was detected.
* @throws FileNotFoundException if a <code>.java</code> or a <code>.class</code> file was not found;
* @throws IOException if there was an I/O problem of any kind;
*/
public void mainProgrammaticControlled(String addedJavaFileNames[], String removedJavaFileNames[], String updatedJavaFileNames[],
String destDirName, String pdbFileName,
Object externalApp, Method externalCompileSourceFilesMethod) throws
PublicExceptions.NoActionRequestedException,
PublicExceptions.InvalidCmdOptionException,
PublicExceptions.PDBCorruptedException,
PublicExceptions.CommandFileReadException,
PublicExceptions.CompilerInteractionException,
PublicExceptions.ClassFileParseException,
PublicExceptions.ClassNameMismatchException,
PublicExceptions.InvalidSourceFileExtensionException,
PublicExceptions.JarDependsOnSourceException,
PublicExceptions.DoubleEntryException,
PublicExceptions.InternalException,
FileNotFoundException,
IOException {
controlledExecution = true;
this.pdbFileName = pdbFileName;
this.destDir = destDirName;
this.addedJavaFileNames = addedJavaFileNames;
this.removedJavaFileNames = removedJavaFileNames;
this.updatedJavaFileNames = updatedJavaFileNames;
this.externalApp = externalApp;
this.externalCompileSourceFilesMethod = externalCompileSourceFilesMethod;
mainProgrammatic(null);
}
/**
* Main entrypoint for applications such as IDEs, that themselves keep track of updated/added/removed sources,
* want to have full control over compilations that <b>jmake</b> invokes, and do not want to handle exceptions
* that it may throw. Error codes returned are the same as <code>mainExternal(String[])</code> returns.
*
* @param addedJavaFileNames names of <code>.java</code> files just added to the project
* @param removedJavaFileNames names of <code>.java</code> files just removed from the project
* @param updatedJavaFileNames names of updated project <code>.java</code> files
* @param destDirName name of the destination directory (<b>jmake</b> will look up binary classes
* in there, it should be the same as the one used by the Java compiler method).
* If <code>null</code> is passed, classes will be looked up in the same directories
* as their sources, in agreement with the default Java compiler behaviour.
* @param pdbFileName project database file name (if <code>null</code> is passed,
* a file with the default name placed in the current directory will be used).
* @param externalApp an object on which to invoke <code>externalCompileSourceFilesMethod</code> method.
* @param externalCompileSourceFilesMethod a method of the form <code>int x(String[] args)</code>. It
* should return <code>0</code> if compilation is successful and any non-zero value
* otherwise. <b>jmake</b> passes it a list of the <code>.java</code> files to
* recompile in the form of canonical full path file names.
*
* @see #mainExternal(String[])
*/
public int mainExternalControlled(String addedJavaFileNames[], String removedJavaFileNames[], String updatedJavaFileNames[],
String destDirName, String pdbFileName,
Object externalApp, Method externalCompileSourceFilesMethod) {
controlledExecution = true;
this.pdbFileName = pdbFileName;
this.destDir = destDirName;
this.addedJavaFileNames = addedJavaFileNames;
this.removedJavaFileNames = removedJavaFileNames;
this.updatedJavaFileNames = updatedJavaFileNames;
this.externalApp = externalApp;
this.externalCompileSourceFilesMethod = externalCompileSourceFilesMethod;
return mainExternal(null);
}
/**
* Main entrypoint for the standalone <b>jmake</b> application. This method calls does little but calling
* <code>mainExternal</code>, and its execution always completes with <code>System.exit(code)</code>,
* where <code>code</code> is the value returned by <code>mainExternal</code>.
*
* @see #mainExternal(String[])
* @see #mainProgrammatic(String[])
*
* @param args command line arguments passed to <b>jmake</b>
*/
public static void main(String args[]) {
Utils.startTiming(Utils.TIMING_TOTAL);
Main m = new Main();
int exitCode = m.mainExternal(args);
Utils.stopAndPrintTiming("Total", Utils.TIMING_TOTAL);
if ( exitCode != 0 ) {
System.exit(exitCode);
}
}
/**
* Customize the output of <b>jmake</b>.
*
* @see #setOutputStreams(PrintStream, PrintStream, PrintStream)
*
* @param printInfoMessages specify whether to print information messages
* @param printWarningMessages specify whether to print warning messages
* @param printErrorMessages specify whether to print error messages
*/
public static void customizeOutput(boolean printInfoMessages,
boolean printWarningMessages,
boolean printErrorMessages) {
Utils.customizeOutput(printInfoMessages, printWarningMessages, printErrorMessages);
}
/**
* Set the class path to be used by the compiler, and also by the dependency checker for the purposes of
* superclass/superinterface change tracking. For the compiler, this class path will be merged with the
* project class path (set via setProjectClassPath(String)). Other than that, its value will be used only to
* look up superclasses/superinterfaces of project classes. Note that non-project superclasses and
* superinterfaces are first looked up at the boot class path, then on the extension class path, and then
* on this class path.
*
* @see #setProjectClassPath(String)
* @see #setBootClassPath(String)
* @see #setExtDirs(String)
*
* @param classPath the value of the class path, in the usual format (i.e. entries that are directories
* or JARs, separated by colon or semicolon depending on the platform).
*
* @throws PublicExceptions.InvalidCmdOptionException if invalid class path value is specified.
*/
public static void setClassPath(String classPath) throws PublicExceptions.InvalidCmdOptionException {
ClassPath.setClassPath(classPath);
}
/**
* Set the class path to be used by the compiler, and also by the dependency checker for the purposes of
* superclass/superinterface change tracking and sourceless class dependency checking. For the compiler,
* and also in order to look up superclasses/superinterfaces of project classes, this class path will be
* merged with the standard class path (set via setClassPath(String)). But in addition, all binary classes
* that are on this class path are stored in the project database and checked for updates every time jmake
* is invoked. Any changes to these classes trigger the standard dependency checking procedure. However,
* dependent classes are looked up only among the "normal" project classes, i.e. those that have sources.
* Therefore sourceless classes are assumed to always be mutually consistent.
*
* Currently only JAR files can be present on this class path.
*
* @see #setClassPath(String)
*
* @param projectClassPath the value of the class path, in the usual format (i.e. entries that are directories
* or JARs, separated by colon or semicolon depending on the platform).
*
* @throws PublicExceptions.InvalidCmdOptionException if invalid class path value is specified.
*/
public static void setProjectClassPath(String projectClassPath) throws PublicExceptions.InvalidCmdOptionException {
ClassPath.setProjectClassPath(projectClassPath);
}
/**
* Set the boot class path to be used by the compiler (-bootclasspath option) and also by the dependency
* checker (by default, the value of "sun.boot.class.path" property is used).
*
* @see #setClassPath(String)
*
* @param classPath the value of the boot class path, in the usual format (i.e. entries that are directories
* or JARs, separated by colon or semicolon depending on the platform).
*
* @throws PublicExceptions.InvalidCmdOptionException if invalid class path value is specified.
*/
public static void setBootClassPath(String classPath) throws PublicExceptions.InvalidCmdOptionException {
ClassPath.setBootClassPath(classPath);
}
/**
* Set the extensions location to be used by the compiler (-extdirs option) and also by the dependency
* checker (by default, the value of "java.ext.dirs" property is used).
*
* @see #setClassPath(String)
*
* @param dirs the value of extension directories, in the usual format (one or more directory names
* separated by colon or semicolon depending on the platform).
*
* @throws PublicExceptions.InvalidCmdOptionException if invalid class path value is specified.
*/
public static void setExtDirs(String dirs) throws PublicExceptions.InvalidCmdOptionException {
ClassPath.setExtDirs(dirs);
}
/**
* Set the virtual path used to find both source and class files that are part of the project
* but are not in the local directory.
*
* @see #setClassPath(String)
*
* @param dirs the value of extension directories, in the usual format (one or more directory names
* separated by colon or semicolon depending on the platform).
*
* @throws PublicExceptions.InvalidCmdOptionException if invalid path value is specified.
*/
public static void setVirtualPath(String dirs) throws PublicExceptions.InvalidCmdOptionException {
ClassPath.setVirtualPath(dirs);
}
/** Produce no warning or error message upon a dependent <code>JAR</code> detection. */
public static final int DEPJAR_NOWARNORERROR = 0;
/** Produce a warning upon a dependent <code>JAR</code> detection. */
public static final int DEPJAR_WARNING = 1;
/** Produce an error message (throw an exception) upon a dependent <code>JAR</code> detection. */
public static final int DEPJAR_ERROR = 2;
/**
* Set the response of <b>jmake</b> in case a dependence of a class located in a <code>JAR</code> file on a
* class with a <code>.java</code> source is detected (such dependencies are highly discouraged, since it is not
* possible to recompile a class in the <code>JAR</code> that has no source).
*
* @param code response type: DEPJAR_NOWARNORERROR, DEPJAR_WARNING (default behaviour) or DEPJAR_ERROR.
*/
public void setResponseOnDependentJar(int code) {
switch (code) {
case DEPJAR_NOWARNORERROR:
noWarnOnDependentJar = true;
failOnDependentJar = false;
break;
case DEPJAR_WARNING:
noWarnOnDependentJar = false;
failOnDependentJar = false;
break;
case DEPJAR_ERROR:
noWarnOnDependentJar = false;
failOnDependentJar = true;
break;
}
}
/**
* Return the names of all classes that <b>jmake</b>, on this invocation, found updated - either because
* <b>jmake</b> itself recompiled them or because they were updated independently (their timestamp/checksum
* found different from those contained in the project database).
*/
public String[] getUpdatedClasses() {
return pcdm.getAllUpdatedClassesAsStringArray();
}
/**
* Set the output print streams to be used by <b>jmake</b>.
*
* @see #customizeOutput(boolean, boolean, boolean)
*
* @param out print stream to be used for information ("logging") messages that <b>jmake</b> emits
* @param warn print stream to be used for warning messages
* @param err print stream to be used for error messages
*/
public static void setOutputStreams(PrintStream out, PrintStream warn, PrintStream err) {
Utils.setOutputStreams(out, warn, err);
}
/** Get the version of this copy of <b>jmake</b> */
public static String getVersion() {
return VERSION;
}
private static final String ERR_IS_INVALID_OPTION =
" is an invalid option or argument.";
private static final String ERR_NO_TWO_COMPILER_OPTIONS =
"You may not specify both compiler class and compiler executable application";
private static final String ERR_SHOULD_BE_EXPLICIT =
" compiler option should be specified directly as a jmake option";
private static void bailOut(String s) {
throw new PrivateException(new PublicExceptions.InvalidCmdOptionException("jmake: " + s + "\nRun \"jmake -h\" for help."));
}
private static void optRequiresArg(String s) {
bailOut("the " + s + " option requires an argument.");
}
private static void printUsage() {
Utils.printInfoMessage("Usage: jmake <options> <.java files> <@files>");
Utils.printInfoMessage("where possible options include:");
Utils.printInfoMessage(" -h, -help print this help message");
Utils.printInfoMessage(" -version print the product version number");
Utils.printInfoMessage(" -pdb <file name> specify non-default project database file");
Utils.printInfoMessage(" -pdb-text-format if specified, pdb file is stored in text format");
Utils.printInfoMessage(" -d <directory> specify where to place generated class files");
Utils.printInfoMessage(" -classpath <path> specify where to find user class files");
Utils.printInfoMessage(" -projclasspath <path> specify where to find sourceless project classes");
Utils.printInfoMessage(" (currently only JARs are allowed on this path)");
Utils.printInfoMessage(" -C<option> specify an option to be passed to the Java compiler");
Utils.printInfoMessage(" (this option's arguments should also be preceded by -C)");
Utils.printInfoMessage(" -jcpath <path> specify the class path for a non-default Java compiler");
Utils.printInfoMessage(" (default is <JAVAHOME>/lib/tools.jar)");
Utils.printInfoMessage(" -jcmainclass <class> specify the main class for a non-default Java compiler");
Utils.printInfoMessage(" (default is com.sun.tools.javac.Main)");
Utils.printInfoMessage(" -jcmethod <method> specify the method to call in the Java compiler class");
Utils.printInfoMessage(" (default is \"compile(String args[])\")");
Utils.printInfoMessage(" -jcexec <file name> specify a binary non-default Java compiler application");
Utils.printInfoMessage(" -failondependentjar fail if a class on projectclasspath depends on a class");
Utils.printInfoMessage(" with .java source (by default, a warning is issued)");
Utils.printInfoMessage(" -nowarnondependentjar no warning or error if a class on projectclasspath");
Utils.printInfoMessage(" depends on a class with a .java source (use with care)");
Utils.printInfoMessage(" -warnlimit <number> specify the maximum number of warnings (20 by default)");
Utils.printInfoMessage(" -bootclasspath <path> override location of bootstrap class files");
Utils.printInfoMessage(" -extdirs <dirs> override location of installed extensions");
Utils.printInfoMessage(" -vpath <dirs> a list of directories to search for Java and class files similar to GNUMake's VPATH");
Utils.printInfoMessage(" -depfile <path> a file generated by the compiler containing additional java->class mappings");
Utils.printInfoMessage("");
Utils.printInfoMessage("Examples:");
Utils.printInfoMessage(" jmake -d classes -classpath .;mylib.jar X.java Y.java Z.java");
Utils.printInfoMessage(" jmake -pdb myproject.pdb -jcexec c:\\java\\jikes\\jikes.exe @myproject.src");
}
}