From 8420606a8cd3416612db0f82ffa2dbc5ed378c08 Mon Sep 17 00:00:00 2001 From: dmharrah Date: Tue, 21 Jul 2009 23:44:29 +0000 Subject: [PATCH] * 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 --- src/main/scala/sbt/Compile.scala | 37 ++++++++--- src/main/scala/sbt/FileUtilities.scala | 1 + src/main/scala/sbt/ManageDependencies.scala | 4 +- src/main/scala/sbt/Path.scala | 9 ++- src/main/scala/sbt/Process.scala | 62 ++++++++++++------- src/main/scala/sbt/Project.scala | 10 ++- src/main/scala/sbt/ProjectInfo.scala | 6 +- src/main/scala/sbt/TaskManager.scala | 22 ++++++- src/main/scala/sbt/impl/ProcessImpl.scala | 55 +++++++++------- src/test/scala/sbt/ProcessSpecification.scala | 3 +- 10 files changed, 143 insertions(+), 66 deletions(-) diff --git a/src/main/scala/sbt/Compile.scala b/src/main/scala/sbt/Compile.scala index 3dd80ec86..b9473ee14 100644 --- a/src/main/scala/sbt/Compile.scala +++ b/src/main/scala/sbt/Compile.scala @@ -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} diff --git a/src/main/scala/sbt/FileUtilities.scala b/src/main/scala/sbt/FileUtilities.scala index c63106b0a..ae3259f09 100644 --- a/src/main/scala/sbt/FileUtilities.scala +++ b/src/main/scala/sbt/FileUtilities.scala @@ -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 diff --git a/src/main/scala/sbt/ManageDependencies.scala b/src/main/scala/sbt/ManageDependencies.scala index c6f0f2461..f154afbe2 100644 --- a/src/main/scala/sbt/ManageDependencies.scala +++ b/src/main/scala/sbt/ManageDependencies.scala @@ -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) } diff --git a/src/main/scala/sbt/Path.scala b/src/main/scala/sbt/Path.scala index 23236371d..7c9aaa696 100644 --- a/src/main/scala/sbt/Path.scala +++ b/src/main/scala/sbt/Path.scala @@ -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 { diff --git a/src/main/scala/sbt/Process.scala b/src/main/scala/sbt/Process.scala index ac6e106b4..9341699dc 100644 --- a/src/main/scala/sbt/Process.scala +++ b/src/main/scala/sbt/Process.scala @@ -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 } diff --git a/src/main/scala/sbt/Project.scala b/src/main/scala/sbt/Project.scala index ac4527d45..fef74bf9a 100644 --- a/src/main/scala/sbt/Project.scala +++ b/src/main/scala/sbt/Project.scala @@ -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 = "" val BootDirectoryName = "boot" val DefaultOutputDirectoryName = "target" val DefaultEnvBackingName = "build.properties" diff --git a/src/main/scala/sbt/ProjectInfo.scala b/src/main/scala/sbt/ProjectInfo.scala index 80002cb1b..b2e4fe349 100644 --- a/src/main/scala/sbt/ProjectInfo.scala +++ b/src/main/scala/sbt/ProjectInfo.scala @@ -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 diff --git a/src/main/scala/sbt/TaskManager.scala b/src/main/scala/sbt/TaskManager.scala index 4d42f4f4b..55960d124 100644 --- a/src/main/scala/sbt/TaskManager.scala +++ b/src/main/scala/sbt/TaskManager.scala @@ -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 = "" +} \ No newline at end of file diff --git a/src/main/scala/sbt/impl/ProcessImpl.scala b/src/main/scala/sbt/impl/ProcessImpl.scala index 42e538a08..ab8157a4a 100644 --- a/src/main/scala/sbt/impl/ProcessImpl.scala +++ b/src/main/scala/sbt/impl/ProcessImpl.scala @@ -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, "") +} +private class InputStreamBuilder(stream: => InputStream, label: String) extends ThreadProcessBuilder(label, _.processOutput(protect(stream))) +{ + def this(stream: => InputStream) = this(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 } \ No newline at end of file diff --git a/src/test/scala/sbt/ProcessSpecification.scala b/src/test/scala/sbt/ProcessSpecification.scala index 5dcbcf67a..15d979232 100644 --- a/src/test/scala/sbt/ProcessSpecification.scala +++ b/src/test/scala/sbt/ProcessSpecification.scala @@ -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