From 2255bd5a6299515033e06a87565fc294b2d3637f Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Sun, 22 Jan 2012 22:06:52 -0500 Subject: [PATCH 01/14] org.scala-tools.sbt -> org.scala-sbt and dropping scala-tools.org from defaults --- ivy/ComponentManager.scala | 2 +- ivy/ConflictWarning.scala | 2 +- ivy/IvyInterface.scala | 3 +-- ivy/IvyScala.scala | 4 ++++ launch/BootConfiguration.scala | 2 +- launch/ConfigurationParser.scala | 2 +- launch/src/main/input_resources/sbt/sbt.boot.properties | 2 -- launch/src/test/scala/ScalaProviderTest.scala | 2 +- main/GlobalPlugin.scala | 2 +- project/Sbt.scala | 2 +- sbt/src/sbt-test/dependency-management/extra/DefineColor.sbt | 2 +- .../sbt-test/dependency-management/extra/changes/UseColor.sbt | 2 +- sbt/src/sbt-test/project/src-plugins/plugin/build.sbt | 2 +- scripted/plugin/ScriptedPlugin.scala | 2 +- src/main/conscript/sbt/launchconfig | 4 +--- src/main/conscript/scalas/launchconfig | 4 +--- src/main/conscript/screpl/launchconfig | 4 +--- 17 files changed, 19 insertions(+), 24 deletions(-) diff --git a/ivy/ComponentManager.scala b/ivy/ComponentManager.scala index 32979ab3b..de87a9890 100644 --- a/ivy/ComponentManager.scala +++ b/ivy/ComponentManager.scala @@ -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) } diff --git a/ivy/ConflictWarning.scala b/ivy/ConflictWarning.scala index 738ab50fc..befe4abd6 100644 --- a/ivy/ConflictWarning.scala +++ b/ivy/ConflictWarning.scala @@ -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) { diff --git a/ivy/IvyInterface.scala b/ivy/IvyInterface.scala index 7bf72b0dc..8d921c2ec 100644 --- a/ivy/IvyInterface.scala +++ b/ivy/IvyInterface.scala @@ -199,8 +199,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. */ diff --git a/ivy/IvyScala.scala b/ivy/IvyScala.scala index 9926f7141..54f95ccf6 100644 --- a/ivy/IvyScala.scala +++ b/ivy/IvyScala.scala @@ -19,6 +19,10 @@ object ScalaArtifacts val CompilerID = "scala-compiler" def libraryDependency(version: String): ModuleID = ModuleID(Organization, LibraryID, version) } +object SbtArtifacts +{ + val Organization = "org.scala-sbt" +} import ScalaArtifacts._ diff --git a/launch/BootConfiguration.scala b/launch/BootConfiguration.scala index 3257df0d2..f2dda2d5d 100644 --- a/launch/BootConfiguration.scala +++ b/launch/BootConfiguration.scala @@ -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" diff --git a/launch/ConfigurationParser.scala b/launch/ConfigurationParser.scala index b4e54046f..0caf3c346 100644 --- a/launch/ConfigurationParser.scala +++ b/launch/ConfigurationParser.scala @@ -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") diff --git a/launch/src/main/input_resources/sbt/sbt.boot.properties b/launch/src/main/input_resources/sbt/sbt.boot.properties index fb2274de2..1b71a3702 100644 --- a/launch/src/main/input_resources/sbt/sbt.boot.properties +++ b/launch/src/main/input_resources/sbt/sbt.boot.properties @@ -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/} diff --git a/launch/src/test/scala/ScalaProviderTest.scala b/launch/src/test/scala/ScalaProviderTest.scala index 810e551ae..fd6823c27 100644 --- a/launch/src/test/scala/ScalaProviderTest.scala +++ b/launch/src/test/scala/ScalaProviderTest.scala @@ -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 = diff --git a/main/GlobalPlugin.scala b/main/GlobalPlugin.scala index 7cc554c69..b0fae7a35 100644 --- a/main/GlobalPlugin.scala +++ b/main/GlobalPlugin.scala @@ -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, diff --git a/project/Sbt.scala b/project/Sbt.scala index 9bcbe7925..b99bcc8d3 100644 --- a/project/Sbt.scala +++ b/project/Sbt.scala @@ -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", diff --git a/sbt/src/sbt-test/dependency-management/extra/DefineColor.sbt b/sbt/src/sbt-test/dependency-management/extra/DefineColor.sbt index b84a7f219..1c02cbcf5 100644 --- a/sbt/src/sbt-test/dependency-management/extra/DefineColor.sbt +++ b/sbt/src/sbt-test/dependency-management/extra/DefineColor.sbt @@ -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" diff --git a/sbt/src/sbt-test/dependency-management/extra/changes/UseColor.sbt b/sbt/src/sbt-test/dependency-management/extra/changes/UseColor.sbt index d7aff5e2e..3f87d7383 100644 --- a/sbt/src/sbt-test/dependency-management/extra/changes/UseColor.sbt +++ b/sbt/src/sbt-test/dependency-management/extra/changes/UseColor.sbt @@ -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 } diff --git a/sbt/src/sbt-test/project/src-plugins/plugin/build.sbt b/sbt/src/sbt-test/project/src-plugins/plugin/build.sbt index 0946055d0..705437046 100644 --- a/sbt/src/sbt-test/project/src-plugins/plugin/build.sbt +++ b/sbt/src/sbt-test/project/src-plugins/plugin/build.sbt @@ -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) } \ No newline at end of file diff --git a/scripted/plugin/ScriptedPlugin.scala b/scripted/plugin/ScriptedPlugin.scala index c94f8bded..3087b9495 100644 --- a/scripted/plugin/ScriptedPlugin.scala +++ b/scripted/plugin/ScriptedPlugin.scala @@ -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, diff --git a/src/main/conscript/sbt/launchconfig b/src/main/conscript/sbt/launchconfig index 66002f9da..a91393272 100644 --- a/src/main/conscript/sbt/launchconfig +++ b/src/main/conscript/sbt/launchconfig @@ -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/} diff --git a/src/main/conscript/scalas/launchconfig b/src/main/conscript/scalas/launchconfig index 4a373d19e..149c98163 100644 --- a/src/main/conscript/scalas/launchconfig +++ b/src/main/conscript/scalas/launchconfig @@ -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 diff --git a/src/main/conscript/screpl/launchconfig b/src/main/conscript/screpl/launchconfig index 5ff7415e7..d8570e91f 100644 --- a/src/main/conscript/screpl/launchconfig +++ b/src/main/conscript/screpl/launchconfig @@ -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 From 5e155900da9b0ad549405bf0e1b0953006385e68 Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Sun, 22 Jan 2012 22:06:53 -0500 Subject: [PATCH 02/14] rework cross versioning to account for prerelease Scala versions --- ivy/CrossVersion.scala | 94 +++++++++++++++++++ ivy/DependencyBuilders.scala | 19 ++-- ivy/Ivy.scala | 21 ++--- ivy/IvyActions.scala | 18 ++-- ivy/IvyInterface.scala | 17 +++- ivy/IvyScala.scala | 13 +-- main/Defaults.scala | 63 ++++++------- main/IvyConsole.scala | 4 +- main/Keys.scala | 3 +- main/actions/CacheIvy.scala | 19 +++- .../info/project/InfoTest.scala | 5 +- .../inline-dependencies-a/build.sbt | 4 +- .../project/build.properties | 4 - .../project/build/src/UpdateTestProject.scala | 8 -- .../make-pom/project/MakePomTest.scala | 1 + .../module-confs/Test.sbt | 2 +- .../module-confs/changes/WrongOrg.sbt | 2 +- .../module-confs/changes/WrongPattern.sbt | 2 +- .../module-confs/changes/WrongVersion.sbt | 2 +- .../parent-publish/project/Parent.scala | 6 +- .../pom-advanced/project/PomRepoTest.scala | 2 +- .../provided-multi/changes/P.scala | 16 +++- .../transitive-plugins/project/Build.scala | 4 + 23 files changed, 225 insertions(+), 104 deletions(-) create mode 100644 ivy/CrossVersion.scala delete mode 100644 sbt/src/sbt-test/dependency-management/inline-dependencies-a/project/build.properties delete mode 100644 sbt/src/sbt-test/dependency-management/inline-dependencies-a/project/build/src/UpdateTestProject.scala diff --git a/ivy/CrossVersion.scala b/ivy/CrossVersion.scala new file mode 100644 index 000000000..59b51c795 --- /dev/null +++ b/ivy/CrossVersion.scala @@ -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 + } + } +} + diff --git a/ivy/DependencyBuilders.scala b/ivy/DependencyBuilders.scala index b878f206c..7d5812aaf 100755 --- a/ivy/DependencyBuilders.scala +++ b/ivy/DependencyBuilders.scala @@ -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) diff --git a/ivy/Ivy.scala b/ivy/Ivy.scala index 19252fb28..400490086 100644 --- a/ivy/Ivy.scala +++ b/ivy/Ivy.scala @@ -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 = { diff --git a/ivy/IvyActions.scala b/ivy/IvyActions.scala index 1cabd0a31..059d48997 100644 --- a/ivy/IvyActions.scala +++ b/ivy/IvyActions.scala @@ -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 diff --git a/ivy/IvyInterface.scala b/ivy/IvyInterface.scala index 8d921c2ec..d5ffc4d6a 100644 --- a/ivy/IvyInterface.scala +++ b/ivy/IvyInterface.scala @@ -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) @@ -418,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) diff --git a/ivy/IvyScala.scala b/ivy/IvyScala.scala index 54f95ccf6..647df693f 100644 --- a/ivy/IvyScala.scala +++ b/ivy/IvyScala.scala @@ -26,7 +26,7 @@ object SbtArtifacts 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 { @@ -34,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) { @@ -54,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 diff --git a/main/Defaults.scala b/main/Defaults.scala index 99e51b8ba..720928af6 100644 --- a/main/Defaults.scala +++ b/main/Defaults.scala @@ -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) ) @@ -379,7 +386,10 @@ object Defaults extends BuildCommon 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 +521,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" @@ -597,25 +608,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 +736,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 +789,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) => diff --git a/main/IvyConsole.scala b/main/IvyConsole.scala index d974c82fe..9cdf09725 100644 --- a/main/IvyConsole.scala +++ b/main/IvyConsole.scala @@ -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 } } diff --git a/main/Keys.scala b/main/Keys.scala index c3e767ab9..ae875d36b 100644 --- a/main/Keys.scala +++ b/main/Keys.scala @@ -138,6 +138,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 +166,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.") diff --git a/main/actions/CacheIvy.scala b/main/actions/CacheIvy.scala index 528bd7e1b..3a5c74b89 100644 --- a/main/actions/CacheIvy.scala +++ b/main/actions/CacheIvy.scala @@ -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 diff --git a/sbt/src/sbt-test/dependency-management/info/project/InfoTest.scala b/sbt/src/sbt-test/dependency-management/info/project/InfoTest.scala index e491e3ea0..a3562c68c 100644 --- a/sbt/src/sbt-test/dependency-management/info/project/InfoTest.scala +++ b/sbt/src/sbt-test/dependency-management/info/project/InfoTest.scala @@ -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. - ) + ) else - + def checkDownload = (dependencyClasspath in Compile) map { cp => if(cp.isEmpty) error("Dependency not downloaded"); () } def checkInfo = (customInfo, delivered) map { (addInfo, d) => diff --git a/sbt/src/sbt-test/dependency-management/inline-dependencies-a/build.sbt b/sbt/src/sbt-test/dependency-management/inline-dependencies-a/build.sbt index 32c57329e..c07e5707e 100644 --- a/sbt/src/sbt-test/dependency-management/inline-dependencies-a/build.sbt +++ b/sbt/src/sbt-test/dependency-management/inline-dependencies-a/build.sbt @@ -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(", ")) -} \ No newline at end of file +} diff --git a/sbt/src/sbt-test/dependency-management/inline-dependencies-a/project/build.properties b/sbt/src/sbt-test/dependency-management/inline-dependencies-a/project/build.properties deleted file mode 100644 index 2a3934bd8..000000000 --- a/sbt/src/sbt-test/dependency-management/inline-dependencies-a/project/build.properties +++ /dev/null @@ -1,4 +0,0 @@ -#Project properties -#Fri Jan 30 20:49:57 EST 2009 -project.name=Inline Dependency Test A -project.version=1.0 diff --git a/sbt/src/sbt-test/dependency-management/inline-dependencies-a/project/build/src/UpdateTestProject.scala b/sbt/src/sbt-test/dependency-management/inline-dependencies-a/project/build/src/UpdateTestProject.scala deleted file mode 100644 index 8f7323f9e..000000000 --- a/sbt/src/sbt-test/dependency-management/inline-dependencies-a/project/build/src/UpdateTestProject.scala +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/sbt/src/sbt-test/dependency-management/make-pom/project/MakePomTest.scala b/sbt/src/sbt-test/dependency-management/make-pom/project/MakePomTest.scala index df2fe56e4..60f39d2f0 100644 --- a/sbt/src/sbt-test/dependency-management/make-pom/project/MakePomTest.scala +++ b/sbt/src/sbt-test/dependency-management/make-pom/project/MakePomTest.scala @@ -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, diff --git a/sbt/src/sbt-test/dependency-management/module-confs/Test.sbt b/sbt/src/sbt-test/dependency-management/module-confs/Test.sbt index 6915c14a5..1a5aca7db 100644 --- a/sbt/src/sbt-test/dependency-management/module-confs/Test.sbt +++ b/sbt/src/sbt-test/dependency-management/module-confs/Test.sbt @@ -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 \ No newline at end of file diff --git a/sbt/src/sbt-test/dependency-management/module-confs/changes/WrongOrg.sbt b/sbt/src/sbt-test/dependency-management/module-confs/changes/WrongOrg.sbt index 887396f7d..6c9395ba8 100644 --- a/sbt/src/sbt-test/dependency-management/module-confs/changes/WrongOrg.sbt +++ b/sbt/src/sbt-test/dependency-management/module-confs/changes/WrongOrg.sbt @@ -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 \ No newline at end of file diff --git a/sbt/src/sbt-test/dependency-management/module-confs/changes/WrongPattern.sbt b/sbt/src/sbt-test/dependency-management/module-confs/changes/WrongPattern.sbt index 10bb7b25d..488cafab1 100644 --- a/sbt/src/sbt-test/dependency-management/module-confs/changes/WrongPattern.sbt +++ b/sbt/src/sbt-test/dependency-management/module-confs/changes/WrongPattern.sbt @@ -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 \ No newline at end of file diff --git a/sbt/src/sbt-test/dependency-management/module-confs/changes/WrongVersion.sbt b/sbt/src/sbt-test/dependency-management/module-confs/changes/WrongVersion.sbt index f9946aa8e..ee1d7ed86 100644 --- a/sbt/src/sbt-test/dependency-management/module-confs/changes/WrongVersion.sbt +++ b/sbt/src/sbt-test/dependency-management/module-confs/changes/WrongVersion.sbt @@ -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 \ No newline at end of file diff --git a/sbt/src/sbt-test/dependency-management/parent-publish/project/Parent.scala b/sbt/src/sbt-test/dependency-management/parent-publish/project/Parent.scala index bf204ab00..9dd7af242 100644 --- a/sbt/src/sbt-test/dependency-management/parent-publish/project/Parent.scala +++ b/sbt/src/sbt-test/dependency-management/parent-publish/project/Parent.scala @@ -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) } diff --git a/sbt/src/sbt-test/dependency-management/pom-advanced/project/PomRepoTest.scala b/sbt/src/sbt-test/dependency-management/pom-advanced/project/PomRepoTest.scala index 9bb920406..7f70d4007 100644 --- a/sbt/src/sbt-test/dependency-management/pom-advanced/project/PomRepoTest.scala +++ b/sbt/src/sbt-test/dependency-management/pom-advanced/project/PomRepoTest.scala @@ -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("")) { result => (makePom, result, streams) map checkPomRepositories }, makePomConfiguration <<= (makePomConfiguration, baseDirectory) { (conf, base) => conf.copy(filterRepositories = pomIncludeRepository(base, conf.filterRepositories) ) diff --git a/sbt/src/sbt-test/dependency-management/provided-multi/changes/P.scala b/sbt/src/sbt-test/dependency-management/provided-multi/changes/P.scala index aa4d3a351..065d6fef6 100644 --- a/sbt/src/sbt-test/dependency-management/provided-multi/changes/P.scala +++ b/sbt/src/sbt-test/dependency-management/provided-multi/changes/P.scala @@ -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 ) -} \ No newline at end of file +} \ No newline at end of file diff --git a/sbt/src/sbt-test/project/transitive-plugins/project/Build.scala b/sbt/src/sbt-test/project/transitive-plugins/project/Build.scala index 5b0cf40e4..1c5482930 100644 --- a/sbt/src/sbt-test/project/transitive-plugins/project/Build.scala +++ b/sbt/src/sbt-test/project/transitive-plugins/project/Build.scala @@ -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")) From 7e71ab7c3dd43aa1dc12c7dd32f6c6c21188a605 Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Sun, 22 Jan 2012 22:06:53 -0500 Subject: [PATCH 03/14] fix forward aggregation enabled check --- main/Aggregation.scala | 2 +- sbt/src/sbt-test/actions/aggregate/test | 22 ++++------------------ 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/main/Aggregation.scala b/main/Aggregation.scala index 831e16eff..839398924 100644 --- a/main/Aggregation.scala +++ b/main/Aggregation.scala @@ -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 diff --git a/sbt/src/sbt-test/actions/aggregate/test b/sbt/src/sbt-test/actions/aggregate/test index 290bcfe74..af3e2a5d8 100644 --- a/sbt/src/sbt-test/actions/aggregate/test +++ b/sbt/src/sbt-test/actions/aggregate/test @@ -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 From 30cca3a6d0013b192c96eab250f7ea2f1cb7d62b Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Sun, 22 Jan 2012 22:06:53 -0500 Subject: [PATCH 04/14] fix launcher options addition to use array for cross-version compatibility --- project/Sbt.scala | 4 ++-- scripted/sbt/ScriptedTests.scala | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/project/Sbt.scala b/project/Sbt.scala index b99bcc8d3..fef29065a 100644 --- a/project/Sbt.scala +++ b/project/Sbt.scala @@ -137,8 +137,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 } } } diff --git a/scripted/sbt/ScriptedTests.scala b/scripted/sbt/ScriptedTests.scala index c22925e42..91a132ac6 100644 --- a/scripted/sbt/ScriptedTests.scala +++ b/scripted/sbt/ScriptedTests.scala @@ -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) ) From 048d5157c77461d11a78975f5cbd89d208624f20 Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Sun, 22 Jan 2012 22:06:53 -0500 Subject: [PATCH 05/14] fix integration test group 'tests' --- sbt/src/sbt-test/tests/arguments/build.sbt | 3 ++- .../sbt-test/tests/it/changes/ClassFailModuleFail.scala | 2 +- .../sbt-test/tests/it/changes/ClassFailModuleSuccess.scala | 2 +- .../sbt-test/tests/it/changes/ClassSuccessModuleFail.scala | 2 +- .../tests/it/changes/ClassSuccessModuleSuccess.scala | 2 +- sbt/src/sbt-test/tests/it/project/B.scala | 7 +++++-- sbt/src/sbt-test/tests/resources/build.sbt | 4 +++- .../tests/resources/src/test/scala/BasicTest.scala | 6 +++--- sbt/src/sbt-test/tests/specs-run/build.sbt | 4 +++- .../tests/specs-run/changes/ClassFailModuleFail.scala | 2 +- .../tests/specs-run/changes/ClassFailModuleSuccess.scala | 2 +- .../tests/specs-run/changes/ClassSuccessModuleFail.scala | 2 +- .../specs-run/changes/ClassSuccessModuleSuccess.scala | 2 +- 13 files changed, 24 insertions(+), 16 deletions(-) diff --git a/sbt/src/sbt-test/tests/arguments/build.sbt b/sbt/src/sbt-test/tests/arguments/build.sbt index 5ec40bea1..027d01f03 100644 --- a/sbt/src/sbt-test/tests/arguments/build.sbt +++ b/sbt/src/sbt-test/tests/arguments/build.sbt @@ -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 diff --git a/sbt/src/sbt-test/tests/it/changes/ClassFailModuleFail.scala b/sbt/src/sbt-test/tests/it/changes/ClassFailModuleFail.scala index 9edb63d32..167d25100 100644 --- a/sbt/src/sbt-test/tests/it/changes/ClassFailModuleFail.scala +++ b/sbt/src/sbt-test/tests/it/changes/ClassFailModuleFail.scala @@ -1,4 +1,4 @@ -import org.specs._ +import org.specs2.mutable._ class B extends Specification { diff --git a/sbt/src/sbt-test/tests/it/changes/ClassFailModuleSuccess.scala b/sbt/src/sbt-test/tests/it/changes/ClassFailModuleSuccess.scala index c82a24321..47d17d531 100644 --- a/sbt/src/sbt-test/tests/it/changes/ClassFailModuleSuccess.scala +++ b/sbt/src/sbt-test/tests/it/changes/ClassFailModuleSuccess.scala @@ -1,4 +1,4 @@ -import org.specs._ +import org.specs2.mutable._ class B extends Specification { diff --git a/sbt/src/sbt-test/tests/it/changes/ClassSuccessModuleFail.scala b/sbt/src/sbt-test/tests/it/changes/ClassSuccessModuleFail.scala index 97f8da28b..7d1fc3ae6 100644 --- a/sbt/src/sbt-test/tests/it/changes/ClassSuccessModuleFail.scala +++ b/sbt/src/sbt-test/tests/it/changes/ClassSuccessModuleFail.scala @@ -1,4 +1,4 @@ -import org.specs._ +import org.specs2.mutable._ class B extends Specification { diff --git a/sbt/src/sbt-test/tests/it/changes/ClassSuccessModuleSuccess.scala b/sbt/src/sbt-test/tests/it/changes/ClassSuccessModuleSuccess.scala index 04302ad2c..353ebbdca 100644 --- a/sbt/src/sbt-test/tests/it/changes/ClassSuccessModuleSuccess.scala +++ b/sbt/src/sbt-test/tests/it/changes/ClassSuccessModuleSuccess.scala @@ -1,4 +1,4 @@ -import org.specs._ +import org.specs2.mutable._ class B extends Specification { diff --git a/sbt/src/sbt-test/tests/it/project/B.scala b/sbt/src/sbt-test/tests/it/project/B.scala index e576dbe8f..88fca7dce 100644 --- a/sbt/src/sbt-test/tests/it/project/B.scala +++ b/sbt/src/sbt-test/tests/it/project/B.scala @@ -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" } \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/resources/build.sbt b/sbt/src/sbt-test/tests/resources/build.sbt index c05ea491e..c2642043e 100644 --- a/sbt/src/sbt-test/tests/resources/build.sbt +++ b/sbt/src/sbt-test/tests/resources/build.sbt @@ -1 +1,3 @@ -libraryDependencies += "org.scala-tools.testing" %% "specs" % "1.6.7.2" intransitive() \ No newline at end of file +libraryDependencies += "org.specs2" %% "specs2" % "1.7.1" % "test" + +resolvers += ScalaToolsReleases \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/resources/src/test/scala/BasicTest.scala b/sbt/src/sbt-test/tests/resources/src/test/scala/BasicTest.scala index 9d2540347..9956f1905 100644 --- a/sbt/src/sbt-test/tests/resources/src/test/scala/BasicTest.scala +++ b/sbt/src/sbt-test/tests/resources/src/test/scala/BasicTest.scala @@ -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 } } \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/specs-run/build.sbt b/sbt/src/sbt-test/tests/specs-run/build.sbt index 1223cc386..8f2e4cc56 100644 --- a/sbt/src/sbt-test/tests/specs-run/build.sbt +++ b/sbt/src/sbt-test/tests/specs-run/build.sbt @@ -1 +1,3 @@ -libraryDependencies += "org.scala-tools.testing" %% "specs" % "1.6.7.2" intransitive() +libraryDependencies += "org.specs2" %% "specs2" % "1.7.1" % "test" + +resolvers += ScalaToolsReleases diff --git a/sbt/src/sbt-test/tests/specs-run/changes/ClassFailModuleFail.scala b/sbt/src/sbt-test/tests/specs-run/changes/ClassFailModuleFail.scala index 7004fd0bf..9b708ef50 100644 --- a/sbt/src/sbt-test/tests/specs-run/changes/ClassFailModuleFail.scala +++ b/sbt/src/sbt-test/tests/specs-run/changes/ClassFailModuleFail.scala @@ -1,4 +1,4 @@ -import org.specs._ +import org.specs2.mutable._ class B extends Specification { diff --git a/sbt/src/sbt-test/tests/specs-run/changes/ClassFailModuleSuccess.scala b/sbt/src/sbt-test/tests/specs-run/changes/ClassFailModuleSuccess.scala index 3a54eca86..814bd6cc1 100644 --- a/sbt/src/sbt-test/tests/specs-run/changes/ClassFailModuleSuccess.scala +++ b/sbt/src/sbt-test/tests/specs-run/changes/ClassFailModuleSuccess.scala @@ -1,4 +1,4 @@ -import org.specs._ +import org.specs2.mutable._ class B extends Specification { diff --git a/sbt/src/sbt-test/tests/specs-run/changes/ClassSuccessModuleFail.scala b/sbt/src/sbt-test/tests/specs-run/changes/ClassSuccessModuleFail.scala index 97f8da28b..7d1fc3ae6 100644 --- a/sbt/src/sbt-test/tests/specs-run/changes/ClassSuccessModuleFail.scala +++ b/sbt/src/sbt-test/tests/specs-run/changes/ClassSuccessModuleFail.scala @@ -1,4 +1,4 @@ -import org.specs._ +import org.specs2.mutable._ class B extends Specification { diff --git a/sbt/src/sbt-test/tests/specs-run/changes/ClassSuccessModuleSuccess.scala b/sbt/src/sbt-test/tests/specs-run/changes/ClassSuccessModuleSuccess.scala index 04302ad2c..353ebbdca 100644 --- a/sbt/src/sbt-test/tests/specs-run/changes/ClassSuccessModuleSuccess.scala +++ b/sbt/src/sbt-test/tests/specs-run/changes/ClassSuccessModuleSuccess.scala @@ -1,4 +1,4 @@ -import org.specs._ +import org.specs2.mutable._ class B extends Specification { From f092fb35c91807d03bbbedada4d65a2b8870adeb Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Wed, 25 Jan 2012 17:29:51 -0500 Subject: [PATCH 06/14] When *::main-class is set, use its value for run::main-class --- main/Defaults.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/Defaults.scala b/main/Defaults.scala index 720928af6..ed48c0a0f 100644 --- a/main/Defaults.scala +++ b/main/Defaults.scala @@ -218,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), From f55d34f6177b2ba6fae5cb105fcb70bbdf0aa1eb Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Thu, 26 Jan 2012 21:28:19 -0500 Subject: [PATCH 07/14] Add Path.allSubpaths and API documentation for mappers --- main/Defaults.scala | 12 ++++----- util/io/Path.scala | 9 ++++++- util/io/PathMapper.scala | 56 ++++++++++++++++++++++++++++++++++------ 3 files changed, 61 insertions(+), 16 deletions(-) diff --git a/main/Defaults.scala b/main/Defaults.scala index ed48c0a0f..ce630aad6 100644 --- a/main/Defaults.scala +++ b/main/Defaults.scala @@ -364,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)]]] @@ -375,12 +373,12 @@ 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]]] = @@ -548,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 diff --git a/util/io/Path.scala b/util/io/Path.scala index 0d8f3a321..bacecd2b0 100644 --- a/util/io/Path.scala +++ b/util/io/Path.scala @@ -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 * descendantsExcept("*.jar", ".svn")*/ 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) diff --git a/util/io/PathMapper.scala b/util/io/PathMapper.scala index c59783385..e6b74f0a7 100644 --- a/util/io/PathMapper.scala +++ b/util/io/PathMapper.scala @@ -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 ) } From d82757f595082b4b44e0f6fcb46bc1df084fda9f Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Thu, 26 Jan 2012 21:28:19 -0500 Subject: [PATCH 08/14] more IO API documentation --- util/io/IO.scala | 65 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 7 deletions(-) diff --git a/util/io/IO.scala b/util/io/IO.scala index deccf50bd..eb478333b 100644 --- a/util/io/IO.scala +++ b/util/io/IO.scala @@ -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) From 26ec82ed733cf06824c9bc5f4afc83729a7e51ff Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Fri, 27 Jan 2012 21:09:11 -0500 Subject: [PATCH 09/14] Append instances for Set and Map --- main/Append.scala | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/main/Append.scala b/main/Append.scala index 8278b544a..1e42b5ebf 100644 --- a/main/Append.scala +++ b/main/Append.scala @@ -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 + } } \ No newline at end of file From 938ca25951f53535a83683ef220304705eb2f78d Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Fri, 27 Jan 2012 21:09:11 -0500 Subject: [PATCH 10/14] basic test for junit integration --- sbt/src/sbt-test/tests/junit/build.sbt | 3 +++ sbt/src/sbt-test/tests/junit/changes/Failure.scala | 8 ++++++++ sbt/src/sbt-test/tests/junit/changes/Success.scala | 8 ++++++++ sbt/src/sbt-test/tests/junit/test | 11 +++++++++++ 4 files changed, 30 insertions(+) create mode 100644 sbt/src/sbt-test/tests/junit/build.sbt create mode 100644 sbt/src/sbt-test/tests/junit/changes/Failure.scala create mode 100644 sbt/src/sbt-test/tests/junit/changes/Success.scala create mode 100644 sbt/src/sbt-test/tests/junit/test diff --git a/sbt/src/sbt-test/tests/junit/build.sbt b/sbt/src/sbt-test/tests/junit/build.sbt new file mode 100644 index 000000000..75cc99d4a --- /dev/null +++ b/sbt/src/sbt-test/tests/junit/build.sbt @@ -0,0 +1,3 @@ +resolvers += ScalaToolsReleases + +libraryDependencies += "com.novocode" % "junit-interface" % "0.8" % "test" diff --git a/sbt/src/sbt-test/tests/junit/changes/Failure.scala b/sbt/src/sbt-test/tests/junit/changes/Failure.scala new file mode 100644 index 000000000..10097c776 --- /dev/null +++ b/sbt/src/sbt-test/tests/junit/changes/Failure.scala @@ -0,0 +1,8 @@ +package com.foo.junit.test.blah + +import org.junit._ + +class Failure +{ + @Test def fail() { error("Fail!") } +} diff --git a/sbt/src/sbt-test/tests/junit/changes/Success.scala b/sbt/src/sbt-test/tests/junit/changes/Success.scala new file mode 100644 index 000000000..e8a088f92 --- /dev/null +++ b/sbt/src/sbt-test/tests/junit/changes/Success.scala @@ -0,0 +1,8 @@ +package com.foo.junit.test.blah + +import org.junit._ + +class Success +{ + @Test def succeed() { } +} diff --git a/sbt/src/sbt-test/tests/junit/test b/sbt/src/sbt-test/tests/junit/test new file mode 100644 index 000000000..ef48c9d40 --- /dev/null +++ b/sbt/src/sbt-test/tests/junit/test @@ -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 From bf472b8ff406d76a10780df50a0bc4f6e43774ea Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Fri, 27 Jan 2012 21:09:11 -0500 Subject: [PATCH 11/14] TestFramework.toString --- testing/TestFramework.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/testing/TestFramework.scala b/testing/TestFramework.scala index 1e411295c..15281ecfa 100644 --- a/testing/TestFramework.scala +++ b/testing/TestFramework.scala @@ -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) { From 98c98f9c2689cd2ee6bf7e9b8e116eb83cccf73b Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Sun, 29 Jan 2012 14:36:27 -0500 Subject: [PATCH 12/14] split command core to main/command/ --- main/Act.scala | 2 +- ...mandSupport.scala => CommandStrings.scala} | 202 ++-------- main/IvyConsole.scala | 2 +- main/Keys.scala | 7 +- main/Load.scala | 4 +- main/LogManager.scala | 38 +- main/Main.scala | 370 ++---------------- main/Project.scala | 2 +- main/Script.scala | 2 +- main/TaskData.scala | 2 +- main/command/BasicCommandStrings.scala | 126 ++++++ main/command/BasicCommands.scala | 223 +++++++++++ main/command/BasicKeys.scala | 10 + main/{ => command}/Command.scala | 0 main/command/CommandUtil.scala | 32 ++ main/{ => command}/MainControl.scala | 0 main/command/MainLoop.scala | 100 +++++ main/{ => command}/State.scala | 6 +- main/{ => command}/Watched.scala | 5 +- project/Sbt.scala | 6 +- sbt/package.scala | 3 + tasks/Incomplete.scala | 2 +- util/collection/Settings.scala | 2 +- util/control/MessageOnlyException.scala | 9 +- util/log/GlobalLogging.scala | 27 ++ util/log/MainLogging.scala | 36 ++ 26 files changed, 662 insertions(+), 556 deletions(-) rename main/{CommandSupport.scala => CommandStrings.scala} (51%) create mode 100644 main/command/BasicCommandStrings.scala create mode 100644 main/command/BasicCommands.scala create mode 100644 main/command/BasicKeys.scala rename main/{ => command}/Command.scala (100%) create mode 100644 main/command/CommandUtil.scala rename main/{ => command}/MainControl.scala (100%) create mode 100644 main/command/MainLoop.scala rename main/{ => command}/State.scala (99%) rename main/{ => command}/Watched.scala (96%) create mode 100644 util/log/GlobalLogging.scala create mode 100644 util/log/MainLogging.scala diff --git a/main/Act.scala b/main/Act.scala index 39c40c135..7d7d98610 100644 --- a/main/Act.scala +++ b/main/Act.scala @@ -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 diff --git a/main/CommandSupport.scala b/main/CommandStrings.scala similarity index 51% rename from main/CommandSupport.scala rename to main/CommandStrings.scala index b65ae5f43..7dc1a62d9 100644 --- a/main/CommandSupport.scala +++ b/main/CommandStrings.scala @@ -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 + " ", "Evaluates the given Scala expression and prints the result and type.") @@ -128,9 +112,8 @@ SetCommand + """ def sessionBrief = (SessionCommand + " ", "Manipulates session settings. For details, run 'help " + SessionCommand + "'.") /** The command name to terminate the program.*/ - val TerminateAction: String = Exit - - def continuousBriefHelp = (ContinuousExecutePrefix + " ", "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 + " *", "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 + " *", ApplyDetailed) - def ApplyDetailed = "Transforms the current State by calling .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 + " (" + Multi + " )*", "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 + " ", 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 ] [-annot ]" - 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 [-cp ] [-d ]" - def CompileDetailed = -CompileSyntax + """ - - Incrementally compiles Scala and Java sources. - - are explicit paths separated by the platform path separator. - - The specified output path will contain the following directory structure: - - scala_/ - 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 + " ", 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 } diff --git a/main/IvyConsole.scala b/main/IvyConsole.scala index 9cdf09725..39318f851 100644 --- a/main/IvyConsole.scala +++ b/main/IvyConsole.scala @@ -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) diff --git a/main/Keys.scala b/main/Keys.scala index ae875d36b..3881f99e2 100644 --- a/main/Keys.scala +++ b/main/Keys.scala @@ -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.") diff --git a/main/Load.scala b/main/Load.scala index 4a7d7c989..bc6bbc39f 100644 --- a/main/Load.scala +++ b/main/Load.scala @@ -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) } diff --git a/main/LogManager.scala b/main/LogManager.scala index 28721d5e2..3f82a3601 100644 --- a/main/LogManager.scala +++ b/main/LogManager.scala @@ -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) diff --git a/main/Main.scala b/main/Main.scala index acb565613..70d67ce5a 100644 --- a/main/Main.scala +++ b/main/Main.scala @@ -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("")) { (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.") } \ No newline at end of file diff --git a/main/Project.scala b/main/Project.scala index b87de657f..80bb4e4c2 100755 --- a/main/Project.scala +++ b/main/Project.scala @@ -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) } diff --git a/main/Script.scala b/main/Script.scala index 2be888410..e315f623d 100644 --- a/main/Script.scala +++ b/main/Script.scala @@ -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) diff --git a/main/TaskData.scala b/main/TaskData.scala index 95286f5f8..999af6f7c 100644 --- a/main/TaskData.scala +++ b/main/TaskData.scala @@ -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) } } diff --git a/main/command/BasicCommandStrings.scala b/main/command/BasicCommandStrings.scala new file mode 100644 index 000000000..ba1c32010 --- /dev/null +++ b/main/command/BasicCommandStrings.scala @@ -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 + " *", "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 + " *", ApplyDetailed) + def ApplyDetailed = "Transforms the current State by calling .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 + " (" + Multi + " )*", "Runs the provided semicolon-separated commands.") + def MultiDetailed = +Multi + " command1 " + Multi + """ command2 ... + + Runs the specified commands.""" + + def AppendCommand = "append" + def AppendLastBrief = (AppendCommand + " ", 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 + " ", 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 + " ", "Executes the specified command whenever source files change.") +} diff --git a/main/command/BasicCommands.scala b/main/command/BasicCommands.scala new file mode 100644 index 000000000..5d0c29e42 --- /dev/null +++ b/main/command/BasicCommands.scala @@ -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("")) { (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.") +} \ No newline at end of file diff --git a/main/command/BasicKeys.scala b/main/command/BasicKeys.scala new file mode 100644 index 000000000..b7f9fa104 --- /dev/null +++ b/main/command/BasicKeys.scala @@ -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.") +} diff --git a/main/Command.scala b/main/command/Command.scala similarity index 100% rename from main/Command.scala rename to main/command/Command.scala diff --git a/main/command/CommandUtil.scala b/main/command/CommandUtil.scala new file mode 100644 index 000000000..b600a5948 --- /dev/null +++ b/main/command/CommandUtil.scala @@ -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) + } +} \ No newline at end of file diff --git a/main/MainControl.scala b/main/command/MainControl.scala similarity index 100% rename from main/MainControl.scala rename to main/command/MainControl.scala diff --git a/main/command/MainLoop.scala b/main/command/MainLoop.scala new file mode 100644 index 000000000..1e1048598 --- /dev/null +++ b/main/command/MainLoop.scala @@ -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.") + } +} diff --git a/main/State.scala b/main/command/State.scala similarity index 99% rename from main/State.scala rename to main/command/State.scala index 6d81f07ac..10ef7ad50 100644 --- a/main/State.scala +++ b/main/command/State.scala @@ -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) diff --git a/main/Watched.scala b/main/command/Watched.scala similarity index 96% rename from main/Watched.scala rename to main/command/Watched.scala index 539dc10c0..dc3d90c50 100644 --- a/main/Watched.scala +++ b/main/command/Watched.scala @@ -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) } diff --git a/project/Sbt.scala b/project/Sbt.scala index fef29065a..aba301339 100644 --- a/project/Sbt.scala +++ b/project/Sbt.scala @@ -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( diff --git a/sbt/package.scala b/sbt/package.scala index e36dfafef..d53be82a5 100644 --- a/sbt/package.scala +++ b/sbt/package.scala @@ -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") diff --git a/tasks/Incomplete.scala b/tasks/Incomplete.scala index cb9e9ca08..b010b8ade 100644 --- a/tasks/Incomplete.scala +++ b/tasks/Incomplete.scala @@ -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 +")" } diff --git a/util/collection/Settings.scala b/util/collection/Settings.scala index 4111c9082..d8945a787 100644 --- a/util/collection/Settings.scala +++ b/util/collection/Settings.scala @@ -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) diff --git a/util/control/MessageOnlyException.scala b/util/control/MessageOnlyException.scala index 6791a9339..7fa43746d 100644 --- a/util/control/MessageOnlyException.scala +++ b/util/control/MessageOnlyException.scala @@ -4,4 +4,11 @@ package sbt final class MessageOnlyException(override val toString: String) extends RuntimeException(toString) -final class NoMessageException extends RuntimeException \ No newline at end of file + +/** 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 \ No newline at end of file diff --git a/util/log/GlobalLogging.scala b/util/log/GlobalLogging.scala new file mode 100644 index 000000000..e54b00a00 --- /dev/null +++ b/util/log/GlobalLogging.scala @@ -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)) + } +} \ No newline at end of file diff --git a/util/log/MainLogging.scala b/util/log/MainLogging.scala new file mode 100644 index 000000000..b07abf4e3 --- /dev/null +++ b/util/log/MainLogging.scala @@ -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) \ No newline at end of file From 68e178144f5f9456b531d0b055f5675c9f0f258c Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Sun, 29 Jan 2012 14:40:53 -0500 Subject: [PATCH 13/14] fix dep-management/pom-packaging test --- .../pom-packaging/project/PomTest.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sbt/src/sbt-test/dependency-management/pom-packaging/project/PomTest.scala b/sbt/src/sbt-test/dependency-management/pom-packaging/project/PomTest.scala index 6e420d780..13e56c32b 100644 --- a/sbt/src/sbt-test/dependency-management/pom-packaging/project/PomTest.scala +++ b/sbt/src/sbt-test/dependency-management/pom-packaging/project/PomTest.scala @@ -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) => From c7b73dc938a22b82e34606d32df5d82b58e8aad3 Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Tue, 31 Jan 2012 07:01:08 -0500 Subject: [PATCH 14/14] substitute variables in custom repository definitions --- launch/ConfigurationParser.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launch/ConfigurationParser.scala b/launch/ConfigurationParser.scala index 0caf3c346..170b43750 100644 --- a/launch/ConfigurationParser.scala +++ b/launch/ConfigurationParser.scala @@ -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) }