From f2d29d86783e175359b6488f3abc45b0eba286f4 Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Thu, 28 Feb 2013 17:59:38 -0500 Subject: [PATCH] Export approximate command lines executed for 'doc', 'compile', and 'console' --- .../main/scala/xsbt/CompilerInterface.scala | 3 ++ .../main/scala/xsbt/ConsoleInterface.scala | 19 +++++++--- .../sbt/compiler/AnalyzingCompiler.scala | 29 ++++++++++++--- .../scala/sbt/compiler/JavaCompiler.scala | 35 +++++++++++++------ .../java/xsbti/compile/CachedCompiler.java | 2 ++ main/actions/src/main/scala/sbt/Doc.scala | 5 +-- main/src/main/scala/sbt/CommandStrings.scala | 12 +++++++ main/src/main/scala/sbt/ConsoleProject.scala | 1 - main/src/main/scala/sbt/Defaults.scala | 28 +++++++++++---- main/src/main/scala/sbt/Main.scala | 20 +++++++---- main/src/main/scala/sbt/Output.scala | 18 +++++++--- 11 files changed, 132 insertions(+), 40 deletions(-) diff --git a/compile/interface/src/main/scala/xsbt/CompilerInterface.scala b/compile/interface/src/main/scala/xsbt/CompilerInterface.scala index 73e184f39..9efed98cb 100644 --- a/compile/interface/src/main/scala/xsbt/CompilerInterface.scala +++ b/compile/interface/src/main/scala/xsbt/CompilerInterface.scala @@ -82,6 +82,9 @@ private final class CachedCompiler0(args: Array[String], output: Output, initial def noErrors(dreporter: DelegatingReporter) = !dreporter.hasErrors && command.ok + def commandArguments(sources: Array[File]): Array[String] = + (command.settings.recreateArgs ++ sources.map(_.getAbsolutePath)).toArray[String] + def run(sources: Array[File], changes: DependencyChanges, callback: AnalysisCallback, log: Logger, delegate: Reporter, progress: CompileProgress): Unit = synchronized { debug(log, "Running cached compiler " + hashCode.toHexString + ", interfacing (CompilerInterface) with Scala compiler " + scala.tools.nsc.Properties.versionString) diff --git a/compile/interface/src/main/scala/xsbt/ConsoleInterface.scala b/compile/interface/src/main/scala/xsbt/ConsoleInterface.scala index d19035b31..8b6160a0a 100644 --- a/compile/interface/src/main/scala/xsbt/ConsoleInterface.scala +++ b/compile/interface/src/main/scala/xsbt/ConsoleInterface.scala @@ -11,18 +11,18 @@ import scala.tools.nsc.util.ClassPath class ConsoleInterface { + def commandArguments(args: Array[String], bootClasspathString: String, classpathString: String, log: Logger): Array[String] = + MakeSettings.sync(args, bootClasspathString, classpathString, log).recreateArgs.toArray[String] + def run(args: Array[String], bootClasspathString: String, classpathString: String, initialCommands: String, cleanupCommands: String, loader: ClassLoader, bindNames: Array[String], bindValues: Array[Any], log: Logger) { - val options = args.toList - lazy val interpreterSettings = MakeSettings.sync(options, log) - val compilerSettings = MakeSettings.sync(options, log) + lazy val interpreterSettings = MakeSettings.sync(args.toList, log) + val compilerSettings = MakeSettings.sync(args, bootClasspathString, classpathString, log) if(!bootClasspathString.isEmpty) compilerSettings.bootclasspath.value = bootClasspathString compilerSettings.classpath.value = classpathString log.info(Message("Starting scala interpreter...")) - log.debug(Message(" Boot classpath: " + compilerSettings.bootclasspath.value)) - log.debug(Message(" Classpath: " + compilerSettings.classpath.value)) log.info(Message("")) val loop = new InterpreterLoop { @@ -68,6 +68,15 @@ object MakeSettings throw new InterfaceCompileFailed(Array(), Array(), command.usageMsg) } + def sync(args: Array[String], bootClasspathString: String, classpathString: String, log: Logger): Settings = + { + val compilerSettings = sync(args.toList, log) + if(!bootClasspathString.isEmpty) + compilerSettings.bootclasspath.value = bootClasspathString + compilerSettings.classpath.value = classpathString + compilerSettings + } + def sync(options: List[String], log: Logger) = { val settings = apply(options, log) diff --git a/compile/src/main/scala/sbt/compiler/AnalyzingCompiler.scala b/compile/src/main/scala/sbt/compiler/AnalyzingCompiler.scala index fe8dd6e37..3c8ce50de 100644 --- a/compile/src/main/scala/sbt/compiler/AnalyzingCompiler.scala +++ b/compile/src/main/scala/sbt/compiler/AnalyzingCompiler.scala @@ -13,8 +13,10 @@ package compiler * provided by scalaInstance. This class requires a ComponentManager in order to obtain the interface code to scalac and * the analysis plugin. Because these call Scala code for a different Scala version than the one used for this class, they must * be compiled for the version of Scala being used.*/ -final class AnalyzingCompiler(val scalaInstance: xsbti.compile.ScalaInstance, val provider: CompilerInterfaceProvider, val cp: xsbti.compile.ClasspathOptions) extends CachedCompilerProvider +final class AnalyzingCompiler private(val scalaInstance: xsbti.compile.ScalaInstance, val provider: CompilerInterfaceProvider, val cp: xsbti.compile.ClasspathOptions, onArgsF: Seq[String] => Unit) extends CachedCompilerProvider { + def this(scalaInstance: xsbti.compile.ScalaInstance, provider: CompilerInterfaceProvider, cp: xsbti.compile.ClasspathOptions) = + this(scalaInstance, provider, cp, _ => ()) def this(scalaInstance: ScalaInstance, provider: CompilerInterfaceProvider) = this(scalaInstance, provider, ClasspathOptions.auto) @deprecated("A Logger is no longer needed.", "0.13.0") @@ -23,6 +25,8 @@ final class AnalyzingCompiler(val scalaInstance: xsbti.compile.ScalaInstance, va @deprecated("A Logger is no longer needed.", "0.13.0") def this(scalaInstance: xsbti.compile.ScalaInstance, provider: CompilerInterfaceProvider, cp: xsbti.compile.ClasspathOptions, log: Logger) = this(scalaInstance, provider, cp) + def onArgs(f: Seq[String] => Unit): AnalyzingCompiler = new AnalyzingCompiler(scalaInstance, provider, cp, f) + def apply(sources: Seq[File], changes: DependencyChanges, classpath: Seq[File], singleOutput: File, options: Seq[String], callback: AnalysisCallback, maximumErrors: Int, cache: GlobalsCache, log: Logger) { val arguments = (new CompilerArguments(scalaInstance, cp))(Nil, classpath, None, options) @@ -39,6 +43,7 @@ final class AnalyzingCompiler(val scalaInstance: xsbti.compile.ScalaInstance, va def compile(sources: Seq[File], changes: DependencyChanges, callback: AnalysisCallback, log: Logger, reporter: Reporter, progress: CompileProgress, compiler: CachedCompiler) { + onArgsF(compiler.commandArguments(sources.toArray)) call("xsbt.CompilerInterface", "run", log)( classOf[Array[File]], classOf[DependencyChanges], classOf[AnalysisCallback], classOf[xLogger], classOf[Reporter], classOf[CompileProgress], classOf[CachedCompiler]) ( sources.toArray, changes, callback, log, reporter, progress, compiler ) @@ -59,19 +64,35 @@ final class AnalyzingCompiler(val scalaInstance: xsbti.compile.ScalaInstance, va def doc(sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String], log: Logger, reporter: Reporter): Unit = { val arguments = (new CompilerArguments(scalaInstance, cp))(sources, classpath, Some(outputDirectory), options) + onArgsF(arguments) call("xsbt.ScaladocInterface", "run", log) (classOf[Array[String]], classOf[xLogger], classOf[Reporter]) ( arguments.toArray[String] : Array[String], log, reporter) } def console(classpath: Seq[File], options: Seq[String], initialCommands: String, cleanupCommands: String, log: Logger)(loader: Option[ClassLoader] = None, bindings: Seq[(String, Any)] = Nil): Unit = { - val arguments = new CompilerArguments(scalaInstance, cp) - val classpathString = CompilerArguments.absString(arguments.finishClasspath(classpath)) - val bootClasspath = if(cp.autoBoot) arguments.createBootClasspathFor(classpath) else "" + onArgsF(consoleCommandArguments(classpath, options, log)) + val (classpathString, bootClasspath) = consoleClasspaths(classpath) val (names, values) = bindings.unzip call("xsbt.ConsoleInterface", "run", log)( classOf[Array[String]], classOf[String], classOf[String], classOf[String], classOf[String], classOf[ClassLoader], classOf[Array[String]], classOf[Array[Any]], classOf[xLogger])( options.toArray[String]: Array[String], bootClasspath, classpathString, initialCommands, cleanupCommands, loader.orNull, names.toArray[String], values.toArray[Any], log) } + + private[this] def consoleClasspaths(classpath: Seq[File]): (String, String) = + { + val arguments = new CompilerArguments(scalaInstance, cp) + val classpathString = CompilerArguments.absString(arguments.finishClasspath(classpath)) + val bootClasspath = if(cp.autoBoot) arguments.createBootClasspathFor(classpath) else "" + (classpathString, bootClasspath) + } + def consoleCommandArguments(classpath: Seq[File], options: Seq[String], log: Logger): Seq[String] = + { + val (classpathString, bootClasspath) = consoleClasspaths(classpath) + val argsObj = call("xsbt.ConsoleInterface", "commandArguments", log)( + classOf[Array[String]], classOf[String], classOf[String], classOf[xLogger])( + options.toArray[String]: Array[String], bootClasspath, classpathString, log) + argsObj.asInstanceOf[Array[String]].toSeq + } def force(log: Logger): Unit = provider(scalaInstance, log) private def call(interfaceClassName: String, methodName: String, log: Logger)(argTypes: Class[_]*)(args: AnyRef*): AnyRef = { diff --git a/compile/src/main/scala/sbt/compiler/JavaCompiler.scala b/compile/src/main/scala/sbt/compiler/JavaCompiler.scala index 61bfa1e7d..8e5b3bba2 100644 --- a/compile/src/main/scala/sbt/compiler/JavaCompiler.scala +++ b/compile/src/main/scala/sbt/compiler/JavaCompiler.scala @@ -20,10 +20,14 @@ trait JavaCompiler extends xsbti.compile.JavaCompiler } apply(sources, classpath, outputDirectory, options)(log) } + + def onArgs(f: Seq[String] => Unit): JavaCompiler } trait Javadoc { def doc(sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String], maximumErrors: Int, log: Logger) + + def onArgs(f: Seq[String] => Unit): Javadoc } trait JavaTool extends Javadoc with JavaCompiler { @@ -34,6 +38,8 @@ trait JavaTool extends Javadoc with JavaCompiler compile(JavaCompiler.javadoc, sources, classpath, outputDirectory, options)(log) def compile(contract: JavacContract, sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String])(implicit log: Logger): Unit + + def onArgs(f: Seq[String] => Unit): JavaTool } object JavaCompiler { @@ -52,18 +58,25 @@ object JavaCompiler } } - def construct(f: Fork, cp: ClasspathOptions, scalaInstance: ScalaInstance): JavaTool = - new JavaTool { - def compile(contract: JavacContract, sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String])(implicit log: Logger) { - val augmentedClasspath = if(cp.autoBoot) classpath ++ Seq(scalaInstance.libraryJar) else classpath - val javaCp = ClasspathOptions.javac(cp.compiler) - val arguments = (new CompilerArguments(scalaInstance, javaCp))(sources, augmentedClasspath, Some(outputDirectory), options) - log.debug("Calling " + contract.name.capitalize + " with arguments:\n\t" + arguments.mkString("\n\t")) - val code: Int = f(contract, arguments, log) - log.debug(contract.name + " returned exit code: " + code) - if( code != 0 ) throw new CompileFailed(arguments.toArray, contract.name + " returned nonzero exit code", Array()) - } + def construct(f: Fork, cp: ClasspathOptions, scalaInstance: ScalaInstance): JavaTool = new JavaTool0(f, cp, scalaInstance, _ => ()) + + private[this] class JavaTool0(f: Fork, cp: ClasspathOptions, scalaInstance: ScalaInstance, onArgsF: Seq[String] => Unit) extends JavaTool + { + def onArgs(g: Seq[String] => Unit): JavaTool = new JavaTool0(f, cp, scalaInstance, g) + def commandArguments(sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String], log: Logger): Seq[String] = + { + val augmentedClasspath = if(cp.autoBoot) classpath ++ Seq(scalaInstance.libraryJar) else classpath + val javaCp = ClasspathOptions.javac(cp.compiler) + (new CompilerArguments(scalaInstance, javaCp))(sources, augmentedClasspath, Some(outputDirectory), options) } + def compile(contract: JavacContract, sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String])(implicit log: Logger) { + val arguments = commandArguments(sources, classpath, outputDirectory, options, log) + onArgsF(arguments) + val code: Int = f(contract, arguments, log) + log.debug(contract.name + " returned exit code: " + code) + if( code != 0 ) throw new CompileFailed(arguments.toArray, contract.name + " returned nonzero exit code", Array()) + } + } def directOrFork(cp: ClasspathOptions, scalaInstance: ScalaInstance)(implicit doFork: Fork): JavaTool = construct(directOrForkJavac, cp, scalaInstance) diff --git a/interface/src/main/java/xsbti/compile/CachedCompiler.java b/interface/src/main/java/xsbti/compile/CachedCompiler.java index 97a1a33b5..0722a68b9 100644 --- a/interface/src/main/java/xsbti/compile/CachedCompiler.java +++ b/interface/src/main/java/xsbti/compile/CachedCompiler.java @@ -7,5 +7,7 @@ import java.io.File; public interface CachedCompiler { + /** Returns an array of arguments representing the nearest command line equivalent of a call to run but without the command name itself.*/ + public String[] commandArguments(File[] sources); public void run(File[] sources, DependencyChanges cpChanges, AnalysisCallback callback, Logger log, Reporter delegate, CompileProgress progress); } diff --git a/main/actions/src/main/scala/sbt/Doc.scala b/main/actions/src/main/scala/sbt/Doc.scala index d233da224..4acbb6b18 100644 --- a/main/actions/src/main/scala/sbt/Doc.scala +++ b/main/actions/src/main/scala/sbt/Doc.scala @@ -3,7 +3,7 @@ */ package sbt - import java.io.File + import java.io.{File, PrintWriter} import compiler.{AnalyzingCompiler, JavaCompiler} import Predef.{conforms => _, _} @@ -20,8 +20,9 @@ object Doc import RawCompileLike._ def scaladoc(label: String, cache: File, compiler: AnalyzingCompiler): Gen = cached(cache, prepare(label + " Scala API documentation", compiler.doc)) + def javadoc(label: String, cache: File, doc: sbt.compiler.Javadoc): Gen = - cached(cache, prepare(label + " Scala API documentation", filterSources(javaSourcesOnly, doc.doc))) + cached(cache, prepare(label + " Java API documentation", filterSources(javaSourcesOnly, doc.doc))) val javaSourcesOnly: File => Boolean = _.getName.endsWith(".java") diff --git a/main/src/main/scala/sbt/CommandStrings.scala b/main/src/main/scala/sbt/CommandStrings.scala index 6b1ba99b4..4e4f87603 100644 --- a/main/src/main/scala/sbt/CommandStrings.scala +++ b/main/src/main/scala/sbt/CommandStrings.scala @@ -34,6 +34,7 @@ ShowCommand + """ val LastCommand = "last" val LastGrepCommand = "last-grep" + val ExportCommand = "export" val lastGrepBrief = (LastGrepCommand, "Shows lines from the last output for 'key' that match 'pattern'.") val lastGrepDetailed = @@ -56,6 +57,17 @@ LastCommand + """ See also '""" + LastGrepCommand + "'." + val exportBrief = (ExportCommand, "Displays the equivalent command line(s) for previously executed tasks.") + val exportDetailed = +s"""$ExportCommand + + Prints the approximate command line(s) for the previously executed tasks. + + NOTE: These command lines are necessarily approximate. Usually tasks do not actually + execute the command line and the actual command line program may not be installed or + on the PATH. Incremental tasks will typically show the command line for the previous + incremental run and not for a full run. +""" + val InspectCommand = "inspect" val inspectBrief = (InspectCommand, "Prints the value for 'key', the defining scope, delegates, related definitions, and dependencies.") val inspectDetailed = diff --git a/main/src/main/scala/sbt/ConsoleProject.scala b/main/src/main/scala/sbt/ConsoleProject.scala index 6f1aa5a4c..bf297d2de 100644 --- a/main/src/main/scala/sbt/ConsoleProject.scala +++ b/main/src/main/scala/sbt/ConsoleProject.scala @@ -4,7 +4,6 @@ package sbt import java.io.File - import compiler.AnalyzingCompiler object ConsoleProject { diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index b5d917929..08f8046cc 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -21,7 +21,7 @@ package sbt import scala.xml.NodeSeq import org.apache.ivy.core.module.{descriptor, id} import descriptor.ModuleDescriptor, id.ModuleRevisionId - import java.io.File + import java.io.{File, PrintWriter} import java.net.{URI,URL,MalformedURLException} import java.util.concurrent.Callable import sbinary.DefaultProtocol.StringFormat @@ -619,9 +619,11 @@ object Defaults extends BuildCommon val label = nameForSrc(config.name) val (options, runDoc) = if(hasScala) - (in.config.options ++ Opts.doc.externalAPI(xapis), Doc.scaladoc(label, s.cacheDirectory / "scala", in.compilers.scalac)) + (in.config.options ++ Opts.doc.externalAPI(xapis), + Doc.scaladoc(label, s.cacheDirectory / "scala", in.compilers.scalac.onArgs(exported(s, "scaladoc")))) else if(hasJava) - (in.config.javacOptions, Doc.javadoc(label, s.cacheDirectory / "java", in.compilers.javac)) + (in.config.javacOptions, + Doc.javadoc(label, s.cacheDirectory / "java", in.compilers.javac.onArgs(exported(s, "javadoc")))) else (Nil, RawCompileLike.nop) runDoc(srcs, cp, out, options, in.config.maxErrors, s.log) @@ -641,15 +643,29 @@ object Defaults extends BuildCommon def consoleTask(classpath: TaskKey[Classpath], task: TaskKey[_]): Initialize[Task[Unit]] = (compilers in task, classpath in task, scalacOptions in task, initialCommands in task, cleanupCommands in task, taskTemporaryDirectory in task, scalaInstance in task, streams) map { (cs, cp, options, initCommands, cleanup, temp, si, s) => - val fullcp = (data(cp) ++ si.jars).distinct + val cpFiles = data(cp) + val fullcp = (cpFiles ++ si.jars).distinct val loader = sbt.classpath.ClasspathUtilities.makeLoader(fullcp, si, IO.createUniqueDirectory(temp)) - (new Console(cs.scalac))(data(cp), options, loader, initCommands, cleanup)()(s.log).foreach(msg => error(msg)) + val compiler = cs.scalac.onArgs(exported(s, "scala")) + (new Console(compiler))(cpFiles, options, loader, initCommands, cleanup)()(s.log).foreach(msg => error(msg)) println() } + private[this] def exported(w: PrintWriter, command: String): Seq[String] => Unit = args => + w.println( (command +: args).mkString(" ") ) + private[this] def exported(s: TaskStreams, command: String): Seq[String] => Unit = args => + exported(s.text("export"), command) + def compileTaskSettings: Seq[Setting[_]] = inTask(compile)(compileInputsSettings) - def compileTask = (compileInputs in compile, streams) map { (i,s) => Compiler(i,s.log) } + def compileTask: Initialize[Task[inc.Analysis]] = Def.task { compileTaskImpl(streams.value, (compileInputs in compile).value) } + private[this] def compileTaskImpl(s: TaskStreams, ci: Compiler.Inputs): inc.Analysis = + { + lazy val x = s.text("export") + def onArgs(cs: Compiler.Compilers) = cs.copy(scalac = cs.scalac.onArgs(exported(x, "scalac")), javac = cs.javac.onArgs(exported(x, "javac"))) + val i = ci.copy(compilers = onArgs(ci.compilers)) + Compiler(i,s.log) + } def compileIncSetupTask = (dependencyClasspath, skip in compile, definesClass, compilerCache, streams, incOptions) map { (cp, skip, definesC, cache, s, incOptions) => Compiler.IncSetup(analysisMap(cp), definesC, skip, s.cacheDirectory / "inc_compile", cache, incOptions) diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index c853f341b..a1399a3d8 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -77,7 +77,7 @@ object BuiltinCommands def ScriptCommands: Seq[Command] = Seq(ignore, exit, Script.command, act, nop) def DefaultCommands: Seq[Command] = Seq(ignore, help, about, tasks, settingsCommand, loadProject, projects, project, reboot, read, history, set, sessionCommand, inspect, loadProjectImpl, loadFailed, Cross.crossBuild, Cross.switchVersion, - setOnFailure, clearOnFailure, ifLast, multi, shell, continuous, eval, alias, append, last, lastGrep, boot, nop, call, exit, act) + setOnFailure, clearOnFailure, ifLast, multi, shell, continuous, eval, alias, append, last, lastGrep, export, boot, nop, call, exit, act) def DefaultBootCommands: Seq[String] = LoadProject :: (IfLast + " " + Shell) :: Nil def boot = Command.make(BootCommand)(bootParser) @@ -295,20 +295,26 @@ object BuiltinCommands val spacedKeyParser = (s: State) => Act.requireSession(s, token(Space) ~> Act.scopedKeyParser(s)) val spacedAggregatedParser = (s: State) => Act.requireSession(s, token(Space) ~> Act.aggregatedKeyParser(s)) - val aggregatedKeyValueParser: State => Parser[Option[AnyKeys]] = - (s: State) => spacedAggregatedParser(s).map(x => Act.keyValues(s)(x) ).? + val exportParser: State => Parser[AnyKeys] = (s: State) => spacedAggregatedParser(s).map(x => Act.keyValues(s)(x) ) + val aggregatedKeyValueParser: State => Parser[Option[AnyKeys]] = s => exportParser(s).? def lastGrepParser(s: State) = Act.requireSession(s, (token(Space) ~> token(NotSpace, "")) ~ aggregatedKeyValueParser(s)) def last = Command(LastCommand, lastBrief, lastDetailed)(aggregatedKeyValueParser) { - case (s,Some(sks)) => - val (str, ref, display) = extractLast(s) - Output.last(sks, str.streams(s), printLast(s))(display) - keepLastLog(s) + case (s,Some(sks)) => lastImpl(s, sks, None) case (s, None) => for(logFile <- lastLogFile(s)) yield Output.last( logFile, printLast(s) ) keepLastLog(s) } + def export = Command(ExportCommand, exportBrief, exportDetailed)(exportParser) { (s, sks) => + lastImpl(s, sks, Some("export")) + } + private[this] def lastImpl(s: State, sks: AnyKeys, sid: Option[String]): State = + { + val (str, ref, display) = extractLast(s) + Output.last(sks, str.streams(s), printLast(s), sid)(display) + keepLastLog(s) + } /** Determines the log file that last* commands should operate on. See also isLastOnly. */ def lastLogFile(s: State) = diff --git a/main/src/main/scala/sbt/Output.scala b/main/src/main/scala/sbt/Output.scala index 6a43b8645..06b1fda4d 100644 --- a/main/src/main/scala/sbt/Output.scala +++ b/main/src/main/scala/sbt/Output.scala @@ -16,8 +16,12 @@ object Output { final val DefaultTail = "> " + @deprecated("Explicitly provide None for the stream ID.", "0.13.0") def last(keys: Values[_], streams: Streams, printLines: Seq[String] => Unit)(implicit display: Show[ScopedKey[_]]): Unit = - printLines( flatLines(lastLines(keys, streams))(idFun) ) + last(keys, streams, printLines, None)(display) + + def last(keys: Values[_], streams: Streams, printLines: Seq[String] => Unit, sid: Option[String])(implicit display: Show[ScopedKey[_]]): Unit = + printLines( flatLines(lastLines(keys, streams, sid))(idFun) ) def last(file: File, printLines: Seq[String] => Unit, tailDelim: String = DefaultTail): Unit = printLines(tailLines(file, tailDelim)) @@ -42,13 +46,19 @@ object Output } } - def lastLines(keys: Values[_], streams: Streams): Values[Seq[String]] = + def lastLines(keys: Values[_], streams: Streams, sid: Option[String] = None): Values[Seq[String]] = { - val outputs = keys map { (kv: KeyValue[_]) => KeyValue(kv.key, lastLines(kv.key, streams)) } + val outputs = keys map { (kv: KeyValue[_]) => KeyValue(kv.key, lastLines(kv.key, streams, sid)) } outputs.filterNot(_.value.isEmpty) } - def lastLines(key: ScopedKey[_], mgr: Streams): Seq[String] = mgr.use(key) { s => IO.readLines(s.readText( Project.fillTaskAxis(key) )) } + + @deprecated("Explicitly provide None for the stream ID.", "0.13.0") + def lastLines(key: ScopedKey[_], mgr: Streams): Seq[String] = lastLines(key, mgr, None) + + def lastLines(key: ScopedKey[_], mgr: Streams, sid: Option[String]): Seq[String] = mgr.use(key) { s => IO.readLines(s.readText( Project.fillTaskAxis(key), sid )) } + def tailLines(file: File, tailDelim: String): Seq[String] = headLines(IO.readLines(file).reverse, tailDelim).reverse + @tailrec def headLines(lines: Seq[String], tailDelim: String): Seq[String] = if(lines.isEmpty) lines