Merge branch '0.12' of git://github.com/harrah/xsbt into 0.12

This commit is contained in:
Eugene Vigdorchik 2012-01-31 17:50:17 +04:00
commit 1f0a45e950
87 changed files with 1103 additions and 752 deletions

View File

@ -61,7 +61,7 @@ class ComponentManager(globalLock: xsbti.GlobalLock, provider: xsbti.ComponentPr
/** Retrieve the file for component 'id' from the local repository. */
private def update(id: String): Unit = ivyCache.withCachedJar(sbtModuleID(id), Some(globalLock), log)(jar => define(id, Seq(jar)) )
private def sbtModuleID(id: String) = ModuleID("org.scala-tools.sbt", id, ComponentManager.stampedVersion)
private def sbtModuleID(id: String) = ModuleID(SbtArtifacts.Organization, id, ComponentManager.stampedVersion)
/** Install the files for component 'id' to the local repository. This is usually used after writing files to the directory returned by 'location'. */
def cache(id: String): Unit = ivyCache.cacheJar(sbtModuleID(id), file(id)(IfMissing.Fail), Some(globalLock), log)
def clearCache(id: String): Unit = lockGlobalCache { ivyCache.clearCachedJar(sbtModuleID(id), Some(globalLock), log) }

View File

@ -5,7 +5,7 @@ package sbt
final case class ConflictWarning(label: String, filter: ModuleFilter, group: ModuleID => String, level: Level.Value, failOnConflict: Boolean)
object ConflictWarning
{
def default(label: String): ConflictWarning = ConflictWarning(label, moduleFilter(organization = GlobFilter("org.scala-tools.sbt") | GlobFilter("org.scala-lang")), (_: ModuleID).organization, Level.Warn, false)
def default(label: String): ConflictWarning = ConflictWarning(label, moduleFilter(organization = GlobFilter(SbtArtifacts.Organization) | GlobFilter(ScalaArtifacts.Organization)), (_: ModuleID).organization, Level.Warn, false)
def apply(config: ConflictWarning, report: UpdateReport, log: Logger)
{

94
ivy/CrossVersion.scala Normal file
View File

@ -0,0 +1,94 @@
package sbt
final case class ScalaVersion(full: String, binary: String)
sealed trait CrossVersion
object CrossVersion
{
val TransitionScalaVersion = "2.10"
val TransitionSbtVersion = "0.12"
object Disabled extends CrossVersion { override def toString = "disabled" }
final class Binary(val remapVersion: String => String) extends CrossVersion {
override def toString = "Binary"
}
final class Full(val remapVersion: String => String) extends CrossVersion {
override def toString = "Full"
}
def full: CrossVersion = new Full(idFun)
def fullMapped(remapVersion: String => String): CrossVersion = new Full(remapVersion)
def binary: CrossVersion = new Binary(idFun)
def binaryMapped(remapVersion: String => String): CrossVersion = new Full(remapVersion)
private[this] def idFun[T]: T => T = x => x
def append(s: String): Option[String => String] = Some(x => crossName(x, s))
def apply(cross: CrossVersion, fullVersion: String, binaryVersion: String): Option[String => String] =
cross match
{
case Disabled => None
case b: Binary => append(b.remapVersion(binaryVersion))
case f: Full => append(f.remapVersion(fullVersion))
}
def apply(module: ModuleID, is: IvyScala): Option[String => String] =
CrossVersion(module.crossVersion, is.scalaFullVersion, is.scalaBinaryVersion)
def apply(module: ModuleID, is: Option[IvyScala]): Option[String => String] =
is flatMap { i => apply(module, i) }
def substituteCross(artifacts: Seq[Artifact], cross: Option[String => String]): Seq[Artifact] =
cross match {
case None => artifacts
case Some(is) => substituteCrossA(artifacts, cross)
}
def applyCross(s: String, fopt: Option[String => String]): String =
fopt match {
case None => s
case Some(fopt) => fopt(s)
}
def crossName(name: String, cross: String): String =
name + "_" + cross
def substituteCross(a: Artifact, cross: Option[String => String]): Artifact =
a.copy(name = applyCross(a.name, cross))
def substituteCrossA(as: Seq[Artifact], cross: Option[String => String]): Seq[Artifact] =
as.map(art => substituteCross(art, cross))
def apply(scalaFullVersion: String, scalaBinaryVersion: String): ModuleID => ModuleID = m =>
{
val cross = apply(m.crossVersion, scalaFullVersion, scalaBinaryVersion)
if(cross.isDefined)
m.copy(name = applyCross(m.name, cross), explicitArtifacts = substituteCrossA(m.explicitArtifacts, cross))
else
m
}
def isStable(v: String): Boolean = !v.contains("-")
def selectVersion(full: String, binary: String): String = if(isStable(full)) binary else full
val PartialVersion = """(\d+)\.(\d+)(?:\..+)?""".r
def partialVersion(s: String): Option[(Int,Int)] =
s match {
case PartialVersion(major, minor) => Some(major.toInt, minor.toInt)
case _ => None
}
private[this] def isNewer(major: Int, minor: Int, minMajor: Int, minMinor: Int): Boolean =
major > minMajor || (major == minMajor && minor >= minMinor)
def binaryScalaVersion(full: String): String = binaryVersion(full, TransitionScalaVersion)
def binarySbtVersion(full: String): String = binaryVersion(full, TransitionSbtVersion)
def binaryVersion(full: String, cutoff: String): String =
{
def sub(major: Int, minor: Int) = major + "." + minor
(partialVersion(full), partialVersion(cutoff)) match {
case (Some((major, minor)), None) => sub(major, minor)
case (Some((major, minor)), Some((minMajor, minMinor))) if isNewer(major, minor, minMajor, minMinor) => sub(major, minor)
case _ => full
}
}
}

View File

@ -27,21 +27,28 @@ trait DependencyBuilders
final class GroupID private[sbt] (groupID: String)
{
def % (artifactID: String) = groupArtifact(artifactID, None)
def %% (artifactID: String, crossVersion: String => String = identity) = groupArtifact(artifactID, Some(crossVersion))
def %% (artifactID: String, alternatives: (String, String)*) = groupArtifact(artifactID, Some(Map(alternatives: _*) orElse { case s => s }))
private def groupArtifact(artifactID: String, cross: Option[String => String]) =
def % (artifactID: String) = groupArtifact(artifactID, CrossVersion.Disabled)
def %% (artifactID: String): GroupArtifactID = groupArtifact(artifactID, CrossVersion.binary)
@deprecated(deprecationMessage, "0.12.0")
def %% (artifactID: String, crossVersion: String => String) = groupArtifact(artifactID, CrossVersion.binaryMapped(crossVersion))
@deprecated(deprecationMessage, "0.12.0")
def %% (artifactID: String, alternatives: (String, String)*) = groupArtifact(artifactID, CrossVersion.binaryMapped(Map(alternatives: _*) orElse { case s => s }))
private def groupArtifact(artifactID: String, cross: CrossVersion) =
{
nonEmpty(artifactID, "Artifact ID")
new GroupArtifactID(groupID, artifactID, cross)
}
private[this] def deprecationMessage = """Use the cross method on the constructed ModuleID. For example: ("a" % "b" % "1").cross(...)"""
}
final class GroupArtifactID private[sbt] (groupID: String, artifactID: String, crossVersion: Option[String => String])
final class GroupArtifactID private[sbt] (groupID: String, artifactID: String, crossVersion: CrossVersion)
{
def % (revision: String): ModuleID =
{
nonEmpty(revision, "Revision")
ModuleID(groupID, artifactID, revision).cross(!crossVersion.isEmpty, crossVersion.getOrElse(identity))
ModuleID(groupID, artifactID, revision).cross(crossVersion)
}
}
final class ModuleIDConfigurable private[sbt] (moduleID: ModuleID)

View File

@ -299,24 +299,19 @@ private object IvySbt
}
private def substituteCross(m: ModuleSettings): ModuleSettings =
m.ivyScala match { case None => m; case Some(is) => substituteCross(m, is.substituteCross) }
private def substituteCross(m: ModuleSettings, sub: ModuleID => ModuleID): ModuleSettings =
m.ivyScala match {
case None => m
case Some(is) => substituteCross(m, is.scalaFullVersion, is.scalaBinaryVersion)
}
private def substituteCross(m: ModuleSettings, scalaFullVersion: String, scalaBinaryVersion: String): ModuleSettings =
{
val sub = CrossVersion(scalaFullVersion, scalaBinaryVersion)
m match {
case ec: EmptyConfiguration => ec.copy(module = sub(ec.module))
case ic: InlineConfiguration => ic.copy(module = sub(ic.module), dependencies = ic.dependencies map sub)
case _ => m
}
def crossName(name: String, cross: String): String =
name + "_" + cross
def substituteCross(a: Artifact, cross: String): Artifact =
a.copy(name = crossName(a.name, cross))
def substituteCrossA(as: Seq[Artifact], cross: String): Seq[Artifact] =
as.map(art => substituteCross(art, cross))
def substituteCross(m: ModuleID, cross: String): ModuleID =
if(m.crossVersion)
m.copy(name = crossName(m.name, m.crossVersionRemap(cross)), explicitArtifacts = substituteCrossA(m.explicitArtifacts, cross))
else
m
}
private def toIvyArtifact(moduleID: ModuleDescriptor, a: Artifact, configurations: Iterable[String]): MDArtifact =
{

View File

@ -88,8 +88,8 @@ object IvyActions
val resolver = ivy.getSettings.getResolver(resolverName)
if(resolver eq null) error("Undefined resolver '" + resolverName + "'")
val ivyArtifact = ivyFile map { file => (MDArtifact.newIvyArtifact(md), file) }
val is = crossIvyScala(module.moduleSettings)
val as = mapArtifacts(md, is, artifacts) ++ ivyArtifact.toList
val cross = crossVersionMap(module.moduleSettings)
val as = mapArtifacts(md, cross, artifacts) ++ ivyArtifact.toList
withChecksums(resolver, checksums) { publish(md, as, resolver, overwrite = true) }
}
}
@ -102,18 +102,16 @@ object IvyActions
try { act }
finally { resolver.setChecksums(previous mkString ",") }
}
private def crossIvyScala(moduleSettings: ModuleSettings): Option[IvyScala] =
private def crossVersionMap(moduleSettings: ModuleSettings): Option[String => String] =
moduleSettings match {
case i: InlineConfiguration if i.module.crossVersion => i.ivyScala
case e: EmptyConfiguration if e.module.crossVersion => e.ivyScala
case i: InlineConfiguration => CrossVersion(i.module, i.ivyScala)
case e: EmptyConfiguration => CrossVersion(e.module, e.ivyScala)
case _ => None
}
def substituteCross(ivyScala: Option[IvyScala], artifacts: Seq[Artifact]): Seq[Artifact] =
ivyScala match { case None => artifacts; case Some(is) => IvySbt.substituteCrossA(artifacts, is.scalaVersion) }
def mapArtifacts(module: ModuleDescriptor, ivyScala: Option[IvyScala], artifacts: Map[Artifact, File]): Seq[(IArtifact, File)] =
def mapArtifacts(module: ModuleDescriptor, cross: Option[String => String], artifacts: Map[Artifact, File]): Seq[(IArtifact, File)] =
{
val rawa = artifacts.keys.toSeq
val seqa = substituteCross(ivyScala, rawa)
val seqa = CrossVersion.substituteCross(rawa, cross)
val zipped = rawa zip IvySbt.mapArtifacts(module, seqa)
zipped map { case (a, ivyA) => (ivyA, artifacts(a)) }
}
@ -199,7 +197,7 @@ object IvyActions
report.allMissing flatMap { case (_, mod, art) => art.classifier.map { c => (restrictedCopy(mod, false), c) } } groupBy(_._1) map { case (mod, pairs) => (mod, pairs.map(_._2).toSet) }
private[this] def restrictedCopy(m: ModuleID, confs: Boolean) =
ModuleID(m.organization, m.name, m.revision, crossVersion = m.crossVersion, crossVersionRemap = m.crossVersionRemap, extraAttributes = m.extraAttributes, configurations = if(confs) m.configurations else None)
ModuleID(m.organization, m.name, m.revision, crossVersion = m.crossVersion, extraAttributes = m.extraAttributes, configurations = if(confs) m.configurations else None)
private[this] def resolve(logging: UpdateLogging.Value)(ivy: Ivy, module: DefaultModuleDescriptor, defaultConf: String): (ResolveReport, Option[ResolveException]) =
{
val resolveOptions = new ResolveOptions

View File

@ -9,14 +9,21 @@ import scala.xml.NodeSeq
import org.apache.ivy.plugins.resolver.{DependencyResolver, IBiblioResolver}
import org.apache.ivy.util.url.CredentialsStore
final case class ModuleID(organization: String, name: String, revision: String, configurations: Option[String] = None, isChanging: Boolean = false, isTransitive: Boolean = true, explicitArtifacts: Seq[Artifact] = Nil, exclusions: Seq[ExclusionRule] = Nil, extraAttributes: Map[String,String] = Map.empty, crossVersion: Boolean = false, crossVersionRemap: String => String = identity)
final case class ModuleID(organization: String, name: String, revision: String, configurations: Option[String] = None, isChanging: Boolean = false, isTransitive: Boolean = true, explicitArtifacts: Seq[Artifact] = Nil, exclusions: Seq[ExclusionRule] = Nil, extraAttributes: Map[String,String] = Map.empty, crossVersion: CrossVersion = CrossVersion.Disabled)
{
override def toString =
organization + ":" + name + ":" + revision +
(configurations match { case Some(s) => ":" + s; case None => "" }) +
(if(extraAttributes.isEmpty) "" else " " + extraString)
def extraString = extraAttributes.map { case (k,v) => k + "=" + v } mkString("(",", ",")")
def cross(v: Boolean, verRemap: String => String = identity) = copy(crossVersion = v, crossVersionRemap = verRemap)
@deprecated("Use the variant accepting a CrossVersion value constructed by a member of the CrossVersion object.", "0.12.0")
def cross(v: Boolean): ModuleID = cross(if(v) CrossVersion.binary else CrossVersion.Disabled)
@deprecated("Use the variant accepting a CrossVersion value constructed by a member of the CrossVersion object.", "0.12.0")
def cross(v: Boolean, verRemap: String => String): ModuleID = cross(if(v) CrossVersion.binaryMapped(verRemap) else CrossVersion.Disabled)
def cross(v: CrossVersion): ModuleID = copy(crossVersion = v)
// () required for chaining
def notTransitive() = intransitive()
def intransitive() = copy(isTransitive = false)
@ -199,8 +206,7 @@ object Resolver
def withDefaultResolvers(userResolvers: Seq[Resolver], mavenCentral: Boolean, scalaTools: Boolean): Seq[Resolver] =
Seq(Resolver.defaultLocal) ++
userResolvers ++
single(DefaultMavenRepository, mavenCentral)++
single(ScalaToolsReleases, scalaTools)
single(DefaultMavenRepository, mavenCentral)
private def single[T](value: T, nonEmpty: Boolean): Seq[T] = if(nonEmpty) Seq(value) else Nil
/** A base class for defining factories for interfaces to Ivy repositories that require a hostname , port, and patterns. */
@ -419,14 +425,14 @@ object Artifact
val base = if(i >= 0) name.substring(0, i) else name
Artifact(base, extract(name, DefaultType), extract(name, DefaultExtension), None, Nil, Some(file.toURI.toURL))
}
def artifactName(scalaVersion: String, module: ModuleID, artifact: Artifact): String =
def artifactName(scalaVersion: ScalaVersion, module: ModuleID, artifact: Artifact): String =
{
import artifact._
val classifierStr = classifier match { case None => ""; case Some(c) => "-" + c }
val base = if(module.crossVersion) IvySbt.crossName(artifact.name, module.crossVersionRemap(scalaVersion)) else artifact.name
val cross = CrossVersion(module.crossVersion, scalaVersion.full, scalaVersion.binary)
val base = CrossVersion.applyCross(artifact.name, cross)
base + "-" + module.revision + classifierStr + "." + artifact.extension
}
def cross(enable: Boolean, scalaVersion: String): String = if(enable) "_" + scalaVersion else ""
val classifierConfMap = Map(SourceClassifier -> Sources, DocClassifier -> Docs)
val classifierTypeMap = Map(SourceClassifier -> SourceType, DocClassifier -> DocType)

View File

@ -19,10 +19,14 @@ object ScalaArtifacts
val CompilerID = "scala-compiler"
def libraryDependency(version: String): ModuleID = ModuleID(Organization, LibraryID, version)
}
object SbtArtifacts
{
val Organization = "org.scala-sbt"
}
import ScalaArtifacts._
final case class IvyScala(scalaVersion: String, configurations: Iterable[Configuration], checkExplicit: Boolean, filterImplicit: Boolean, overrideScalaVersion: Boolean, substituteCross: ModuleID => ModuleID)
final case class IvyScala(scalaFullVersion: String, scalaBinaryVersion: String, configurations: Iterable[Configuration], checkExplicit: Boolean, filterImplicit: Boolean, overrideScalaVersion: Boolean)
private object IvyScala
{
@ -30,11 +34,11 @@ private object IvyScala
def checkModule(module: DefaultModuleDescriptor, conf: String)(check: IvyScala)
{
if(check.checkExplicit)
checkDependencies(module, check.scalaVersion, check.configurations)
checkDependencies(module, check.scalaBinaryVersion, check.configurations)
if(check.filterImplicit)
excludeScalaJars(module, check.configurations)
if(check.overrideScalaVersion)
overrideScalaVersion(module, check.scalaVersion)
overrideScalaVersion(module, check.scalaFullVersion)
}
def overrideScalaVersion(module: DefaultModuleDescriptor, version: String)
{
@ -50,14 +54,15 @@ private object IvyScala
/** Checks the immediate dependencies of module for dependencies on scala jars and verifies that the version on the
* dependencies matches scalaVersion. */
private def checkDependencies(module: ModuleDescriptor, scalaVersion: String, configurations: Iterable[Configuration])
private def checkDependencies(module: ModuleDescriptor, scalaBinaryVersion: String, configurations: Iterable[Configuration])
{
val configSet = if(configurations.isEmpty) (c: String) => true else configurationSet(configurations)
for(dep <- module.getDependencies.toList)
{
val id = dep.getDependencyRevisionId
if(id.getOrganisation == Organization && id.getRevision != scalaVersion && dep.getModuleConfigurations.exists(configSet))
error("Version specified for dependency " + id + " differs from Scala version in project (" + scalaVersion + ").")
val depBinaryVersion = CrossVersion.binaryScalaVersion(id.getRevision)
if(id.getOrganisation == Organization && depBinaryVersion != scalaBinaryVersion && dep.getModuleConfigurations.exists(configSet))
error("Binary version for dependency " + id + " (" + depBinaryVersion + ") differs from Scala binary version in project (" + scalaBinaryVersion + ").")
}
}
private def configurationSet(configurations: Iterable[Configuration]) = configurations.map(_.toString).toSet

View File

@ -18,7 +18,7 @@ private object BootConfiguration
val JUnitName = "junit"
val SbtOrg = "org.scala-tools.sbt"
val SbtOrg = "org.scala-sbt"
/** The Ivy conflict manager to use for updating.*/
val ConflictManagerName = "latest-revision"

View File

@ -152,7 +152,7 @@ class ConfigurationParser
def getApplication(m: LabelMap): (Application, Value[List[String]]) =
{
val (org, m1) = id(m, "org", "org.scala-tools.sbt")
val (org, m1) = id(m, "org", BootConfiguration.SbtOrg)
val (name, m2) = id(m1, "name", "sbt")
val (rev, m3) = getVersion(m2, name + " version", name + ".version")
val (main, m4) = id(m3, "class", "xsbt.Main")
@ -171,7 +171,7 @@ class ConfigurationParser
m.toList.map {
case (key, None) => Predefined(key)
case (key, Some(value)) =>
val r = trim(value.split(",",3))
val r = trim(substituteVariables(value).split(",",3))
val url = try { new URL(r(0)) } catch { case e: MalformedURLException => error("Invalid URL specified for '" + key + "': " + e.getMessage) }
if(r.length == 3) Ivy(key, url, r(1), r(2)) else if(r.length == 2) Ivy(key, url, r(1), r(1)) else Maven(key, url)
}

View File

@ -13,8 +13,6 @@
local
${{repositories}}
maven-central
scala-tools-releases
scala-tools-snapshots
[boot]
directory: ${sbt.boot.directory-${sbt.global.base-${user.home}/.sbt}/boot/}

View File

@ -62,7 +62,7 @@ object ScalaProviderTest extends Specification
object LaunchTest
{
def testApp(main: String): Application = testApp(main, Array[File]())
def testApp(main: String, extra: Array[File]): Application = Application("org.scala-tools.sbt", "launch-test", new Explicit(AppVersion), main, Nil, false, extra)
def testApp(main: String, extra: Array[File]): Application = Application("org.scala-sbt", "launch-test", new Explicit(AppVersion), main, Nil, false, extra)
import Predefined._
def testRepositories = List(Local, ScalaToolsReleases, ScalaToolsSnapshots).map(Repository.Predefined.apply)
def withLauncher[T](f: xsbti.Launcher => T): T =

View File

@ -11,7 +11,7 @@ package sbt
import DefaultParsers._
import Types.idFun
import java.net.URI
import CommandSupport.ShowCommand
import CommandStrings.ShowCommand
final class ParsedKey(val key: ScopedKey[_], val mask: ScopeMask)
object Act

View File

@ -127,7 +127,7 @@ final object Aggregation
Dag.topologicalSort(key) { k =>
if(reverse)
reverseAggregatedKeys(k, extra, mask)
else if(aggregationEnabled(key, extra.data))
else if(aggregationEnabled(k, extra.data))
aggregatedKeys(k, extra, mask)
else
Nil

View File

@ -38,4 +38,12 @@ object Append
def appendValues(a: Classpath, b: Seq[File]): Classpath = a ++ Attributed.blankSeq(b)
def appendValue(a: Classpath, b: File): Classpath = a :+ Attributed.blank(b)
}
implicit def appendSet[T, V <: T]: Sequence[Set[T], Set[V], V] = new Sequence[Set[T], Set[V], V] {
def appendValues(a: Set[T], b: Set[V]): Set[T] = a ++ b
def appendValue(a: Set[T], b: V): Set[T] = a + b
}
implicit def appendMap[A,B, X <: A, Y <: B]: Sequence[Map[A,B], Map[X,Y], (X,Y)] = new Sequence[Map[A,B], Map[X,Y], (X,Y)] {
def appendValues(a: Map[A,B], b: Map[X,Y]): Map[A,B] = a ++ b
def appendValue(a: Map[A,B], b: (X,Y)): Map[A,B] = a + b
}
}

View File

@ -3,37 +3,15 @@
*/
package sbt
import complete.HistoryCommands
import scala.annotation.tailrec
import java.io.File
import Path._
object CommandSupport
object CommandStrings
{
def logger(s: State) = globalLogging(s).full
def globalLogging(s: State) = s get Keys.globalLogging getOrElse error("Global logging misconfigured")
@deprecated("Use the `log` member of a State instance directly.", "0.12.0")
def logger(s: State) = s.log
// slightly better fallback in case of older launcher
def bootDirectory(state: State): File =
try { state.configuration.provider.scalaProvider.launcher.bootDirectory }
catch { case e: NoSuchMethodError => new File(".").getAbsoluteFile }
private def canRead = (_: File).canRead
def notReadable(files: Seq[File]): Seq[File] = files filterNot canRead
def readable(files: Seq[File]): Seq[File] = files filter canRead
def sbtRCs(s: State): Seq[File] =
(Path.userHome / sbtrc) ::
(s.baseDir / sbtrc asFile) ::
Nil
def readLines(files: Seq[File]): Seq[String] = files flatMap (line => IO.readLines(line)) flatMap processLine
def processLine(s: String) = { val trimmed = s.trim; if(ignoreLine(trimmed)) None else Some(trimmed) }
def ignoreLine(s: String) = s.isEmpty || s.startsWith("#")
@deprecated("Use the `globalLogging` member of a State instance directly.", "0.12.0")
def globalLogging(s: State) = s.globalLogging
/** The prefix used to identify a request to execute the remaining input on source changes.*/
val ContinuousExecutePrefix = "~"
val HelpCommand = "help"
val AboutCommand = "about"
val TasksCommand = "tasks"
val ProjectCommand = "project"
@ -41,8 +19,14 @@ object CommandSupport
val ShowCommand = "show"
val BootCommand = "boot"
val Exit = "exit"
val Quit = "quit"
@deprecated("Moved to BasicCommandStrings", "0.12.0")
val ContinuousExecutePrefix = BasicCommandStrings.ContinuousExecutePrefix
@deprecated("Moved to BasicCommandStrings", "0.12.0")
val Exit = BasicCommandStrings.Exit
@deprecated("Moved to BasicCommandStrings", "0.12.0")
val Quit = BasicCommandStrings.Quit
val EvalCommand = "eval"
val evalBrief = (EvalCommand + " <expression>", "Evaluates the given Scala expression and prints the result and type.")
@ -128,9 +112,8 @@ SetCommand + """ <setting-expression>
def sessionBrief = (SessionCommand + " <session-command>", "Manipulates session settings. For details, run 'help " + SessionCommand + "'.")
/** The command name to terminate the program.*/
val TerminateAction: String = Exit
def continuousBriefHelp = (ContinuousExecutePrefix + " <command>", "Executes the specified command whenever source files change.")
@deprecated("Moved to BasicCommandStrings", "0.12.0")
val TerminateAction: String = BasicCommandStrings.TerminateAction
def tasksPreamble = """
This is a list of tasks defined for the current project.
@ -140,11 +123,6 @@ Tasks produce values. Use the 'show' command to run the task and print the resu
def tasksBrief = "Displays the tasks defined for the current project."
def tasksDetailed = "Displays the tasks defined directly or indirectly for the current project."
def helpBrief = (HelpCommand + " [command]*", "Displays this help message or prints detailed help on requested commands.")
def helpDetailed = """
If an argument is provided, this prints detailed help for that command.
Otherwise, this prints a help summary."""
def aboutBrief = "Displays basic information about sbt and the build."
def aboutDetailed = aboutBrief
@ -175,126 +153,23 @@ ProjectCommand +
def projectsBrief = projectsDetailed
def projectsDetailed = "Displays the names of available projects."
def historyHelp = Help.briefDetail(HistoryCommands.descriptions)
def exitBrief = "Terminates the build."
def sbtrc = ".sbtrc"
def ReadCommand = "<"
def ReadFiles = " file1 file2 ..."
def ReadBrief = (ReadCommand + " <file>*", "Reads command lines from the provided files.")
def ReadDetailed =
ReadCommand + ReadFiles + """
Reads the lines from the given files and inserts them as commands.
All empty lines and lines that start with '#' are ignored.
If a file does not exist or is not readable, this command fails.
All the lines from all the files are read before any of the commands
are executed. Thus, if any file is not readable, none of commands
from any of the files (even the existing ones) will be run.
You probably need to escape this command if entering it at your shell."""
def ApplyCommand = "apply"
def ApplyBrief = (ApplyCommand + " <module-name>*", ApplyDetailed)
def ApplyDetailed = "Transforms the current State by calling <module-name>.apply(currentState) for each listed."
@deprecated("Moved to BasicCommandStrings", "0.12.0")
def ReadCommand = BasicCommandStrings.ReadCommand
def DefaultsCommand = "add-default-commands"
def DefaultsBrief = (DefaultsCommand, DefaultsDetailed)
def DefaultsDetailed = "Registers default built-in commands"
@deprecated("Moved to BasicCommandStrings", "0.12.0")
def RebootCommand = "reboot"
def RebootSummary = RebootCommand + " [full]"
def RebootBrief = (RebootSummary, "Reboots sbt and then executes the remaining commands.")
def RebootDetailed =
RebootSummary + """
This command is equivalent to exiting sbt, restarting, and running the
remaining commands with the exception that the JVM is not shut down.
If 'full' is specified, the boot directory (`~/.sbt/boot` by default)
is deleted before restarting. This forces an update of sbt and Scala
and is useful when working with development versions of sbt or Scala."""
@deprecated("Moved to BasicCommandStrings", "0.12.0")
def Multi = ";"
def MultiBrief = (Multi + " <command> (" + Multi + " <command>)*", "Runs the provided semicolon-separated commands.")
def MultiDetailed =
Multi + " command1 " + Multi + """ command2 ...
Runs the specified commands."""
@deprecated("Moved to BasicCommandStrings", "0.12.0")
def AppendCommand = "append"
def AppendLastBrief = (AppendCommand + " <command>", AppendLastDetailed)
def AppendLastDetailed = "Appends 'command' to list of commands to run."
val AliasCommand = "alias"
def AliasBrief = (AliasCommand, "Adds, removes, or prints command aliases.")
def AliasDetailed =
AliasCommand + """
Prints a list of defined aliases.
""" +
AliasCommand + """ name
Prints the alias defined for `name`.
""" +
AliasCommand + """ name=value
Sets the alias `name` to `value`, replacing any existing alias with that name.
Whenever `name` is entered, the corresponding `value` is run.
If any argument is provided to `name`, it is appended as argument to `value`.
""" +
AliasCommand + """ name=
Removes the alias for `name`."""
def Discover = "discover"
def DiscoverBrief = (DiscoverSyntax, "Finds annotated classes and subclasses.")
def DiscoverSyntax = Discover + " [-module true|false] [-sub <names>] [-annot <names>]"
def DiscoverDetailed =
DiscoverSyntax + """
Looks for public, concrete classes that match the requested query using the current sbt.inc.Analysis instance.
-module
Specifies whether modules (true) or classes (false) are found.
The default is classes/traits (false).
-sub
Specifies comma-separated class names.
Classes that have one or more of these classes as an ancestor are included in the resulting list.
-annot
Specifies comma-separated annotation names.
Classes with one or more of these annotations on the class or one of its non-private methods are included in the resulting list.
"""
def CompileName = "direct-compile"
def CompileBrief = (CompileSyntax, "Incrementally compiles the provided sources.")
def CompileSyntax = CompileName + " -src <paths> [-cp <paths>] [-d <path>]"
def CompileDetailed =
CompileSyntax + """
Incrementally compiles Scala and Java sources.
<paths> are explicit paths separated by the platform path separator.
The specified output path will contain the following directory structure:
scala_<version>/
classes/
cache/
Compiled classes will be written to the 'classes' directory.
Cached information about the compilation will be written to 'cache'.
"""
val FailureWall = "---"
def Load = "load"
def LoadLabel = "a project"
@ -308,31 +183,20 @@ CompileSyntax + """
def LoadProjectBrief = (LoadProject, LoadProjectDetailed)
def LoadProjectDetailed = "Loads the project in the current directory"
def Shell = "shell"
def ShellBrief = ShellDetailed
def ShellDetailed = "Provides an interactive prompt from which commands can be run."
@deprecated("Moved to State", "0.12.0")
val FailureWall = State.FailureWall
@deprecated("Moved to BasicCommandStrings", "0.12.0")
def Shell = BasicCommandStrings.Shell
@deprecated("Moved to BasicCommandStrings", "0.12.0")
def ClearOnFailure = "--"
@deprecated("Moved to BasicCommandStrings", "0.12.0")
def OnFailure = "-"
def OnFailureBrief = (OnFailure + " command", "Registers 'command' to run if a command fails.")
def OnFailureDetailed =
OnFailure + """ command
Registers 'command' to run when a command fails to complete normally.
Only one failure command may be registered at a time, so this command
replaces the previous command if there is one.
The failure command resets when it runs once, so it must be added
again if desired."""
@deprecated("Moved to BasicCommandStrings", "0.12.0")
def IfLast = "iflast"
def IfLastBrief = (IfLast + " <command>", IfLastCommon)
def IfLastCommon = "If there are no more commands after this one, 'command' is run."
def IfLastDetailed =
IfLast + """ command
""" + IfLastCommon
def InitCommand = "initialize"
def InitBrief = (InitCommand, "Initializes command processing.")
@ -352,4 +216,12 @@ load-commands -base ~/.sbt/commands
< .sbtrc
Runs commands from ~/.sbtrc and ./.sbtrc if they exist
"""
import java.io.File
import Path._
def sbtRCs(s: State): Seq[File] =
(Path.userHome / sbtrc) ::
(s.baseDir / sbtrc asFile) ::
Nil
}

View File

@ -10,6 +10,7 @@ package sbt
import Load.LoadedBuild
import Artifact.{DocClassifier, SourceClassifier}
import Configurations.{Compile, CompilerPlugin, IntegrationTest, names, Provided, Runtime, Test}
import CrossVersion.{binarySbtVersion, binaryScalaVersion, isStable, selectVersion}
import complete._
import std.TaskExtra._
import inc.{FileValueCache, Locate}
@ -48,16 +49,16 @@ object Defaults extends BuildCommon
def buildCore: Seq[Setting[_]] = thisBuildCore ++ globalCore
def thisBuildCore: Seq[Setting[_]] = inScope(GlobalScope.copy(project = Select(ThisBuild)))(Seq(
managedDirectory <<= baseDirectory(_ / "lib_managed")
))
def globalCore: Seq[Setting[_]] = inScope(GlobalScope)(Seq(
crossVersion :== CrossVersion.Disabled,
buildDependencies <<= buildDependencies or Classpaths.constructBuildDependencies,
taskTemporaryDirectory := IO.createTemporaryDirectory,
onComplete <<= taskTemporaryDirectory { dir => () => IO.delete(dir); IO.createDirectory(dir) },
concurrentRestrictions <<= concurrentRestrictions or defaultRestrictions,
parallelExecution :== true,
sbtVersion <<= appConfiguration { _.provider.id.version },
sbtBinaryVersion <<= sbtVersion(v => binaryVersion(v, "0.12")),
sbtBinaryVersion <<= sbtVersion apply binarySbtVersion,
sbtResolver <<= sbtVersion { sbtV => if(sbtV endsWith "-SNAPSHOT") Classpaths.typesafeSnapshots else Classpaths.typesafeResolver },
pollInterval :== 500,
logBuffered :== false,
@ -190,7 +191,13 @@ object Defaults extends BuildCommon
scalacOptions in GlobalScope :== Nil,
scalaInstance <<= scalaInstanceSetting,
scalaVersion in GlobalScope <<= appConfiguration( _.provider.scalaProvider.version),
scalaBinaryVersion <<= scalaVersion(v => binaryVersion(v, "2.10")),
scalaBinaryVersion in GlobalScope <<= scalaVersion apply binaryScalaVersion,
crossVersion <<= (crossPaths, scalaVersion) { (enabled, sv) =>
if(enabled)
if(isStable(sv)) CrossVersion.binary else CrossVersion.full
else
CrossVersion.Disabled
},
crossScalaVersions in GlobalScope <<= Seq(scalaVersion).join,
crossTarget <<= (target, scalaBinaryVersion, sbtBinaryVersion, sbtPlugin, crossPaths)(makeCrossTarget)
)
@ -211,7 +218,7 @@ object Defaults extends BuildCommon
discoveredMainClasses <<= compile map discoverMainClasses storeAs discoveredMainClasses triggeredBy compile,
definedSbtPlugins <<= discoverPlugins,
inTask(run)(runnerTask :: Nil).head,
selectMainClass <<= discoveredMainClasses map selectRunMain,
selectMainClass <<= (discoveredMainClasses, mainClass) map { (classes, explicit) => explicit orElse selectRunMain(classes) },
mainClass in run <<= selectMainClass in run,
mainClass <<= discoveredMainClasses map selectPackageMain,
run <<= runTask(fullClasspath, mainClass in run, runner in run),
@ -357,10 +364,8 @@ object Defaults extends BuildCommon
packageTasks(packageSrc, packageSrcTask) ++
packageTasks(packageDoc, packageDocTask)
private[this] val allSubpaths = (dir: File) => (dir.*** --- dir) x (relativeTo(dir)|flat)
def packageBinTask = products map { ps => ps flatMap { p => allSubpaths(p) } }
def packageDocTask = doc map allSubpaths
def packageBinTask = products map { _ flatMap Path.allSubpaths }
def packageDocTask = doc map { p => Path.allSubpaths(p).toSeq }
def packageSrcTask = concatMappings(resourceMappings, sourceMappings)
private type Mappings = Initialize[Task[Seq[(File, String)]]]
@ -368,18 +373,21 @@ object Defaults extends BuildCommon
// drop base directories, since there are no valid mappings for these
def sourceMappings = (unmanagedSources, unmanagedSourceDirectories, baseDirectory) map { (srcs, sdirs, base) =>
( (srcs --- sdirs --- base) x (relativeTo(sdirs)|relativeTo(base)|flat)) toSeq
( (srcs --- sdirs --- base) pair (relativeTo(sdirs)|relativeTo(base)|flat)) toSeq
}
def resourceMappings = relativeMappings(unmanagedResources, unmanagedResourceDirectories)
def relativeMappings(files: ScopedTaskable[Seq[File]], dirs: ScopedTaskable[Seq[File]]): Initialize[Task[Seq[(File, String)]]] =
(files, dirs) map { (rs, rdirs) =>
(rs --- rdirs) x (relativeTo(rdirs)|flat) toSeq
(rs --- rdirs) pair (relativeTo(rdirs)|flat) toSeq
}
def collectFiles(dirs: ScopedTaskable[Seq[File]], filter: ScopedTaskable[FileFilter], excludes: ScopedTaskable[FileFilter]): Initialize[Task[Seq[File]]] =
(dirs, filter, excludes) map { (d,f,excl) => d.descendantsExcept(f,excl).get }
def artifactPathSetting(art: SettingKey[Artifact]) = (crossTarget, projectID, art, scalaBinaryVersion in artifactName, artifactName) { (t, module, a, sv, toString) => t / toString(sv, module, a) asFile }
def artifactPathSetting(art: SettingKey[Artifact]) = (crossTarget, projectID, art, scalaVersion in artifactName, scalaBinaryVersion in artifactName, artifactName) {
(t, module, a, sv, sbv, toString) =>
t / toString(ScalaVersion(sv, sbv), module, a) asFile
}
def artifactSetting = ((artifact, artifactClassifier).identity zipWith configuration.?) { case ((a,classifier),cOpt) =>
val cPart = cOpt flatMap { c => if(c == Compile) None else Some(c.name) }
val combined = cPart.toList ++ classifier.toList
@ -511,7 +519,8 @@ object Defaults extends BuildCommon
})
}
def sbtPluginExtra(m: ModuleID, sbtV: String, scalaV: String): ModuleID = m.extra(CustomPomParser.SbtVersionKey -> sbtV, CustomPomParser.ScalaVersionKey -> scalaV).copy(crossVersion = false)
def sbtPluginExtra(m: ModuleID, sbtV: String, scalaV: String): ModuleID =
m.extra(CustomPomParser.SbtVersionKey -> sbtV, CustomPomParser.ScalaVersionKey -> scalaV).copy(crossVersion = CrossVersion.Disabled)
def writePluginsDescriptor(plugins: Set[String], dir: File): Seq[File] =
{
val descriptor: File = dir / "sbt" / "sbt.plugins"
@ -537,7 +546,7 @@ object Defaults extends BuildCommon
def copyResourcesTask =
(classDirectory, cacheDirectory, resources, resourceDirectories, streams) map { (target, cache, resrcs, dirs, s) =>
val cacheFile = cache / "copy-resources"
val mappings = (resrcs --- dirs) x (rebase(dirs, target) | flat(target))
val mappings = (resrcs --- dirs) pair (rebase(dirs, target) | flat(target))
s.log.debug("Copy resource mappings: " + mappings.mkString("\n\t","\n\t",""))
Sync(cacheFile)( mappings )
mappings
@ -597,25 +606,6 @@ object Defaults extends BuildCommon
(if(aggregate) p.aggregate else Nil)
}
val PartialVersion = """(\d+)\.(\d+)(?:\..+)?""".r
def partialVersion(s: String): Option[(Int,Int)] =
s match {
case PartialVersion(major, minor) => Some(major.toInt, minor.toInt)
case _ => None
}
private[this] def isNewer(major: Int, minor: Int, minMajor: Int, minMinor: Int): Boolean =
major > minMajor || (major == minMajor && minor >= minMinor)
def binaryVersion(full: String, cutoff: String): String =
{
def sub(major: Int, minor: Int) = major + "." + minor
(partialVersion(full), partialVersion(cutoff)) match {
case (Some((major, minor)), None) => sub(major, minor)
case (Some((major, minor)), Some((minMajor, minMinor))) if isNewer(major, minor, minMajor, minMinor) => sub(major, minor)
case _ => full
}
}
val CompletionsID = "completions"
def noAggregation: Seq[Scoped] = Seq(run, console, consoleQuick, consoleProject)
@ -744,16 +734,16 @@ object Classpaths
ivyLoggingLevel in GlobalScope :== UpdateLogging.DownloadOnly,
ivyXML in GlobalScope :== NodeSeq.Empty,
ivyValidate in GlobalScope :== false,
ivyScala <<= ivyScala or (scalaHome, scalaVersion, scalaBinaryVersion in update) { (sh,v,vu) =>
Some(new IvyScala(v, Nil, filterImplicit = true, checkExplicit = true, overrideScalaVersion = sh.isEmpty, substituteCross = x => IvySbt.substituteCross(x, vu)))
ivyScala <<= ivyScala or (scalaHome, scalaVersion in update, scalaBinaryVersion in update) { (sh,fv,bv) =>
Some(new IvyScala(fv, bv, Nil, filterImplicit = true, checkExplicit = true, overrideScalaVersion = sh.isEmpty))
},
moduleConfigurations in GlobalScope :== Nil,
publishTo in GlobalScope :== None,
artifactPath in makePom <<= artifactPathSetting(artifact in makePom),
publishArtifact in makePom <<= publishMavenStyle,
artifact in makePom <<= moduleName(Artifact.pom),
projectID <<= (organization,moduleName,version,artifacts,crossPaths){ (org,module,version,as,crossEnabled) =>
ModuleID(org, module, version).cross(crossEnabled).artifacts(as : _*)
projectID <<= (organization,moduleName,version,artifacts,crossVersion in projectID){ (org,module,version,as,cross) =>
ModuleID(org, module, version).cross(cross).artifacts(as : _*)
},
projectID <<= pluginProjectID,
resolvers in GlobalScope :== Nil,
@ -797,12 +787,19 @@ object Classpaths
} tag(Tags.Update, Tags.Network),
sbtDependency in GlobalScope <<= appConfiguration { app =>
val id = app.provider.id
val base = ModuleID(id.groupID, id.name, id.version, crossVersion = id.crossVersioned)
IvySbt.substituteCross(base, app.provider.scalaProvider.version).copy(crossVersion = false)
val scalaVersion = app.provider.scalaProvider.version
val binVersion = binaryScalaVersion(scalaVersion)
val cross = if(id.crossVersioned) if(isStable(scalaVersion)) CrossVersion.binary else CrossVersion.full else CrossVersion.Disabled
val base = ModuleID(id.groupID, id.name, id.version, crossVersion = cross)
CrossVersion(scalaVersion, binVersion)(base).copy(crossVersion = CrossVersion.Disabled)
}
)
def pluginProjectID: Initialize[ModuleID] = (sbtBinaryVersion in update, scalaBinaryVersion in update, projectID, sbtPlugin) { (sbtV, scalaV, pid, isPlugin) =>
if(isPlugin) sbtPluginExtra(pid, sbtV, scalaV) else pid
def pluginProjectID: Initialize[ModuleID] = (sbtVersion in update, sbtBinaryVersion in update, scalaVersion in update, scalaBinaryVersion in update, projectID, sbtPlugin) {
(sbtV, sbtBV, scalaV, scalaBV, pid, isPlugin) =>
if(isPlugin)
sbtPluginExtra(pid, selectVersion(sbtV, sbtBV), selectVersion(scalaV, scalaBV))
else
pid
}
def ivySbt0: Initialize[Task[IvySbt]] =
(ivyConfiguration, credentials, streams) map { (conf, creds, s) =>

View File

@ -59,7 +59,7 @@ object GlobalPlugin
}
}
val globalPluginSettings = inScope(Scope.GlobalScope in LocalRootProject)(Seq(
organization := "org.scala-tools.sbt",
organization := "org.scala-sbt",
onLoadMessage <<= Keys.baseDirectory("Loading global plugins from " + _),
name := "global-plugin",
sbtPlugin := true,

View File

@ -14,7 +14,7 @@ object IvyConsole
lazy val command =
Command.command(Name) { state =>
val Dependencies(managed, repos, unmanaged) = parseDependencies(state.remainingCommands, state.log)
val base = new File(CommandSupport.bootDirectory(state), Name)
val base = new File(CommandUtil.bootDirectory(state), Name)
IO.createDirectory(base)
val (eval, structure) = Load.defaultLoad(state, base, state.log)
@ -56,7 +56,9 @@ object IvyConsole
def parseManaged(arg: String, log: Logger): Seq[ModuleID] =
arg match
{
case DepPattern(group, cross, name, version) => ModuleID(group.trim, name.trim, version.trim, crossVersion = !cross.trim.isEmpty) :: Nil
case DepPattern(group, cross, name, version) =>
val crossV = if(cross.trim.isEmpty) CrossVersion.Disabled else CrossVersion.binary
ModuleID(group.trim, name.trim, version.trim, crossVersion = crossV) :: Nil
case _ => log.warn("Ignoring invalid argument '" + arg + "'"); Nil
}
}

View File

@ -56,11 +56,10 @@ object Keys
// val onComplete = SettingKey[RMap[Task,Result] => RMap[Task,Result]]("on-complete", "Transformation to apply to the final task result map. This may also be used to register hooks to run when task evaluation completes.")
// Command keys
val globalLogging = AttributeKey[GlobalLogging]("global-logging", "Provides a global Logger, including command logging.")
val historyPath = SettingKey[Option[File]]("history", "The location where command line history is persisted.")
val shellPrompt = SettingKey[State => String]("shell-prompt", "The function that constructs the command prompt from the current build state.")
val historyPath = SettingKey(BasicKeys.historyPath)
val shellPrompt = SettingKey(BasicKeys.shellPrompt)
val analysis = AttributeKey[inc.Analysis]("analysis", "Analysis of compilation, including dependencies and generated outputs.")
val watch = SettingKey[Watched]("watch", "Continuous execution configuration.")
val watch = SettingKey(BasicKeys.watch)
val pollInterval = SettingKey[Int]("poll-interval", "Interval between checks for modified sources by the continuous execution command.")
val watchSources = TaskKey[Seq[File]]("watch-sources", "Defines the sources in this project for continuous execution to watch for changes.")
val watchTransitiveSources = TaskKey[Seq[File]]("watch-transitive-sources", "Defines the sources in all projects for continuous execution to watch.")
@ -138,6 +137,7 @@ object Keys
val scalaVersion = SettingKey[String]("scala-version", "The version of Scala used for building.")
val scalaBinaryVersion = SettingKey[String]("scala-binary-version", "The Scala version substring describing binary compatibility.")
val crossScalaVersions = SettingKey[Seq[String]]("cross-scala-versions", "The versions of Scala used when cross-building.")
val crossVersion = SettingKey[CrossVersion]("cross-version", "Configures handling of the Scala version when cross-building.")
val classpathOptions = SettingKey[ClasspathOptions]("classpath-options", "Configures handling of Scala classpaths.")
val definedSbtPlugins = TaskKey[Set[String]]("defined-sbt-plugins", "The set of names of Plugin implementations defined by this project.")
val sbtPlugin = SettingKey[Boolean]("sbt-plugin", "If true, enables adding sbt as a dependency and auto-generation of the plugin descriptor file.")
@ -165,7 +165,7 @@ object Keys
val artifactPath = SettingKey[File]("artifact-path", "The location of a generated artifact.")
val artifact = SettingKey[Artifact]("artifact", "Describes an artifact.")
val artifactClassifier = SettingKey[Option[String]]("artifact-classifier", "Sets the classifier used by the default artifact definition.")
val artifactName = SettingKey[(String, ModuleID, Artifact) => String]("artifact-name", "Function that produces the artifact name from its definition.")
val artifactName = SettingKey[(ScalaVersion, ModuleID, Artifact) => String]("artifact-name", "Function that produces the artifact name from its definition.")
val mappings = TaskKey[Seq[(File,String)]]("mappings", "Defines the mappings from a file to a path, used by packaging, for example.")
val fileMappings = TaskKey[Seq[(File,File)]]("file-mappings", "Defines the mappings from a file to a file, used for copying files, for example.")

View File

@ -14,7 +14,7 @@ package sbt
import inc.{FileValueCache, Locate}
import Project.{inScope, ScopedKey, ScopeLocal, Setting}
import Keys.{appConfiguration, baseDirectory, configuration, streams, Streams, thisProject, thisProjectRef}
import Keys.{globalLogging, isDummy, loadedBuild, parseResult, resolvedScoped, taskDefinitionKey}
import Keys.{isDummy, loadedBuild, parseResult, resolvedScoped, taskDefinitionKey}
import tools.nsc.reporters.ConsoleReporter
import Build.{analyzed, data}
import Scope.{GlobalScope, ThisScope}
@ -489,7 +489,7 @@ object Load
val inputs = Compiler.inputs(data(classpath), sources, target, Nil, Nil, definesClass, Compiler.DefaultMaxErrors, CompileOrder.Mixed)(compilers, log)
val analysis =
try { Compiler(inputs, log) }
catch { case _: xsbti.CompileFailed => throw new NoMessageException } // compiler already logged errors
catch { case _: xsbti.CompileFailed => throw new AlreadyHandledException } // compiler already logged errors
(inputs, analysis)
}

View File

@ -9,6 +9,7 @@ package sbt
import std.Transform
import Project.ScopedKey
import Scope.GlobalScope
import MainLogging._
import Keys.{logLevel, logManager, persistLogLevel, persistTraceLevel, state, traceLevel}
object LogManager
@ -21,11 +22,6 @@ object LogManager
lazy val default: LogManager = withLoggers()
def defaults(extra: ScopedKey[_] => Seq[AbstractLogger]): LogManager = withLoggers(extra = extra)
def defaultScreen: AbstractLogger = ConsoleLogger()
def defaultBacked(useColor: Boolean = ConsoleLogger.formatEnabled): PrintWriter => ConsoleLogger =
to => ConsoleLogger(ConsoleLogger.printWriterOut(to), useColor = useColor) // TODO: should probably filter ANSI codes when useColor=false
def withScreenLogger(mk: => AbstractLogger): LogManager = withLoggers(mk)
def withLoggers(screen: => AbstractLogger = defaultScreen, backed: PrintWriter => AbstractLogger = defaultBacked(), extra: ScopedKey[_] => Seq[AbstractLogger] = _ => Nil): LogManager =
@ -42,40 +38,12 @@ object LogManager
val backingLevel = getOr(persistLogLevel.key, Level.Debug)
val screenTrace = getOr(traceLevel.key, -1)
val backingTrace = getOr(persistTraceLevel.key, Int.MaxValue)
val extraBacked = (state get Keys.globalLogging).map(_.backed).toList
val extraBacked = state.globalLogging.backed :: Nil
multiLogger( new MultiLoggerConfig(console, backed, extraBacked ::: extra, screenLevel, backingLevel, screenTrace, backingTrace) )
}
def multiLogger(config: MultiLoggerConfig): Logger =
{
import config._
val multi = new MultiLogger(console :: backed :: extra)
// sets multi to the most verbose for clients that inspect the current level
multi setLevel Level.unionAll(backingLevel :: screenLevel :: extra.map(_.getLevel))
// set the specific levels
console setLevel screenLevel
backed setLevel backingLevel
console setTrace screenTrace
backed setTrace backingTrace
multi: Logger
}
def globalDefault(writer: PrintWriter, backing: GlobalLogBacking): GlobalLogging =
{
val backed = defaultBacked()(writer)
val full = multiLogger(defaultMultiConfig( backed ) )
GlobalLogging(full, backed, backing)
}
def defaultMultiConfig(backing: AbstractLogger): MultiLoggerConfig =
new MultiLoggerConfig(defaultScreen, backing, Nil, Level.Info, Level.Debug, -1, Int.MaxValue)
}
final case class MultiLoggerConfig(console: AbstractLogger, backed: AbstractLogger, extra: List[AbstractLogger], screenLevel: Level.Value, backingLevel: Level.Value, screenTrace: Int, backingTrace: Int)
trait LogManager
{
def apply(data: Settings[Scope], state: State, task: ScopedKey[_], writer: PrintWriter): Logger
}
final case class GlobalLogBacking(file: File, last: Option[File])
{
def shift(newFile: File) = GlobalLogBacking(newFile, Some(file))
def unshift = GlobalLogBacking(last getOrElse file, None)
}
final case class GlobalLogging(full: Logger, backed: ConsoleLogger, backing: GlobalLogBacking)

View File

@ -3,132 +3,64 @@
*/
package sbt
import Execute.NodeView
import complete.{DefaultParsers, HistoryCommands, Parser}
import HistoryCommands.{Start => HistoryPrefix}
import complete.{DefaultParsers, Parser}
import compiler.EvalImports
import Types.{const,idFun}
import Types.idFun
import Aggregation.AnyKeys
import Command.applyEffect
import Keys.{analysis,historyPath,globalLogging,shellPrompt}
import scala.annotation.tailrec
import scala.collection.JavaConversions._
import Function.tupled
import java.net.URI
import java.lang.reflect.InvocationTargetException
import Path._
import StandardMain._
import java.io.File
import java.net.URI
/** This class is the entry point for sbt.*/
final class xMain extends xsbti.AppMain
{
def run(configuration: xsbti.AppConfiguration): xsbti.MainResult =
{
import BuiltinCommands.{initialAttributes, initialize, defaults, DefaultBootCommands}
import CommandSupport.{BootCommand, DefaultsCommand, InitCommand}
val initialCommandDefs = Seq(initialize, defaults)
val commands = DefaultsCommand +: InitCommand +: BootCommand +: configuration.arguments.map(_.trim)
val state = State( configuration, initialCommandDefs, Set.empty, None, commands, State.newHistory, initialAttributes, State.Continue )
MainLoop.runLogged(state)
import BuiltinCommands.{initialize, defaults}
import CommandStrings.{BootCommand, DefaultsCommand, InitCommand}
MainLoop.runLogged( initialState(configuration,
Seq(initialize, defaults),
DefaultsCommand :: InitCommand :: BootCommand :: Nil)
)
}
}
final class ScriptMain extends xsbti.AppMain
{
def run(configuration: xsbti.AppConfiguration): xsbti.MainResult =
{
import BuiltinCommands.{initialAttributes, ScriptCommands}
val commands = Script.Name +: configuration.arguments.map(_.trim)
val state = State( configuration, ScriptCommands, Set.empty, None, commands, State.newHistory, initialAttributes, State.Continue )
MainLoop.runLogged(state)
}
MainLoop.runLogged( initialState(configuration,
BuiltinCommands.ScriptCommands,
Script.Name :: Nil)
)
}
final class ConsoleMain extends xsbti.AppMain
{
def run(configuration: xsbti.AppConfiguration): xsbti.MainResult =
{
import BuiltinCommands.{initialAttributes, ConsoleCommands}
val commands = IvyConsole.Name +: configuration.arguments.map(_.trim)
val state = State( configuration, ConsoleCommands, Set.empty, None, commands, State.newHistory, initialAttributes, State.Continue )
MainLoop.runLogged(state)
}
MainLoop.runLogged( initialState(configuration,
BuiltinCommands.ConsoleCommands,
IvyConsole.Name :: Nil)
)
}
object MainLoop
object StandardMain
{
/** Entry point to run the remaining commands in State with managed global logging.*/
def runLogged(state: State): xsbti.MainResult =
runLoggedLoop(state, GlobalLogBacking(newBackingFile(), None))
/** Constructs a new, (weakly) unique, temporary file to use as the backing for global logging. */
def newBackingFile(): File = File.createTempFile("sbt",".log")
/** Run loop that evaluates remaining commands and manages changes to global logging configuration.*/
@tailrec def runLoggedLoop(state: State, logBacking: GlobalLogBacking): xsbti.MainResult =
runAndClearLast(state, logBacking) match {
case ret: Return => // delete current and last log files when exiting normally
logBacking.file.delete()
deleteLastLog(logBacking)
ret.result
case clear: ClearGlobalLog => // delete previous log file, move current to previous, and start writing to a new file
deleteLastLog(logBacking)
runLoggedLoop(clear.state, logBacking shift newBackingFile())
case keep: KeepGlobalLog => // make previous log file the current log file
logBacking.file.delete
runLoggedLoop(keep.state, logBacking.unshift)
}
/** Runs the next sequence of commands, cleaning up global logging after any exceptions. */
def runAndClearLast(state: State, logBacking: GlobalLogBacking): RunNext =
try
runWithNewLog(state, logBacking)
catch {
case e: xsbti.FullReload =>
deleteLastLog(logBacking)
throw e // pass along a reboot request
case e =>
System.err.println("sbt appears to be exiting abnormally.\n The log file for this session is at " + logBacking.file)
deleteLastLog(logBacking)
throw e
}
/** Deletes the previous global log file. */
def deleteLastLog(logBacking: GlobalLogBacking): Unit =
logBacking.last.foreach(_.delete())
/** Runs the next sequence of commands with global logging in place. */
def runWithNewLog(state: State, logBacking: GlobalLogBacking): RunNext =
Using.fileWriter(append = true)(logBacking.file) { writer =>
val out = new java.io.PrintWriter(writer)
val loggedState = state.put(globalLogging, LogManager.globalDefault(out, logBacking))
try run(loggedState) finally out.close()
}
sealed trait RunNext
final class ClearGlobalLog(val state: State) extends RunNext
final class KeepGlobalLog(val state: State) extends RunNext
final class Return(val result: xsbti.MainResult) extends RunNext
/** Runs the next sequence of commands that doesn't require global logging changes.*/
@tailrec def run(state: State): RunNext =
state.next match
{
case State.Continue => run(next(state))
case State.ClearGlobalLog => new ClearGlobalLog(state.continue)
case State.KeepLastLog => new KeepGlobalLog(state.continue)
case ret: State.Return => new Return(ret.result)
}
def next(state: State): State =
ErrorHandling.wideConvert { state.process(Command.process) } match
{
case Right(s) => s
case Left(t: xsbti.FullReload) => throw t
case Left(t) => BuiltinCommands.handleException(t, state)
}
def initialState(configuration: xsbti.AppConfiguration, initialDefinitions: Seq[Command], preCommands: Seq[String]): State =
{
val commands = preCommands ++ configuration.arguments.map(_.trim)
State( configuration, initialDefinitions, Set.empty, None, commands, State.newHistory, BuiltinCommands.initialAttributes, initialGlobalLogging, State.Continue )
}
def initialGlobalLogging: GlobalLogging =
GlobalLogging.initial(MainLogging.globalDefault _, File.createTempFile("sbt",".log"))
}
import DefaultParsers._
import CommandSupport._
import CommandStrings._
import BasicCommands._
import CommandUtil._
object BuiltinCommands
{
def initialAttributes = AttributeMap.empty
@ -140,22 +72,9 @@ object BuiltinCommands
def DefaultBootCommands: Seq[String] = LoadProject :: (IfLast + " " + Shell) :: Nil
def boot = Command.make(BootCommand)(bootParser)
def nop = Command.custom(s => success(() => s))
def ignore = Command.command(FailureWall)(idFun)
def detail(selected: Seq[String], detailMap: Map[String, String]): Seq[String] =
selected.distinct flatMap { detailMap get _ }
def help = Command.make(HelpCommand, helpBrief, helpDetailed)(helpParser)
def about = Command.command(AboutCommand, aboutBrief, aboutDetailed) { s => logger(s).info(aboutString(s)); s }
def helpParser(s: State) =
{
val h = (Help.empty /: s.definedCommands)(_ ++ _.help(s))
val helpCommands = h.detail.keySet
val args = (token(Space) ~> token( NotSpace examples helpCommands )).*
applyEffect(args)(runHelp(s, h))
}
// This parser schedules the default boot commands unless overridden by an alias
def bootParser(s: State) =
{
@ -163,16 +82,6 @@ object BuiltinCommands
delegateToAlias(BootCommand, success(orElse) )(s)
}
def runHelp(s: State, h: Help)(args: Seq[String]): State =
{
val message =
if(args.isEmpty)
aligned(" ", " ", h.brief).mkString("\n", "\n", "\n")
else
detail(args, h.detail) mkString("\n", "\n\n", "\n")
System.out.println(message)
s
}
def sbtVersion(s: State): String = s.configuration.provider.id.version
def scalaVersion(s: State): String = s.configuration.provider.scalaProvider.version
def aboutString(s: State): String =
@ -230,154 +139,15 @@ object BuiltinCommands
aligned(" ", " ", taskDetail(s)) mkString("\n", "\n", "")
def taskStrings(key: AttributeKey[_]): Option[(String, String)] = key.description map { d => (key.label, d) }
def aligned(pre: String, sep: String, in: Seq[(String, String)]): Seq[String] =
{
val width = in.map(_._1.length).max
in.map { case (a, b) => (" " + fill(a, width) + sep + b) }
}
def fill(s: String, size: Int) = s + " " * math.max(size - s.length, 0)
def alias = Command.make(AliasCommand, AliasBrief, AliasDetailed) { s =>
val name = token(OpOrID.examples( aliasNames(s) : _*) )
val assign = token(OptSpace ~ '=' ~ OptSpace)
val sfree = removeAliases(s)
val to = matched(sfree.combinedParser, partial = true) | any.+.string
val base = (OptSpace ~> (name ~ (assign ~> to.?).?).?)
applyEffect(base)(t => runAlias(s, t) )
}
def runAlias(s: State, args: Option[(String, Option[Option[String]])]): State =
args match
{
case None => printAliases(s); s
case Some(x ~ None) if !x.isEmpty => printAlias(s, x.trim); s
case Some(name ~ Some(None)) => removeAlias(s, name.trim)
case Some(name ~ Some(Some(value))) => addAlias(s, name.trim, value.trim)
}
def shell = Command.command(Shell, ShellBrief, ShellDetailed) { s =>
val history = (s get historyPath.key) getOrElse Some((s.baseDir / ".history").asFile)
val prompt = (s get shellPrompt.key) match { case Some(pf) => pf(s); case None => "> " }
val reader = new FullReader(history, s.combinedParser)
val line = reader.readLine(prompt)
line match {
case Some(line) =>
val newState = s.copy(onFailure = Some(Shell), remainingCommands = line +: Shell +: s.remainingCommands)
if(line.trim.isEmpty) newState else newState.clearGlobalLog
case None => s
}
}
def multiParser(s: State): Parser[Seq[String]] =
{
val nonSemi = token(charClass(_ != ';').+, hide= const(true))
( token(';' ~> OptSpace) flatMap { _ => matched((s.combinedParser&nonSemi) | nonSemi) <~ token(OptSpace) } map (_.trim) ).+
}
def multiApplied(s: State) =
Command.applyEffect( multiParser(s) )( _ ::: s )
def multi = Command.custom(multiApplied, Help(Multi, MultiBrief, MultiDetailed) )
lazy val otherCommandParser = (s: State) => token(OptSpace ~> combinedLax(s, any.+) )
def combinedLax(s: State, any: Parser[_]): Parser[String] =
matched(s.combinedParser | token(any, hide= const(true)))
def ifLast = Command(IfLast, IfLastBrief, IfLastDetailed)(otherCommandParser) { (s, arg) =>
if(s.remainingCommands.isEmpty) arg :: s else s
}
def append = Command(AppendCommand, AppendLastBrief, AppendLastDetailed)(otherCommandParser) { (s, arg) =>
s.copy(remainingCommands = s.remainingCommands :+ arg)
}
def setOnFailure = Command(OnFailure, OnFailureBrief, OnFailureDetailed)(otherCommandParser) { (s, arg) =>
s.copy(onFailure = Some(arg))
}
def clearOnFailure = Command.command(ClearOnFailure)(s => s.copy(onFailure = None))
def reboot = Command(RebootCommand, RebootBrief, RebootDetailed)(rebootParser) { (s, full) =>
s.reboot(full)
}
def rebootParser(s: State) = token(Space ~> "full" ^^^ true) ?? false
def defaults = Command.command(DefaultsCommand) { s =>
s ++ DefaultCommands
}
def call = Command(ApplyCommand, ApplyBrief, ApplyDetailed)(_ => spaceDelimited("<class name>")) { (state,args) =>
val loader = getClass.getClassLoader
val loaded = args.map(arg => ModuleUtilities.getObject(arg, loader))
(state /: loaded) { case (s, obj: (State => State)) => obj(s) }
}
def initialize = Command.command(InitCommand) { s =>
/*"load-commands -base ~/.sbt/commands" :: */readLines( readable( sbtRCs(s) ) ) ::: s
}
def readParser(s: State) =
{
val files = (token(Space) ~> fileParser(s.baseDir)).+
val portAndSuccess = token(OptSpace) ~> Port
portAndSuccess || files
}
def read = Command.make(ReadCommand, ReadBrief, ReadDetailed)(s => applyEffect(readParser(s))(doRead(s)) )
def doRead(s: State)(arg: Either[Int, Seq[File]]): State =
arg match
{
case Left(portAndSuccess) =>
val port = math.abs(portAndSuccess)
val previousSuccess = portAndSuccess >= 0
readMessage(port, previousSuccess) match
{
case Some(message) => (message :: (ReadCommand + " " + port) :: s).copy(onFailure = Some(ReadCommand + " " + (-port)))
case None =>
System.err.println("Connection closed.")
s.fail
}
case Right(from) =>
val notFound = notReadable(from)
if(notFound.isEmpty)
readLines(from) ::: s // this means that all commands from all files are loaded, parsed, and inserted before any are executed
else {
logger(s).error("Command file(s) not readable: \n\t" + notFound.mkString("\n\t"))
s
}
}
private def readMessage(port: Int, previousSuccess: Boolean): Option[String] =
{
// split into two connections because this first connection ends the previous communication
xsbt.IPC.client(port) { _.send(previousSuccess.toString) }
// and this second connection starts the next communication
xsbt.IPC.client(port) { ipc =>
val message = ipc.receive
if(message eq null) None else Some(message)
}
}
def continuous =
Command(ContinuousExecutePrefix, Help(continuousBriefHelp) )(otherCommandParser) { (s, arg) =>
withAttribute(s, Watched.Configuration, "Continuous execution not configured.") { w =>
val repeat = ContinuousExecutePrefix + (if(arg.startsWith(" ")) arg else " " + arg)
Watched.executeContinuously(w, s, arg, repeat)
}
}
def history = Command.custom(historyParser, historyHelp)
def historyParser(s: State): Parser[() => State] =
Command.applyEffect(HistoryCommands.actionParser) { histFun =>
val logError = (msg: String) => s.log.error(msg)
val hp = s get historyPath.key getOrElse None
val lines = hp.toList.flatMap( p => IO.readLines(p) ).toIndexedSeq
histFun( complete.History(lines, hp, logError) ) match
{
case Some(commands) =>
commands foreach println //printing is more appropriate than logging
(commands ::: s).continue
case None => s.fail
}
}
def eval = Command.single(EvalCommand, evalBrief, evalDetailed) { (s, arg) =>
val log = logger(s)
val extracted = Project extract s
@ -482,7 +252,7 @@ object BuiltinCommands
/** Determines the log file that last* commands should operate on. See also isLastOnly. */
def lastLogFile(s: State) =
{
val backing = CommandSupport.globalLogging(s).backing
val backing = s.globalLogging.backing
if(isLastOnly(s)) backing.last else Some(backing.file)
}
@ -514,7 +284,7 @@ object BuiltinCommands
}
def act = Command.customHelp(Act.actParser, actHelp)
def actHelp = (s: State) => CommandSupport.showHelp ++ keysHelp(s)
def actHelp = (s: State) => CommandStrings.showHelp ++ keysHelp(s)
def keysHelp(s: State): Help =
if(Project.isProjectLoaded(s))
Help.detailOnly(taskDetail(s))
@ -530,16 +300,9 @@ object BuiltinCommands
for( (uri, build) <- structure.units if curi != uri) listBuild(uri, build, false, cid, log)
s
}
def withAttribute[T](s: State, key: AttributeKey[T], ifMissing: String)(f: T => State): State =
(s get key) match {
case None => logger(s).error(ifMissing); s.fail
case Some(nav) => f(nav)
}
def project = Command.make(ProjectCommand, projectBrief, projectDetailed)(ProjectNavigation.command)
def exit = Command.command(TerminateAction, exitBrief, exitBrief ) ( _ exit true )
def loadFailed = Command.command(LoadFailed)(handleLoadFailed)
@tailrec def handleLoadFailed(s: State): State =
{
@ -576,71 +339,4 @@ object BuiltinCommands
SessionSettings.checkSession(session, s)
Project.setProject(session, structure, s)
}
def handleException(e: Throwable, s: State): State =
handleException(e, s, logger(s))
def handleException(e: Throwable, s: State, log: Logger): State =
{
e match
{
case _: Incomplete => () // already handled by evaluateTask
case _: NoMessageException => ()
case ite: InvocationTargetException =>
val cause = ite.getCause
if(cause == null || cause == ite) logFullException(ite, log) else handleException(cause, s, log)
case _: MessageOnlyException => log.error(e.toString)
case _: Project.Uninitialized => logFullException(e, log, true)
case _ => logFullException(e, log)
}
s.fail
}
def logFullException(e: Throwable, log: Logger, messageOnly: Boolean = false)
{
log.trace(e)
log.error(if(messageOnly) e.getMessage else ErrorHandling reducedToString e)
log.error("Use 'last' for the full log.")
}
def addAlias(s: State, name: String, value: String): State =
if(Command validID name) {
val removed = removeAlias(s, name)
if(value.isEmpty) removed else removed.copy(definedCommands = newAlias(name, value) +: removed.definedCommands)
} else {
System.err.println("Invalid alias name '" + name + "'.")
s.fail
}
def removeAliases(s: State): State = removeTagged(s, CommandAliasKey)
def removeAlias(s: State, name: String): State = s.copy(definedCommands = s.definedCommands.filter(c => !isAliasNamed(name, c)) )
def removeTagged(s: State, tag: AttributeKey[_]): State = s.copy(definedCommands = removeTagged(s.definedCommands, tag))
def removeTagged(as: Seq[Command], tag: AttributeKey[_]): Seq[Command] = as.filter(c => ! (c.tags contains tag))
def isAliasNamed(name: String, c: Command): Boolean = isNamed(name, getAlias(c))
def isNamed(name: String, alias: Option[(String,String)]): Boolean = alias match { case None => false; case Some((n,_)) => name == n }
def getAlias(c: Command): Option[(String,String)] = c.tags get CommandAliasKey
def printAlias(s: State, name: String): Unit = printAliases(aliases(s,(n,v) => n == name) )
def printAliases(s: State): Unit = printAliases(allAliases(s))
def printAliases(as: Seq[(String,String)]): Unit =
for( (name,value) <- as)
println("\t" + name + " = " + value)
def aliasNames(s: State): Seq[String] = allAliases(s).map(_._1)
def allAliases(s: State): Seq[(String,String)] = aliases(s, (n,v) => true)
def aliases(s: State, pred: (String,String) => Boolean): Seq[(String,String)] =
s.definedCommands.flatMap(c => getAlias(c).filter(tupled(pred)))
def newAlias(name: String, value: String): Command =
Command.make(name, (name, "'" + value + "'"), "Alias of '" + value + "'")(aliasBody(name, value)).tag(CommandAliasKey, (name, value))
def aliasBody(name: String, value: String)(state: State): Parser[() => State] =
OptSpace ~> Parser(Command.combine(removeAlias(state,name).definedCommands)(state))(value)
def delegateToAlias(name: String, orElse: Parser[() => State])(state: State): Parser[() => State] =
aliases(state, (nme,_) => nme == name).headOption match {
case None => orElse
case Some((n,v)) => aliasBody(n,v)(state)
}
val CommandAliasKey = AttributeKey[(String,String)]("is-command-alias", "Internal: marker for Commands created as aliases for another command.")
}

View File

@ -203,7 +203,7 @@ object Project extends Init[Scope] with ProjectExtra
val prompt = get(shellPrompt)
val watched = get(watch)
val commandDefs = allCommands.distinct.flatten[Command].map(_ tag (projectCommand, true))
val newDefinedCommands = commandDefs ++ BuiltinCommands.removeTagged(s.definedCommands, projectCommand)
val newDefinedCommands = commandDefs ++ BasicCommands.removeTagged(s.definedCommands, projectCommand)
val newAttrs = setCond(Watched.Configuration, watched, s.attributes).put(historyPath.key, history)
s.copy(attributes = setCond(shellPrompt.key, prompt, newAttrs), definedCommands = newDefinedCommands)
}

