* Fixed manual `reboot` not changing the version of Scala when it is manually `set`

* Split out functionality to filter sub tasks
 * Added convenience methods for constructing a CompoundTask
 * fixed tab completion for cross-building
 * added empty scheduler



git-svn-id: https://simple-build-tool.googlecode.com/svn/trunk@825 d89573ee-9141-11dd-94d4-bdf5e562f29c
This commit is contained in:
dmharrah 2009-07-01 04:04:36 +00:00
parent 545a138c11
commit aa4b084ecc
9 changed files with 91 additions and 42 deletions

View File

@ -265,7 +265,7 @@ trait BasicManagedProject extends ManagedProject with ReflectiveManagedProject w
}
def useIntegrationTestConfiguration = false
def defaultConfiguration: Option[Configuration] = Some(Configurations.DefaultConfiguration(useDefaultConfigurations))
def useMavenConfigurations = true // TBD: set to true and deprecate
def useMavenConfigurations = true // TODO: deprecate after going through a minor version series to verify that this works ok
def useDefaultConfigurations = useMavenConfigurations
def managedStyle: ManagedType = Auto
protected implicit final val defaultPatterns: RepositoryHelpers.Patterns =

View File

@ -106,7 +106,7 @@ private sealed abstract class BasicBuilderProject extends InternalProject with S
override final def methods = Map.empty
}
/** The project definition used to build project definitions. */
private final class BuilderProject(val info: ProjectInfo, val pluginPath: Path, override protected val logImpl: Logger) extends BasicBuilderProject
private final class BuilderProject(val info: ProjectInfo, val pluginPath: Path, additional: Iterable[Path], override protected val logImpl: Logger) extends BasicBuilderProject
{
private lazy val pluginProject =
{
@ -115,11 +115,13 @@ private final class BuilderProject(val info: ProjectInfo, val pluginPath: Path,
else
None
}
override def projectClasspath = super.projectClasspath +++ pluginProject.map(_.pluginClasspath).getOrElse(Path.emptyPathFinder)
override def projectClasspath = super.projectClasspath +++
pluginProject.map(_.pluginClasspath).getOrElse(Path.emptyPathFinder) +++
Path.lazyPathFinder{ additional }
def tpe = "project definition"
override def compileTask = super.compileTask dependsOn(pluginProject.map(_.syncPlugins).toList : _*)
final class PluginBuilderProject(val info: ProjectInfo) extends BasicBuilderProject
{
override protected def logImpl = BuilderProject.this.log
@ -158,7 +160,7 @@ private final class BuilderProject(val info: ProjectInfo, val pluginPath: Path,
case Some(definition) =>
logInfo("\nUpdating plugins")
val pluginInfo = ProjectInfo(info.projectPath.asFile, Nil, None)
val pluginBuilder = Project.constructProject(pluginInfo, Project.getProjectClass[PluginDefinition](definition, projectClasspath))
val pluginBuilder = Project.constructProject(pluginInfo, Project.getProjectClass[PluginDefinition](definition, projectClasspath, getClass.getClassLoader))
pluginBuilder.projectName() = "Plugin builder"
pluginBuilder.projectVersion() = OpaqueVersion("1.0")
val result = pluginBuilder.update.run
@ -203,7 +205,7 @@ class PluginDefinition(val info: ProjectInfo) extends InternalProject with Basic
class PluginProject(info: ProjectInfo) extends DefaultProject(info)
{
override def unmanagedClasspath = super.unmanagedClasspath +++ Path.lazyPathFinder(Path.fromFile(FileUtilities.sbtJar) :: Nil)
override def packageAction = packageSrc
override def packageAction = packageSrc dependsOn(test)
override def packageSrcJar = jarPath
override def useMavenConfigurations = true
override def managedStyle = ManagedStyle.Maven

View File

@ -14,7 +14,9 @@ private[sbt] object ClasspathUtilities
def toClasspath(finder: PathFinder): Array[URL] = toClasspath(finder.get)
def toClasspath(paths: Iterable[Path]): Array[URL] = paths.map(_.asURL).toSeq.toArray
def toLoader(finder: PathFinder): ClassLoader = toLoader(finder.get)
def toLoader(finder: PathFinder, parent: ClassLoader): ClassLoader = toLoader(finder.get, parent)
def toLoader(paths: Iterable[Path]): ClassLoader = new URLClassLoader(toClasspath(paths), getClass.getClassLoader)
def toLoader(paths: Iterable[Path], parent: ClassLoader): ClassLoader = new URLClassLoader(toClasspath(paths), parent)
def isArchive(path: Path): Boolean = isArchive(path.asFile)
def isArchive(file: File): Boolean = isArchiveName(file.getName)

View File

@ -192,7 +192,7 @@ object Main
private def interactive(baseProject: Project): RunCompleteAction =
{
val projectNames = baseProject.topologicalSort.map(_.name)
val prefixes = ContinuousExecutePrefix :: Nil
val prefixes = ContinuousExecutePrefix :: CrossBuildPrefix :: Nil
val completors = new Completors(ProjectAction, projectNames, interactiveCommands, List(GetAction, SetAction), prefixes)
val reader = new JLineReader(baseProject.historyPath, completors, baseProject.log)
def updateTaskCompletions(project: Project)
@ -221,7 +221,10 @@ object Main
else if(ReloadAction == trimmed)
Reload
else if(RebootCommand == trimmed)
{
System.setProperty(ScalaVersion.LiveKey, "")
new Exit(RebootExitCode)
}
else if(trimmed.startsWith(CrossBuildPrefix))
{
if(startCrossBuild(currentProject, trimmed.substring(CrossBuildPrefix.length).trim))

View File

@ -48,6 +48,21 @@ object ParallelRunner
new DagScheduler(info, defaultStrategy(info))
}
private def defaultStrategy[D <: Dag[D]](info: DagInfo[D]) = MaxPathStrategy((d: D) => 1, info)
def emptyScheduler[D]: Scheduler[D] =
new Scheduler[D]
{
/** Starts a new run. The returned object is a new Run, representing a single scheduler run. All state for the run
* is encapsulated in this object.*/
def run: Run = new Run
{
def complete(d: D, result: Option[String]) {}
def hasPending = false
/**Returns true if this scheduler has no more work to be done, ever.*/
def isComplete = true
def next(max: Int) = Nil
def failures = Nil
}
}
}
/** Requests work from `scheduler` and processes it using `doWork`. This class limits the amount of work processing at any given time
* to `workers`.*/
@ -161,6 +176,9 @@ private trait ScheduleStrategy[D] extends NotNull
/** Provides up to `max` units of work. `max` is always positive and this method is not called
* if hasReady is false. The returned list cannot be empty is there is work ready to be run.*/
def next(max: Int): List[D]
/** If this strategy returns different work from `next` than is provided to `workReady`,
* this method must map back to the original work.*/
def reverseMap(dep: D): Iterable[D]
}
}
@ -184,12 +202,15 @@ private[sbt] final class DagScheduler[D <: Dag[D]](info: DagInfo[D], strategy: S
def next(max: Int) = strategyRun.next(max)
def complete(work: D, result: Option[String])
{
result match
for(originalWork <- strategyRun.reverseMap(work))
{
case None => infoRun.complete(work, strategyRun.workReady)
case Some(errorMessage) =>
infoRun.clear(work)
failures += WorkFailure(work, errorMessage)
result match
{
case None => infoRun.complete(originalWork, strategyRun.workReady)
case Some(errorMessage) =>
infoRun.clear(originalWork)
failures += WorkFailure(originalWork, errorMessage)
}
}
}
def isComplete = !strategyRun.hasReady && infoRun.reverseDepsRun.isEmpty
@ -253,6 +274,7 @@ private class OrderedStrategy[D](ready: TreeSet[D]) extends ScheduleStrategy[D]
}
def workReady(dep: D) { readyRun += dep }
def hasReady = !readyRun.isEmpty
def reverseMap(dep: D) = dep :: Nil
}
}
/** A class that represents state for a DagScheduler and that MaxPathStrategy uses to initialize an OrderedStrategy. */
@ -456,7 +478,14 @@ private object CompoundScheduler
private final class FinalWork[D](val compound: D, val doFinally: Scheduler[D]) extends NotNull
/** This represents nested work. The work provided by `scheduler` is processed first. The work provided by `doFinally` is processed
* after `scheduler` completes regardless of the success of `scheduler`.*/
final class SubWork[D](val scheduler: Scheduler[D], val doFinally: Scheduler[D]) extends NotNull
final class SubWork[D] private (val scheduler: Scheduler[D], val doFinally: Scheduler[D]) extends NotNull
object SubWork
{
def apply[D](scheduler: Scheduler[D], doFinally: Scheduler[D]): SubWork[D] = new SubWork(scheduler, doFinally)
def apply[D](scheduler: Scheduler[D]): SubWork[D] = SubWork(scheduler, ParallelRunner.emptyScheduler)
def apply[D <: Dag[D]](node: D): SubWork[D] = SubWork(ParallelRunner.dagScheduler(node))
def apply[D <: Dag[D]](node: D, doFinally: D): SubWork[D] = SubWork(ParallelRunner.dagScheduler(node), ParallelRunner.dagScheduler(doFinally))
}
/** Work that implements this interface provides nested work to be done before this work is processed.*/
trait CompoundWork[D] extends NotNull
{

View File

@ -4,6 +4,7 @@
package sbt
import java.io.File
import java.net.URLClassLoader
import scala.collection._
import FileUtilities._
import Project._
@ -289,24 +290,26 @@ object Project
loadProject(path.asFile, deps, parent, log)
/** Loads the project in the directory given by 'projectDirectory' and with the given dependencies.*/
private[sbt] def loadProject(projectDirectory: File, deps: Iterable[Project], parent: Option[Project], log: Logger): LoadResult =
loadProject(projectDirectory, deps, parent, getClass.getClassLoader, log)
private[sbt] def loadProject(projectDirectory: File, deps: Iterable[Project], parent: Option[Project], additional: ClassLoader, log: Logger): LoadResult =
{
val info = ProjectInfo(projectDirectory, deps, parent)
ProjectInfo.setup(info, log) match
{
case err: SetupError => new LoadSetupError(err.message)
case SetupDeclined => LoadSetupDeclined
case AlreadySetup => loadProject(info, None, log)
case setup: SetupInfo => loadProject(info, Some(setup), log)
case AlreadySetup => loadProject(info, None, additional, log)
case setup: SetupInfo => loadProject(info, Some(setup), additional, log)
}
}
private def loadProject(info: ProjectInfo, setupInfo: Option[SetupInfo], log: Logger): LoadResult =
private def loadProject(info: ProjectInfo, setupInfo: Option[SetupInfo], additional: ClassLoader, log: Logger): LoadResult =
{
try
{
val oldLevel = log.getLevel
log.setLevel(Level.Warn)
val result =
for(builderClass <- getProjectDefinition(info, log).right) yield
for(builderClass <- getProjectDefinition(info, additional, log).right) yield
initialize(constructProject(info, builderClass), setupInfo, log)
log.setLevel(oldLevel)
result.fold(new LoadError(_), new LoadSuccess(_))
@ -367,16 +370,17 @@ object Project
}
/** Compiles the project definition classes and returns the project definition class name
* and the class loader that should be used to load the definition. */
private def getProjectDefinition(info: ProjectInfo, buildLog: Logger): Either[String, Class[P] forSome { type P <: Project }] =
private def getProjectDefinition(info: ProjectInfo, additional: ClassLoader, buildLog: Logger): Either[String, Class[P] forSome { type P <: Project }] =
{
val builderProjectPath = info.builderPath / BuilderProjectDirectoryName
if(builderProjectPath.asFile.isDirectory)
{
val pluginProjectPath = info.builderPath / PluginProjectDirectoryName
val builderProject = new BuilderProject(ProjectInfo(builderProjectPath.asFile, Nil, None), pluginProjectPath, buildLog)
val additionalPaths = additional match { case u: URLClassLoader => u.getURLs.map(url => Path.fromFile(new File(url.toURI))); case _ => Nil }
val builderProject = new BuilderProject(ProjectInfo(builderProjectPath.asFile, Nil, None), pluginProjectPath, additionalPaths, buildLog)
builderProject.compile.run.toLeft(()).right.flatMap { ignore =>
builderProject.projectDefinition.right.map {
case Some(definition) => getProjectClass[Project](definition, builderProject.projectClasspath)
case Some(definition) => getProjectClass[Project](definition, builderProject.projectClasspath, additional)
case None => DefaultBuilderClass
}
}
@ -435,9 +439,9 @@ object Project
}
}
import scala.reflect.Manifest
private[sbt] def getProjectClass[P <: Project](name: String, classpath: PathFinder)(implicit mf: Manifest[P]): Class[P] =
private[sbt] def getProjectClass[P <: Project](name: String, classpath: PathFinder, additional: ClassLoader)(implicit mf: Manifest[P]): Class[P] =
{
val loader =ClasspathUtilities.toLoader(classpath)
val loader =ClasspathUtilities.toLoader(classpath, additional)
val builderClass = Class.forName(name, false, loader)
val projectClass = mf.erasure
require(projectClass.isAssignableFrom(builderClass), "Builder class '" + builderClass + "' does not extend " + projectClass.getName + ".")

View File

@ -26,8 +26,10 @@ object Resources
private val LoadErrorPrefix = "Error loading initial project: "
}
class Resources(val baseDirectory: File)
class Resources(val baseDirectory: File, additional: ClassLoader)
{
def this(baseDirectory: File) = this(baseDirectory, getClass.getClassLoader)
import Resources._
// The returned directory is not actually read-only, but it should be treated that way
def readOnlyResourceDirectory(group: String, name: String): Either[String, File] =
@ -85,7 +87,7 @@ class Resources(val baseDirectory: File)
}
buffered.startRecording()
resultToEither(Project.loadProject(dir, Nil, None, buffered)) match
resultToEither(Project.loadProject(dir, Nil, None, additional, buffered)) match
{
case Left(msg) =>
reload match

View File

@ -54,7 +54,7 @@ trait SimpleScalaProject extends Project
}
}
}
trait ScalaProject extends SimpleScalaProject with FileTasks
trait ScalaProject extends SimpleScalaProject with FileTasks with MultiTaskProject
{
import ScalaProject._
@ -149,7 +149,7 @@ trait ScalaProject extends SimpleScalaProject with FileTasks
val endTasks = end.map(toTask).toSeq // tasks that perform test cleanup and are run regardless of success of tests
val endTask = task { None } named("test-cleanup") dependsOn(endTasks : _*)
val rootTask = task { None } named("test-complete") dependsOn(workTasks.toSeq : _*) // the task that depends on all test subtasks
new SubWork[Project#Task](ParallelRunner.dagScheduler(rootTask), ParallelRunner.dagScheduler(endTask))
SubWork[Project#Task](rootTask, endTask)
}
new CompoundTask(work)
}
@ -263,22 +263,9 @@ trait ScalaProject extends SimpleScalaProject with FileTasks
private def flatten[T](i: Iterable[Iterable[T]]) = i.flatMap(x => x)
protected def testQuickMethod(testAnalysis: CompileAnalysis, options: => Seq[TestOption])(toRun: Seq[TestOption] => Task) =
task { tests =>
val (exactFilters, testFilters) = tests.toList.map(GlobFilter.apply).partition(_.isInstanceOf[ExactFilter])
val includeTests = exactFilters.map(_.asInstanceOf[ExactFilter].matchName)
val toCheck = scala.collection.mutable.HashSet(includeTests: _*)
toCheck --= testAnalysis.allTests.map(_.testClassName)
if(!toCheck.isEmpty && log.atLevel(Level.Warn))
{
log.warn("Test(s) not found:")
toCheck.foreach(test => log.warn("\t" + test))
}
val includeTestsSet = scala.collection.mutable.HashSet(includeTests: _*)
val newOptions =
if(includeTests.isEmpty && testFilters.isEmpty) options
else TestFilter(test => includeTestsSet.contains(test) || testFilters.exists(_.accept(test))) :: options.toList
toRun(newOptions)
} completeWith testAnalysis.allTests.map(_.testClassName).toList
multiTask(testAnalysis.allTests.map(_.testClassName).toList) { includeFunction =>
toRun(TestFilter(includeFunction) :: options.toList)
}
protected final def maximumErrors[T <: ActionOption](options: Seq[T]) =
(for( MaxCompileErrors(maxErrors) <- options) yield maxErrors).firstOption.getOrElse(DefaultMaximumCompileErrors)
@ -343,3 +330,22 @@ object ScalaProject
def optionsAsString(options: Seq[ScalaProject#CompileOption]) = options.map(_.asString).filter(!_.isEmpty)
def javaOptionsAsString(options: Seq[ScalaProject#JavaCompileOption]) = options.map(_.asString)
}
trait MultiTaskProject extends Project
{
def multiTask(allTests: => List[String])(run: (String => Boolean) => Task) =
task { tests =>
val (exactFilters, testFilters) = tests.toList.map(GlobFilter.apply).partition(_.isInstanceOf[ExactFilter])
val includeTests = exactFilters.map(_.asInstanceOf[ExactFilter].matchName)
val toCheck = scala.collection.mutable.HashSet(includeTests: _*)
toCheck --= allTests
if(!toCheck.isEmpty && log.atLevel(Level.Warn))
{
log.warn("Test(s) not found:")
toCheck.foreach(test => log.warn("\t" + test))
}
val includeTestsSet = Set(includeTests: _*)
val includeFunction = (test: String) => includeTestsSet.contains(test) || testFilters.exists(_.accept(test))
run(includeFunction)
} completeWith allTests
}

View File

@ -77,6 +77,7 @@ 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))
private def checkTaskDependencies(dependencyList: List[ManagedTask])
{