mirror of https://github.com/sbt/sbt.git
cleanup
This commit is contained in:
parent
0c59e9d5a6
commit
b4eea78959
|
|
@ -1,136 +0,0 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2009, 2010 Mark Harrah
|
||||
*/
|
||||
package sbt
|
||||
|
||||
private object DependencyTracking
|
||||
{
|
||||
import scala.collection.mutable.{Set, HashMap, Map, MultiMap}
|
||||
type DependencyMap[T] = HashMap[T, Set[T]] with MultiMap[T, T]
|
||||
def newMap[T]: DependencyMap[T] = new HashMap[T, Set[T]] with MultiMap[T, T]
|
||||
type TagMap[T] = Map[T, Array[Byte]]
|
||||
def newTagMap[T] = new HashMap[T, Array[Byte]]
|
||||
}
|
||||
|
||||
trait UpdateTracking[T] extends NotNull
|
||||
{
|
||||
def dependency(source: T, dependsOn: T): Unit
|
||||
def use(source: T, uses: T): Unit
|
||||
def product(source: T, output: T): Unit
|
||||
def tag(source: T, t: Array[Byte]): Unit
|
||||
def read: ReadTracking[T]
|
||||
// removes files from all maps, both keys and values
|
||||
def removeAll(files: Iterable[T]): Unit
|
||||
// removes sources as keys/values in source, product maps and as values in reverseDependencies map
|
||||
def pending(sources: Iterable[T]): Unit
|
||||
}
|
||||
trait ReadTracking[T] extends NotNull
|
||||
{
|
||||
def isProduct(file: T): Boolean
|
||||
def isSource(file: T): Boolean
|
||||
def isUsed(file: T): Boolean
|
||||
def dependsOn(file: T): Set[T]
|
||||
def products(file: T): Set[T]
|
||||
def sources(file: T): Set[T]
|
||||
def usedBy(file: T): Set[T]
|
||||
def tag(file: T): Array[Byte]
|
||||
def allProducts: Set[T]
|
||||
def allSources: Set[T]
|
||||
def allUsed: Set[T]
|
||||
def allTags: Seq[(T,Array[Byte])]
|
||||
}
|
||||
import DependencyTracking.{DependencyMap => DMap, newMap, newTagMap, TagMap}
|
||||
private object DefaultTracking
|
||||
{
|
||||
def apply[T](translateProducts: Boolean): DependencyTracking[T] =
|
||||
new DefaultTracking(translateProducts)(newMap, newMap, newMap, newTagMap)
|
||||
}
|
||||
private final class DefaultTracking[T](translateProducts: Boolean)
|
||||
(val reverseDependencies: DMap[T], val reverseUses: DMap[T], val sourceMap: DMap[T], val tagMap: TagMap[T])
|
||||
extends DependencyTracking[T](translateProducts)
|
||||
{
|
||||
val productMap: DMap[T] = forward(sourceMap) // map from a source to its products. Keep in sync with sourceMap
|
||||
}
|
||||
// if translateProducts is true, dependencies on a product are translated to dependencies on a source
|
||||
// if there is a source recorded as generating that product
|
||||
private abstract class DependencyTracking[T](translateProducts: Boolean) extends ReadTracking[T] with UpdateTracking[T]
|
||||
{
|
||||
val reverseDependencies: DMap[T] // map from a file to the files that depend on it
|
||||
val reverseUses: DMap[T] // map from a file to the files that use it
|
||||
val sourceMap: DMap[T] // map from a product to its sources. Keep in sync with productMap
|
||||
val productMap: DMap[T] // map from a source to its products. Keep in sync with sourceMap
|
||||
val tagMap: TagMap[T]
|
||||
|
||||
def read = this
|
||||
|
||||
final def dependsOn(file: T): Set[T] = get(reverseDependencies, file)
|
||||
final def products(file: T): Set[T] = get(productMap, file)
|
||||
final def sources(file: T): Set[T] = get(sourceMap, file)
|
||||
final def usedBy(file: T): Set[T] = get(reverseUses, file)
|
||||
final def tag(file: T): Array[Byte] = tagMap.getOrElse(file, new Array[Byte](0))
|
||||
|
||||
def isProduct(file: T): Boolean = exists(sourceMap, file)
|
||||
def isSource(file: T): Boolean = exists(productMap, file)
|
||||
def isUsed(file: T): Boolean = exists(reverseUses, file)
|
||||
|
||||
|
||||
final def allProducts = sourceMap.keysIterator.toSet
|
||||
final def allSources = productMap.keysIterator.toSet
|
||||
final def allUsed = reverseUses.keysIterator.toSet
|
||||
final def allTags = tagMap.toSeq
|
||||
|
||||
private def exists(map: DMap[T], value: T): Boolean = map.contains(value)
|
||||
private def get(map: DMap[T], value: T): Set[T] = map.getOrElse[collection.Set[T]](value, Set.empty[T]).toSet
|
||||
|
||||
final def dependency(sourceFile: T, dependsOn: T)
|
||||
{
|
||||
val actualDependencies =
|
||||
if(!translateProducts)
|
||||
Seq(dependsOn)
|
||||
else
|
||||
sourceMap.getOrElse[Iterable[T]](dependsOn, Seq(dependsOn))
|
||||
actualDependencies.foreach { actualDependency => reverseDependencies.add(actualDependency, sourceFile) }
|
||||
}
|
||||
final def product(sourceFile: T, product: T)
|
||||
{
|
||||
productMap.add(sourceFile, product)
|
||||
sourceMap.add(product, sourceFile)
|
||||
}
|
||||
final def use(sourceFile: T, usesFile: T) { reverseUses.add(usesFile, sourceFile) }
|
||||
final def tag(sourceFile: T, t: Array[Byte]) { tagMap(sourceFile) = t }
|
||||
|
||||
private def removeOneWay(a: DMap[T], files: Iterable[T]): Unit =
|
||||
a.values.foreach { _ --= files }
|
||||
private def remove(a: DMap[T], b: DMap[T], file: T): Unit =
|
||||
for(x <- a.removeKey(file)) b --= x
|
||||
private def removeAll(files: Iterable[T], a: DMap[T], b: DMap[T]): Unit =
|
||||
files.foreach { file => remove(a, b, file); remove(b, a, file) }
|
||||
final def removeAll(files: Iterable[T])
|
||||
{
|
||||
removeAll(files, forward(reverseDependencies), reverseDependencies)
|
||||
removeAll(files, productMap, sourceMap)
|
||||
removeAll(files, forward(reverseUses), reverseUses)
|
||||
tagMap --= files
|
||||
}
|
||||
def pending(sources: Iterable[T])
|
||||
{
|
||||
removeOneWay(reverseDependencies, sources)
|
||||
removeOneWay(reverseUses, sources)
|
||||
removeAll(sources, productMap, sourceMap)
|
||||
tagMap --= sources
|
||||
}
|
||||
protected final def forward(map: DMap[T]): DMap[T] =
|
||||
{
|
||||
val f = newMap[T]
|
||||
for( (key, values) <- map; value <- values) f.add(value, key)
|
||||
f
|
||||
}
|
||||
override def toString =
|
||||
(graph("Reverse source dependencies", reverseDependencies) ::
|
||||
graph("Sources and products", productMap) ::
|
||||
graph("Reverse uses", reverseUses) ::
|
||||
Nil) mkString "\n"
|
||||
def graph(title: String, map: DMap[T]) =
|
||||
"\"" + title + "\" {\n\t" + graphEntries(map) + "\n}"
|
||||
def graphEntries(map: DMap[T]) = map.map{ case (key, values) => values.map(key + " -> " + _).mkString("\n\t") }.mkString("\n\t")
|
||||
}
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2009, 2010 Mark Harrah
|
||||
*/
|
||||
package sbt
|
||||
|
||||
import java.io.File
|
||||
import scala.collection.mutable.{HashMap, Map, MultiMap, Set}
|
||||
import scala.reflect.Manifest
|
||||
import sbinary.{DefaultProtocol, Format}
|
||||
import DefaultProtocol._
|
||||
import TrackingFormat._
|
||||
import CacheIO.{fromFile, toFile}
|
||||
import DependencyTracking.{DependencyMap => DMap, newMap, TagMap}
|
||||
|
||||
private class TrackingFormat[T](directory: File, translateProducts: Boolean)(implicit tFormat: Format[T], mf: Manifest[T]) extends NotNull
|
||||
{
|
||||
val indexFile = new File(directory, "index")
|
||||
val dependencyFile = new File(directory, "dependencies")
|
||||
def read(): DependencyTracking[T] =
|
||||
{
|
||||
val indexMap = CacheIO.fromFile[Map[Int,T]](indexFile, new HashMap[Int,T])
|
||||
val indexedFormat = wrap[T,Int](ignore => error("Read-only"), i => indexMap.getOrElse(i, error("Index " + i + " not found")))
|
||||
val trackFormat = trackingFormat(translateProducts)(indexedFormat)
|
||||
fromFile(trackFormat, DefaultTracking[T](translateProducts))(dependencyFile)
|
||||
}
|
||||
def write(tracking: DependencyTracking[T])
|
||||
{
|
||||
val index = new IndexMap[T]
|
||||
val indexedFormat = wrap[T,Int](t => index(t), ignore => error("Write-only"))
|
||||
val trackFormat = trackingFormat(translateProducts)(indexedFormat)
|
||||
toFile(trackFormat)(tracking)(dependencyFile)
|
||||
toFile(index.indices)(indexFile)
|
||||
}
|
||||
}
|
||||
private object TrackingFormat
|
||||
{
|
||||
implicit def mutableMapFormat[S, T](implicit binS : Format[S], binT : Format[T]) : Format[HashMap[S, T]] =
|
||||
new LengthEncoded[HashMap[S, T], (S, T)] {
|
||||
def build(size : Int, ts : Iterator[(S, T)]) : HashMap[S, T] = {
|
||||
val b = new HashMap[S, T]
|
||||
b ++= ts
|
||||
b
|
||||
}
|
||||
}
|
||||
implicit def depMapFormat[T](implicit bin: Format[T]) : Format[DMap[T]] =
|
||||
new LengthEncoded[DMap[T], (T, Set[T])] {
|
||||
def build(size : Int, ts : Iterator[(T, Set[T])]) : DMap[T] = {
|
||||
val b = newMap[T]
|
||||
b ++= ts
|
||||
b
|
||||
}
|
||||
}
|
||||
def trackingFormat[T](translateProducts: Boolean)(implicit tFormat: Format[T]): Format[DependencyTracking[T]] =
|
||||
asProduct4((a: DMap[T],b: DMap[T],c: DMap[T], d:TagMap[T]) => new DefaultTracking(translateProducts)(a,b,c,d) : DependencyTracking[T]
|
||||
)(dt => (dt.reverseDependencies, dt.reverseUses, dt.sourceMap, dt.tagMap))
|
||||
}
|
||||
|
||||
private final class IndexMap[T] extends NotNull
|
||||
{
|
||||
private[this] var lastIndex = 0
|
||||
private[this] val map = new HashMap[T, Int]
|
||||
private[this] def nextIndex = { lastIndex += 1; lastIndex }
|
||||
def indices = HashMap(map.map( (_: (T,Int)).swap ).toSeq : _*)
|
||||
def apply(t: T) = map.getOrElseUpdate(t, nextIndex)
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2008, 2009 Mark Harrah
|
||||
*/
|
||||
package sbt
|
||||
|
||||
import java.io.File
|
||||
import xsbt.{AnalyzingCompiler, CompileFailed}
|
||||
|
||||
final class Scaladoc(maximumErrors: Int, compiler: AnalyzingCompiler)
|
||||
{
|
||||
final def apply(label: String, sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String])(implicit log: Logger)
|
||||
{
|
||||
log.info(actionStartMessage(label))
|
||||
if(sources.isEmpty)
|
||||
log.info(actionNothingToDoMessage)
|
||||
else
|
||||
{
|
||||
IO.createDirectory(outputDirectory)
|
||||
compiler.doc(sources, classpath, outputDirectory, options, maximumErrors, log)
|
||||
log.info(actionSuccessfulMessage)
|
||||
}
|
||||
}
|
||||
def actionStartMessage(label: String) = "Generating API documentation for " + label + " sources..."
|
||||
val actionNothingToDoMessage = "No sources specified."
|
||||
val actionSuccessfulMessage = "API documentation generation successful."
|
||||
def actionUnsuccessfulMessage = "API documentation generation unsuccessful."
|
||||
}
|
||||
final class Console(compiler: AnalyzingCompiler)
|
||||
{
|
||||
/** Starts an interactive scala interpreter session with the given classpath.*/
|
||||
def apply(classpath: Seq[File])(implicit log: Logger): Option[String] =
|
||||
apply(classpath, Nil, "", log)
|
||||
def apply(classpath: Iterable[File], options: Seq[String], initialCommands: String)(implicit log: Logger): Option[String] =
|
||||
{
|
||||
def console0 = compiler.console(Path.getFiles(classpath), options, initialCommands, log)
|
||||
JLine.withJLine( Run.executeTrapExit(console0, log) )
|
||||
}
|
||||
}
|
||||
|
|
@ -1,213 +0,0 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2010 Mark Harrah
|
||||
*/
|
||||
package sbt
|
||||
|
||||
/** A trait that provides a task for updating sbt. */
|
||||
trait UpdateSbt extends Project
|
||||
{
|
||||
/** The first argument is the version to update to and is mandatory.
|
||||
* The second argument is the location of the launcher jar. If omitted, the launcher used to launch the currently running instance of sbt is used.*/
|
||||
lazy val sbtUpdate = task { args => task { (new Update(this))(args) } } describedAs("Updates the version of sbt used to build this project and updates the launcher jar.")
|
||||
}
|
||||
|
||||
import java.io.{File, InputStream, IOException}
|
||||
import java.net.{HttpURLConnection, URL}
|
||||
import HttpURLConnection.{HTTP_NOT_FOUND , HTTP_OK}
|
||||
import SimpleReader.readLine
|
||||
import xsbt.FileUtilities.{classLocationFile, copyFile, readLines, transfer, unzip, withTemporaryDirectory, write, zip}
|
||||
import xsbt.PathMapper.relativeTo
|
||||
import xsbt.Paths._
|
||||
import xsbt.OpenResource.{fileOutputStream, urlInputStream}
|
||||
|
||||
private class Update(project: Project)
|
||||
{
|
||||
val info = project.info
|
||||
val app = info.app
|
||||
val log = project.log
|
||||
|
||||
/** The location of the jar used to launch the currently running instance of sbt.*/
|
||||
lazy val launcherJar = classLocationFile[xsbti.AppProvider]
|
||||
/** A temporary jar file to use in the given directory. */
|
||||
def tempJar(dir: File) = dir / launcherJar.getName
|
||||
|
||||
/** Implementation of the sbt-update task: reads arguments and hands off to the other `apply`.*/
|
||||
def apply(args: Array[String]): Option[String] =
|
||||
args match
|
||||
{
|
||||
case Array(version) if validVersion(version) => apply(version, None)
|
||||
case Array(version, temporaryJar) if validVersion(version) => apply(version, Some(new File(temporaryJar) getAbsoluteFile))
|
||||
case _ => Some("Expected '<version>' or '<version> <new-launcher-file>', got '" + args.mkString(" ") + "'")
|
||||
}
|
||||
|
||||
def validVersion(version: String) = !version.trim.isEmpty
|
||||
|
||||
/** Implementation of the sbt-update task after arguments have checked. Gives user a chance to cancel and continues with `doUpdate`.*/
|
||||
def apply(version: String, temporaryJar: Option[File]): Option[String] =
|
||||
{
|
||||
readLine("Updating the sbt version requires a restart. Continue? (Y/n) ") match
|
||||
{
|
||||
case Some(line) if(isYes(line)) => doUpdate(version, temporaryJar)
|
||||
case _ => Some("Update declined.")
|
||||
}
|
||||
}
|
||||
/** Implementation of the sbt-update task: high-level control after initial verification.*/
|
||||
def doUpdate(version: String, temporaryJar: Option[File]): Option[String] =
|
||||
{
|
||||
retrieveNewVersion(version)
|
||||
log.info("Version is valid. Setting 'sbt.version' to " + version + "...")
|
||||
setNewVersion(version)
|
||||
|
||||
log.info("'sbt.version' updated.")
|
||||
if(temporaryJar.isDefined || updateInPlace(version))
|
||||
{
|
||||
log.info("Downloading new launcher ...")
|
||||
|
||||
if(downloadLauncher(version, temporaryJar))
|
||||
log.info("Downloaded launcher.")
|
||||
else
|
||||
tryUpdateLauncher(version, temporaryJar)
|
||||
}
|
||||
else
|
||||
log.info("Launcher update declined.")
|
||||
|
||||
log.info("Please restart sbt.")
|
||||
System.exit(0)
|
||||
None
|
||||
}
|
||||
/** Updates 'sbt.version' in `project/build.properties`.*/
|
||||
def setNewVersion(version: String)
|
||||
{
|
||||
project.sbtVersion() = version
|
||||
project.saveEnvironment()
|
||||
}
|
||||
/** Retrieves the given `version` of sbt in order to verify the version is valid.*/
|
||||
def retrieveNewVersion(version: String)
|
||||
{
|
||||
val newAppID = changeVersion(app.id, version)
|
||||
log.info("Checking repositories for sbt " + version + " ...")
|
||||
app.scalaProvider.app(newAppID)
|
||||
}
|
||||
/** Asks the user whether the current launcher should be overrwritten. Called when no file is explicitly specified as an argument. */
|
||||
def updateInPlace(version: String) =
|
||||
{
|
||||
val input = readLine(" The current launcher (" + launcherJar + ") will be updated in place. Continue? (Y/n) ")
|
||||
isYes(input)
|
||||
}
|
||||
def isYes(line: Option[String]): Boolean = line.filter(isYes).isDefined
|
||||
|
||||
/** Updates the launcher as in `updateLauncher` but performs various checks and logging around it. */
|
||||
def tryUpdateLauncher(version: String, temporaryJar: Option[File])
|
||||
{
|
||||
log.warn("No launcher found for '" + version + "'")
|
||||
def promptStart = if(temporaryJar.isDefined) " Copy current launcher but with " else " Modify current launcher to use "
|
||||
val input = readLine(promptStart + version + " as the default for new projects? (Y/n) ")
|
||||
val updated = isYes(input)
|
||||
if(updated) updateLauncher(version, temporaryJar)
|
||||
|
||||
def extra = if(temporaryJar.isDefined) " at " + temporaryJar.get + "." else "."
|
||||
log.info(if(updated) "Launcher updated" + extra else "Launcher not updated.")
|
||||
}
|
||||
/** The jar to copy/download to. If `userJar` is not defined, it is a temporary file in `tmpDir` that should then be moved to the current launcher file.
|
||||
* If it is defined and is a directory, the jar is defined in that directory. If it is a file, that file is returned. */
|
||||
def targetJar(tmpDir: File, userJar: Option[File]): File =
|
||||
userJar match { case Some(file) => if(file.isDirectory) tempJar(file) else file; case None => tempJar(tmpDir) }
|
||||
|
||||
/** Gets the given `version` of the launcher from Google Code. If `userProvidedJar` is defined,
|
||||
* this updated launcher is downloaded there, otherwise it overwrites the current launcher. */
|
||||
def downloadLauncher(version: String, userProvidedJar: Option[File]): Boolean =
|
||||
{
|
||||
def getLauncher(tmp: File): Boolean =
|
||||
{
|
||||
val temporaryJar = targetJar(tmp, userProvidedJar)
|
||||
temporaryJar.getParentFile.mkdirs()
|
||||
val url = launcherURL(version)
|
||||
val connection = url.openConnection.asInstanceOf[HttpURLConnection]
|
||||
connection.setInstanceFollowRedirects(false)
|
||||
|
||||
def download(in: InputStream): Unit = fileOutputStream(false)(temporaryJar) { out => transfer(in, out) }
|
||||
def checkAndRetrieve(in: InputStream): Boolean = (connection.getResponseCode == HTTP_OK) && { download(in); true }
|
||||
def handleError(e: IOException) = if(connection.getResponseCode == HTTP_NOT_FOUND ) false else throw e
|
||||
def retrieve() =
|
||||
{
|
||||
val in = connection.getInputStream
|
||||
try { checkAndRetrieve(in) } finally { in.close() }
|
||||
}
|
||||
|
||||
val success = try { retrieve() } catch { case e: IOException => handleError(e)} finally { connection.disconnect() }
|
||||
if(success && userProvidedJar.isEmpty)
|
||||
move(temporaryJar, launcherJar)
|
||||
success
|
||||
}
|
||||
withTemporaryDirectory(getLauncher)
|
||||
}
|
||||
/** The location of the launcher for the given version, if it exists. */
|
||||
def launcherURL(version: String): URL =
|
||||
new URL("http://simple-build-tool.googlecode.com/files/sbt-launch-" + version + ".jar")
|
||||
|
||||
/** True iff the given user input is empty, 'y' or 'yes' (case-insensitive).*/
|
||||
def isYes(line: String) =
|
||||
{
|
||||
val lower = line.toLowerCase
|
||||
lower.isEmpty || lower == "y" || lower == "yes"
|
||||
}
|
||||
/** Copies the current launcher but with the default 'sbt.version' set to `version`. If `userProvidedJar` is defined,
|
||||
* the updated launcher is copied there, otherwise the copy overwrites the current launcher. */
|
||||
def updateLauncher(version: String, userProvidedJar: Option[File])
|
||||
{
|
||||
def makeUpdated(base: File, newJar: File)
|
||||
{
|
||||
val files = unzip(launcherJar, base)
|
||||
updateBootProperties(files, version)
|
||||
zip(relativeTo(base)( files ), newJar)
|
||||
}
|
||||
def updateLauncher(tmp: File)
|
||||
{
|
||||
val basePath = tmp / "launcher-jar"
|
||||
val temporaryJar = targetJar(tmp, userProvidedJar)
|
||||
makeUpdated(basePath, temporaryJar)
|
||||
if(userProvidedJar.isEmpty)
|
||||
move(temporaryJar, launcherJar)
|
||||
}
|
||||
|
||||
withTemporaryDirectory(updateLauncher)
|
||||
}
|
||||
|
||||
/** Copies the `src` file to the `dest` file, preferably by renaming. The `src` file may or may not be removed.*/
|
||||
def move(src: File, dest: File)
|
||||
{
|
||||
val renameSuccess = src renameTo dest
|
||||
if(!renameSuccess)
|
||||
copyFile(src, dest)
|
||||
}
|
||||
|
||||
/** Updates the default value used for 'sbt.version' in the 'sbt.boot.properties' file in the launcher `files` to be `version`.*/
|
||||
def updateBootProperties(files: Iterable[File], version: String): Unit =
|
||||
files.find(_.getName == "sbt.boot.properties").foreach(updateBootProperties(version))
|
||||
/** Updates the default value used for 'sbt.version' in the given `file` to be `version`.*/
|
||||
def updateBootProperties(version: String)(file: File)
|
||||
{
|
||||
val newContent = readLines(file) map updateSbtVersion(version)
|
||||
write(file, newContent.mkString("\n"))
|
||||
}
|
||||
|
||||
/** If the given `line` is the 'sbt.version' configuration, update it to use the `newVersion`.*/
|
||||
def updateSbtVersion(newVersion: String)(line: String) =
|
||||
if(line.trim.startsWith("sbt.version")) sbtVersion(newVersion) else line
|
||||
|
||||
/** The configuration line that defines the 'sbt.version' property, using the provided `version` for defaults.*/
|
||||
def sbtVersion(version: String) =
|
||||
" sbt.version: quick=set(" + version + "), new=prompt(sbt version)[" + version + "], fill=prompt(sbt version)[" + version + "]"
|
||||
|
||||
/** Copies the given ApplicationID but with the specified version.*/
|
||||
def changeVersion(appID: xsbti.ApplicationID, versionA: String): xsbti.ApplicationID =
|
||||
new xsbti.ApplicationID {
|
||||
def groupID = appID.groupID
|
||||
def name = appID.name
|
||||
def version = versionA
|
||||
def mainClass = appID.mainClass
|
||||
def mainComponents = appID.mainComponents
|
||||
def crossVersioned = appID.crossVersioned
|
||||
def classpathExtra = appID.classpathExtra
|
||||
}
|
||||
}
|
||||
|
|
@ -1,154 +0,0 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2009 Mark Harrah
|
||||
*/
|
||||
package sbt.impl
|
||||
import sbt._
|
||||
|
||||
import scala.collection.{immutable, mutable}
|
||||
import scala.collection.Map
|
||||
import sbt.wrap.Wrappers.identityMap
|
||||
|
||||
private[sbt] object RunTask
|
||||
{
|
||||
final type Task = Project#Task
|
||||
def apply(root: Task, rootName: String): List[WorkFailure[Task]] = apply(root, rootName, true)
|
||||
def apply(root: Task, rootName: String, parallelExecution: Boolean): List[WorkFailure[Task]] =
|
||||
apply(root, rootName, if(parallelExecution) Runtime.getRuntime.availableProcessors else 1)
|
||||
def apply(root: Task, rootName: String, maximumTasks: Int): List[WorkFailure[Task]] = (new RunTask(root, rootName, maximumTasks)).run()
|
||||
}
|
||||
import RunTask._
|
||||
private final class RunTask(root: Task, rootName: String, maximumTasks: Int) extends NotNull
|
||||
{
|
||||
require(maximumTasks >= 1)
|
||||
def parallel = maximumTasks > 1
|
||||
def multiProject = allProjects.size >= 2
|
||||
def run(): List[WorkFailure[Task]] =
|
||||
{
|
||||
try
|
||||
{
|
||||
runTasksExceptRoot() match
|
||||
{
|
||||
case Nil =>
|
||||
val result = runTask(root, rootName)
|
||||
result.map( errorMessage => WorkFailure(root, "Error running " + rootName + ": " + errorMessage) ).toList
|
||||
case failures => failures
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
for(project <- allProjects; saveError <- project.saveEnvironment)
|
||||
project.log.warn("Could not save properties for project " + project.name + ": " + saveError)
|
||||
}
|
||||
}
|
||||
// This runs all tasks except the root.task.
|
||||
// It uses a buffered logger in record mode to ensure that all output for a given task is consecutive
|
||||
// it ignores the root task so that the root task may be run with buffering disabled so that the output
|
||||
// occurs without delay.
|
||||
private def runTasksExceptRoot() =
|
||||
ParallelRunner.run(expandedRoot, expandedTaskName, runIfNotRoot, maximumTasks, (t: Task) => t.manager.log)
|
||||
private def withBuffered(f: BufferedLogger => Unit)
|
||||
{
|
||||
for(buffered <- bufferedLoggers)
|
||||
Control.trap(f(buffered))
|
||||
}
|
||||
/** Will be called in its own thread. Runs the given task if it is not the root task.*/
|
||||
private def runIfNotRoot(action: Task): Option[String] =
|
||||
{
|
||||
if(isRoot(action))
|
||||
None
|
||||
else
|
||||
runTask(action, expandedTaskName(action))
|
||||
}
|
||||
private val headerStart = System.getProperty("sbt.start.delimiter", "==")
|
||||
private val headerEnd = System.getProperty("sbt.end.delimiter", "==")
|
||||
private def isRoot(t: Task) = t == expandedRoot
|
||||
/** Will be called in its own thread except for the root task. */
|
||||
private def runTask(action: Task, actionName: String): Option[String] =
|
||||
{
|
||||
val label = if(multiProject) (action.manager.name + " / " + actionName) else actionName
|
||||
def banner(event: ControlEvent.Value, firstSeparator: String, secondSeparator: String) =
|
||||
Control.trap(action.manager.log.control(event, firstSeparator + " " + label + " " + secondSeparator))
|
||||
val buffered = parallel && !isRoot(action)
|
||||
if(buffered)
|
||||
banner(ControlEvent.Start, "\n ", "...")
|
||||
def doRun() =
|
||||
{
|
||||
banner(ControlEvent.Header, "\n" + headerStart, headerStart)
|
||||
try { action.invoke }
|
||||
catch { case e: Exception => action.manager.log.trace(e); Some(e.toString) }
|
||||
finally { banner(ControlEvent.Finish, headerEnd, headerEnd) }
|
||||
}
|
||||
|
||||
if(buffered)
|
||||
bufferLogging(action, doRun())
|
||||
else
|
||||
doRun()
|
||||
}
|
||||
private def bufferLogging[T](action: Task, f: => T) =
|
||||
bufferedLogger(action.manager) match
|
||||
{
|
||||
case Some(buffered) => buffered.buffer { f }
|
||||
case None => f
|
||||
}
|
||||
|
||||
/* Most of the following is for implicitly adding dependencies (see the expand method)*/
|
||||
private val projectDependencyCache = identityMap[Project, Iterable[Project]]
|
||||
private def dependencies(project: Project) = projectDependencyCache.getOrElseUpdate(project, project.topologicalSort.dropRight(1))
|
||||
|
||||
private val expandedCache = identityMap[Task, Task]
|
||||
private def expanded(task: Task): Task = expandedCache.getOrElseUpdate(task, expandImpl(task))
|
||||
|
||||
private val expandedTaskNameCache = identityMap[Task, String]
|
||||
private def expandedTaskName(task: Task) =
|
||||
if(task == expandedRoot)
|
||||
rootName
|
||||
else
|
||||
expandedTaskNameCache.getOrElse(task, task.name)
|
||||
|
||||
private val nameToTaskCache = identityMap[Project, Map[String, Task]]
|
||||
private def nameToTaskMap(project: Project): Map[String, Task] = nameToTaskCache.getOrElseUpdate(project, project.tasks)
|
||||
private def taskForName(project: Project, name: String): Option[Task] = nameToTaskMap(project).get(name)
|
||||
|
||||
private val taskNameCache = identityMap[Project, Map[Task, String]]
|
||||
private def taskName(task: Task) =
|
||||
{
|
||||
val project = task.manager
|
||||
taskNameCache.getOrElseUpdate(project, taskNameMap(project)).get(task)
|
||||
}
|
||||
|
||||
private val expandedRoot = expand(root)
|
||||
private val allTasks = expandedRoot.topologicalSort
|
||||
private val allProjects = Set(allTasks.map(_.manager).toSeq : _*)
|
||||
private val bufferedLoggers = if(parallel) allProjects.toList.flatMap(bufferedLogger) else Nil
|
||||
|
||||
/** Adds implicit dependencies, which are tasks with the same name in the project dependencies
|
||||
* of the enclosing project of the task.*/
|
||||
private def expand(root: Task): Task = expanded(root)
|
||||
private def expandImpl(task: Task): Task =
|
||||
{
|
||||
val nameOption = taskName(task)
|
||||
val explicitDependencies = task.dependencies
|
||||
val implicitDependencies = nameOption.map(name => dependencies(task.manager).flatMap(noninteractiveTask(name)) ).getOrElse(Nil)
|
||||
val allDependencies = mutable.HashSet( (explicitDependencies ++ implicitDependencies).toSeq : _* )
|
||||
val expandedTask = task.setDependencies(allDependencies.toList.map(expanded))
|
||||
nameOption.foreach(name => expandedTaskNameCache(expandedTask) = name)
|
||||
expandedTask
|
||||
}
|
||||
private def noninteractiveTask(name: String)(project: Project): Option[Task] =
|
||||
taskForName(project, name) flatMap { task =>
|
||||
if(task.interactive)
|
||||
{
|
||||
project.log.debug("Not including task " + name + " in project " + project.name + ": interactive tasks can only be run directly.")
|
||||
None
|
||||
}
|
||||
else
|
||||
Some(task)
|
||||
}
|
||||
private def taskNameMap(project: Project) = mutable.Map(nameToTaskMap(project).map(_.swap).toSeq : _*)
|
||||
private def bufferedLogger(project: Project): Option[BufferedLogger] =
|
||||
project.log match
|
||||
{
|
||||
case buffered: BufferedLogger => Some(buffered)
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue