This commit is contained in:
Mark Harrah 2011-03-19 16:11:03 -04:00
parent 4ce57596d9
commit 7b4c16f294
11 changed files with 4 additions and 542 deletions

View File

@ -2,7 +2,6 @@
* Copyright 2010 Mark Harrah
*/
package sbt
package build
import inc._

View File

@ -97,7 +97,7 @@ object Compiler
import in.config._
import in.incSetup._
val agg = new build.AggressiveCompile(cacheDirectory)
val agg = new AggressiveCompile(cacheDirectory)
agg(scalac, javac, sources, classpath, classesDirectory, options, javacOptions, analysisMap, maxErrors)(log)
}
}

View File

@ -26,14 +26,8 @@ final class Console(compiler: AnalyzingCompiler)
}
object Console
{
def apply(conf: build.Compile)(implicit log: Logger): Console = new Console( compiler(conf) )
def apply(conf: Compiler.Inputs): Console = new Console( conf.compilers.scalac )
def compiler(conf: build.Compile)(implicit log: Logger): AnalyzingCompiler =
{
val componentManager = new ComponentManager(conf.launcher.globalLock, conf.configuration.provider.components, log)
new AnalyzingCompiler(conf.instance, componentManager, log)
}
def sbt(state: State, extra: String)(implicit log: Logger)
{
val extracted = Project extract state

View File

@ -6,7 +6,6 @@ package sbt
import Execute.NodeView
import complete.HistoryCommands
import HistoryCommands.{Start => HistoryPrefix}
import sbt.build.{AggressiveCompile, Auto, BuildException, LoadCommand, Parse, ParseException, ProjectLoad, SourceLoad}
import compiler.EvalImports
import sbt.complete.{DefaultParsers, Parser}
@ -60,7 +59,7 @@ class xMain extends xsbti.AppMain
import CommandSupport._
object BuiltinCommands
{
def DefaultCommands: Seq[Command] = Seq(ignore, help, reboot, read, history, continuous, exit, loadCommands, loadProject, loadProjectImpl, loadFailed, compile, discover,
def DefaultCommands: Seq[Command] = Seq(ignore, help, reboot, read, history, continuous, exit, loadProject, loadProjectImpl, loadFailed,
projects, project, setOnFailure, clearOnFailure, ifLast, multi, shell, set, inspect, eval, alias, append, last, lastGrep, nop, sessionCommand, act)
def DefaultBootCommands: Seq[String] = LoadProject :: (IfLast + " " + Shell) :: Nil
@ -294,24 +293,6 @@ object BuiltinCommands
def doExit(s: State): State = s.runExitHooks().exit(true)
// TODO: tab completion, low priority
def discover = Command.single(Discover, DiscoverBrief, DiscoverDetailed) { (s, arg) =>
withAttribute(s, analysis, "No analysis to process.") { a =>
val command = Parse.discover(arg)
val discovered = build.Build.discover(a, command)
println(discovered.mkString("\n"))
s
}
}
// TODO: tab completion, low priority
def compile = Command.single(CompileName, CompileBrief, CompileDetailed ) { (s, arg) =>
val command = Parse.compile(arg)(s.baseDir)
try {
val a = build.Build.compile(command, s.configuration)
s.put(analysis, a)
} catch { case e: xsbti.CompileFailed => s.fail /* already logged */ }
}
def loadFailed = Command.command(LoadFailed)(handleLoadFailed)
@tailrec def handleLoadFailed(s: State): State =
{
@ -349,57 +330,6 @@ object BuiltinCommands
s.fail
}
// TODO: tab completion, low priority
def loadCommands = Command.single(LoadCommand, Parse.helpBrief(LoadCommand, LoadCommandLabel), Parse.helpDetail(LoadCommand, LoadCommandLabel, true) ) { (s, arg) =>
applyCommands(s, buildCommands(arg, s.configuration))
}
def buildCommands(arguments: String, configuration: xsbti.AppConfiguration): Either[Throwable, Seq[Any]] =
loadCommand(arguments, configuration, true, classOf[CommandDefinitions].getName)
def applyCommands(s: State, commands: Either[Throwable, Seq[Any]]): State =
commands match {
case Right(newCommands) =>
val asCommands = newCommands flatMap {
case c: CommandDefinitions => c.commands
case x => error("Not an instance of CommandDefinitions: " + x.asInstanceOf[AnyRef].getClass)
}
s.copy(processors = asCommands ++ s.processors)
case Left(e) => handleException(e, s, false)
}
def loadCommand(line: String, configuration: xsbti.AppConfiguration, allowMultiple: Boolean, defaultSuper: String): Either[Throwable, Seq[Any]] =
try
{
val parsed = Parse(line)(configuration.baseDirectory)
Right( build.Build( translateEmpty(parsed, defaultSuper), configuration, allowMultiple) )
}
catch { case e @ (_: ParseException | _: BuildException | _: xsbti.CompileFailed) => Left(e) }
def translateEmpty(load: LoadCommand, defaultSuper: String): LoadCommand =
load match {
case ProjectLoad(base, Auto.Explicit, "") => ProjectLoad(base, Auto.Subclass, defaultSuper)
case s @ SourceLoad(_, _, _, _, Auto.Explicit, "") => s.copy(auto = Auto.Subclass, name = defaultSuper)
case x => x
}
def runTask[Task[_] <: AnyRef](root: Task[State], checkCycles: Boolean, maxWorkers: Int)(implicit taskToNode: NodeView[Task]): Result[State] =
{
val (service, shutdown) = CompletionService[Task[_], Completed](maxWorkers)
val x = new Execute[Task](checkCycles)(taskToNode)
try { x.run(root)(service) } finally { shutdown() }
}
def processResult[State](result: Result[State], original: State, onFailure: => State): State =
result match
{
case Value(v) => v
case Inc(inc) =>
println(Incomplete.show(inc, true))
println("Task did not complete successfully")
onFailure
}
def addAlias(s: State, name: String, value: String): State =
if(Command validID name) {
val removed = removeAlias(s, name)

View File

@ -1,133 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2010 Mark Harrah
*/
package sbt
package build
import java.io.File
import classpath.ClasspathUtilities.toLoader
import ModuleUtilities.getObject
import compiler.{AnalyzingCompiler, Discovery, JavaCompiler}
import inc.Analysis
import Path._
import GlobFilter._
final class BuildException(msg: String) extends RuntimeException(msg)
object Build
{
def loader(configuration: xsbti.AppConfiguration): ClassLoader =
configuration.provider.mainClass.getClassLoader
def apply(command: LoadCommand, configuration: xsbti.AppConfiguration, allowMultiple: Boolean): Seq[Any] =
command match
{
case BinaryLoad(classpath, module, name) =>
binary(classpath, module, name, loader(configuration), allowMultiple)
case SourceLoad(classpath, sourcepath, output, module, auto, name) =>
source(classpath, sourcepath, output, module, auto, name, configuration, allowMultiple)
case ProjectLoad(base, auto, name) =>
project(base, auto, name, configuration, allowMultiple)
}
def project(base: File, auto: Auto.Value, name: String, configuration: xsbti.AppConfiguration, allowMultiple: Boolean): Seq[Any] =
{
val buildDir = base / "project" / "build"
val sources = (buildDir * "*.scala") +++ (buildDir / "src" / "main" / "scala" ** "*.scala")
source(Nil, sources.get.toSeq, Some(buildDir / "target" asFile), None, auto, name, configuration, allowMultiple)
}
def binary(classpath: Seq[File], module: Boolean, name: String, parent: ClassLoader, allowMultiple: Boolean): Seq[Any] =
{
if(name.isEmpty)
error("Class name required to load binary project.")
else
{
val names = if(allowMultiple) name.split(",").toSeq else Seq(name)
binaries(classpath, names.map(n => ToLoad(n,module)), parent)(_.newInstance)
}
}
def compile(command: CompileCommand, configuration: xsbti.AppConfiguration): Analysis =
{
import command._
compile(classpath, sources, output, options, configuration)
}
def compile(classpath: Seq[File], sources: Seq[File], output: Option[File], options: Seq[String], configuration: xsbti.AppConfiguration): Analysis =
compile(new Compile(classpath, sources, output, options, configuration))
def compile(conf : Compile): Analysis =
{
import conf._
// TODO: accept Logger as an argument
val log = ConsoleLogger()
val componentManager = new ComponentManager(launcher.globalLock, configuration.provider.components, log)
val compiler = new AnalyzingCompiler(instance, componentManager, log)
val javac = JavaCompiler.directOrFork(compiler.cp, compiler.scalaInstance)( (args: Seq[String], log: Logger) => Process("javac", args) ! log )
val agg = new AggressiveCompile(cacheDirectory)
agg(compiler, javac, sources, compileClasspath, outputDirectory, Nil, options)(log)
}
def source(classpath: Seq[File], sources: Seq[File], output: Option[File], module: Option[Boolean], auto: Auto.Value, name: String, configuration: xsbti.AppConfiguration, allowMultiple: Boolean = false): Seq[Any] =
{
val conf = new Compile(classpath, sources, output, Nil, configuration)
val analysis = compile(conf)
val discovered = discover(analysis, module, auto, name)
binaries(conf.projectClasspath, check(discovered, allowMultiple), loader(configuration))( constructCompiled(new Compiled(conf, analysis) ) )
}
def constructCompiled(compiled: Compiled): Class[_] => Any = clazz =>
constructor(clazz, classOf[Compiled]) match {
case Some(c) => c.newInstance(compiled)
case None => clazz.newInstance
}
def constructor(c: Class[_], args: Class[_]*): Option[java.lang.reflect.Constructor[_]] =
try { Some( c.getConstructor( args : _*) ) }
catch { case e: NoSuchMethodException => None }
def discover(analysis: inc.Analysis, module: Option[Boolean], auto: Auto.Value, name: String): Seq[ToLoad] =
{
import Auto.{Annotation, Explicit, Subclass}
auto match {
case Explicit => if(name.isEmpty) error("No name specified to load explicitly.") else Seq(new ToLoad(name))
case Subclass => discover(analysis, module, new Discovery(Set(name), Set.empty))
case Annotation => discover(analysis, module, new Discovery(Set.empty, Set(name)))
}
}
def discover(analysis: inc.Analysis, command: DiscoverCommand): Seq[ToLoad] =
discover(analysis, command.module, command.discovery)
def discover(analysis: inc.Analysis, module: Option[Boolean], discovery: Discovery): Seq[ToLoad] =
{
for(src <- analysis.apis.internal.values.toSeq;
(df, found) <- discovery(src.definitions) if !found.isEmpty && moduleMatches(found.isModule, module))
yield
new ToLoad(df.name, found.isModule)
}
def moduleMatches(isModule: Boolean, expected: Option[Boolean]): Boolean =
expected.isEmpty || (Some(isModule) == expected)
def binaries(classpath: Seq[File], toLoad: Seq[ToLoad], parent: ClassLoader)(newImpl: Class[_] => Any): Seq[Any] =
loadBinaries(toLoad, toLoader(classpath, parent))(newImpl)
def loadBinaries(toLoad: Seq[ToLoad], loader: ClassLoader)(newImpl: Class[_] => Any): Seq[Any] =
for(ToLoad(name, module) <- toLoad if !name.isEmpty) yield
loadBinary(name, module, loader)(newImpl)
def loadBinary(name: String, module: Boolean, loader: ClassLoader)(newImpl: Class[_] => Any): Any =
if(module)
getObject(name, loader)
else
newImpl( Class.forName(name, true, loader) )
def check[T](discovered: Seq[T], allowMultiple: Boolean): Seq[T] =
discovered match
{
case Seq() => error("No project found")
case Seq(x) => discovered
case _ => if(allowMultiple) discovered else error("Multiple projects found: " + discovered.mkString(", "))
}
def error(msg: String) = throw new BuildException(msg)
}

View File

@ -1,24 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2010 Mark Harrah
*/
package sbt
package build
import inc.Analysis
import java.io.File
import Path._
final class Compile(val classpath: Seq[File], val sources: Seq[File], output: Option[File], val options: Seq[String], val configuration: xsbti.AppConfiguration)
{
val scalaProvider = configuration.provider.scalaProvider
val launcher = scalaProvider.launcher
val instance = ScalaInstance(scalaProvider.version, scalaProvider)
val out = output.getOrElse(configuration.baseDirectory / "target" asFile)
val target = out / ("scala_" + instance.actualVersion)
val outputDirectory = target / "classes"
val cacheDirectory = target / "cache"
val projectClasspath = outputDirectory.asFile +: classpath
val compileClasspath = projectClasspath ++ configuration.provider.mainClasspath.toSeq
}
final class Compiled(val config: Compile, val analysis: Analysis)

View File

@ -1,27 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2010 Mark Harrah
*/
package sbt
package build
import java.io.File
import compiler.Discovery
sealed trait LoadCommand
final case class BinaryLoad(classpath: Seq[File], module: Boolean, name: String) extends LoadCommand
final case class SourceLoad(classpath: Seq[File], sourcepath: Seq[File], output: Option[File], module: Option[Boolean], auto: Auto.Value, name: String) extends LoadCommand
final case class ProjectLoad(base: File, auto: Auto.Value, name: String) extends LoadCommand
object Auto extends Enumeration
{
val Subclass, Annotation, Explicit = Value
}
final case class CompileCommand(classpath: Seq[File], sources: Seq[File], output: Option[File], options: Seq[String])
final case class DiscoverCommand(module: Option[Boolean], discovery: Discovery)
final case class ToLoad(name: String, isModule: Boolean = false)
{
override def toString = tpe + " " + name
def tpe = if(isModule) "object" else "class"
}

View File

@ -1,151 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2010 Mark Harrah
*/
package sbt
package build
import compiler.Discovery
import java.io.File
final class ParseException(msg: String) extends RuntimeException(msg)
/** Parses a load command. The implementation is a quick hack.
It is not robust and errors are not helpful.*/
object Parse
{
def helpBrief(name: String, label: String): (String, String) = (name + " <options>", "Loads " + label + " according to the specified options.")
def helpDetail(name: String, label: String, multiple: Boolean) =
"Loads " + label + """ by one of the following methods:
1) binary: loads a class from an existing classpath
2) source: compiles provided sources and loads a specific class
3) project: builds from source using default settings
The command has the following syntax:
load ::= """ + name + """ ('-help' | binary | source | project)
binary ::= classpath module? name
source ::= classpath? sources out? module? (detect | name)
project ::= base? name?
base ::= '-project' path
sources ::= '-src' paths
out ::= '-d' dir
detect ::= '-auto' ('sub' | 'annot')
name ::= '-name' nameString
module ::= '-module' ('true'|'false')
classpath ::= '-cp' paths
path ::= pathChar+
paths ::= path (pathSep path)*
""" +
( if(multiple) "\nTo specify multiple names, separate them by commas." else "")
import File.{pathSeparatorChar => sep}
def error(msg: String) = throw new ParseException(msg)
def apply(commandString: String)(implicit base: File): LoadCommand =
{
val args = arguments(commandString)
val srcs = sourcepath(args)
val nme = name(args)
lazy val cp = classpath(args)
lazy val mod = module(args)
lazy val proj = project(args).getOrElse(base)
if(!srcs.isEmpty)
SourceLoad(cp, srcs, output(args), mod, auto(args), nme)
else if(!cp.isEmpty)
BinaryLoad(cp, mod.getOrElse(false), nme)
else
ProjectLoad(proj, auto(args), nme)
}
def arguments(in: String) = in.split("""\s+""").toSeq
def compile(commandString: String)(implicit base: File): CompileCommand =
{
val args = arguments(commandString)
CompileCommand(classpath(args), sourcepath(args), output(args), Nil)
}
def discover(commandString: String): DiscoverCommand =
{
val args = arguments(commandString)
val subs = names("sub", args)
val annots = names("annot", args)
DiscoverCommand(module(args), new Discovery(subs, annots))
}
def auto(args: Seq[String]): Auto.Value =
getArg(args, "auto") match {
case None => Auto.Explicit
case Some("sub") => Auto.Subclass
case Some("annot") => Auto.Annotation
case Some(x) => error("Illegal auto argument '" + x + "'")
}
def module(args: Seq[String]): Option[Boolean] =
getArg(args, "module") map {
case "false" => false
case "true" => true
case x => error("Expected boolean, got '" + x + "'")
}
def names(label: String, args: Seq[String]): Set[String] =
getArg(args, label) match { case Some(ns) => ns.split(",").toSet; case None => Set.empty }
def name(args: Seq[String]): String =
getArg(args, "name") getOrElse("")
def output(args: Seq[String])(implicit base: File): Option[File] =
getArg(args, "d") map file(base)
def project(args: Seq[String])(implicit base: File): Option[File] =
getArg(args, "project") map file(base)
def pathArg(args: Seq[String], name: String)(implicit base: File): Seq[File] =
getArg(args, name).toSeq flatMap paths
def classpath(args: Seq[String])(implicit base: File): Seq[File] = pathArg(args, "cp")
def sourcepath(args: Seq[String])(implicit base: File): Seq[File] = pathArg(args, "src")
def getArg(args: Seq[String], name: String): Option[String] =
{
val opt = "-" + name
val found = args.dropWhile(_ != opt)
if(found.isEmpty)
None
else
found.drop(1).headOption match
{
case x @ Some(arg) if !arg.startsWith("-") => x
case _ => error("No argument provided for -" + name)
}
}
def paths(implicit base: File): String => Seq[File] =
_ split sep flatMap files(base)
def files(base: File)(path: String): Seq[File] = readFinder(Path.fromFile(base), path).getFiles
def file(base: File) = (path: String) => Path.fromString(base, path).asFile
def readFinder(base: File, s: String): PathFinder =
{
val f = new File(s)
asFinder( if(f.isAbsolute) f else new File(base, s) )
}
def asFinder(f: File): PathFinder =
{
val parent = f.getParentFile
if(parent eq null)
Path.fromFile(f)
else
{
val finder = asFinder(parent)
val sub = f.getName
if(sub == "**") (finder ***) else if(sub contains "*") finder * GlobFilter(sub) else finder / sub
}
}
}

View File

@ -1,17 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2010 Mark Harrah
*/
package sbt
import std._
import compile.{Discovered,Discovery}
abstract class BasicProject
{
override def watchPaths: PathFinder = (info.projectDirectory: Path) * sourceFilter +++ descendents("src","*")
def javapCompiledTask(conf: Configuration): Task[Unit] =
javapTask(taskData(fullClasspath(conf)), buildScalaInstance)
// lazy val test-quick, test-failed, javap, javap-quick, jetty-{run,stop,restart}, prepare-webapp, watch paths
}

View File

@ -1,104 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2010 Mark Harrah
*/
package sbt
import std._
import Path._
import TaskExtra._
import Types._
import OutputUtil._
import java.io.{BufferedReader,File}
import java.util.regex.Pattern
trait LastOutput
{
def input: Task[Input]
def streams: Task[TaskStreams]
def context: Task[Transform.Context[Project]]
lazy val last = (streams, input, context) flatMap { case s :+: i :+: ctx :+: HNil =>
mapLast(s, ctx, i.arguments) { reader =>
val out = s.text()
foreachLine(reader) { line =>
out.println(line)
println(line)
}
}
}
lazy val grepLast = (streams, input, context) flatMap { case s :+: i :+: ctx :+: HNil =>
val i2 = new Input(i.arguments, None)
val pattern = Pattern.compile(i2.arguments)
mapLast(s, ctx, i2.name) { reader =>
val out = s.text()
foreachLine(reader) { line =>
showMatches(pattern, line) foreach { line =>
out.println(line)
println(line)
}
}
}
}
}
object OutputUtil
{
def showMatches(pattern: Pattern, line: String): Option[String] =
{
val matcher = pattern.matcher(line)
if(ConsoleLogger.formatEnabled)
{
val highlighted = matcher.replaceAll(scala.Console.RED + "$0" + scala.Console.RESET)
if(highlighted == line) None else Some(highlighted)
}
else if(matcher.find)
Some(line)
else
None
}
def mapLast[T](s: TaskStreams, ctx: Transform.Context[Project], taskName: String)(f: BufferedReader => T): Task[Task.Cross[T]] =
{
val task = taskForName(ctx, taskName)
s.readText( task, update = false ).map( f ).merge
}
def foreachLine(reader: BufferedReader)(f: String => Unit)
{
def readL()
{
val line = reader.readLine()
if(line ne null) {
f(line)
readL()
}
}
readL()
}
def taskForName(ctx: Transform.Context[Project], name: String): Task[_] =
ctx.static(ctx.rootOwner, MultiProject.transformName(name)).getOrElse(error("No task '" + name + "'"))
}
trait Exec
{
def input: Task[Input]
def streams: Task[TaskStreams]
def fork(p: Seq[String], log: Logger): Unit = fork(Process(p), log)
def fork(p: ProcessBuilder, log: Logger): Unit =
{
val exitValue = p ! log
if(exitValue != 0) error("Nonzero exit value: " + exitValue)
}
def forkWithCode(p: ProcessBuilder, log: Logger): Task[Int] = streams map { s => p ! log }
lazy val sh = (input, streams) map { case in :+: s :+: HNil => fork("sh" :: "-c" :: in.arguments :: Nil, s.log) }
lazy val exec = (input, streams) map { case in :+: s :+: HNil => fork( Process(in.splitArgs), s.log ) }
}
trait Javap extends Exec
{
def javapTask(classpath: Task[Seq[File]], scalaInstance: Task[ScalaInstance]): Task[Unit] =
javapTask( (classpath, scalaInstance) map { case cp :+: si :+: HNil => cp ++ si.jars } )
def javapTask(classpath: Task[Seq[File]]): Task[Unit] =
(input, classpath, streams) map { case in :+: cp :+: s :+: HNil =>
val args = in.splitArgs
val argsWithClasspath = if(cp.isEmpty) args else "-classpath" +: Path.makeString(cp) +: args
fork("javap" +: argsWithClasspath, s.log)
}
}

View File

@ -74,11 +74,6 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) with NoCrossPaths
// Searches the source API data structures, currently looks for subclasses and annotations
val discoverySub = project(compilePath / "discover", "Discovery", new DiscoveryProject(_), compileIncrementalSub, apiSub)
// mostly for implementing 'load' command, could perhaps be trimmed and merged into 'main'
val buildSub = baseProject("main" / "build", "Project Builder",
classfileSub, classpathSub, compilePersistSub, compilerSub, compileIncrementalSub, interfaceSub, ivySub, launchInterfaceSub, logSub, discoverySub, processSub)
val scriptedBaseSub = project("scripted" / "base", "Scripted Framework", new TestProject(_), ioSub, processSub)
val scriptedSbtSub = baseProject("scripted" / "sbt", "Scripted sbt", ioSub, logSub, processSub, scriptedBaseSub, launchInterfaceSub /*should really be a 'provided' dependency*/)
val scriptedPluginSub = project("scripted" / "plugin", "Scripted Plugin", new Scripted(_))
@ -87,8 +82,8 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) with NoCrossPaths
val stdTaskSub = testedBase(tasksPath / "standard", "Task System", taskSub, collectionSub, logSub, ioSub, processSub)
// The main integration project for sbt. It brings all of the subsystems together, configures them, and provides for overriding conventions.
val mainSub = baseProject("main", "Main",
buildSub, compileIncrementalSub, compilerSub, completeSub, discoverySub,
ioSub, logSub, processSub, taskSub, stdTaskSub, runSub, trackingSub, testingSub)
classfileSub, classpathSub, compileIncrementalSub, compilePersistSub, compilerSub, completeSub, discoverySub,
interfaceSub, ioSub, ivySub, launchInterfaceSub, logSub, processSub, taskSub, stdTaskSub, runSub, trackingSub, testingSub)
// Strictly for bringing implicits and aliases from subsystems into the top-level sbt namespace through a single package object
val sbtSub = project(sbtPath, "Simple Build Tool", new Sbt(_), mainSub) // technically, we need a dependency on all of mainSub's dependencies, but we don't do that since this is strictly an integration project