/* * The Apache Software License, Version 1.1 * * Copyright (c) 1999 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, if * any, must include the following acknowlegement: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowlegement may appear in the software itself, * if and wherever such third-party acknowlegements normally appear. * * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software * Foundation" must not be used to endorse or promote products derived * from this software without prior written permission. For written * permission, please contact apache@apache.org. * * 5. Products derived from this software may not be called "Apache" * nor may "Apache" appear in their names without prior written * permission of the Apache Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * . */ package org.apache.tools.ant.taskdefs; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.DirectoryScanner; import org.apache.tools.ant.Project; import org.apache.tools.ant.types.*; import org.apache.tools.ant.util.*; import java.lang.reflect.Method; import java.lang.reflect.Constructor; import java.io.*; import java.util.*; /** * Task to compile Java source files. This task can take the following * arguments: * * Of these arguments, the sourcedir and destdir are required. *

* When this task executes, it will recursively scan the sourcedir and * destdir looking for Java source files to compile. This task makes its * compile decision based on timestamp. * * @author James Davidson duncan@x180.com * @author Robin Green greenrd@hotmail.com * @author Stefan Bodewig */ public class Javac extends MatchingTask { /** * Integer returned by the "Modern" jdk1.3 compiler to indicate success. */ private static final int MODERN_COMPILER_SUCCESS = 0; private static final String FAIL_MSG = "Compile failed, messages should have been provided."; private Path src; private File destDir; private Path compileClasspath; private String encoding; private boolean debug = false; private boolean optimize = false; private boolean deprecation = false; private boolean depend = false; private boolean verbose = false; private String target; private Path bootclasspath; private Path extdirs; private static String lSep = System.getProperty("line.separator"); protected File[] compileList = new File[0]; /** * Create a nested element for multiple source path * support. * * @return a nexted src element. */ public Path createSrc() { if (src == null) { src = new Path(project); } return src.createPath(); } /** * Set the source dirs to find the source Java files. */ public void setSrcdir(Path srcDir) { if (src == null) { src = srcDir; } else { src.append(srcDir); } } /** * Set the destination directory into which the Java source * files should be compiled. */ public void setDestdir(File destDir) { this.destDir = destDir; } /** * Set the classpath to be used for this compilation. */ public void setClasspath(Path classpath) { if (compileClasspath == null) { compileClasspath = classpath; } else { compileClasspath.append(classpath); } } /** * Maybe creates a nested classpath element. */ public Path createClasspath() { if (compileClasspath == null) { compileClasspath = new Path(project); } return compileClasspath.createPath(); } /** * Adds a reference to a CLASSPATH defined elsewhere. */ public void setClasspathRef(Reference r) { createClasspath().setRefid(r); } /** * Sets the bootclasspath that will be used to compile the classes * against. */ public void setBootclasspath(Path bootclasspath) { if (this.bootclasspath == null) { this.bootclasspath = bootclasspath; } else { this.bootclasspath.append(bootclasspath); } } /** * Maybe creates a nested classpath element. */ public Path createBootclasspath() { if (bootclasspath == null) { bootclasspath = new Path(project); } return bootclasspath.createPath(); } /** * Adds a reference to a CLASSPATH defined elsewhere. */ public void setBootClasspathRef(Reference r) { createBootclasspath().setRefid(r); } /** * Sets the extension directories that will be used during the * compilation. */ public void setExtdirs(Path extdirs) { if (this.extdirs == null) { this.extdirs = extdirs; } else { this.extdirs.append(extdirs); } } /** * Maybe creates a nested classpath element. */ public Path createExtdirs() { if (extdirs == null) { extdirs = new Path(project); } return extdirs.createPath(); } /** * Set the deprecation flag. */ public void setDeprecation(boolean deprecation) { this.deprecation = deprecation; } /** * Set the Java source file encoding name. */ public void setEncoding(String encoding) { this.encoding = encoding; } /** * Set the debug flag. */ public void setDebug(boolean debug) { this.debug = debug; } /** * Set the optimize flag. */ public void setOptimize(boolean optimize) { this.optimize = optimize; } /** * Set the depend flag. */ public void setDepend(boolean depend) { this.depend = depend; } /** * Set the verbose flag. */ public void setVerbose(boolean verbose) { this.verbose = verbose; } /** * Sets the target VM that the classes will be compiled for. Valid * strings are "1.1", "1.2", and "1.3". */ public void setTarget(String target) { this.target = target; } /** * Executes the task. */ public void execute() throws BuildException { // first off, make sure that we've got a srcdir if (src == null) { throw new BuildException("srcdir attribute must be set!", location); } String [] list = src.list(); if (list.length == 0) { throw new BuildException("srcdir attribute must be set!", location); } if (destDir != null && !destDir.isDirectory()) { throw new BuildException("destination directory \"" + destDir + "\" does not exist or is not a directory", location); } // scan source directories and dest directory to build up both copy lists and // compile lists resetFileLists(); for (int i=0; i 0) { log("Compiling " + compileList.length + " source file" + (compileList.length == 1 ? "" : "s") + (destDir != null ? " to " + destDir : "")); if (compiler.equalsIgnoreCase("classic")) { doClassicCompile(); } else if (compiler.equalsIgnoreCase("modern")) { doModernCompile(); } else if (compiler.equalsIgnoreCase("jikes")) { doJikesCompile(); } else if (compiler.equalsIgnoreCase("jvc")) { doJvcCompile(); } else { String msg = "Don't know how to use compiler " + compiler; throw new BuildException(msg, location); } } } /** * Clear the list of files to be compiled and copied.. */ protected void resetFileLists() { compileList = new File[0]; } /** * Scans the directory looking for source files to be compiled. * The results are returned in the class variable compileList */ protected void scanDir(File srcDir, File destDir, String files[]) { GlobPatternMapper m = new GlobPatternMapper(); m.setFrom("*.java"); m.setTo("*.class"); SourceFileScanner sfs = new SourceFileScanner(this); File[] newFiles = sfs.restrictAsFiles(files, srcDir, destDir, m); if (newFiles.length > 0) { File[] newCompileList = new File[compileList.length + newFiles.length]; System.arraycopy(compileList, 0, newCompileList, 0, compileList.length); System.arraycopy(newFiles, 0, newCompileList, compileList.length, newFiles.length); compileList = newCompileList; } } /** * Builds the compilation classpath. * * @param addRuntime Shall rt.jar or * classes.zip be added to the classpath. */ protected Path getCompileClasspath(boolean addRuntime) { Path classpath = new Path(project); // add dest dir to classpath so that previously compiled and // untouched classes are on classpath if (destDir != null) { classpath.setLocation(destDir); } // Compine the build classpath with the system classpath, in an // order determined by the value of build.classpath if (compileClasspath == null) { classpath.addExisting(Path.systemClasspath); } else { String order = project.getProperty("build.sysclasspath"); if (order == null) order="first"; if (order.equals("only")) { // only: the developer knows what (s)he is doing classpath.addExisting(Path.systemClasspath); } else if (order.equals("last")) { // last: don't trust the developer classpath.addExisting(compileClasspath); classpath.addExisting(Path.systemClasspath); } else if (order.equals("ignore")) { // ignore: don't trust anyone classpath.addExisting(compileClasspath); addRuntime = true; } else { // first: developer could use a little help classpath.addExisting(Path.systemClasspath); classpath.addExisting(compileClasspath); } } // optionally add the runtime classes if (addRuntime) { if (System.getProperty("java.vendor").toLowerCase().indexOf("microsoft") >= 0) { // Pull in *.zip from packages directory FileSet msZipFiles = new FileSet(); msZipFiles.setDir(new File(System.getProperty("java.home") + File.separator + "Packages")); msZipFiles.setIncludes("*.ZIP"); classpath.addFileset(msZipFiles); } else if (Project.getJavaVersion() == Project.JAVA_1_1) { classpath.addExisting(new Path(null, System.getProperty("java.home") + File.separator + "lib" + File.separator + "classes.zip")); } else { // JDK > 1.1 seems to set java.home to the JRE directory. classpath.addExisting(new Path(null, System.getProperty("java.home") + File.separator + "lib" + File.separator + "rt.jar")); // Just keep the old version as well and let addExistingToPath // sort it out. classpath.addExisting(new Path(null, System.getProperty("java.home") + File.separator +"jre" + File.separator + "lib" + File.separator + "rt.jar")); } } return classpath; } /** * Peforms a compile using the classic compiler that shipped with * JDK 1.1 and 1.2. */ private void doClassicCompile() throws BuildException { log("Using classic compiler", Project.MSG_VERBOSE); Commandline cmd = setupJavacCommand(); // Use reflection to be able to build on all JDKs /* // provide the compiler a different message sink - namely our own sun.tools.javac.Main compiler = new sun.tools.javac.Main(new LogOutputStream(this, Project.MSG_WARN), "javac"); if (!compiler.compile(cmd.getArguments())) { throw new BuildException("Compile failed"); } */ try { // Create an instance of the compiler, redirecting output to // the project log OutputStream logstr = new LogOutputStream(this, Project.MSG_WARN); Class c = Class.forName("sun.tools.javac.Main"); Constructor cons = c.getConstructor(new Class[] { OutputStream.class, String.class }); Object compiler = cons.newInstance(new Object[] { logstr, "javac" }); // Call the compile() method Method compile = c.getMethod("compile", new Class [] { String[].class }); Boolean ok = (Boolean)compile.invoke(compiler, new Object[] {cmd.getArguments()}); if (!ok.booleanValue()) { throw new BuildException(FAIL_MSG, location); } } catch (ClassNotFoundException ex) { throw new BuildException("Cannot use classic compiler, as it is not available"+ " A common solution is to set the environment variable"+ " JAVA_HOME to your jdk directory.", location); } catch (Exception ex) { if (ex instanceof BuildException) { throw (BuildException) ex; } else { throw new BuildException("Error starting classic compiler: ", ex, location); } } } /** * Performs a compile using the newer compiler that ships with JDK 1.3 */ private void doModernCompile() throws BuildException { try { Class.forName("com.sun.tools.javac.Main"); } catch (ClassNotFoundException cnfe) { log("Modern compiler is not available - using classic compiler", Project.MSG_WARN); doClassicCompile(); return; } log("Using modern compiler", Project.MSG_VERBOSE); Commandline cmd = setupJavacCommand(); PrintStream err = System.err; PrintStream out = System.out; // Use reflection to be able to build on all JDKs >= 1.1: try { PrintStream logstr = new PrintStream(new LogOutputStream(this, Project.MSG_WARN)); System.setOut(logstr); System.setErr(logstr); Class c = Class.forName ("com.sun.tools.javac.Main"); Object compiler = c.newInstance (); Method compile = c.getMethod ("compile", new Class [] {(new String [] {}).getClass ()}); int result = ((Integer) compile.invoke (compiler, new Object[] {cmd.getArguments()})) .intValue (); if (result != MODERN_COMPILER_SUCCESS) { throw new BuildException(FAIL_MSG, location); } } catch (Exception ex) { if (ex instanceof BuildException) { throw (BuildException) ex; } else { throw new BuildException("Error starting modern compiler", ex, location); } } finally { System.setErr(err); System.setOut(out); } } /** * Does the command line argument processing common to classic and * modern. */ private Commandline setupJavacCommand() { Commandline cmd = new Commandline(); Path classpath = getCompileClasspath(false); if (deprecation == true) { cmd.createArgument().setValue("-deprecation"); } if (destDir != null) { cmd.createArgument().setValue("-d"); cmd.createArgument().setFile(destDir); } cmd.createArgument().setValue("-classpath"); // Just add "sourcepath" to classpath ( for JDK1.1 ) if (Project.getJavaVersion().startsWith("1.1")) { cmd.createArgument().setValue(classpath.toString() + File.pathSeparator + src.toString()); } else { cmd.createArgument().setPath(classpath); cmd.createArgument().setValue("-sourcepath"); cmd.createArgument().setPath(src); if (target != null) { cmd.createArgument().setValue("-target"); cmd.createArgument().setValue(target); } } if (encoding != null) { cmd.createArgument().setValue("-encoding"); cmd.createArgument().setValue(encoding); } if (debug) { cmd.createArgument().setValue("-g"); } if (optimize) { cmd.createArgument().setValue("-O"); } if (bootclasspath != null) { cmd.createArgument().setValue("-bootclasspath"); cmd.createArgument().setPath(bootclasspath); } if (extdirs != null) { cmd.createArgument().setValue("-extdirs"); cmd.createArgument().setPath(extdirs); } if (depend) { if (Project.getJavaVersion().startsWith("1.1")) { cmd.createArgument().setValue("-depend"); } else if (Project.getJavaVersion().startsWith("1.2")) { cmd.createArgument().setValue("-Xdepend"); } else { log("depend attribute is not supported by the modern compiler", Project.MSG_WARN); } } if (verbose) { cmd.createArgument().setValue("-verbose"); } logAndAddFilesToCompile(cmd); return cmd; } /** * Logs the compilation parameters, adds the files to compile and logs the * &qout;niceSourceList" */ protected void logAndAddFilesToCompile(Commandline cmd) { log("Compilation args: " + cmd.toString(), Project.MSG_VERBOSE); StringBuffer niceSourceList = new StringBuffer("File"); if (compileList.length != 1) { niceSourceList.append("s"); } niceSourceList.append(" to be compiled:"); niceSourceList.append(lSep); for (int i=0; i < compileList.length; i++) { String arg = compileList[i].getAbsolutePath(); cmd.createArgument().setValue(arg); niceSourceList.append(" " + arg + lSep); } log(niceSourceList.toString(), Project.MSG_VERBOSE); } /** * Performs a compile using the Jikes compiler from IBM.. * Mostly of this code is identical to doClassicCompile() * However, it does not support all options like * bootclasspath, extdirs, deprecation and so on, because * there is no option in jikes and I don't understand * what they should do. * * It has been successfully tested with jikes >1.10 * * @author skanthak@muehlheim.de */ private void doJikesCompile() throws BuildException { log("Using jikes compiler", Project.MSG_VERBOSE); Path classpath = new Path(project); // Jikes doesn't support bootclasspath dir (-bootclasspath) // so we'll emulate it for compatibility and convenience. if (bootclasspath != null) { classpath.append(bootclasspath); } // Jikes doesn't support an extension dir (-extdir) // so we'll emulate it for compatibility and convenience. addExtdirsToClasspath(classpath); classpath.append(getCompileClasspath(true)); // Jikes has no option for source-path so we // will add it to classpath. classpath.append(src); // if the user has set JIKESPATH we should add the contents as well String jikesPath = System.getProperty("jikes.class.path"); if (jikesPath != null) { classpath.append(new Path(project, jikesPath)); } Commandline cmd = new Commandline(); cmd.setExecutable("jikes"); if (deprecation == true) cmd.createArgument().setValue("-deprecation"); if (destDir != null) { cmd.createArgument().setValue("-d"); cmd.createArgument().setFile(destDir); } cmd.createArgument().setValue("-classpath"); cmd.createArgument().setPath(classpath); if (encoding != null) { cmd.createArgument().setValue("-encoding"); cmd.createArgument().setValue(encoding); } if (debug) { cmd.createArgument().setValue("-g"); } if (optimize) { cmd.createArgument().setValue("-O"); } if (verbose) { cmd.createArgument().setValue("-verbose"); } if (depend) { cmd.createArgument().setValue("-depend"); } /** * XXX * Perhaps we shouldn't use properties for these * three options (emacs mode, warnings and pedantic), * but include it in the javac directive? */ /** * Jikes has the nice feature to print error * messages in a form readable by emacs, so * that emacs can directly set the cursor * to the place, where the error occured. */ String emacsProperty = project.getProperty("build.compiler.emacs"); if (emacsProperty != null && Project.toBoolean(emacsProperty)) { cmd.createArgument().setValue("+E"); } /** * Jikes issues more warnings that javac, for * example, when you have files in your classpath * that don't exist. As this is often the case, these * warning can be pretty annoying. */ String warningsProperty = project.getProperty("build.compiler.warnings"); if (warningsProperty != null && !Project.toBoolean(warningsProperty)) { cmd.createArgument().setValue("-nowarn"); } /** * Jikes can issue pedantic warnings. */ String pedanticProperty = project.getProperty("build.compiler.pedantic"); if (pedanticProperty != null && Project.toBoolean(pedanticProperty)) { cmd.createArgument().setValue("+P"); } /** * Jikes supports something it calls "full dependency * checking", see the jikes documentation for differences * between -depend and +F. */ String fullDependProperty = project.getProperty("build.compiler.fulldepend"); if (fullDependProperty != null && Project.toBoolean(fullDependProperty)) { cmd.createArgument().setValue("+F"); } int firstFileName = cmd.size(); logAndAddFilesToCompile(cmd); if (executeJikesCompile(cmd.getCommandline(), firstFileName) != 0) { throw new BuildException(FAIL_MSG, location); } } /** * Do the compile with the specified arguments. * @param args - arguments to pass to process on command line * @param firstFileName - index of the first source file in args */ protected int executeJikesCompile(String[] args, int firstFileName) { String[] commandArray = null; File tmpFile = null; try { /* * Many system have been reported to get into trouble with * long command lines - no, not only Windows ;-). * * POSIX seems to define a lower limit of 4k, so use a temporary * file if the total length of the command line exceeds this limit. */ if (Commandline.toString(args).length() > 4096) { PrintWriter out = null; try { tmpFile = new File("jikes"+(new Random(System.currentTimeMillis())).nextLong()); out = new PrintWriter(new FileWriter(tmpFile)); for (int i = firstFileName; i < args.length; i++) { out.println(args[i]); } out.flush(); commandArray = new String[firstFileName+1]; System.arraycopy(args, 0, commandArray, 0, firstFileName); commandArray[firstFileName] = "@" + tmpFile.getAbsolutePath(); } catch (IOException e) { throw new BuildException("Error creating temporary file", e, location); } finally { if (out != null) { try {out.close();} catch (Throwable t) {} } } } else { commandArray = args; } try { Execute exe = new Execute(new LogStreamHandler(this, Project.MSG_INFO, Project.MSG_WARN)); exe.setAntRun(project); exe.setWorkingDirectory(project.getBaseDir()); exe.setCommandline(commandArray); exe.execute(); return exe.getExitValue(); } catch (IOException e) { throw new BuildException("Error running Jikes compiler", e, location); } } finally { if (tmpFile != null) { tmpFile.delete(); } } } /** * Emulation of extdirs feature in java >= 1.2. * This method adds all files in the given * directories (but not in sub-directories!) to the classpath, * so that you don't have to specify them all one by one. * @param classpath - Path to append files to */ protected void addExtdirsToClasspath(Path classpath) { if (extdirs == null) { String extProp = System.getProperty("java.ext.dirs"); if (extProp != null) { extdirs = new Path(project, extProp); } else { return; } } String[] dirs = extdirs.list(); for (int i=0; i