summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGilles Dubochet <gilles.dubochet@epfl.ch>2005-10-07 19:53:56 +0000
committerGilles Dubochet <gilles.dubochet@epfl.ch>2005-10-07 19:53:56 +0000
commit269e0a057948a273675e00bf49b6074575c3b1fc (patch)
tree6c18a073720ef860cd84e05746dc21e0dcb022a8
parent72b4cec44aaee31d2573cb6d94fab61a07d32b9d (diff)
downloadscala-269e0a057948a273675e00bf49b6074575c3b1fc.tar.gz
scala-269e0a057948a273675e00bf49b6074575c3b1fc.tar.bz2
scala-269e0a057948a273675e00bf49b6074575c3b1fc.zip
The new Ant NSC task.
-rw-r--r--sources/scala/tools/nsc/ant/NSC.scala599
1 files changed, 599 insertions, 0 deletions
diff --git a/sources/scala/tools/nsc/ant/NSC.scala b/sources/scala/tools/nsc/ant/NSC.scala
new file mode 100644
index 0000000000..a8545adc8a
--- /dev/null
+++ b/sources/scala/tools/nsc/ant/NSC.scala
@@ -0,0 +1,599 @@
+/* __ ______________ *\
+** / |/ / ____/ ____/ **
+** / | | /___ / /___ **
+** /_/|__/_____/_____/ Copyright 2005 LAMP/EPFL **
+\* */
+
+// $Id$
+
+import java.io.File;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Vector;
+
+import org.apache.tools.ant.AntClassLoader;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.DirectoryScanner;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.taskdefs.MatchingTask;
+import org.apache.tools.ant.types.Path;
+import org.apache.tools.ant.util.FileUtils;
+import org.apache.tools.ant.util.GlobPatternMapper;
+import org.apache.tools.ant.util.SourceFileScanner;
+import org.apache.tools.ant.types.Reference;
+import org.apache.tools.ant.types.EnumeratedAttribute;
+
+import scala.tools.util.ConsoleReporter;
+import scala.tools.util.Reporter;
+
+package scala.tools.nsc.ant {
+
+ /**
+ * An Ant task to compile with the new Scala compiler (NSC).
+ * This task can take the following parameters as attributes:<ul>
+ * <li>srcdir (mandatory),</li>
+ * <li>srcref,</li>
+ * <li>destdir,</li>
+ * <li>classpath,</li>
+ * <li>classpathref,</li>
+ * <li>sourcepath,</li>
+ * <li>sourcepathref,</li>
+ * <li>bootclasspath,</li>
+ * <li>bootclasspathref,</li>
+ * <li>extdirs,</li>
+ * <li>extdirsref,</li>
+ * <li>encoding,</li>
+ * <li>verbose,</li>
+ * <li>debug,</li>
+ * <li>usepredefs,</li>
+ * <li>useimports,</li>
+ * <li>force,</li>
+ * <li>stop,</li>
+ * <li>skip,</li>
+ * <li>check.</li>
+ * </ul>
+ * It also takes the following parameters as nested elements:<ul>
+ * <li>src (for srcdir),</li>
+ * <li>classpath,</li>
+ * <li>sourcepath,</li>
+ * <li>bootclasspath,</li>
+ * <li>extdirs.</li>
+ * </ul>
+ *
+ * @author Gilles Dubochet
+ */
+ class NSC extends MatchingTask {
+
+ private val SCALA_PRODUCT: String =
+ System.getProperty("scala.product", "Scalac Ant compiler");
+ private val SCALA_VERSION: String =
+ System.getProperty("scala.version", "Unknown version");
+
+ /** The unique Ant file utilities instance to use in this task. */
+ private val fileUtils = FileUtils.newFileUtils();
+
+ // ###################################################################
+ // ##### Ant Properties #####
+ // ###################################################################
+
+ /** Defines valid values for the logging property. */
+ class LoggingLevel extends EnumeratedAttribute {
+ def getValues () = Predef.Array("", "none", "verbose", "debug");
+ }
+
+ /** Defines valid values for properties that refer to compiler phases. */
+ class CompilerPhase extends EnumeratedAttribute {
+ def getValues () = Predef.Array("", "namer", "typer", "pickler", "uncurry", "tailcalls", "transmatch", "explicitouter", "erasure", "lambdalift", "flatten", "constructors", "mixin", "icode", "jvm");
+ }
+
+ /** The directories that contain source files to compile. */
+ private var origin: Option[Path] = None;
+ /** The directory to put the compiled files in. */
+ private var destination: Option[File] = None;
+
+ /** The class path to use for this compilation. */
+ private var classpath: Option[Path] = None;
+ /** The source path to use for this compilation. */
+ private var sourcepath: Option[Path] = None;
+ /** The boot class path to use for this compilation. */
+ private var bootclasspath: Option[Path] = None;
+ /** The external extensions path to use for this compilation. */
+ private var extpath: Option[Path] = None;
+
+ /** The text encoding of the files to compile. */
+ private var encoding: Option[String] = None;
+
+ /** How much logging output to print. Either none (default), verbose or debug. */
+ private var logging: Option[String] = None;
+ /** Whether to use implicit predefined values or not. */
+ private var usepredefs: Boolean = true;
+ /** Whether to implicitly import or not. */
+ private var useimports: Boolean = true;
+ /** Whether to force compilation of all files or not. */
+ private var force: Boolean = false;
+ /** After which phase the compilation should stop. */
+ private var stop: Option[String] = None;
+ /** Which compilation phases should be skipped during compilation. */
+ private var skip: List[String] = Nil;
+ /** Which compilation phases results should be checked for consistency. */
+ private var check: List[String] = Nil;
+
+ // ###################################################################
+ // ##### Properties setters #####
+ // ###################################################################
+
+ /**
+ * Sets the srcdir attribute. Used by Ant.
+ * @param input The value of <code>origin</code>.
+ */
+ def setSrcdir (input: Path) = {
+ if (origin.isEmpty) {
+ origin = Some(input);
+ } else {
+ origin.get.append(input);
+ }
+ }
+
+ /**
+ * Sets the <code>origin</code> as a nested src Ant parameter.
+ * @return An origin path to be configured.
+ */
+ def createSrc (): Path = {
+ if (origin.isEmpty) {
+ origin = Some(new Path(getProject()));
+ }
+ origin.get.createPath();
+ }
+
+ /**
+ * Sets the <code>origin</code> as an external reference Ant parameter.
+ * @param input A reference to an origin path.
+ */
+ def setSrcref (input: Reference) = {
+ createSrc().setRefid(input);
+ }
+
+ /**
+ * Gets the value of the origin attribute in a Scala-friendly form.
+ * @returns The origin path as a list of files.
+ */
+ private def getOrigin: List[File] = {
+ if (origin.isEmpty)
+ throw new ArrayIndexOutOfBoundsException("Member 'origin' is empty.");
+ else List.fromArray(origin.get.list()).map(nameToFile("src"));
+ }
+
+ /**
+ * Sets the destdir attribute. Used by Ant.
+ * @param input The value of <code>destination</code>.
+ */
+ def setDestdir (input: File) = {
+ destination = Some(input);
+ }
+
+ /**
+ * Gets the value of the destination attribute in a Scala-friendly form.
+ * @returns The destination as a file.
+ */
+ private def getDestination: File = {
+ if (destination.isEmpty)
+ throw new ArrayIndexOutOfBoundsException("Member 'destination' is empty.");
+ else testReadableFile("destdir")(getProject().resolveFile(destination.get.toString()));
+ }
+
+ /**
+ * Sets the classpath attribute. Used by Ant.
+ * @param input The value of <code>classpath</code>.
+ */
+ def setClasspath (input: Path) = {
+ if (classpath.isEmpty) {
+ classpath = Some(input);
+ } else {
+ classpath.get.append(input);
+ }
+ }
+
+ /**
+ * Sets the <code>classpath</code> as a nested classpath Ant parameter.
+ * @return A class path to be configured.
+ */
+ def createClasspath (): Path = {
+ if (classpath.isEmpty) {
+ classpath = Some(new Path(getProject()));
+ }
+ classpath.get.createPath();
+ }
+
+ /**
+ * Sets the <code>classpath</code> as an external reference Ant parameter.
+ * @param input A reference to a class path.
+ */
+ def setClasspathref (input: Reference) = {
+ createClasspath().setRefid(input);
+ }
+
+ /**
+ * Gets the value of the classpath attribute in a Scala-friendly form.
+ * @returns The class path as a list of files.
+ */
+ private def getClasspath: List[File] = {
+ if (classpath.isEmpty)
+ throw new ArrayIndexOutOfBoundsException("Member 'classpath' is empty.");
+ else List.fromArray(classpath.get.list()).map(nameToFile("classpath"));
+ }
+
+ /**
+ * Sets the sourcepath attribute. Used by Ant.
+ * @param input The value of <code>sourcepath</code>.
+ */
+ def setSourcepath (input: Path) = {
+ if (sourcepath.isEmpty) {
+ sourcepath = Some(input);
+ } else {
+ sourcepath.get.append(input);
+ }
+ }
+
+ /**
+ * Sets the <code>sourcepath</code> as a nested sourcepath Ant parameter.
+ * @return A source path to be configured.
+ */
+ def createSourcepath (): Path = {
+ if (sourcepath.isEmpty) {
+ sourcepath = Some(new Path(getProject()));
+ }
+ sourcepath.get.createPath();
+ }
+
+ /**
+ * Sets the <code>sourcepath</code> as an external reference Ant parameter.
+ * @param input A reference to a source path.
+ */
+ def setSourcepathref (input: Reference) = {
+ createSourcepath().setRefid(input);
+ }
+
+ /**
+ * Gets the value of the sourcepath attribute in a Scala-friendly form.
+ * @returns The source path as a list of files.
+ */
+ private def getSourcepath: List[File] = {
+ if (sourcepath.isEmpty)
+ throw new ArrayIndexOutOfBoundsException("Member 'sourcepath' is empty.");
+ else List.fromArray(sourcepath.get.list()).map(nameToFile("sourcepath"));
+ }
+
+ /**
+ * Sets the boot classpath attribute. Used by Ant.
+ * @param input The value of <code>bootclasspath</code>.
+ */
+ def setBootclasspath (input: Path) = {
+ if (bootclasspath.isEmpty) {
+ bootclasspath = Some(input);
+ } else {
+ bootclasspath.get.append(input);
+ }
+ }
+
+ /**
+ * Sets the <code>bootclasspath</code> as a nested sourcepath Ant parameter.
+ * @return A source path to be configured.
+ */
+ def createBootclasspath (): Path = {
+ if (bootclasspath.isEmpty) {
+ bootclasspath = Some(new Path(getProject()));
+ }
+ bootclasspath.get.createPath();
+ }
+
+ /**
+ * Sets the <code>bootclasspath</code> as an external reference Ant parameter.
+ * @param input A reference to a source path.
+ */
+ def setBootclasspathref (input: Reference) = {
+ createBootclasspath().setRefid(input);
+ }
+
+ /**
+ * Gets the value of the bootclasspath attribute in a Scala-friendly form.
+ * @returns The boot class path as a list of files.
+ */
+ private def getBootclasspath: List[File] = {
+ if (bootclasspath.isEmpty)
+ throw new ArrayIndexOutOfBoundsException("Member 'bootclasspath' is empty.");
+ else List.fromArray(bootclasspath.get.list()).map(nameToFile("bootclasspath"));
+ }
+
+ /**
+ * Sets the external extensions path attribute. Used by Ant.
+ * @param input The value of <code>extpath</code>.
+ */
+ def setExtdirs (input: Path) = {
+ if (extpath.isEmpty) {
+ extpath = Some(input);
+ } else {
+ extpath.get.append(input);
+ }
+ }
+
+ /**
+ * Sets the <code>extpath</code> as a nested sourcepath Ant parameter.
+ * @return An extensions path to be configured.
+ */
+ def createExtdirs (): Path = {
+ if (extpath.isEmpty) {
+ extpath = Some(new Path(getProject()));
+ }
+ extpath.get.createPath();
+ }
+
+ /**
+ * Sets the <code>extpath</code> as an external reference Ant parameter.
+ * @param input A reference to an extensions path.
+ */
+ def setExtdirsref (input: Reference) = {
+ createExtdirs().setRefid(input);
+ }
+
+ /**
+ * Gets the value of the extpath attribute in a Scala-friendly form.
+ * @returns The extensions path as a list of files.
+ */
+ private def getExtpath: List[File] = {
+ if (extpath.isEmpty)
+ throw new ArrayIndexOutOfBoundsException("Member 'extdirs' is empty.");
+ else List.fromArray(extpath.get.list()).map(nameToFile("extdirs"));
+ }
+
+ /**
+ * Sets the encoding attribute. Used by Ant.
+ * @param input The value of <code>encoding</code>.
+ */
+ def setEncoding (input: String) = {
+ encoding = Some(input);
+ }
+
+ /**
+ * Sets the logging level attribute. Used by Ant.
+ * @param input The value for <code>logging</code>.
+ */
+ def setLogging (input: LoggingLevel) = {
+ logging = Some(input.getValue());
+ }
+
+ /**
+ * Sets the use predefs attribute. Used by Ant.
+ * @param input The value for <code>usepredefs</code>.
+ */
+ def setUsepredefs (input: Boolean) = {
+ usepredefs = input;
+ }
+
+ /**
+ * Sets the use imports attribute. Used by Ant.
+ * @param input The value for <code>useimport</code>.
+ */
+ def setUseimports (input: Boolean) = {
+ useimports = input;
+ }
+
+ /**
+ * Sets the force attribute. Used by Ant.
+ * @param input The value for <code>force</code>.
+ */
+ def setForce (input: Boolean) = {
+ force = input;
+ }
+
+ /**
+ * Sets the force attribute. Used by Ant.
+ * @param input The value for <code>force</code>.
+ */
+ def setStop (input: CompilerPhase) = {
+ if (input != "")
+ stop = Some(input.getValue());
+ }
+
+ /**
+ * Sets the force attribute. Used by Ant.
+ * @param input The value for <code>force</code>.
+ */
+ def setSkip (input: String) = {
+ val cp = new CompilerPhase();
+ skip = List.fromArray(input.split(",")).flatMap(s:String=>{
+ val st = s.trim();
+ if (cp.containsValue(st)) (if (st != "") List(st) else Nil)
+ else {error("Phase " + st + " in skip does not exist."); Nil}
+ });
+ }
+
+ /**
+ * Sets the force attribute. Used by Ant.
+ * @param input The value for <code>force</code>.
+ */
+ def setCheck (input: String) = {
+ val cp = new CompilerPhase();
+ check = List.fromArray(input.split(",")).flatMap(s:String=>{
+ val st = s.trim();
+ if (cp.containsValue(st)) (if (st != "") List(st) else Nil)
+ else {error("Phase " + st + " in check does not exist."); Nil}
+ });
+ }
+
+ // ###################################################################
+ // ##### Compilation and support methods #####
+ // ###################################################################
+
+ /**
+ * Creates a file from a given string.
+ * @param test A method to test whether the file is valid.
+ * @param name The path of the file as a string.
+ * @return The file corresponding to the provided name.
+ */
+ private def nameToFile (test: File=>File) (name: String): File = {
+ test(getProject().resolveFile(name));
+ }
+
+ /**
+ * Creates a file from a given string.
+ * @param test A method to test whether the file is valid.
+ * @param name The path of the file as a string.
+ * @return The file corresponding to the provided name.
+ */
+ private def nameToFile (test: File=>File, origin: File) (name: String): File = {
+ test(fileUtils.resolveFile(origin, name));
+ }
+
+ /**
+ * Creates a file from a given string and tests its validity using the <code>testReadableFile</code> method.
+ * @param pathName The name of the path in which the file is.
+ * @param name The path of the file as a string.
+ * @return The file corresponding to the provided name.
+ */
+ private def nameToFile (pathName: String, origin: File) (name: String): File = {
+ nameToFile(testReadableFile(pathName), origin)(name);
+ }
+
+ /**
+ * Creates a file from a given string and tests its validity using the <code>testReadableFile</code> method.
+ * @param pathName The name of the path in which the file is.
+ * @param name The path of the file as a string.
+ * @return The file corresponding to the provided name.
+ */
+ private def nameToFile (pathName: String) (name: String): File = {
+ nameToFile(testReadableFile(pathName))(name);
+ }
+
+ /**
+ * Tests whether a file is readable (if it does not exist, it is not readable.
+ * If it is not readable, prints a warning message.
+ * @param pathName The name of the path in which the file is (used for printing-out warning message).
+ * @param file The file to test.
+ * @return The same file as provided.
+ */
+ private def testReadableFile (pathName: String)(file: File): File = {
+ if (!file.exists())
+ log("Element '" + file.toString() + "' in " + pathName + " does not exist.", Project.MSG_WARN);
+ file
+ }
+
+ private def asString (path: List[File]): String = {
+ path.map(file:File=>asString(file)).mkString("", ":", "")
+ }
+
+ private def asString (file: File): String = {
+ file.getAbsolutePath()
+ }
+
+ /**
+ * Generates a build error. Error location will be the current task in the ant file.
+ * @param message The message of the error. This message should be end-user readable.
+ * @throws org.apache.tools.ant.BuildException The build error exception. Will be thrown in all conditions.
+ */
+ private def error (message: String) = {
+ throw new BuildException(message, getLocation());
+ }
+
+ private def getClassLoaderClasspath (classLoader: ClassLoader): List[File] = {
+ val parentClassLoader = classLoader.getParent();
+ val classloaderName = classLoader.getClass().getName();
+ (if (parentClassLoader != null && parentClassLoader != classLoader)
+ getClassLoaderClasspath(parentClassLoader)
+ else Nil) :::
+ (if (classloaderName.endsWith("URLClassLoader"))
+ List.fromArray((classLoader.asInstanceOf[URLClassLoader]).getURLs()).map(url:URL=>new File(url.getFile()))
+ else if (classloaderName.endsWith("AntClassLoader2") || classloaderName.endsWith("AntClassLoader"))
+ List.fromArray(( classLoader.asInstanceOf[AntClassLoader]).getClasspath().split(File.pathSeparator)).map(nameToFile(f:File=>f))
+ else Nil)
+ }
+
+ /**
+ * Performs the compilation.
+ */
+ override def execute () = {
+
+ // Tests if all mandatory attributes are set and valid.
+ if (origin.isEmpty) error("Attribute 'srcdir' is not set.");
+ if (getOrigin.isEmpty) error("Attribute 'srcdir' is not set.");
+ if (!destination.isEmpty && !destination.get.isDirectory())
+ error("Attribute 'destdir' does not refer to an existing directory.");
+ if (destination.isEmpty) {
+ destination = Some(getOrigin.head);
+ }
+
+ val mapper = new GlobPatternMapper();
+ mapper.setTo("*.symbl");
+ mapper.setFrom("*.scala");
+
+ // Scans source directories to build up a compile lists.
+ // If force is false, only files were the .class file in destination is older than
+ // the .scala file will be used.
+ val sourceFiles: List[File] =
+ for (val originDir <- getOrigin;
+ val originFile <- {
+ var includedFiles = getDirectoryScanner(originDir).getIncludedFiles();
+ if (!force) {
+ includedFiles = new SourceFileScanner(this).restrict(includedFiles, originDir, destination.get, mapper)
+ }
+ (List.fromArray(includedFiles)).map(nameToFile("srcdir", originDir))
+ }
+ ) yield {
+ log(originFile.toString(), Project.MSG_VERBOSE);
+ originFile
+ }
+
+ if (sourceFiles.length == 0)
+ log("No files selected for compilation")
+ else
+ log("Compiling " + sourceFiles.length + " source file" + (if (sourceFiles.length == 1) "s" else "") + (" to " + getDestination.toString()));
+
+ System.setProperty("scala.library.class.path", "");
+ System.setProperty("scala.library.source.path", "");
+
+ // Builds-up the compilation settings for Scalac with the existing Ant parameters.
+ val reporter = new ConsoleReporter();
+ val settings = new Settings(error);
+ settings.outdir.value = asString(destination.get);
+ if (!classpath.isEmpty) settings.classpath.value = asString(getClasspath);
+ if (!sourcepath.isEmpty) settings.sourcepath.value = asString(getSourcepath);
+ if (!bootclasspath.isEmpty) settings.bootclasspath.value = asString(getBootclasspath);
+ if (!extpath.isEmpty) settings.extdirs.value = asString(getExtpath);
+ if (!encoding.isEmpty) settings.encoding.value = encoding.get;
+ if (!logging.isEmpty && logging.get == "verbose") {
+ settings.verbose.value = true;
+ } else if (!logging.isEmpty && logging.get == "debug") {
+ settings.verbose.value = true;
+ settings.debug.value = true;
+ }
+ settings.noimports.value = !useimports;
+ settings.nopredefs.value = !usepredefs;
+ if (!stop.isEmpty) settings.stop.value = List(stop.get);
+ if (!skip.isEmpty) settings.skip.value = skip;
+ if (!check.isEmpty) settings.check.value = check;
+
+ // Sets path properties to prevent ClassPath from being corrupted.
+ // It this isn't done, classpath will contain more than
+ //System.setProperty("scala.library.class.path", "");
+ //System.setProperty("scala.library.source.path", "");
+
+ // Compiles the actual code
+ val compiler = new Global(settings, reporter);
+ try {
+ (new compiler.Run).compile(sourceFiles.map(f:File=>f.toString()));
+ if (reporter.errors() > 0)
+ error("Compile failed with " + reporter.errors() + " error" + (if (reporter.errors() == 1) "" else "s") + "; see the compiler error output for details.");
+ } catch {
+ case exception @ FatalError(msg) => {
+ if (settings.debug.value) exception.printStackTrace();
+ error("Compile failed because of an internal compiler error (" + msg + "); see the error output for details.");
+ }
+ }
+ if (reporter.warnings() > 0)
+ log("Compile suceeded with " + reporter.errors() + " warning" + (if (reporter.warnings() == 1) "" else "s") + "; see the compiler output for details.");
+ reporter.printSummary()
+ }
+
+ }
+
+} \ No newline at end of file