View File

@ -16,7 +16,7 @@ object Script
val scriptArg = state.remainingCommands.headOption getOrElse error("No script file specified")
val script = new File(scriptArg).getAbsoluteFile
val hash = Hash.halve(Hash.toHex(Hash(script.getAbsolutePath)))
val base = new File(CommandSupport.bootDirectory(state), hash)
val base = new File(CommandUtil.bootDirectory(state), hash)
IO.createDirectory(base)
val (eval, structure) = Load.defaultLoad(state, base, state.log)

View File

@ -47,6 +47,6 @@ object TaskData
private[this] def fakeState(structure: BuildStructure): State =
{
val config = Keys.appConfiguration in Scope.GlobalScope get structure.data
State(config.get, Nil, Set.empty, None, Nil, State.newHistory, AttributeMap.empty, State.Continue)
State(config.get, Nil, Set.empty, None, Nil, State.newHistory, AttributeMap.empty, StandardMain.initialGlobalLogging, State.Continue)
}
}

View File

@ -8,7 +8,7 @@ package sbt
import FileInfo.{exists, hash}
import java.io.File
import java.net.URL
import Types.:+:
import Types.{:+:, idFun}
import scala.xml.NodeSeq
import sbinary.{DefaultProtocol,Format}
import DefaultProtocol.{immutableMapFormat, immutableSetFormat, optionsAreFormat}
@ -80,8 +80,19 @@ object CacheIvy
)
implicit def exclusionRuleFormat(implicit sf: Format[String]): Format[ExclusionRule] =
wrap[ExclusionRule, (String, String, String, Seq[String])]( e => (e.organization, e.name, e.artifact, e.configurations), { case (o,n,a,cs) => ExclusionRule(o,n,a,cs) })
implicit def crossVersionFormat: Format[CrossVersion] = wrap(crossToInt, crossFromInt)
private[this] final val DisabledValue = 0
private[this] final val BinaryValue = 1
private[this] final val FullValue = 2
import CrossVersion.{Binary, Disabled, Full}
private[this] val crossFromInt = (i: Int) => i match { case BinaryValue => new Binary(idFun); case FullValue => new Full(idFun); case _ => Disabled }
private[this] val crossToInt = (c: CrossVersion) => c match { case Disabled => 0; case b: Binary => BinaryValue; case f: Full => FullValue }
implicit def moduleIDFormat(implicit sf: Format[String], af: Format[Artifact], bf: Format[Boolean], ef: Format[ExclusionRule]): Format[ModuleID] =
wrap[ModuleID, ((String,String,String,Option[String]),(Boolean,Boolean,Seq[Artifact],Seq[ExclusionRule],Map[String,String],Boolean))](
wrap[ModuleID, ((String,String,String,Option[String]),(Boolean,Boolean,Seq[Artifact],Seq[ExclusionRule],Map[String,String],CrossVersion))](
m => ((m.organization,m.name,m.revision,m.configurations), (m.isChanging, m.isTransitive, m.explicitArtifacts, m.exclusions, m.extraAttributes, m.crossVersion)),
{ case ((o,n,r,cs),(ch,t,as,excl,x,cv)) => ModuleID(o,n,r,cs,ch,t,as,excl,x,cv) }
)
@ -144,6 +155,7 @@ object CacheIvy
implicit def artifactToHL = (a: Artifact) => a.name :+: a.`type` :+: a.extension :+: a.classifier :+: names(a.configurations) :+: a.url :+: a.extraAttributes :+: HNil
implicit def exclusionToHL = (e: ExclusionRule) => e.organization :+: e.name :+: e.artifact :+: e.configurations :+: HNil
implicit def crossToHL = (c: CrossVersion) => crossToInt(c) :+: HNil
/* implicit def deliverConfToHL = (p: DeliverConfiguration) => p.deliverIvyPattern :+: p.status :+: p.configurations :+: HNil
implicit def publishConfToHL = (p: PublishConfiguration) => p.ivyFile :+: p.resolverName :+: p.artifacts :+: HNil*/
@ -156,13 +168,14 @@ object CacheIvy
implicit def connectionIC: InputCache[SshConnection] = wrapIn
implicit def artifactIC: InputCache[Artifact] = wrapIn
implicit def exclusionIC: InputCache[ExclusionRule] = wrapIn
implicit def crossVersionIC: InputCache[CrossVersion] = wrapIn
/* implicit def publishConfIC: InputCache[PublishConfiguration] = wrapIn
implicit def deliverConfIC: InputCache[DeliverConfiguration] = wrapIn*/
object L1 {
implicit def retrieveToHL = (r: RetrieveConfiguration) => exists(r.retrieveDirectory) :+: r.outputPattern :+: HNil
implicit def ivyPathsToHL = (p: IvyPaths) => exists(p.baseDirectory) :+: p.ivyHome.map(exists.apply) :+: HNil
implicit def ivyScalaHL = (i: IvyScala) => i.scalaVersion :+: names(i.configurations) :+: i.checkExplicit :+: i.filterImplicit :+: HNil
implicit def ivyScalaHL = (i: IvyScala) => i.scalaFullVersion :+: i.scalaBinaryVersion :+: names(i.configurations) :+: i.checkExplicit :+: i.filterImplicit :+: HNil
implicit def configurationToHL = (c: Configuration) => c.name :+: c.description :+: c.isPublic :+: names(c.extendsConfigs) :+: c.transitive :+: HNil
implicit def passwordToHL = (s: PasswordAuthentication) => Hash(s.user) :+: password(s.password) :+: HNil

View File

@ -0,0 +1,126 @@
/* sbt -- Simple Build Tool
* Copyright 2010 Mark Harrah
*/
package sbt
import complete.HistoryCommands
import scala.annotation.tailrec
import java.io.File
import Path._
object BasicCommandStrings
{
val HelpCommand = "help"
val Exit = "exit"
val Quit = "quit"
/** The command name to terminate the program.*/
val TerminateAction: String = Exit
def helpBrief = (HelpCommand + " [command]*", "Displays this help message or prints detailed help on requested commands.")
def helpDetailed = """
If an argument is provided, this prints detailed help for that command.
Otherwise, this prints a help summary."""
def historyHelp = Help.briefDetail(HistoryCommands.descriptions)
def exitBrief = "Terminates the build."
def ReadCommand = "<"
def ReadFiles = " file1 file2 ..."
def ReadBrief = (ReadCommand + " <file>*", "Reads command lines from the provided files.")
def ReadDetailed =
ReadCommand + ReadFiles + """
Reads the lines from the given files and inserts them as commands.
All empty lines and lines that start with '#' are ignored.
If a file does not exist or is not readable, this command fails.
All the lines from all the files are read before any of the commands
are executed. Thus, if any file is not readable, none of commands
from any of the files (even the existing ones) will be run.
You probably need to escape this command if entering it at your shell."""
def ApplyCommand = "apply"
def ApplyBrief = (ApplyCommand + " <module-name>*", ApplyDetailed)
def ApplyDetailed = "Transforms the current State by calling <module-name>.apply(currentState) for each listed module name."
def RebootCommand = "reboot"
def RebootSummary = RebootCommand + " [full]"
def RebootBrief = (RebootSummary, "Reboots sbt and then executes the remaining commands.")
def RebootDetailed =
RebootSummary + """
This command is equivalent to exiting sbt, restarting, and running the
remaining commands with the exception that the JVM is not shut down.
If 'full' is specified, the boot directory (`~/.sbt/boot` by default)
is deleted before restarting. This forces an update of sbt and Scala
and is useful when working with development versions of sbt or Scala."""
def Multi = ";"
def MultiBrief = (Multi + " <command> (" + Multi + " <command>)*", "Runs the provided semicolon-separated commands.")
def MultiDetailed =
Multi + " command1 " + Multi + """ command2 ...
Runs the specified commands."""
def AppendCommand = "append"
def AppendLastBrief = (AppendCommand + " <command>", AppendLastDetailed)
def AppendLastDetailed = "Appends 'command' to list of commands to run."
val AliasCommand = "alias"
def AliasBrief = (AliasCommand, "Adds, removes, or prints command aliases.")
def AliasDetailed =
AliasCommand + """
Prints a list of defined aliases.
""" +
AliasCommand + """ name
Prints the alias defined for `name`.
""" +
AliasCommand + """ name=value
Sets the alias `name` to `value`, replacing any existing alias with that name.
Whenever `name` is entered, the corresponding `value` is run.
If any argument is provided to `name`, it is appended as argument to `value`.
""" +
AliasCommand + """ name=
Removes the alias for `name`."""
def Shell = "shell"
def ShellBrief = ShellDetailed
def ShellDetailed = "Provides an interactive prompt from which commands can be run."
def ClearOnFailure = "--"
def OnFailure = "-"
def OnFailureBrief = (OnFailure + " command", "Registers 'command' to run if a command fails.")
def OnFailureDetailed =
OnFailure + """ command
Registers 'command' to run when a command fails to complete normally.
Only one failure command may be registered at a time, so this command
replaces the previous command if there is one.
The failure command resets when it runs once, so it must be added
again if desired."""
def IfLast = "iflast"
def IfLastBrief = (IfLast + " <command>", IfLastCommon)
def IfLastCommon = "If there are no more commands after this one, 'command' is run."
def IfLastDetailed =
IfLast + """ command
""" + IfLastCommon
val ContinuousExecutePrefix = "~"
def continuousBriefHelp = (ContinuousExecutePrefix + " <command>", "Executes the specified command whenever source files change.")
}

View File

@ -0,0 +1,223 @@
package sbt
import complete.{DefaultParsers, HistoryCommands, Parser}
import DefaultParsers._
import Types.{const,idFun}
import Function.tupled
import Command.applyEffect
import State.FailureWall
import HistoryCommands.{Start => HistoryPrefix}
import BasicCommandStrings._
import CommandUtil._
import BasicKeys._
import java.io.File
object BasicCommands
{
lazy val allBasicCommands = Seq(nop, ignore, help, multi, ifLast, append, setOnFailure, clearOnFailure, reboot, call, exit, continuous, history, shell, read, alias)
def nop = Command.custom(s => success(() => s))
def ignore = Command.command(FailureWall)(idFun)
def help = Command.make(HelpCommand, helpBrief, helpDetailed)(helpParser)
def helpParser(s: State) =
{
val h = (Help.empty /: s.definedCommands)(_ ++ _.help(s))
val helpCommands = h.detail.keySet
val args = (token(Space) ~> token( NotSpace examples helpCommands )).*
applyEffect(args)(runHelp(s, h))
}
def runHelp(s: State, h: Help)(args: Seq[String]): State =
{
val message =
if(args.isEmpty)
aligned(" ", " ", h.brief).mkString("\n", "\n", "\n")
else
detail(args, h.detail) mkString("\n", "\n\n", "\n")
System.out.println(message)
s
}
def detail(selected: Seq[String], detailMap: Map[String, String]): Seq[String] =
selected.distinct flatMap { detailMap get _ }
def multiParser(s: State): Parser[Seq[String]] =
{
val nonSemi = token(charClass(_ != ';').+, hide= const(true))
( token(';' ~> OptSpace) flatMap { _ => matched((s.combinedParser&nonSemi) | nonSemi) <~ token(OptSpace) } map (_.trim) ).+
}
def multiApplied(s: State) =
Command.applyEffect( multiParser(s) )( _ ::: s )
def multi = Command.custom(multiApplied, Help(Multi, MultiBrief, MultiDetailed) )
lazy val otherCommandParser = (s: State) => token(OptSpace ~> combinedLax(s, any.+) )
def combinedLax(s: State, any: Parser[_]): Parser[String] =
matched(s.combinedParser | token(any, hide= const(true)))
def ifLast = Command(IfLast, IfLastBrief, IfLastDetailed)(otherCommandParser) { (s, arg) =>
if(s.remainingCommands.isEmpty) arg :: s else s
}
def append = Command(AppendCommand, AppendLastBrief, AppendLastDetailed)(otherCommandParser) { (s, arg) =>
s.copy(remainingCommands = s.remainingCommands :+ arg)
}
def setOnFailure = Command(OnFailure, OnFailureBrief, OnFailureDetailed)(otherCommandParser) { (s, arg) =>
s.copy(onFailure = Some(arg))
}
def clearOnFailure = Command.command(ClearOnFailure)(s => s.copy(onFailure = None))
def reboot = Command(RebootCommand, RebootBrief, RebootDetailed)(rebootParser) { (s, full) =>
s.reboot(full)
}
def rebootParser(s: State) = token(Space ~> "full" ^^^ true) ?? false
def call = Command(ApplyCommand, ApplyBrief, ApplyDetailed)(_ => spaceDelimited("<class name>")) { (state,args) =>
val loader = getClass.getClassLoader
val loaded = args.map(arg => ModuleUtilities.getObject(arg, loader))
(state /: loaded) { case (s, obj: (State => State)) => obj(s) }
}
def exit = Command.command(TerminateAction, exitBrief, exitBrief ) ( _ exit true )
def continuous =
Command(ContinuousExecutePrefix, Help(continuousBriefHelp) )(otherCommandParser) { (s, arg) =>
withAttribute(s, Watched.Configuration, "Continuous execution not configured.") { w =>
val repeat = ContinuousExecutePrefix + (if(arg.startsWith(" ")) arg else " " + arg)
Watched.executeContinuously(w, s, arg, repeat)
}
}
def history = Command.custom(historyParser, BasicCommandStrings.historyHelp)
def historyParser(s: State): Parser[() => State] =
Command.applyEffect(HistoryCommands.actionParser) { histFun =>
val logError = (msg: String) => s.log.error(msg)
val hp = s get historyPath getOrElse None
val lines = hp.toList.flatMap( p => IO.readLines(p) ).toIndexedSeq
histFun( complete.History(lines, hp, logError) ) match
{
case Some(commands) =>
commands foreach println //printing is more appropriate than logging
(commands ::: s).continue
case None => s.fail
}
}
def shell = Command.command(Shell, ShellBrief, ShellDetailed) { s =>
val history = (s get historyPath) getOrElse Some(new File(s.baseDir, ".history"))
val prompt = (s get shellPrompt) match { case Some(pf) => pf(s); case None => "> " }
val reader = new FullReader(history, s.combinedParser)
val line = reader.readLine(prompt)
line match {
case Some(line) =>
val newState = s.copy(onFailure = Some(Shell), remainingCommands = line +: Shell +: s.remainingCommands)
if(line.trim.isEmpty) newState else newState.clearGlobalLog
case None => s
}
}
def read = Command.make(ReadCommand, ReadBrief, ReadDetailed)(s => applyEffect(readParser(s))(doRead(s)) )
def readParser(s: State) =
{
val files = (token(Space) ~> fileParser(s.baseDir)).+
val portAndSuccess = token(OptSpace) ~> Port
portAndSuccess || files
}
def doRead(s: State)(arg: Either[Int, Seq[File]]): State =
arg match
{
case Left(portAndSuccess) =>
val port = math.abs(portAndSuccess)
val previousSuccess = portAndSuccess >= 0
readMessage(port, previousSuccess) match
{
case Some(message) => (message :: (ReadCommand + " " + port) :: s).copy(onFailure = Some(ReadCommand + " " + (-port)))
case None =>
System.err.println("Connection closed.")
s.fail
}
case Right(from) =>
val notFound = notReadable(from)
if(notFound.isEmpty)
readLines(from) ::: s // this means that all commands from all files are loaded, parsed, and inserted before any are executed
else {
s.log.error("Command file(s) not readable: \n\t" + notFound.mkString("\n\t"))
s
}
}
private def readMessage(port: Int, previousSuccess: Boolean): Option[String] =
{
// split into two connections because this first connection ends the previous communication
xsbt.IPC.client(port) { _.send(previousSuccess.toString) }
// and this second connection starts the next communication
xsbt.IPC.client(port) { ipc =>
val message = ipc.receive
if(message eq null) None else Some(message)
}
}
def alias = Command.make(AliasCommand, AliasBrief, AliasDetailed) { s =>
val name = token(OpOrID.examples( aliasNames(s) : _*) )
val assign = token(OptSpace ~ '=' ~ OptSpace)
val sfree = removeAliases(s)
val to = matched(sfree.combinedParser, partial = true) | any.+.string
val base = (OptSpace ~> (name ~ (assign ~> to.?).?).?)
applyEffect(base)(t => runAlias(s, t) )
}
def runAlias(s: State, args: Option[(String, Option[Option[String]])]): State =
args match
{
case None => printAliases(s); s
case Some(x ~ None) if !x.isEmpty => printAlias(s, x.trim); s
case Some(name ~ Some(None)) => removeAlias(s, name.trim)
case Some(name ~ Some(Some(value))) => addAlias(s, name.trim, value.trim)
}
def addAlias(s: State, name: String, value: String): State =
if(Command validID name) {
val removed = removeAlias(s, name)
if(value.isEmpty) removed else removed.copy(definedCommands = newAlias(name, value) +: removed.definedCommands)
} else {
System.err.println("Invalid alias name '" + name + "'.")
s.fail
}
def removeAliases(s: State): State = removeTagged(s, CommandAliasKey)
def removeAlias(s: State, name: String): State = s.copy(definedCommands = s.definedCommands.filter(c => !isAliasNamed(name, c)) )
def removeTagged(s: State, tag: AttributeKey[_]): State = s.copy(definedCommands = removeTagged(s.definedCommands, tag))
def removeTagged(as: Seq[Command], tag: AttributeKey[_]): Seq[Command] = as.filter(c => ! (c.tags contains tag))
def isAliasNamed(name: String, c: Command): Boolean = isNamed(name, getAlias(c))
def isNamed(name: String, alias: Option[(String,String)]): Boolean = alias match { case None => false; case Some((n,_)) => name == n }
def getAlias(c: Command): Option[(String,String)] = c.tags get CommandAliasKey
def printAlias(s: State, name: String): Unit = printAliases(aliases(s,(n,v) => n == name) )
def printAliases(s: State): Unit = printAliases(allAliases(s))
def printAliases(as: Seq[(String,String)]): Unit =
for( (name,value) <- as)
println("\t" + name + " = " + value)
def aliasNames(s: State): Seq[String] = allAliases(s).map(_._1)
def allAliases(s: State): Seq[(String,String)] = aliases(s, (n,v) => true)
def aliases(s: State, pred: (String,String) => Boolean): Seq[(String,String)] =
s.definedCommands.flatMap(c => getAlias(c).filter(tupled(pred)))
def newAlias(name: String, value: String): Command =
Command.make(name, (name, "'" + value + "'"), "Alias of '" + value + "'")(aliasBody(name, value)).tag(CommandAliasKey, (name, value))
def aliasBody(name: String, value: String)(state: State): Parser[() => State] =
OptSpace ~> Parser(Command.combine(removeAlias(state,name).definedCommands)(state))(value)
def delegateToAlias(name: String, orElse: Parser[() => State])(state: State): Parser[() => State] =
aliases(state, (nme,_) => nme == name).headOption match {
case None => orElse
case Some((n,v)) => aliasBody(n,v)(state)
}
val CommandAliasKey = AttributeKey[(String,String)]("is-command-alias", "Internal: marker for Commands created as aliases for another command.")
}

View File

@ -0,0 +1,10 @@
package sbt
import java.io.File
object BasicKeys
{
val historyPath = AttributeKey[Option[File]]("history", "The location where command line history is persisted.")
val shellPrompt = AttributeKey[State => String]("shell-prompt", "The function that constructs the command prompt from the current build state.")
val watch = AttributeKey[Watched]("watch", "Continuous execution configuration.")
}

View File

@ -0,0 +1,32 @@
package sbt
import java.io.File
object CommandUtil
{
def readLines(files: Seq[File]): Seq[String] = files flatMap (line => IO.readLines(line)) flatMap processLine
def processLine(s: String) = { val trimmed = s.trim; if(ignoreLine(trimmed)) None else Some(trimmed) }
def ignoreLine(s: String) = s.isEmpty || s.startsWith("#")
private def canRead = (_: File).canRead
def notReadable(files: Seq[File]): Seq[File] = files filterNot canRead
def readable(files: Seq[File]): Seq[File] = files filter canRead
// slightly better fallback in case of older launcher
def bootDirectory(state: State): File =
try { state.configuration.provider.scalaProvider.launcher.bootDirectory }
catch { case e: NoSuchMethodError => new File(".").getAbsoluteFile }
def aligned(pre: String, sep: String, in: Seq[(String, String)]): Seq[String] =
{
val width = in.map(_._1.length).max
in.map { case (a, b) => (" " + fill(a, width) + sep + b) }
}
def fill(s: String, size: Int) = s + " " * math.max(size - s.length, 0)
def withAttribute[T](s: State, key: AttributeKey[T], ifMissing: String)(f: T => State): State =
(s get key) match {
case None => s.log.error(ifMissing); s.fail
case Some(nav) => f(nav)
}
}

100
main/command/MainLoop.scala Normal file
View File

@ -0,0 +1,100 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009, 2010, 2011 Mark Harrah
*/
package sbt
import scala.annotation.tailrec
import java.io.{File, PrintWriter}
import java.lang.reflect.InvocationTargetException
object MainLoop
{
/** Entry point to run the remaining commands in State with managed global logging.*/
def runLogged(state: State): xsbti.MainResult =
runLoggedLoop(state, state.globalLogging.backing)
/** Run loop that evaluates remaining commands and manages changes to global logging configuration.*/
@tailrec def runLoggedLoop(state: State, logBacking: GlobalLogBacking): xsbti.MainResult =
runAndClearLast(state, logBacking) match {
case ret: Return => // delete current and last log files when exiting normally
logBacking.file.delete()
deleteLastLog(logBacking)
ret.result
case clear: ClearGlobalLog => // delete previous log file, move current to previous, and start writing to a new file
deleteLastLog(logBacking)
runLoggedLoop(clear.state, logBacking.shiftNew())
case keep: KeepGlobalLog => // make previous log file the current log file
logBacking.file.delete
runLoggedLoop(keep.state, logBacking.unshift)
}
/** Runs the next sequence of commands, cleaning up global logging after any exceptions. */
def runAndClearLast(state: State, logBacking: GlobalLogBacking): RunNext =
try
runWithNewLog(state, logBacking)
catch {
case e: xsbti.FullReload =>
deleteLastLog(logBacking)
throw e // pass along a reboot request
case e =>
System.err.println("sbt appears to be exiting abnormally.\n The log file for this session is at " + logBacking.file)
deleteLastLog(logBacking)
throw e
}
/** Deletes the previous global log file. */
def deleteLastLog(logBacking: GlobalLogBacking): Unit =
logBacking.last.foreach(_.delete())
/** Runs the next sequence of commands with global logging in place. */
def runWithNewLog(state: State, logBacking: GlobalLogBacking): RunNext =
Using.fileWriter(append = true)(logBacking.file) { writer =>
val out = new java.io.PrintWriter(writer)
val loggedState = state.copy(globalLogging = logBacking.newLogger(out, logBacking))
try run(loggedState) finally out.close()
}
sealed trait RunNext
final class ClearGlobalLog(val state: State) extends RunNext
final class KeepGlobalLog(val state: State) extends RunNext
final class Return(val result: xsbti.MainResult) extends RunNext
/** Runs the next sequence of commands that doesn't require global logging changes.*/
@tailrec def run(state: State): RunNext =
state.next match
{
case State.Continue => run(next(state))
case State.ClearGlobalLog => new ClearGlobalLog(state.continue)
case State.KeepLastLog => new KeepGlobalLog(state.continue)
case ret: State.Return => new Return(ret.result)
}
def next(state: State): State =
ErrorHandling.wideConvert { state.process(Command.process) } match
{
case Right(s) => s
case Left(t: xsbti.FullReload) => throw t
case Left(t) => handleException(t, state)
}
def handleException(e: Throwable, s: State): State =
handleException(e, s, s.log)
def handleException(e: Throwable, s: State, log: Logger): State =
{
e match
{
case _: AlreadyHandledException | _: UnprintableException => ()
case ite: InvocationTargetException =>
val cause = ite.getCause
if(cause == null || cause == ite) logFullException(ite, log) else handleException(cause, s, log)
case _: MessageOnlyException => log.error(e.toString)
case _ => logFullException(e, log)
}
s.fail
}
def logFullException(e: Throwable, log: Logger)
{
log.trace(e)
log.error(ErrorHandling reducedToString e)
log.error("Use 'last' for the full log.")
}
}

View File

@ -5,7 +5,6 @@ package sbt
import java.io.File
import java.util.concurrent.Callable
import CommandSupport.{FailureWall, logger}
/**
Data structure representing all command execution information.
@ -27,6 +26,7 @@ final case class State(
remainingCommands: Seq[String],
history: State.History,
attributes: AttributeMap,
globalLogging: GlobalLogging,
next: State.Next
) extends Identity {
lazy val combinedParser = Command.combine(definedCommands)(this)
@ -112,6 +112,8 @@ trait StateOps {
object State
{
final val FailureWall = "---"
/** Represents the next action for the command processor.*/
sealed trait Next
/** Indicates that the command processor should process the next command.*/
@ -175,7 +177,7 @@ object State
def update[T](key: AttributeKey[T])(f: Option[T] => T): State = put(key, f(get(key)))
def has(key: AttributeKey[_]) = s.attributes contains key
def remove(key: AttributeKey[_]) = s.copy(attributes = s.attributes remove key)
def log = CommandSupport.logger(s)
def log = s.globalLogging.full
def fail =
{
val remaining = s.remainingCommands.dropWhile(_ != FailureWall)

View File

@ -3,7 +3,8 @@
*/
package sbt
import CommandSupport.{ClearOnFailure,FailureWall}
import BasicCommandStrings.ClearOnFailure
import State.FailureWall
import annotation.tailrec
import java.io.File
import Types.const
@ -63,7 +64,7 @@ object Watched
catch { case e: Exception =>
val log = s.log
log.error("Error occurred obtaining files to watch. Terminating continuous execution...")
BuiltinCommands.handleException(e, s, log)
MainLoop.handleException(e, s, log)
(false, watchState, s.fail)
}

View File

@ -14,7 +14,7 @@ object Sbt extends Build
{
override lazy val settings = super.settings ++ buildSettings ++ Status.settings
def buildSettings = Seq(
organization := "org.scala-tools.sbt",
organization := "org.scala-sbt",
version := "0.12.0-SNAPSHOT",
publishArtifact in packageDoc := false,
scalaVersion := "2.9.1",
@ -111,8 +111,11 @@ object Sbt extends Build
classfileSub, classpathSub, compileIncrementalSub, compilePersistSub, compilerSub, completeSub, apiSub,
interfaceSub, ioSub, ivySub, logSub, processSub, runSub, stdTaskSub, taskSub, trackingSub, testingSub)
lazy val commandSub = testedBaseProject(commandPath, "Command") dependsOn(interfaceSub, ioSub, launchInterfaceSub, logSub, completeSub, classpathSub)
// The main integration project for sbt. It brings all of the subsystems together, configures them, and provides for overriding conventions.
lazy val mainSub = testedBaseProject(mainPath, "Main") dependsOn(actionsSub, interfaceSub, ioSub, ivySub, launchInterfaceSub, logSub, processSub, runSub)
lazy val mainSub = testedBaseProject(mainPath, "Main") dependsOn(actionsSub, interfaceSub, ioSub, ivySub, launchInterfaceSub, logSub, processSub, runSub, commandSub)
// Strictly for bringing implicits and aliases from subsystems into the top-level sbt namespace through a single package object
// technically, we need a dependency on all of mainSub's dependencies, but we don't do that since this is strictly an integration project
// with the sole purpose of providing certain identifiers without qualification (with a package object)
@ -126,6 +129,7 @@ object Sbt extends Build
def utilPath = file("util")
def compilePath = file("compile")
def mainPath = file("main")
def commandPath = mainPath / "command"
def scriptedPath = file("scripted")
def sbtSettings = Seq(
@ -137,8 +141,8 @@ object Sbt extends Build
(launcher, scriptedSbtClasspath, scriptedSbtInstance, _, v, sv, ssv, sourcePath, args) =>
val loader = classpath.ClasspathUtilities.toLoader(scriptedSbtClasspath.files, scriptedSbtInstance.loader)
val m = ModuleUtilities.getObject("sbt.test.ScriptedTests", loader)
val r = m.getClass.getMethod("run", classOf[File], classOf[Boolean], classOf[String], classOf[String], classOf[String], classOf[Array[String]], classOf[File])
try { r.invoke(m, sourcePath, true: java.lang.Boolean, v, sv, ssv, args.toArray[String], launcher) }
val r = m.getClass.getMethod("run", classOf[File], classOf[Boolean], classOf[String], classOf[String], classOf[String], classOf[Array[String]], classOf[File], classOf[Array[String]])
try { r.invoke(m, sourcePath, true: java.lang.Boolean, v, sv, ssv, args.toArray[String], launcher, Array[String]()) }
catch { case ite: java.lang.reflect.InvocationTargetException => throw ite.getCause }
}
}

View File

@ -4,6 +4,9 @@
package object sbt extends sbt.std.TaskExtra with sbt.Types with sbt.ProcessExtra with sbt.impl.DependencyBuilders
with sbt.PathExtra with sbt.ProjectExtra with sbt.DependencyFilterExtra with sbt.BuildExtra
{
@deprecated("Renamed to CommandStrings.", "0.12.0")
val CommandSupport = CommandStrings
@deprecated("Use SettingKey, which is a drop-in replacement.", "0.11.1")
type ScopedSetting[T] = SettingKey[T]
@deprecated("Use TaskKey, which is a drop-in replacement.", "0.11.1")

View File

@ -8,13 +8,13 @@ $ absent ran
$ exists ran
$ delete ran
# single project, Aggregate = Enabled on Mark
> set aggregate in Mark := false
# single project, aggregate = true on Mark
> set aggregate in Mark := true
> mark
$ exists ran
$ delete ran
# single project, Aggregate = Disabled on Mark
# single project, aggregate = false on Mark
> set aggregate in Mark := false
> mark
$ exists ran
@ -45,7 +45,7 @@ $ absent ran
$ exists sub/ran
$ delete sub/ran
# unset the root task. the sub task shouldn't be runnable from root
# unset the root task. the sub task shouldn't be runnable from root without aggregation
> session remove 1
-> mark
$ absent ran sub/ran
@ -80,20 +80,6 @@ $ exists ran sub/ran
$ absent sub/sub/ran
$ delete ran sub/ran
# check explicit aggregation. running on root should run root/mark and sub2/mark
> set aggregate in Mark := Aggregation(sub2 :: Nil)
> mark
$ exists ran sub/sub/ran
$ absent sub/ran
$ delete ran sub/sub/ran
# check intransitive aggregation. running on root should not continue to sub2/mark
> set aggregate in Mark := Aggregation(sub :: Nil, false)
> mark
$ exists ran sub/ran
$ absent sub/sub/ran
$ delete ran sub/ran
# the aggregation setting in a leaf shouldn't affect whether it can be run directly
> set aggregate in (sub2, Mark) := false
> sub2/mark

View File

@ -8,7 +8,7 @@ publishTo <<= baseDirectory { base =>
projectID <<= projectID { _.extra("e:color" -> "red") }
organization := "org.scala-tools.sbt"
organization := "org.scala-sbt"
version := "1.0"

View File

@ -8,7 +8,7 @@ resolvers <<= baseDirectory( base =>
libraryDependencies <<= baseDirectory { base =>
val color = IO.read(base / "color")
val dep = "org.scala-tools.sbt" %% "define-color" % "1.0" extra("e:color" -> color)
val dep = "org.scala-sbt" %% "define-color" % "1.0" extra("e:color" -> color)
dep :: Nil
}

View File

@ -7,6 +7,7 @@ object InfoTest extends Build
lazy val root = Project("root", file(".")) settings(
ivyPaths <<= (baseDirectory, target)( (dir, t) => new IvyPaths(dir, Some(t / "ivy-cache"))),
ivyXML <<= (customInfo, organization, moduleName, version) apply inlineXML,
scalaVersion := "2.9.0",
projectID ~= (_ cross false),
customInfo <<= baseDirectory{_ / "info" exists },
TaskKey[Unit]("check-download") <<= checkDownload,
@ -24,9 +25,9 @@ object InfoTest extends Build
ScalaQuery is a type-safe database query API for Scala.
</description>
</info>
<dependency org="org.scalacheck" name="scalacheck" rev="1.5"/>)
<dependency org="org.scala-tools.testing" name="scalacheck_2.9.0" rev="1.9"/>)
else
<dependency org="org.scalacheck" name="scalacheck" rev="1.5"/>
<dependency org="org.scala-tools.testing" name="scalacheck_2.9.0" rev="1.9"/>
def checkDownload = (dependencyClasspath in Compile) map { cp => if(cp.isEmpty) error("Dependency not downloaded"); () }
def checkInfo = (customInfo, delivered) map { (addInfo, d) =>

View File

@ -1,3 +1,5 @@
resolvers += ScalaToolsReleases
libraryDependencies += "org.scalacheck" % "scalacheck" % "1.5"
ivyPaths <<= baseDirectory( dir => new IvyPaths(dir, Some(dir / "ivy-home")))
@ -7,4 +9,4 @@ TaskKey[Unit]("check") <<= update map { report =>
assert(!files.isEmpty, "ScalaCheck module not found in update report")
val missing = files.filter(! _.exists)
assert(missing.isEmpty, "Reported ScalaCheck artifact files don't exist: " + missing.mkString(", "))
}
}

View File

@ -1,4 +0,0 @@
#Project properties
#Fri Jan 30 20:49:57 EST 2009
project.name=Inline Dependency Test A
project.version=1.0

View File

@ -1,8 +0,0 @@
import sbt._
class UpdateTestProject(info: ProjectInfo) extends DefaultProject(info)
{
val sc = "org.scalacheck" % "scalacheck" % "1.5"
override def ivyCacheDirectory = Some(outputPath / "ivy-cache")
override def disableCrossPaths = true
}

View File

@ -5,6 +5,7 @@
object MakePomTest extends Build
{
lazy val root = Project("root", file(".")) settings(
resolvers += ScalaToolsReleases,
readPom <<= makePom map XML.loadFile,
TaskKey[Unit]("check-pom") <<= checkPom,
TaskKey[Unit]("check-extra") <<= checkExtra,

View File

@ -4,6 +4,6 @@
moduleConfigurations += ModuleConfiguration("org.scala-lang", "*", "2.10.0-.*", scalaSnapshots)
}
libraryDependencies += "org.scala-lang" % "scala-compiler" % "2.10.0-20111001.020530-165"
libraryDependencies += "org.scala-lang" % "scala-compiler" % "2.10.0-20120122.024228-256"
resolvers := Nil

View File

@ -4,6 +4,6 @@
moduleConfigurations += ModuleConfiguration("org.not-scala-lang", "*", "2.10.0-.*", scalaSnapshots)
}
libraryDependencies += "org.scala-lang" % "scala-compiler" % "2.10.0-20111001.020530-165"
libraryDependencies += "org.scala-lang" % "scala-compiler" % "2.10.0-20120122.024228-256"
resolvers := Nil

View File

@ -4,6 +4,6 @@
moduleConfigurations += ModuleConfiguration("org.scala-lang", "*", "2.10.0-.*", scalaSnapshots)
}
libraryDependencies += "org.scala-lang" % "scala-compiler" % "2.10.0-20111001.020530-165"
libraryDependencies += "org.scala-lang" % "scala-compiler" % "2.10.0-20120122.024228-256"
resolvers := Nil

View File

@ -4,6 +4,6 @@
moduleConfigurations += ModuleConfiguration("org.scala-lang", "*", "2.10.0-.*", scalaSnapshots)
}
libraryDependencies += "org.scala-lang" % "scala-compiler" % "2.10.0-20111001.020530-164"
libraryDependencies += "org.scala-lang" % "scala-compiler" % "2.10.0-20120122.024228-255"
resolvers := Nil

View File

@ -3,7 +3,7 @@
object ParentTest extends Build
{
lazy val parent: Project = Project("Flowmodel", file(".")) aggregate(core, reporters)
lazy val core: Project = Project("Flowmodel core", file("core"), delegates = parent :: Nil)
lazy val reporters: Project = Project("Extra reporters", file("reporters"), delegates = parent :: Nil) aggregate(jfreechart) dependsOn(jfreechart)
lazy val jfreechart: Project = Project("JFreeChart reporters", file("jfreechart")/*, delegates = reporters :: Nil*/) dependsOn(core)
lazy val core: Project = Project("Flowmodel-core", file("core"), delegates = parent :: Nil)
lazy val reporters: Project = Project("Extra-reporters", file("reporters"), delegates = parent :: Nil) aggregate(jfreechart) dependsOn(jfreechart)
lazy val jfreechart: Project = Project("JFreeChart-reporters", file("jfreechart")/*, delegates = reporters :: Nil*/) dependsOn(core)
}

View File

@ -5,7 +5,7 @@
object PomRepoTest extends Build
{
lazy val root = Project("root", file(".")) settings(
resolvers ++= Seq(local, ScalaToolsSnapshots),
resolvers ++= Seq(local, ScalaToolsReleases, ScalaToolsSnapshots),
InputKey[Unit]("check-pom") <<= InputTask(_ => spaceDelimited("<args>")) { result => (makePom, result, streams) map checkPomRepositories },
makePomConfiguration <<= (makePomConfiguration, baseDirectory) { (conf, base) =>
conf.copy(filterRepositories = pomIncludeRepository(base, conf.filterRepositories) )

View File

@ -5,9 +5,9 @@ object PomTest extends Build
{
override def settings = super.settings :+ (TaskKey[Unit]("check-pom") <<= checkPom)
lazy val subJar = Project("Sub Jar", file("subJar"))
lazy val subWar = Project("Sub War", file("subWar")) settings( warArtifact)
lazy val subParent = Project("Sub Parent", file("subParent")) settings( publishArtifact in Compile := false )
lazy val subJar = Project("sub-jar", file("subJar"))
lazy val subWar = Project("sub-war", file("subWar")) settings( warArtifact)
lazy val subParent = Project("sub-parent", file("subParent")) settings( publishArtifact in Compile := false )
def art(p: ProjectReference) = makePom in p
def checkPom = (art(subJar), art(subWar), art(subParent)) map { (jar, war, pom) =>

View File

@ -2,15 +2,23 @@ import sbt._
import Keys._
object P extends Build
{
override def settings = super.settings ++ Seq( scalaVersion in update := "2.9.0" )
override def settings = super.settings ++ Seq(
scalaBinaryVersion in update := "2.9.0",
resolvers += ScalaToolsReleases
)
def configIvyScala =
ivyScala ~= { _.map(_.copy(checkExplicit = false)) }
val declared = SettingKey[Boolean]("declared")
lazy val a = Project("A", file("a")) settings(
libraryDependencies += "org.scala-tools.sbinary" %% "sbinary" % "0.4.0" % "provided"
libraryDependencies += "org.scala-tools.sbinary" %% "sbinary" % "0.4.0" % "provided",
configIvyScala
)
lazy val b = Project("B", file("b")) dependsOn(a) settings(
libraryDependencies <<= declared(d => if(d) Seq("org.scala-tools.sbinary" %% "sbinary" % "0.4.0" % "provided") else Nil),
declared <<= baseDirectory(_ / "declare.lib" exists)
declared <<= baseDirectory(_ / "declare.lib" exists),
configIvyScala
)
}
}

View File

@ -1,3 +1,3 @@
libraryDependencies <<= (libraryDependencies, appConfiguration) { (deps, conf) =>
deps :+ ("org.scala-tools.sbt" %% "sbt" % conf.provider.id.version)
deps :+ ("org.scala-sbt" %% "sbt" % conf.provider.id.version)
}

View File

@ -3,6 +3,10 @@ import Keys._
object Build extends Build
{
override def settings = super.settings ++ Seq(
sbtBinaryVersion <<= sbtVersion
)
lazy val root = Project("root", file(".")) aggregate(a,b,c)
lazy val a = Project("a", file("a"))
lazy val b = Project("b", file("b"))

View File

@ -1,4 +1,5 @@
libraryDependencies += "org.scalatest" % "scalatest" % "1.3"
libraryDependencies += "org.scalatest" %% "scalatest" % "1.6.1" % "test"
testOptions in Configurations.Test ++= {
def args(path: String, args: String*): Seq[TestOption] = if(file(path).exists) Tests.Argument(args : _*) :: Nil else Nil

View File

@ -1,4 +1,4 @@
import org.specs._
import org.specs2.mutable._
class B extends Specification
{

View File

@ -1,4 +1,4 @@
import org.specs._
import org.specs2.mutable._
class B extends Specification
{

View File

@ -1,4 +1,4 @@
import org.specs._
import org.specs2.mutable._
class B extends Specification
{

View File

@ -1,4 +1,4 @@
import org.specs._
import org.specs2.mutable._
class B extends Specification
{

View File

@ -6,8 +6,11 @@ object B extends Build
lazy val root =
Project("root", file("."))
.configs( IntegrationTest )
.settings( libraryDependencies += specs )
.settings( Defaults.itSettings : _*)
.settings(
libraryDependencies += specs,
resolvers += ScalaToolsReleases
)
lazy val specs = "org.scala-tools.testing" %% "specs" % "1.6.7.2" % "it,test" intransitive()
lazy val specs = "org.specs2" %% "specs2" % "1.7.1" % "it,test"
}

View File

@ -0,0 +1,3 @@
resolvers += ScalaToolsReleases
libraryDependencies += "com.novocode" % "junit-interface" % "0.8" % "test"

View File

@ -0,0 +1,8 @@
package com.foo.junit.test.blah
import org.junit._
class Failure
{
@Test def fail() { error("Fail!") }
}

View File

@ -0,0 +1,8 @@
package com.foo.junit.test.blah
import org.junit._
class Success
{
@Test def succeed() { }
}

View File

@ -0,0 +1,11 @@
> test
$ copy-file changes/Success.scala src/test/scala/Success.scala
> test
> test-only com.foo.junit.test.blah.Success
$ copy-file changes/Failure.scala src/test/scala/Failure.scala
-> test
-> test-only com.foo.junit.test.blah.Failure
> test-only com.foo.junit.test.blah.Success

View File

@ -1 +1,3 @@
libraryDependencies += "org.scala-tools.testing" %% "specs" % "1.6.7.2" intransitive()
libraryDependencies += "org.specs2" %% "specs2" % "1.7.1" % "test"
resolvers += ScalaToolsReleases

View File

@ -1,11 +1,11 @@
import org.specs._
import org.specs2.mutable._
object BasicTest extends Specification
{
"Test resource on test classpath" in {
getClass.getResource("TestResource.txt") mustNotBe null
getClass.getResource("TestResource.txt") must not beNull
}
"Main resource on test classpath" in {
getClass.getResource("MainResource.txt") mustNotBe null
getClass.getResource("MainResource.txt") must not beNull
}
}

View File

@ -1 +1,3 @@
libraryDependencies += "org.scala-tools.testing" %% "specs" % "1.6.7.2" intransitive()
libraryDependencies += "org.specs2" %% "specs2" % "1.7.1" % "test"
resolvers += ScalaToolsReleases

View File

@ -1,4 +1,4 @@
import org.specs._
import org.specs2.mutable._
class B extends Specification
{

View File

@ -1,4 +1,4 @@
import org.specs._
import org.specs2.mutable._
class B extends Specification
{

View File

@ -1,4 +1,4 @@
import org.specs._
import org.specs2.mutable._
class B extends Specification
{

View File

@ -1,4 +1,4 @@
import org.specs._
import org.specs2.mutable._
class B extends Specification
{

View File

@ -49,7 +49,7 @@ object ScriptedPlugin extends Plugin {
ivyConfigurations += scriptedConf,
scriptedSbt <<= (appConfiguration)(_.provider.id.version),
scriptedScalas <<= (scalaVersion) { (scala) => ScriptedScalas(scala, scala) },
libraryDependencies <<= (libraryDependencies, scriptedScalas, scriptedSbt) {(deps, scalas, version) => deps :+ "org.scala-tools.sbt" % ("scripted-sbt_" + scalas.build) % version % scriptedConf.toString },
libraryDependencies <<= (libraryDependencies, scriptedScalas, scriptedSbt) {(deps, scalas, version) => deps :+ "org.scala-sbt" % ("scripted-sbt_" + scalas.build) % version % scriptedConf.toString },
sbtLauncher <<= (appConfiguration)(app => IO.classLocationFile(app.provider.scalaProvider.launcher.getClass)),
sbtTestDirectory <<= sourceDirectory / "sbt-test",
scriptedBufferLog := true,

View File

@ -92,12 +92,12 @@ object ScriptedTests
val bootProperties = new File(args(5))
val tests = args.drop(6)
val logger = ConsoleLogger()
run(directory, buffer, sbtVersion, defScalaVersion, buildScalaVersions, tests, logger, bootProperties, Seq())
run(directory, buffer, sbtVersion, defScalaVersion, buildScalaVersions, tests, logger, bootProperties, Array())
}
def run(resourceBaseDirectory: File, bufferLog: Boolean, sbtVersion: String, defScalaVersion: String, buildScalaVersions: String, tests: Array[String], bootProperties: File, launchOpts: Seq[String]): Unit =
def run(resourceBaseDirectory: File, bufferLog: Boolean, sbtVersion: String, defScalaVersion: String, buildScalaVersions: String, tests: Array[String], bootProperties: File, launchOpts: Array[String]): Unit =
run(resourceBaseDirectory, bufferLog, sbtVersion, defScalaVersion, buildScalaVersions, tests, ConsoleLogger(), bootProperties, launchOpts)//new FullLogger(Logger.xlog2Log(log)))
def run(resourceBaseDirectory: File, bufferLog: Boolean, sbtVersion: String, defScalaVersion: String, buildScalaVersions: String, tests: Array[String], logger: AbstractLogger, bootProperties: File, launchOpts: Seq[String])
def run(resourceBaseDirectory: File, bufferLog: Boolean, sbtVersion: String, defScalaVersion: String, buildScalaVersions: String, tests: Array[String], logger: AbstractLogger, bootProperties: File, launchOpts: Array[String])
{
val runner = new ScriptedTests(resourceBaseDirectory, bufferLog, sbtVersion, defScalaVersion, buildScalaVersions, bootProperties, launchOpts)
for( ScriptedTest(group, name) <- get(tests, resourceBaseDirectory, logger) )

View File

@ -2,7 +2,7 @@
version: 2.9.1
[app]
org: org.scala-tools.sbt
org: org.scala-sbt
name: sbt
version: read(sbt.version)[0.12.0-SNAPSHOT]
class: ${sbt.main.class-sbt.xMain}
@ -13,8 +13,6 @@
local
typesafe-ivy-releases: http://repo.typesafe.com/typesafe/ivy-releases/, [organization]/[module]/[revision]/[type]s/[artifact](-[classifier]).[ext]
maven-central
scala-tools-releases
scala-tools-snapshots
[ivy]
ivy-home: ${sbt.ivy.home-${user.home}/.ivy2/}

View File

@ -2,7 +2,7 @@
version: 2.9.1
[app]
org: org.scala-tools.sbt
org: org.scala-sbt
name: sbt
version: 0.12.0-SNAPSHOT
class: sbt.ScriptMain
@ -13,5 +13,3 @@
local
typesafe-ivy-releases: http://repo.typesafe.com/typesafe/ivy-releases/, [organization]/[module]/[revision]/[type]s/[artifact](-[classifier]).[ext]
maven-central
scala-tools-releases
scala-tools-snapshots

View File

@ -2,7 +2,7 @@
version: 2.9.1
[app]
org: org.scala-tools.sbt
org: org.scala-sbt
name: sbt
version: 0.12.0-SNAPSHOT
class: sbt.ConsoleMain
@ -13,5 +13,3 @@
local
typesafe-ivy-releases: http://repo.typesafe.com/typesafe/ivy-releases/, [organization]/[module]/[revision]/[type]s/[artifact](-[classifier]).[ext]
maven-central
scala-tools-releases
scala-tools-snapshots

View File

@ -13,7 +13,7 @@ import Incomplete.{Error, Value => IValue}
* @param causes a list of incompletions that prevented `node` from completing
* @param directCause the exception that caused `node` to not complete */
final case class Incomplete(node: Option[AnyRef], tpe: IValue = Error, message: Option[String] = None, causes: Seq[Incomplete] = Nil, directCause: Option[Throwable] = None)
extends Exception(message.orNull, directCause.orNull) {
extends Exception(message.orNull, directCause.orNull) with UnprintableException {
override def toString = "Incomplete(node=" + node + ", tpe=" + tpe + ", msg=" + message + ", causes=" + causes + ", directCause=" + directCause +")"
}

View File

@ -34,6 +34,7 @@ class TestFramework(val implClassName: String)
try { Some(Class.forName(implClassName, true, loader).newInstance.asInstanceOf[Framework]) }
catch { case e: ClassNotFoundException => log.debug("Framework implementation '" + implClassName + "' not present."); None }
}
override def toString = "TestFramework(" + implClassName + ")"
}
final class TestDefinition(val name: String, val fingerprint: Fingerprint)
{

View File

@ -177,7 +177,7 @@ trait Init[Scope]
if(dist < 0) None else Some(dist)
}
final class Uninitialized(val undefined: Seq[Undefined], msg: String) extends Exception(msg)
final class Uninitialized(val undefined: Seq[Undefined], override val toString: String) extends Exception(toString)
final class Undefined(val definingKey: ScopedKey[_], val referencedKey: ScopedKey[_])
final class RuntimeUndefined(val undefined: Seq[Undefined]) extends RuntimeException("References to undefined settings at runtime.")
def Undefined(definingKey: ScopedKey[_], referencedKey: ScopedKey[_]): Undefined = new Undefined(definingKey, referencedKey)

View File

@ -4,4 +4,11 @@
package sbt
final class MessageOnlyException(override val toString: String) extends RuntimeException(toString)
final class NoMessageException extends RuntimeException
/** A dummy exception for the top-level exception handler to know that an exception
* has been handled, but is being passed further up to indicate general failure. */
final class AlreadyHandledException extends RuntimeException
/** A marker trait for a top-level exception handler to know that this exception
* doesn't make sense to display. */
trait UnprintableException extends Throwable

View File

@ -17,6 +17,7 @@ import scala.collection.mutable.{HashMap,HashSet}
import scala.reflect.{Manifest => SManifest}
import Function.tupled
/** A collection of File, URL, and I/O utility methods.*/
object IO
{
/** The maximum number of times a unique temporary filename is attempted to be created.*/
@ -26,21 +27,40 @@ object IO
val temporaryDirectory = new File(System.getProperty("java.io.tmpdir"))
/** The size of the byte or char buffer used in various methods.*/
private val BufferSize = 8192
/** The newline string for this system, as obtained by the line.separator system property. */
val Newline = System.getProperty("line.separator")
val utf8 = Charset.forName("UTF-8")
/** Returns a URL for the directory or jar containing the the class file `cl`.
* If the location cannot be determined, an error is generated.
* Note that Java standard library classes typically do not have a location associated with them.*/
def classLocation(cl: Class[_]): URL =
{
val codeSource = cl.getProtectionDomain.getCodeSource
if(codeSource == null) error("No class location for " + cl)
else codeSource.getLocation
}
/** Returns the directory or jar file containing the the class file `cl`.
* If the location cannot be determined or it is not a file, an error is generated.
* Note that Java standard library classes typically do not have a location associated with them.*/
def classLocationFile(cl: Class[_]): File = toFile(classLocation(cl))
/** Returns a URL for the directory or jar containing the class file for type `T` (as determined by an implicit Manifest).
* If the location cannot be determined, an error is generated.
* Note that Java standard library classes typically do not have a location associated with them.*/
def classLocation[T](implicit mf: SManifest[T]): URL = classLocation(mf.erasure)
/** Returns the directory or jar file containing the the class file for type `T` (as determined by an implicit Manifest).
* If the location cannot be determined, an error is generated.
* Note that Java standard library classes typically do not have a location associated with them.*/
def classLocationFile[T](implicit mf: SManifest[T]): File = classLocationFile(mf.erasure)
def toFile(url: URL) =
/** Constructs a File corresponding to `url`, which must have a scheme of `file`.
* This method properly works around an issue with a simple conversion to URI and then to a File. */
def toFile(url: URL): File =
try { new File(url.toURI) }
catch { case _: URISyntaxException => new File(url.getPath) }
@ -61,6 +81,13 @@ object IO
def assertDirectories(file: File*) { file.foreach(assertDirectory) }
// "base.extension" -> (base, extension)
/** Splits the given string into base and extension strings.
* If `name` contains no period, the base string is the input string and the extension is the empty string.
* Otherwise, the base is the substring up until the last period (exclusive) and
* the extension is the substring after the last period.
*
* For example, `split("Build.scala") == ("Build", "scala")`
*/
def split(name: String): (String, String) =
{
val lastDot = name.lastIndexOf('.')
@ -70,8 +97,13 @@ object IO
(name, "")
}
/** Each input file in `files` is created if it doesn't exist.
* If a file already exists, the last modified time is set to the current time.
* It is not guaranteed that all files will have the same last modified time after this call.*/
def touch(files: Traversable[File]): Unit = files.foreach(f => touch(f))
/** Creates a file at the given location.*/
/** Creates a file at the given location if it doesn't exist.
* If the file already exists and `setModified` is true, this method sets the last modified time to the current time.*/
def touch(file: File, setModified: Boolean = true)
{
val absFile = file.getAbsoluteFile
@ -178,13 +210,16 @@ object IO
transfer(inputStream, to)
}
/** Copies the contents of `in` to `out`.*/
def transfer(in: File, out: File): Unit =
fileInputStream(in){ in => transfer(in, out) }
/** Copies the contents of the input file `in` to the `out` stream.
* The output stream is not closed by this method.*/
def transfer(in: File, out: OutputStream): Unit =
fileInputStream(in){ in => transfer(in, out) }
/** Copies all bytes from the given input stream to the given File.*/
/** Copies all bytes from the given input stream to the given File. The input stream is not closed by this method.*/
def transfer(in: InputStream, to: File): Unit =
Using.fileOutputStream()(to) { outputStream =>
transfer(in, outputStream)
@ -223,7 +258,11 @@ object IO
try { action(dir) }
finally { delete(dir) }
}
/** Creates a directory in the default temporary directory with a name generated from a random integer. */
def createTemporaryDirectory: File = createUniqueDirectory(temporaryDirectory)
/** Creates a directory in `baseDirectory` with a name generated from a random integer */
def createUniqueDirectory(baseDirectory: File): File =
{
def create(tries: Int): File =
@ -241,6 +280,8 @@ object IO
}
create(0)
}
/** Creates a file in the default temporary directory, calls `action` with the file, deletes the file, and returns the result of calling `action`.
* The name of the file will begin with `prefix`, which must be at least three characters long, and end with `postfix`, which has no minimum length. */
def withTemporaryFile[T](prefix: String, postfix: String)(action: File => T): T =
{
val file = File.createTempFile(prefix, postfix)
@ -250,6 +291,7 @@ object IO
private[sbt] def jars(dir: File): Iterable[File] = listFiles(dir, GlobFilter("*.jar"))
/** Deletes all empty directories in the set. Any non-empty directories are ignored. */
def deleteIfEmpty(dirs: collection.Set[File]): Unit =
{
val isEmpty = new HashMap[File, Boolean]
@ -259,7 +301,10 @@ object IO
for( (f, true) <- isEmpty) f.delete
}
/** Deletes each file or directory (recursively) in `files`.*/
def delete(files: Iterable[File]): Unit = files.foreach(delete)
/** Deletes `file`, recursively if it is a directory. */
def delete(file: File)
{
translate("Error deleting file " + file + ": ")
@ -273,26 +318,32 @@ object IO
file.delete
}
}
/** Returns the children of directory `dir` that match `filter` in a non-null array.*/
def listFiles(filter: java.io.FileFilter)(dir: File): Array[File] = wrapNull(dir.listFiles(filter))
/** Returns the children of directory `dir` that match `filter` in a non-null array.*/
def listFiles(dir: File, filter: java.io.FileFilter): Array[File] = wrapNull(dir.listFiles(filter))
/** Returns the children of directory `dir` in a non-null array.*/
def listFiles(dir: File): Array[File] = wrapNull(dir.listFiles())
private[sbt] def wrapNull(a: Array[File]) =
{
if(a == null)
new Array[File](0)
else
a
}
/** Creates a jar file.
* @param sources The files to include in the jar file paired with the entry name in the jar.
* @param sources The files to include in the jar file paired with the entry name in the jar. Only the pairs explicitly listed are included.
* @param outputJar The file to write the jar to.
* @param manifest The manifest for the jar.*/
def jar(sources: Traversable[(File,String)], outputJar: File, manifest: Manifest): Unit =
archive(sources.toSeq, outputJar, Some(manifest))
/** Creates a zip file.
* @param sources The files to include in the zip file paired with the entry name in the zip.
* @param sources The files to include in the zip file paired with the entry name in the zip. Only the pairs explicitly listed are included.
* @param outputZip The file to write the zip to.*/
def zip(sources: Traversable[(File,String)], outputZip: File): Unit =
archive(sources.toSeq, outputZip, None)

View File

@ -139,6 +139,13 @@ sealed abstract class PathFinder
final def \ (literal: String): PathFinder = this / literal
def x_![T](mapper: File => Option[T]): Traversable[(File,T)] = x(mapper, false)
/** Applies `mapper` to each path selected by this PathFinder and returns the path paired with the non-empty result.
* If the result is empty (None) and `errorIfNone` is true, an exception is thrown.
* If `errorIfNone` is false, the path is dropped from the returned Traversable.*/
def pair[T](mapper: File => Option[T], errorIfNone: Boolean = true): Seq[(File,T)] =
x(mapper, errorIfNone)
/** Applies `mapper` to each path selected by this PathFinder and returns the path paired with the non-empty result.
* If the result is empty (None) and `errorIfNone` is true, an exception is thrown.
* If `errorIfNone` is false, the path is dropped from the returned Traversable.*/
@ -154,7 +161,7 @@ sealed abstract class PathFinder
* <code>descendantsExcept("*.jar", ".svn")</code>*/
def descendantsExcept(include: FileFilter, intermediateExclude: FileFilter): PathFinder =
(this ** include) --- (this ** intermediateExclude ** include)
@deprecated("Use `descendantsExcept` instead.", "0.11.3")
@deprecated("Use `descendantsExcept` instead.", "0.12.0")
def descendentsExcept(include: FileFilter, intermediateExclude: FileFilter): PathFinder =
descendantsExcept(include, intermediateExclude)

View File

@ -10,35 +10,62 @@ trait Mapper
type PathMap = File => Option[String]
type FileMap = File => Option[File]
/** A path mapper that pairs a File with the path returned by calling `getPath` on it.*/
val basic: PathMap = f => Some(f.getPath)
/** A path mapper that pairs a File with its path relative to `base`.
* If the File is not a descendant of `base`, it is not handled (None is returned by the mapper). */
def relativeTo(base: File): PathMap = IO.relativize(base, _)
def relativeTo(bases: Iterable[File], zero: PathMap = transparent): PathMap = fold(zero, bases)(relativeTo)
def rebase(oldBase: File, newBase0: String): PathMap =
/** A path mapper that pairs a descendent of `oldBase` with `newBase` prepended to the path relative to `oldBase`.
* For example, if `oldBase = /old/x/` and `newBase = new/a/`, then `/old/x/y/z.txt` gets paired with `new/a/y/z.txt`. */
def rebase(oldBase: File, newBase: String): PathMap =
{
val newBase = normalizeBase(newBase0)
val normNewBase = normalizeBase(newBase)
(file: File) =>
if(file == oldBase)
Some( if(newBase.isEmpty) "." else newBase )
Some( if(normNewBase.isEmpty) "." else normNewBase )
else
IO.relativize(oldBase, file).map(newBase + _)
IO.relativize(oldBase, file).map(normNewBase + _)
}
/** A mapper that throws an exception for any input. This is useful as the last mapper in a pipeline to ensure every input gets mapped.*/
def fail: Any => Nothing = f => error("No mapping for " + f)
/** A path mapper that pairs a File with its name. For example, `/x/y/z.txt` gets paired with `z.txt`.*/
val flat: PathMap = f => Some(f.getName)
def flatRebase(newBase0: String): PathMap =
/** A path mapper that pairs a File with a path constructed from `newBase` and the file's name.
* For example, if `newBase = /new/a/`, then `/old/x/z.txt` gets paired with `/new/a/z.txt`. */
def flatRebase(newBase: String): PathMap =
{
val newBase = normalizeBase(newBase0)
f => Some(newBase + f.getName)
val newBase0 = normalizeBase(newBase)
f => Some(newBase0 + f.getName)
}
/** A mapper that is defined on all inputs by the function `f`.*/
def total[A,B](f: A => B): A => Some[B] = x => Some(f(x))
/** A mapper that ignores all inputs.*/
def transparent: Any => Option[Nothing] = _ => None
def normalizeBase(base: String) = if(!base.isEmpty && !base.endsWith("/")) base + "/" else base
/** Pairs a File with the absolute File obtained by calling `getAbsoluteFile`.
* Note that this usually means that relative files are resolved against the current working directory.*/
def abs: FileMap = f => Some(f.getAbsoluteFile)
def resolve(newDirectory: File): FileMap = file => Some(new File(newDirectory, file.getPath))
/** Returns a File mapper that resolves a relative File against `newDirectory` and pairs the original File with the resolved File.
* The mapper ignores absolute files. */
def resolve(newDirectory: File): FileMap = file => if(file.isAbsolute) None else Some(new File(newDirectory, file.getPath))
def rebase(oldBases: Iterable[File], newBase: File, zero: FileMap = transparent): FileMap =
fold(zero, oldBases)(old => rebase(old, newBase))
/** Produces a File mapper that pairs a descendant of `oldBase` with a file in `newBase` that preserving the relative path of the original file against `oldBase`.
* For example, if `oldBase` is `/old/x/` and `newBase` is `/new/a/`, `/old/x/y/z.txt` gets paired with `/new/a/y/z.txt`.
* */
def rebase(oldBase: File, newBase: File): FileMap =
file =>
if(file == oldBase)
@ -46,9 +73,22 @@ trait Mapper
else
IO.relativize(oldBase, file) map { r => new File(newBase, r) }
/** Constructs a File mapper that pairs a file with a file with the same name in `newDirectory`.
* For example, if `newDirectory` is `/a/b`, then `/r/s/t/d.txt` will be paired with `/a/b/d.txt`*/
def flat(newDirectory: File): FileMap = file => Some(new File(newDirectory, file.getName))
import Alternatives._
/** Selects all descendents of `base` directory and maps them to a path relative to `base`.
* `base` itself is not included. */
def allSubpaths(base: File): Traversable[(File,String)] =
selectSubpaths(base, AllPassFilter)
/** Selects descendents of `base` directory matching `filter` and maps them to a path relative to `base`.
* `base` itself is not included. */
def selectSubpaths(base: File, filter: FileFilter): Traversable[(File,String)] =
(PathFinder(base) ** filter --- PathFinder(base)) pair (relativeTo(base)|flat)
private[this] def fold[A,B,T](zero: A => Option[B], in: Iterable[T])(f: T => A => Option[B]): A => Option[B] =
(zero /: in)( (mapper, base) => f(base) | mapper )
}

View File

@ -0,0 +1,27 @@
/* sbt -- Simple Build Tool
* Copyright 2010 Mark Harrah
*/
package sbt
import java.io.{File, PrintWriter}
final case class GlobalLogging(full: Logger, backed: ConsoleLogger, backing: GlobalLogBacking)
final case class GlobalLogBacking(file: File, last: Option[File], newLogger: (PrintWriter, GlobalLogBacking) => GlobalLogging, newBackingFile: () => File)
{
def shift(newFile: File) = GlobalLogBacking(newFile, Some(file), newLogger, newBackingFile)
def shiftNew() = shift(newBackingFile())
def unshift = GlobalLogBacking(last getOrElse file, None, newLogger, newBackingFile)
}
object GlobalLogBacking
{
def apply(newLogger: (PrintWriter, GlobalLogBacking) => GlobalLogging, newBackingFile: => File): GlobalLogBacking =
GlobalLogBacking(newBackingFile, None, newLogger, newBackingFile _)
}
object GlobalLogging
{
def initial(newLogger: (PrintWriter, GlobalLogBacking) => GlobalLogging, newBackingFile: => File): GlobalLogging =
{
val log = ConsoleLogger()
GlobalLogging(log, log, GlobalLogBacking(newLogger, newBackingFile))
}
}

View File

@ -0,0 +1,36 @@
package sbt
import java.io.PrintWriter
object MainLogging
{
def multiLogger(config: MultiLoggerConfig): Logger =
{
import config._
val multi = new MultiLogger(console :: backed :: extra)
// sets multi to the most verbose for clients that inspect the current level
multi setLevel Level.unionAll(backingLevel :: screenLevel :: extra.map(_.getLevel))
// set the specific levels
console setLevel screenLevel
backed setLevel backingLevel
console setTrace screenTrace
backed setTrace backingTrace
multi: Logger
}
def globalDefault(writer: PrintWriter, backing: GlobalLogBacking): GlobalLogging =
{
val backed = defaultBacked()(writer)
val full = multiLogger(defaultMultiConfig( backed ) )
GlobalLogging(full, backed, backing)
}
def defaultMultiConfig(backing: AbstractLogger): MultiLoggerConfig =
new MultiLoggerConfig(defaultScreen, backing, Nil, Level.Info, Level.Debug, -1, Int.MaxValue)
def defaultScreen: AbstractLogger = ConsoleLogger()
def defaultBacked(useColor: Boolean = ConsoleLogger.formatEnabled): PrintWriter => ConsoleLogger =
to => ConsoleLogger(ConsoleLogger.printWriterOut(to), useColor = useColor) // TODO: should probably filter ANSI codes when useColor=false
}
final case class MultiLoggerConfig(console: AbstractLogger, backed: AbstractLogger, extra: List[AbstractLogger], screenLevel: Level.Value, backingLevel: Level.Value, screenTrace: Int, backingTrace: Int)