diff options
Diffstat (limited to 'src/compiler/scala/tools/nsc/ant/NSC.scala')
-rw-r--r-- | src/compiler/scala/tools/nsc/ant/NSC.scala | 645 |
1 files changed, 645 insertions, 0 deletions
diff --git a/src/compiler/scala/tools/nsc/ant/NSC.scala b/src/compiler/scala/tools/nsc/ant/NSC.scala new file mode 100644 index 0000000000..9010a92bf9 --- /dev/null +++ b/src/compiler/scala/tools/nsc/ant/NSC.scala @@ -0,0 +1,645 @@ +/* __ ______________ *\ +** / |/ / ____/ ____/ ** +** / | | /___ / /___ ** +** /_/|__/_____/_____/ Copyright 2005-2006 LAMP/EPFL ** +\* */ + +// $Id$ + + +package scala.tools.nsc.ant { + + 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.EnumeratedAttribute; + import org.apache.tools.ant.types.Reference; + + import scala.tools.nsc.reporters.{Reporter,ConsoleReporter}; + + /** + * 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> + * <li>print,</li> + * <li>showicode,</li> + * <li>log,</li> + * <li>debuginfo.</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 ##### + // ################################################################### + + abstract class PermissibleValue { + val values: List[String]; + def isPermissible(value: String): Boolean = ( + (value == "") || + values.exists(v: String => v startsWith value) + ) + } + + /** Defines valid values for the logging property. */ + object LoggingLevel extends PermissibleValue { + val values = List("none", "verbose", "debug"); + } + + /** Defines valid values for properties that refer to compiler phases. */ + object CompilerPhase extends PermissibleValue { + val values = List( + "namer", "typer", "pickler", "uncurry", "tailcalls", + "transmatch", "explicitouter", "erasure", "lambdalift", + "flatten", "constructors", "mixin", "icode", "jvm", "terminal"); + } + + /** 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 should be logged during compilation. */ + private var logPhase: List[String] = Nil; + /** Which compilation phases results should be checked for consistency. */ + private var check: List[String] = Nil; + /** Which compilation phases results should be printed-out. */ + private var print: List[String] = Nil; + /** Print ICode files along with class files (debug option). */ + private var showICode: Boolean = false; + + /** Instruct the compiler to generate debugging information (pass '-g') */ + private var debugInfo: Boolean = false; + + + // ################################################################### + // ##### 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): Unit = + encoding = Some(input); + + /** + * Sets the logging level attribute. Used by Ant. + * @param input The value for <code>logging</code>. + */ + def setLogging(input: String) = + if (LoggingLevel.isPermissible(input)) + logging = Some(input); + else + error("Logging level '" + input + "' does not exist."); + + /** + * Sets the use predefs attribute. Used by Ant. + * @param input The value for <code>usepredefs</code>. + */ + def setUsepredefs(input: Boolean): Unit = + usepredefs = input; + + /** + * Sets the use imports attribute. Used by Ant. + * @param input The value for <code>useimport</code>. + */ + def setUseimports(input: Boolean): Unit = + useimports = input; + + /** + * Sets the force attribute. Used by Ant. + * @param input The value for <code>force</code>. + */ + def setForce(input: Boolean): Unit = + force = input; + + /** + * Sets the force attribute. Used by Ant. + * @param input The value for <code>force</code>. + */ + def setStop(input: String) = + if (CompilerPhase.isPermissible(input)) { + if (input != "") + stop = Some(input); + } + else + error("Phase '" + input + "' in stop does not exist."); + + /** + * Sets the force attribute. Used by Ant. + * @param input The value for <code>force</code>. + */ + def setSkip(input: String) = { + skip = List.fromArray(input.split(",")).flatMap(s: String => { + val st = s.trim(); + if (CompilerPhase.isPermissible(st)) (if (input != "") List(st) else Nil) + else {error("Phase '" + st + "' in skip does not exist."); Nil} + }); + } + + /** + * Sets the log attribute. Used by Ant. + * @param input The value for <code>logPhase</code>. + */ + def setLog(input: String) = { + logPhase = List.fromArray(input.split(",")).flatMap(s: String => { + val st = s.trim(); + if (CompilerPhase.isPermissible(st)) (if (input != "") List(st) else Nil) + else {error("Phase " + st + " in log does not exist."); Nil} + }); + } + + /** + * Sets the check attribute. Used by Ant. + * @param input The value for <code>check</code>. + */ + def setCheck(input: String) = { + check = List.fromArray(input.split(",")).flatMap(s: String => { + val st = s.trim(); + if (CompilerPhase.isPermissible(st)) (if (input != "") List(st) else Nil) + else {error("Phase " + st + " in check does not exist."); Nil} + }); + } + + /** + * Sets the print attribute. Used by Ant. + * @param input The value for <code>print</code>. + */ + def setPrint(input: String) = { + print = List.fromArray(input.split(",")).flatMap(s: String => { + val st = s.trim(); + if (CompilerPhase.isPermissible(st)) (if (input != "") List(st) else Nil) + else {error("Phase " + st + " in print does not exist."); Nil} + }); + } + + def setShowicode(input: Boolean): Unit = + showICode = input; + + /** + * Set the debug info attribute. + */ + def setDebuginfo(input: Boolean): Unit = + debugInfo = input; + + // ################################################################### + // ##### Compilation and support methods ##### + // ################################################################### + + override protected def getDirectoryScanner (baseDir: java.io.File) = + super.getDirectoryScanner(baseDir); + + /** + * 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((f: File) => testReadableFile(pathName)(f), 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((f: File) => testReadableFile(pathName)(f))(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("", File.pathSeparator, "") + } + + 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()); + } + + /** + * 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("*.class"); + 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) + else if (origin.get.size() > 0) settings.sourcepath.value = origin.get.list()(0); + 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; + if (!print.isEmpty) settings.print.value = print; + settings.Xshowicode.value = showICode; + settings.debuginfo.value = debugInfo; + if (!logPhase.isEmpty) settings.log.value = logPhase; + + // 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) "s" else "") + + "; see the compiler error output for details."); + } + catch { + case exception @ FatalError(msg) => { + exception.printStackTrace(); + 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) "s" else "") + + "; see the compiler output for details."); + reporter.printSummary() + } + + } + +} |