diff --git a/src/main/scala/sbt/BasicProjectTypes.scala b/src/main/scala/sbt/BasicProjectTypes.scala index ccce8f088..d8340bf39 100644 --- a/src/main/scala/sbt/BasicProjectTypes.scala +++ b/src/main/scala/sbt/BasicProjectTypes.scala @@ -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 = diff --git a/src/main/scala/sbt/BuilderProject.scala b/src/main/scala/sbt/BuilderProject.scala index 0cf3a82a1..b5d04bf87 100644 --- a/src/main/scala/sbt/BuilderProject.scala +++ b/src/main/scala/sbt/BuilderProject.scala @@ -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 diff --git a/src/main/scala/sbt/ClasspathUtilities.scala b/src/main/scala/sbt/ClasspathUtilities.scala index 48dc42ade..ed2ff8d5d 100644 --- a/src/main/scala/sbt/ClasspathUtilities.scala +++ b/src/main/scala/sbt/ClasspathUtilities.scala @@ -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) diff --git a/src/main/scala/sbt/Main.scala b/src/main/scala/sbt/Main.scala index e17c4b555..877599824 100644 --- a/src/main/scala/sbt/Main.scala +++ b/src/main/scala/sbt/Main.scala @@ -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)) diff --git a/src/main/scala/sbt/ParallelRunner.scala b/src/main/scala/sbt/ParallelRunner.scala index 74ce7a8ee..24d5507f9 100644 --- a/src/main/scala/sbt/ParallelRunner.scala +++ b/src/main/scala/sbt/ParallelRunner.scala @@ -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 { diff --git a/src/main/scala/sbt/Project.scala b/src/main/scala/sbt/Project.scala index 8eaf126ee..69fa9f5e1 100644 --- a/src/main/scala/sbt/Project.scala +++ b/src/main/scala/sbt/Project.scala @@ -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 + ".") diff --git a/src/main/scala/sbt/Resources.scala b/src/main/scala/sbt/Resources.scala index 388e82b3c..b92cd5e73 100644 --- a/src/main/scala/sbt/Resources.scala +++ b/src/main/scala/sbt/Resources.scala @@ -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 diff --git a/src/main/scala/sbt/ScalaProject.scala b/src/main/scala/sbt/ScalaProject.scala index d9b38592f..47f25094f 100644 --- a/src/main/scala/sbt/ScalaProject.scala +++ b/src/main/scala/sbt/ScalaProject.scala @@ -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 + +} \ No newline at end of file diff --git a/src/main/scala/sbt/TaskManager.scala b/src/main/scala/sbt/TaskManager.scala index 23678c6d2..4d42f4f4b 100644 --- a/src/main/scala/sbt/TaskManager.scala +++ b/src/main/scala/sbt/TaskManager.scala @@ -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]) {