mirror of https://github.com/sbt/sbt.git
* 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:
parent
545a138c11
commit
aa4b084ecc
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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 + ".")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
@ -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])
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue