* fixed tab completion for method tasks for multi-project builds

* check that tasks in compoundTask do not reference static tasks
 * make toString of Paths in subprojects relative to root project directory
 * crossScalaVersions is now inherited from parent
 * Added scala-library.jar to the javac classpath
 * Added Process.cat that will send contents of URLs and Files to standard output



git-svn-id: https://simple-build-tool.googlecode.com/svn/trunk@877 d89573ee-9141-11dd-94d4-bdf5e562f29c
This commit is contained in:
dmharrah 2009-07-21 23:44:29 +00:00
parent f2fe9bcfe3
commit 8420606a8c
10 changed files with 143 additions and 66 deletions

View File

@ -3,10 +3,16 @@
*/
package sbt
import java.io.File
object CompileOrder extends Enumeration
{
val Mixed, JavaThenScala, ScalaThenJava = Value
}
private object CompilerCore
{
def scalaClasspathForJava = FileUtilities.scalaJars.map(_.getAbsolutePath).mkString(File.pathSeparator)
}
sealed abstract class CompilerCore
{
val ClasspathOptionString = "-classpath"
@ -16,25 +22,35 @@ sealed abstract class CompilerCore
protected def process(args: List[String], log: Logger): Boolean
// Returns false if there were errors, true if there were not.
protected def processJava(args: List[String], log: Logger): Boolean = true
protected def scalaClasspathForJava: String
def actionStartMessage(label: String): String
def actionNothingToDoMessage: String
def actionSuccessfulMessage: String
def actionUnsuccessfulMessage: String
private def classpathString(rawClasspathString: String, includeScala: Boolean) =
if(includeScala)
List(rawClasspathString, scalaClasspathForJava).mkString(File.pathSeparator)
else
rawClasspathString
final def apply(label: String, sources: Iterable[Path], classpathString: String, outputDirectory: Path, options: Seq[String], log: Logger): Option[String] =
apply(label, sources, classpathString, outputDirectory, options, Nil, CompileOrder.Mixed, log)
final def apply(label: String, sources: Iterable[Path], classpathString: String, outputDirectory: Path, options: Seq[String], javaOptions: Seq[String], order: CompileOrder.Value, log: Logger): Option[String] =
final def apply(label: String, sources: Iterable[Path], rawClasspathString: String, outputDirectory: Path, options: Seq[String], javaOptions: Seq[String], order: CompileOrder.Value, log: Logger): Option[String] =
{
log.info(actionStartMessage(label))
val classpathOption: List[String] =
if(classpathString.isEmpty)
def classpathOption(includeScala: Boolean): List[String] =
{
val classpath = classpathString(rawClasspathString, includeScala)
if(classpath.isEmpty)
Nil
else
List(ClasspathOptionString, classpathString)
List(ClasspathOptionString, classpath)
}
val outputDir = outputDirectory.asFile
FileUtilities.createDirectory(outputDir, log) orElse
{
val classpathAndOut: List[String] = OutputOptionString :: outputDir.getAbsolutePath :: classpathOption
def classpathAndOut(javac: Boolean): List[String] = OutputOptionString :: outputDir.getAbsolutePath :: classpathOption(javac)
Control.trapUnit("Compiler error: ", log)
{
@ -47,7 +63,7 @@ sealed abstract class CompilerCore
else
{
def filteredSources(extension: String) = sourceList.filter(_.endsWith(extension))
def compile(label: String, sources: List[String], options: Seq[String])(process: (List[String], Logger) => Boolean) =
def compile(label: String, sources: List[String], options: Seq[String], includeScala: Boolean)(process: (List[String], Logger) => Boolean) =
{
if(sources.isEmpty)
{
@ -56,7 +72,7 @@ sealed abstract class CompilerCore
}
else
{
val arguments = (options ++ classpathAndOut ++ sources).toList
val arguments = (options ++ classpathAndOut(includeScala) ++ sources).toList
log.debug(label + " arguments: " + arguments.mkString(" "))
process(arguments, log)
}
@ -64,12 +80,12 @@ sealed abstract class CompilerCore
def scalaCompile = () =>
{
val scalaSourceList = if(order == CompileOrder.Mixed) sourceList else filteredSources(".scala")
compile("Scala", scalaSourceList, options)(process)
compile("Scala", scalaSourceList, options, false)(process)
}
def javaCompile = () =>
{
val javaSourceList = filteredSources(".java")
compile("Java", javaSourceList, javaOptions)(processJava)
compile("Java", javaSourceList, javaOptions, true)(processJava)
}
val (first, second) = if(order == CompileOrder.JavaThenScala) (javaCompile, scalaCompile) else (scalaCompile, javaCompile)
@ -100,6 +116,7 @@ final class ForkCompile(config: ForkScalaCompiler) extends CompilerBase
Fork.scalac(config.javaHome, config.compileJVMOptions, config.scalaJars, arguments, log) == 0
override protected def processJava(args: List[String], log: Logger) =
Fork.javac(config.javaHome, args, log) == 0
override protected def scalaClasspathForJava = config.scalaJars.mkString(File.pathSeparator)
}
object ForkCompile
{
@ -152,9 +169,11 @@ final class Compile(maximumErrors: Int) extends CompilerBase
}
override protected def processJava(args: List[String], log: Logger) =
(Process("javac", args) ! log) == 0
protected def scalaClasspathForJava = CompilerCore.scalaClasspathForJava
}
final class Scaladoc(maximumErrors: Int) extends CompilerCore
{
protected def scalaClasspathForJava = CompilerCore.scalaClasspathForJava
protected def process(arguments: List[String], log: Logger) =
{
import scala.tools.nsc.{doc, CompilerCommand, FatalError, Global, reporters, util}

View File

@ -803,6 +803,7 @@ object FileUtilities
lazy val sbtJar: File = classLocationFile(getClass)
lazy val scalaLibraryJar: File = classLocationFile[scala.ScalaObject]
lazy val scalaCompilerJar: File = classLocationFile[scala.tools.nsc.Settings]
def scalaJars: Iterable[File] = List(scalaLibraryJar, scalaCompilerJar)
/** The producer of randomness for unique name generation.*/
private val random = new java.util.Random

View File

@ -309,8 +309,8 @@ object ManageDependencies
configSet.toArray
}
}
def excludeScalaJar(name: String)
{ module.addExcludeRule(excludeRule(ScalaArtifacts.Organization, name, configurationNames)) }
def excludeScalaJar(name: String): Unit =
module.addExcludeRule(excludeRule(ScalaArtifacts.Organization, name, configurationNames))
excludeScalaJar(ScalaArtifacts.LibraryID)
excludeScalaJar(ScalaArtifacts.CompilerID)
}

View File

@ -82,13 +82,16 @@ private[sbt] final class FilePath(file: File) extends Path
def projectRelativePathString(separator: String) = relativePathString(separator)
private[sbt] def prependTo(s: String) = absolutePath + sep + s
}
private[sbt] final class ProjectDirectory(file: File) extends Path
// toRoot is the path between this and the root project path and is used for toString
private[sbt] final class ProjectDirectory(file: File, toRoot: Option[Path]) extends Path
{
def this(file: File) = this(file, None)
lazy val asFile = absolute(file)
override def toString = "."
override def toString = foldToRoot(_.toString, ".")
def relativePathString(separator: String) = ""
def projectRelativePathString(separator: String) = ""
private[sbt] def prependTo(s: String) = "." + sep + s
private[sbt] def prependTo(s: String) = foldToRoot(_.prependTo(s), "." + sep + s)
private[sbt] def foldToRoot[T](f: Path => T, orElse: T) = toRoot.map(f).getOrElse(orElse)
}
private[sbt] final class RelativePath(val parentPath: Path, val component: String) extends Path
{

View File

@ -20,23 +20,51 @@ object Process
implicit def apply(command: scala.xml.Elem): ProcessBuilder = apply(command.text.trim)
def apply(value: Boolean): ProcessBuilder = apply(value.toString, if(value) 0 else 1)
def apply(name: String, exitValue: => Int): ProcessBuilder = new DummyProcessBuilder(name, exitValue)
def cat(file: SourcePartialBuilder, files: SourcePartialBuilder*): ProcessBuilder = cat(file :: files.toList)
private[this] def cat(files: Seq[SourcePartialBuilder]): ProcessBuilder =
{
require(!files.isEmpty)
files.map(_.cat).reduceLeft(_ #&& _)
}
}
trait URLPartialBuilder extends NotNull
trait SourcePartialBuilder extends NotNull
{
def #>(b: ProcessBuilder): ProcessBuilder
def #>>(b: File): ProcessBuilder
def #>(b: File): ProcessBuilder
/** Writes the output stream of this process to the given file. */
def #> (f: File): ProcessBuilder = toFile(f, false)
/** Appends the output stream of this process to the given file. */
def #>> (f: File): ProcessBuilder = toFile(f, true)
/** Writes the output stream of this process to the given OutputStream. The
* argument is call-by-name, so the stream is recreated, written, and closed each
* time this process is executed. */
def #>(out: => OutputStream): ProcessBuilder = #> (new OutputStreamBuilder(out))
def #>(b: ProcessBuilder): ProcessBuilder = new PipedProcessBuilder(toSource, b, false)
private def toFile(f: File, append: Boolean) = #> (new FileOutput(f, append))
def cat = toSource
protected def toSource: ProcessBuilder
}
trait FilePartialBuilder extends NotNull
trait SinkPartialBuilder extends NotNull
{
def #>(b: ProcessBuilder): ProcessBuilder
def #<(b: ProcessBuilder): ProcessBuilder
def #<(url: URL): ProcessBuilder
def #>>(b: File): ProcessBuilder
def #>(b: File): ProcessBuilder
def #<(file: File): ProcessBuilder
def #<<(file: File): ProcessBuilder
/** Reads the given file into the input stream of this process. */
def #< (f: File): ProcessBuilder = #< (new FileInput(f))
/** Reads the given URL into the input stream of this process. */
def #< (f: URL): ProcessBuilder = #< (new URLInput(f))
/** Reads the given InputStream into the input stream of this process. The
* argument is call-by-name, so the stream is recreated, read, and closed each
* time this process is executed. */
def #<(in: => InputStream): ProcessBuilder = #< (new InputStreamBuilder(in))
def #<(b: ProcessBuilder): ProcessBuilder = new PipedProcessBuilder(b, toSink, false)
protected def toSink: ProcessBuilder
}
trait URLPartialBuilder extends SourcePartialBuilder
trait FilePartialBuilder extends SinkPartialBuilder with SourcePartialBuilder
{
def #<<(f: File): ProcessBuilder
def #<<(u: URL): ProcessBuilder
def #<<(i: => InputStream): ProcessBuilder
def #<<(p: ProcessBuilder): ProcessBuilder
}
/** Represents a process that is running or has finished running.
@ -49,7 +77,7 @@ trait Process extends NotNull
def destroy(): Unit
}
/** Represents a runnable process. */
trait ProcessBuilder extends NotNull
trait ProcessBuilder extends SourcePartialBuilder with SinkPartialBuilder
{
/** Starts the process represented by this builder, blocks until it exits, and returns the exit code. Standard output and error are
* sent to the console.*/
@ -85,14 +113,6 @@ trait ProcessBuilder extends NotNull
def #| (other: ProcessBuilder): ProcessBuilder
/** Constructs a command that will run this command and then `other`. The exit code will be the exit code of `other`.*/
def ## (other: ProcessBuilder): ProcessBuilder
/** Reads the given file into the input stream of this process. */
def #< (f: File): ProcessBuilder
/** Reads the given URL into the input stream of this process. */
def #< (f: URL): ProcessBuilder
/** Writes the output stream of this process to the given file. */
def #> (f: File): ProcessBuilder
/** Appends the output stream of this process to the given file. */
def #>> (f: File): ProcessBuilder
def canPipeTo: Boolean
}

View File

@ -46,7 +46,7 @@ trait Project extends TaskManager with Dag[Project] with BasicEnvironment
* are different tasks with the same name, only one will be included. */
def taskList: String = descriptionList(deepTasks)
final def taskName(task: Task) = tasks.find( _._2 eq task ).map(_._1).getOrElse(UnnamedName)
final def taskName(task: Task) = tasks.find( _._2 eq task ).map(_._1)
/** A description of all available tasks in this project and all dependencies and all
* available method tasks in this project, but not of dependencies. If there
* are different tasks or methods with the same name, only one will be included. */
@ -233,7 +233,12 @@ trait Project extends TaskManager with Dag[Project] with BasicEnvironment
protected def disableCrossPaths = crossScalaVersions.isEmpty
/** By default, this is empty and cross-building is disabled. Overriding this to a Set of Scala versions
* will enable cross-building against those versions.*/
def crossScalaVersions = scala.collection.immutable.Set.empty[String]
def crossScalaVersions: immutable.Set[String] =
info.parent match
{
case Some(p) => p.crossScalaVersions
case None => immutable.Set.empty[String]
}
/** A `PathFinder` that determines the files watched when an action is run with a preceeding ~ when this is the current
* project. This project does not need to include the watched paths for projects that this project depends on.*/
def watchPaths: PathFinder = Path.emptyPathFinder
@ -256,7 +261,6 @@ private[sbt] final class LoadSetupError(val message: String) extends LoadResult
object Project
{
val UnnamedName = "<anonymous>"
val BootDirectoryName = "boot"
val DefaultOutputDirectoryName = "target"
val DefaultEnvBackingName = "build.properties"

View File

@ -8,7 +8,11 @@ import FileUtilities._
final case class ProjectInfo(projectDirectory: File, dependencies: Iterable[Project], parent: Option[Project]) extends NotNull
{
val projectPath = new ProjectDirectory(projectDirectory)
val projectPath: Path =
{
val toRoot = parent.flatMap(p => Path.relativize(p.info.projectPath, projectDirectory))
new ProjectDirectory(projectDirectory, toRoot)
}
val builderPath = projectPath / ProjectInfo.MetadataDirectoryName
def bootPath = builderPath / Project.BootDirectoryName
def builderProjectPath = builderPath / Project.BuilderProjectDirectoryName

View File

@ -3,6 +3,8 @@
*/
package sbt
import TaskManager._
trait Described extends NotNull
{
def description: Option[String]
@ -19,7 +21,8 @@ trait TaskManager{
/** Creates a method task that executes the given action when invoked. */
def task(action: Array[String] => ManagedTask) = new MethodTask(None, action, Nil)
def taskName(t: Task): String
def taskName(t: Task): Option[String]
final def taskNameString(task: Task): String = taskName(task).getOrElse(UnnamedName)
/** A method task is an action that has parameters. Note that it is not a Task, though,
* because it requires arguments to perform its work. It therefore cannot be a dependency of
@ -42,7 +45,8 @@ trait TaskManager{
this(None, description, dependencies, interactive, action)
checkTaskDependencies(dependencies)
def manager: ManagerType = TaskManager.this
def name = explicitName.getOrElse(taskName(this))
def name = explicitName.getOrElse(implicitName)
private[sbt] def implicitName = taskNameString(this)
def named(name: String) = construct(Some(name), description,dependencies, interactive, action)
override def toString = "Task " + name
@ -77,8 +81,16 @@ trait TaskManager{
interactive: Boolean, action : => Option[String]) = new CompoundTask(explicitName, description, dependencies, interactive, action, createWork)
def work = createWork
}
def compoundTask(createTask: => Project#Task) = new CompoundTask(SubWork[Project#Task](createTask))
def dynamic(createTask: => Project#Task) = new CompoundTask(SubWork[Project#Task](checkDynamic(createTask)))
/** Verifies that the given dynamically created task does not depend on any statically defined tasks.
* Returns the task if it is valid.*/
private def checkDynamic(task: Project#Task) =
{
for(t <- task.topologicalSort; staticName <- t.implicitName)
error("Dynamic task " + task.name + " depends on static task " + staticName)
task
}
private def checkTaskDependencies(dependencyList: List[ManagedTask])
{
val nullDependencyIndex = dependencyList.findIndexOf(_ == null)
@ -87,3 +99,7 @@ trait TaskManager{
require(interactiveDependencyIndex < 0, "Dependency (at index " + interactiveDependencyIndex + ") is interactive. Interactive tasks cannot be dependencies.")
}
}
object TaskManager
{
val UnnamedName = "<anonymous>"
}

View File

@ -5,7 +5,7 @@ package sbt
import java.lang.{Process => JProcess, ProcessBuilder => JProcessBuilder}
import java.io.{BufferedReader, Closeable, InputStream, InputStreamReader, IOException, OutputStream, PrintStream}
import java.io.{PipedInputStream, PipedOutputStream}
import java.io.{FilterInputStream, FilterOutputStream, PipedInputStream, PipedOutputStream}
import java.io.{File, FileInputStream, FileOutputStream}
import java.net.URL
@ -79,7 +79,7 @@ private object BasicIO
}
private abstract class AbstractProcessBuilder extends ProcessBuilder
private abstract class AbstractProcessBuilder extends ProcessBuilder with SinkPartialBuilder with SourcePartialBuilder
{
def #&&(other: ProcessBuilder): ProcessBuilder = new AndProcessBuilder(this, other)
def #||(other: ProcessBuilder): ProcessBuilder = new OrProcessBuilder(this, other)
@ -90,10 +90,8 @@ private abstract class AbstractProcessBuilder extends ProcessBuilder
}
def ##(other: ProcessBuilder): ProcessBuilder = new SequenceProcessBuilder(this, other)
def #< (f: File): ProcessBuilder = new PipedProcessBuilder(new FileInput(f), this, false)
def #< (url: URL): ProcessBuilder = new PipedProcessBuilder(new URLInput(url), this, false)
def #> (f: File): ProcessBuilder = new PipedProcessBuilder(this, new FileOutput(f, false), false)
def #>> (f: File): ProcessBuilder = new PipedProcessBuilder(this, new FileOutput(f, true), true)
protected def toSource = this
protected def toSink = this
def run(): Process = run(false)
def run(connectInput: Boolean): Process = run(BasicIO.standard(connectInput))
@ -115,23 +113,19 @@ private abstract class AbstractProcessBuilder extends ProcessBuilder
def canPipeTo = false
}
private[sbt] class URLBuilder(url: URL) extends URLPartialBuilder
private[sbt] class URLBuilder(url: URL) extends URLPartialBuilder with SourcePartialBuilder
{
def #>(b: ProcessBuilder): ProcessBuilder = b #< url
def #>>(file: File): ProcessBuilder = toFile(file, true)
def #>(file: File): ProcessBuilder = toFile(file, false)
private def toFile(file: File, append: Boolean) = new PipedProcessBuilder(new URLInput(url), new FileOutput(file, append), false)
protected def toSource = new URLInput(url)
}
private[sbt] class FileBuilder(base: File) extends FilePartialBuilder
private[sbt] class FileBuilder(base: File) extends FilePartialBuilder with SinkPartialBuilder with SourcePartialBuilder
{
def #>(b: ProcessBuilder): ProcessBuilder = b #< base
def #<(b: ProcessBuilder): ProcessBuilder = b #> base
def #<(url: URL): ProcessBuilder = new URLBuilder(url) #> base
def #>>(file: File): ProcessBuilder = pipe(base, file, true)
def #>(file: File): ProcessBuilder = pipe(base, file, false)
def #<(file: File): ProcessBuilder = pipe(file, base, false)
def #<<(file: File): ProcessBuilder = pipe(file, base, true)
private def pipe(from: File, to: File, append: Boolean) = new PipedProcessBuilder(new FileInput(from), new FileOutput(to, append), false)
protected def toSource = new FileInput(base)
protected def toSink = new FileOutput(base, false)
def #<<(f: File): ProcessBuilder = #<<(new FileInput(f))
def #<<(u: URL): ProcessBuilder = #<<(new URLInput(u))
def #<<(s: => InputStream): ProcessBuilder = #<<(new InputStreamBuilder(s))
def #<<(b: ProcessBuilder): ProcessBuilder = new PipedProcessBuilder(b, new FileOutput(base, true), false)
}
private abstract class BasicBuilder extends AbstractProcessBuilder
@ -369,8 +363,15 @@ private class FileOutput(file: File, append: Boolean) extends OutputStreamBuilde
private class URLInput(url: URL) extends InputStreamBuilder(url.openStream, url.toString)
private class FileInput(file: File) extends InputStreamBuilder(new FileInputStream(file), file.getAbsolutePath)
private class OutputStreamBuilder(stream: => OutputStream, label: String) extends ThreadProcessBuilder(label, _.writeInput(stream))
private class InputStreamBuilder(stream: => InputStream, label: String) extends ThreadProcessBuilder(label, _.processOutput(stream))
import Uncloseable.protect
private class OutputStreamBuilder(stream: => OutputStream, label: String) extends ThreadProcessBuilder(label, _.writeInput(protect(stream)))
{
def this(stream: => OutputStream) = this(stream, "<output stream>")
}
private class InputStreamBuilder(stream: => InputStream, label: String) extends ThreadProcessBuilder(label, _.processOutput(protect(stream)))
{
def this(stream: => InputStream) = this(stream, "<input stream>")
}
private abstract class ThreadProcessBuilder(override val toString: String, runImpl: ProcessIO => Unit) extends AbstractProcessBuilder
{
@ -381,7 +382,7 @@ private abstract class ThreadProcessBuilder(override val toString: String, runIm
new ThreadProcess(Spawn {runImpl(io); success.set(true) }, success)
}
}
private class ThreadProcess(thread: Thread, success: SyncVar[Boolean]) extends Process
private final class ThreadProcess(thread: Thread, success: SyncVar[Boolean]) extends Process
{
override def exitValue() =
{
@ -389,4 +390,12 @@ private class ThreadProcess(thread: Thread, success: SyncVar[Boolean]) extends P
if(success.get) 0 else 1
}
override def destroy() { thread.interrupt() }
}
object Uncloseable
{
def apply(in: InputStream): InputStream = new FilterInputStream(in) { override def close() {} }
def apply(out: OutputStream): OutputStream = new FilterOutputStream(out) { override def close() {} }
def protect(in: InputStream): InputStream = if(in eq System.in) Uncloseable(in) else in
def protect(out: OutputStream): OutputStream = if( (out eq System.out) || (out eq System.err)) Uncloseable(out) else out
}

View File

@ -13,6 +13,7 @@ object ProcessSpecification extends Properties("Process I/O")
specify("Correct exit code", (exitCode: Byte) => checkExit(exitCode))
specify("#&& correct", (exitCodes: Array[Byte]) => checkBinary(exitCodes)(_ #&& _)(_ && _))
specify("#|| correct", (exitCodes: Array[Byte]) => checkBinary(exitCodes)(_ #|| _)(_ || _))
specify("## correct", (exitCodes: Array[Byte]) => checkBinary(exitCodes)(_ ## _)( (x,latest) => latest))
specify("Pipe to output file", (data: Array[Byte]) => checkFileOut(data))
specify("Pipe to input file", (data: Array[Byte]) => checkFileIn(data))
specify("Pipe to process", (data: Array[Byte]) => checkPipe(data))
@ -28,7 +29,7 @@ object ProcessSpecification extends Properties("Process I/O")
}
}
private def toBoolean(exitCode: Int) = exitCode == 0
private def checkExit(code: Byte) =
private def checkExit(code: Byte) =
{
val exitCode = unsigned(code)
(process("sbt.exit " + exitCode) !) == exitCode