Merge pull request #3125 from eed3si9n/wip/format

[sbt 1.0] Reformat using Scalafmt
This commit is contained in:
eugene yokota 2017-04-21 12:05:27 -04:00 committed by GitHub
commit edf7e566ba
177 changed files with 11885 additions and 7406 deletions

18
.scalafmt.conf Normal file
View File

@ -0,0 +1,18 @@
maxColumn = 100
project.git = true
project.excludeFilters = [ /sbt-test/, /input_sources/, /contraband-scala/ ]
# http://docs.scala-lang.org/style/scaladoc.html recommends the JavaDoc style.
# scala/scala is written that way too https://github.com/scala/scala/blob/v2.12.2/src/library/scala/Predef.scala
docstrings = JavaDoc
# This also seems more idiomatic to include whitespace in import x.{ yyy }
spaces.inImportCurlyBraces = true
# This works around sequence wildcard (`_*`) turning into `_ *`
spaces.beforeSeqWildcard = true
# Vertical alignment only => for pattern matching
align.tokens.add = [
{ code = "=>", owner = "Case" }
]

View File

@ -16,7 +16,7 @@ matrix:
env:
matrix:
- SBT_CMD=";test:compile;scalariformCheck"
- SBT_CMD=";test:compile;scalafmtCheck"
- SBT_CMD="mimaReportBinaryIssues"
- SBT_CMD="safeUnitTests"
- SBT_CMD="otherUnitTests"

448
build.sbt
View File

@ -3,71 +3,77 @@ import Dependencies._
import Sxr.sxr
import com.typesafe.tools.mima.core._, ProblemFilters._
import com.typesafe.tools.mima.plugin.MimaKeys.{ binaryIssueFilters, previousArtifact}
import com.typesafe.tools.mima.plugin.MimaKeys.{ binaryIssueFilters, previousArtifact }
import com.typesafe.tools.mima.plugin.MimaPlugin.mimaDefaultSettings
// ThisBuild settings take lower precedence,
// but can be shared across the multi projects.
def buildLevelSettings: Seq[Setting[_]] = inThisBuild(Seq(
organization := "org.scala-sbt",
version := "1.0.0-SNAPSHOT",
description := "sbt is an interactive build tool",
bintrayOrganization := Some("sbt"),
bintrayRepository := {
if (publishStatus.value == "releases") "maven-releases"
else "maven-snapshots"
},
bintrayPackage := "sbt",
bintrayReleaseOnPublish := false,
licenses := List("BSD New" -> url("https://github.com/sbt/sbt/blob/0.13/LICENSE")),
developers := List(
Developer("harrah", "Mark Harrah", "@harrah", url("https://github.com/harrah")),
Developer("eed3si9n", "Eugene Yokota", "@eed3si9n", url("https://github.com/eed3si9n")),
Developer("jsuereth", "Josh Suereth", "@jsuereth", url("https://github.com/jsuereth")),
Developer("dwijnand", "Dale Wijnand", "@dwijnand", url("https://github.com/dwijnand")),
Developer("gkossakowski", "Grzegorz Kossakowski", "@gkossakowski", url("https://github.com/gkossakowski")),
Developer("Duhemm", "Martin Duhem", "@Duhemm", url("https://github.com/Duhemm"))
),
homepage := Some(url("https://github.com/sbt/sbt")),
scmInfo := Some(ScmInfo(url("https://github.com/sbt/sbt"), "git@github.com:sbt/sbt.git")),
resolvers += Resolver.mavenLocal
))
def buildLevelSettings: Seq[Setting[_]] =
inThisBuild(
Seq(
organization := "org.scala-sbt",
version := "1.0.0-SNAPSHOT",
description := "sbt is an interactive build tool",
bintrayOrganization := Some("sbt"),
bintrayRepository := {
if (publishStatus.value == "releases") "maven-releases"
else "maven-snapshots"
},
bintrayPackage := "sbt",
bintrayReleaseOnPublish := false,
licenses := List("BSD New" -> url("https://github.com/sbt/sbt/blob/0.13/LICENSE")),
developers := List(
Developer("harrah", "Mark Harrah", "@harrah", url("https://github.com/harrah")),
Developer("eed3si9n", "Eugene Yokota", "@eed3si9n", url("https://github.com/eed3si9n")),
Developer("jsuereth", "Josh Suereth", "@jsuereth", url("https://github.com/jsuereth")),
Developer("dwijnand", "Dale Wijnand", "@dwijnand", url("https://github.com/dwijnand")),
Developer("gkossakowski",
"Grzegorz Kossakowski",
"@gkossakowski",
url("https://github.com/gkossakowski")),
Developer("Duhemm", "Martin Duhem", "@Duhemm", url("https://github.com/Duhemm"))
),
homepage := Some(url("https://github.com/sbt/sbt")),
scmInfo := Some(ScmInfo(url("https://github.com/sbt/sbt"), "git@github.com:sbt/sbt.git")),
resolvers += Resolver.mavenLocal
))
def commonSettings: Seq[Setting[_]] = Seq[SettingsDefinition](
scalaVersion := baseScalaVersion,
componentID := None,
resolvers += Resolver.typesafeIvyRepo("releases"),
resolvers += Resolver.sonatypeRepo("snapshots"),
resolvers += "bintray-sbt-maven-releases" at "https://dl.bintray.com/sbt/maven-releases/",
concurrentRestrictions in Global += Util.testExclusiveRestriction,
testOptions += Tests.Argument(TestFrameworks.ScalaCheck, "-w", "1"),
javacOptions in compile ++= Seq("-target", "6", "-source", "6", "-Xlint", "-Xlint:-serial"),
incOptions := incOptions.value.withNameHashing(true),
crossScalaVersions := Seq(baseScalaVersion),
bintrayPackage := (bintrayPackage in ThisBuild).value,
bintrayRepository := (bintrayRepository in ThisBuild).value,
mimaDefaultSettings,
publishArtifact in Test := false,
mimaPreviousArtifacts := Set.empty, // Set(organization.value % moduleName.value % "1.0.0"),
mimaBinaryIssueFilters ++= Seq(
)
) flatMap (_.settings)
def commonSettings: Seq[Setting[_]] =
Seq[SettingsDefinition](
scalaVersion := baseScalaVersion,
componentID := None,
resolvers += Resolver.typesafeIvyRepo("releases"),
resolvers += Resolver.sonatypeRepo("snapshots"),
resolvers += "bintray-sbt-maven-releases" at "https://dl.bintray.com/sbt/maven-releases/",
concurrentRestrictions in Global += Util.testExclusiveRestriction,
testOptions += Tests.Argument(TestFrameworks.ScalaCheck, "-w", "1"),
javacOptions in compile ++= Seq("-target", "6", "-source", "6", "-Xlint", "-Xlint:-serial"),
incOptions := incOptions.value.withNameHashing(true),
crossScalaVersions := Seq(baseScalaVersion),
bintrayPackage := (bintrayPackage in ThisBuild).value,
bintrayRepository := (bintrayRepository in ThisBuild).value,
mimaDefaultSettings,
publishArtifact in Test := false,
mimaPreviousArtifacts := Set.empty, // Set(organization.value % moduleName.value % "1.0.0"),
mimaBinaryIssueFilters ++= Seq(
)
) flatMap (_.settings)
def minimalSettings: Seq[Setting[_]] =
commonSettings ++ customCommands ++
publishPomSettings ++ Release.javaVersionCheckSettings
publishPomSettings ++ Release.javaVersionCheckSettings
def baseSettings: Seq[Setting[_]] =
minimalSettings ++ Seq(projectComponent) ++ baseScalacOptions ++ Licensed.settings ++ Formatting.settings
minimalSettings ++ Seq(projectComponent) ++ baseScalacOptions ++ Licensed.settings
def testedBaseSettings: Seq[Setting[_]] =
baseSettings ++ testDependencies
lazy val sbtRoot: Project = (project in file(".")).
enablePlugins(ScriptedPlugin).
configs(Sxr.sxrConf).
aggregate(nonRoots: _*).
settings(
lazy val sbtRoot: Project = (project in file("."))
.enablePlugins(ScriptedPlugin)
.configs(Sxr.sxrConf)
.aggregate(nonRoots: _*)
.settings(
buildLevelSettings,
minimalSettings,
rootSettings,
@ -77,112 +83,121 @@ lazy val sbtRoot: Project = (project in file(".")).
// This is used to configure an sbt-launcher for this version of sbt.
lazy val bundledLauncherProj =
(project in file("launch")).
settings(
minimalSettings,
inConfig(Compile)(Transform.configSettings),
Release.launcherSettings(sbtLaunchJar)
).
enablePlugins(SbtLauncherPlugin).
settings(
name := "sbt-launch",
moduleName := "sbt-launch",
description := "sbt application launcher",
autoScalaLibrary := false,
crossPaths := false,
publish := Release.deployLauncher.value,
publishLauncher := Release.deployLauncher.value,
packageBin in Compile := sbtLaunchJar.value
)
(project in file("launch"))
.settings(
minimalSettings,
inConfig(Compile)(Transform.configSettings),
Release.launcherSettings(sbtLaunchJar)
)
.enablePlugins(SbtLauncherPlugin)
.settings(
name := "sbt-launch",
moduleName := "sbt-launch",
description := "sbt application launcher",
autoScalaLibrary := false,
crossPaths := false,
publish := Release.deployLauncher.value,
publishLauncher := Release.deployLauncher.value,
packageBin in Compile := sbtLaunchJar.value
)
/* ** subproject declarations ** */
/* **** Intermediate-level Modules **** */
// Runner for uniform test interface
lazy val testingProj = (project in file("testing")).
enablePlugins(ContrabandPlugin, JsonCodecPlugin).
dependsOn(testAgentProj).
settings(
lazy val testingProj = (project in file("testing"))
.enablePlugins(ContrabandPlugin, JsonCodecPlugin)
.dependsOn(testAgentProj)
.settings(
baseSettings,
name := "Testing",
libraryDependencies ++= Seq(testInterface,launcherInterface, sjsonNewScalaJson),
libraryDependencies ++= Seq(testInterface, launcherInterface, sjsonNewScalaJson),
managedSourceDirectories in Compile +=
baseDirectory.value / "src" / "main" / "contraband-scala",
sourceManaged in (Compile, generateContrabands) := baseDirectory.value / "src" / "main" / "contraband-scala",
contrabandFormatsForType in generateContrabands in Compile := ContrabandConfig.getFormats
).
configure(addSbtIO, addSbtCompilerClasspath, addSbtUtilLogging)
)
.configure(addSbtIO, addSbtCompilerClasspath, addSbtUtilLogging)
// Testing agent for running tests in a separate process.
lazy val testAgentProj = (project in file("testing") / "agent").
settings(
minimalSettings,
crossScalaVersions := Seq(baseScalaVersion),
crossPaths := false,
autoScalaLibrary := false,
name := "Test Agent",
libraryDependencies += testInterface
)
lazy val testAgentProj = (project in file("testing") / "agent").settings(
minimalSettings,
crossScalaVersions := Seq(baseScalaVersion),
crossPaths := false,
autoScalaLibrary := false,
name := "Test Agent",
libraryDependencies += testInterface
)
// Basic task engine
lazy val taskProj = (project in file("tasks")).
settings(
lazy val taskProj = (project in file("tasks"))
.settings(
testedBaseSettings,
name := "Tasks"
).
configure(addSbtUtilControl, addSbtUtilCollection)
)
.configure(addSbtUtilControl, addSbtUtilCollection)
// Standard task system. This provides map, flatMap, join, and more on top of the basic task model.
lazy val stdTaskProj = (project in file("tasks-standard")).
dependsOn (taskProj % "compile;test->test").
settings(
lazy val stdTaskProj = (project in file("tasks-standard"))
.dependsOn(taskProj % "compile;test->test")
.settings(
testedBaseSettings,
name := "Task System",
testExclusive
).
configure(addSbtUtilCollection, addSbtUtilLogging, addSbtUtilCache, addSbtIO)
)
.configure(addSbtUtilCollection, addSbtUtilLogging, addSbtUtilCache, addSbtIO)
// Embedded Scala code runner
lazy val runProj = (project in file("run")).
settings(
lazy val runProj = (project in file("run"))
.settings(
testedBaseSettings,
name := "Run"
).
configure(addSbtIO, addSbtUtilLogging, addSbtCompilerClasspath)
)
.configure(addSbtIO, addSbtUtilLogging, addSbtCompilerClasspath)
lazy val scriptedSbtProj = (project in scriptedPath / "sbt").
dependsOn(commandProj).
settings(
lazy val scriptedSbtProj = (project in scriptedPath / "sbt")
.dependsOn(commandProj)
.settings(
baseSettings,
name := "Scripted sbt",
libraryDependencies ++= Seq(launcherInterface % "provided")
).
configure(addSbtIO, addSbtUtilLogging, addSbtCompilerInterface, addSbtUtilScripted)
)
.configure(addSbtIO, addSbtUtilLogging, addSbtCompilerInterface, addSbtUtilScripted)
lazy val scriptedPluginProj = (project in scriptedPath / "plugin").
dependsOn(sbtProj).
settings(
lazy val scriptedPluginProj = (project in scriptedPath / "plugin")
.dependsOn(sbtProj)
.settings(
baseSettings,
name := "Scripted Plugin"
).
configure(addSbtCompilerClasspath)
)
.configure(addSbtCompilerClasspath)
// Implementation and support code for defining actions.
lazy val actionsProj = (project in file("main-actions")).
dependsOn(runProj, stdTaskProj, taskProj, testingProj).
settings(
lazy val actionsProj = (project in file("main-actions"))
.dependsOn(runProj, stdTaskProj, taskProj, testingProj)
.settings(
testedBaseSettings,
name := "Actions",
libraryDependencies += sjsonNewScalaJson
).
configure(addSbtCompilerClasspath, addSbtUtilCompletion, addSbtCompilerApiInfo,
addSbtZinc, addSbtCompilerIvyIntegration, addSbtCompilerInterface,
addSbtIO, addSbtUtilLogging, addSbtUtilRelation, addSbtLm, addSbtUtilTracking)
)
.configure(
addSbtCompilerClasspath,
addSbtUtilCompletion,
addSbtCompilerApiInfo,
addSbtZinc,
addSbtCompilerIvyIntegration,
addSbtCompilerInterface,
addSbtIO,
addSbtUtilLogging,
addSbtUtilRelation,
addSbtLm,
addSbtUtilTracking
)
lazy val protocolProj = (project in file("protocol")).
enablePlugins(ContrabandPlugin, JsonCodecPlugin).
settings(
lazy val protocolProj = (project in file("protocol"))
.enablePlugins(ContrabandPlugin, JsonCodecPlugin)
.settings(
testedBaseSettings,
name := "Protocol",
libraryDependencies ++= Seq(sjsonNewScalaJson),
@ -190,14 +205,14 @@ lazy val protocolProj = (project in file("protocol")).
baseDirectory.value / "src" / "main" / "contraband-scala",
sourceManaged in (Compile, generateContrabands) := baseDirectory.value / "src" / "main" / "contraband-scala",
contrabandFormatsForType in generateContrabands in Compile := ContrabandConfig.getFormats
).
configure(addSbtUtilLogging)
)
.configure(addSbtUtilLogging)
// General command support and core commands not specific to a build system
lazy val commandProj = (project in file("main-command")).
enablePlugins(ContrabandPlugin, JsonCodecPlugin).
dependsOn(protocolProj).
settings(
lazy val commandProj = (project in file("main-command"))
.enablePlugins(ContrabandPlugin, JsonCodecPlugin)
.dependsOn(protocolProj)
.settings(
testedBaseSettings,
name := "Command",
libraryDependencies ++= Seq(launcherInterface, sjsonNewScalaJson, templateResolverApi),
@ -205,44 +220,61 @@ lazy val commandProj = (project in file("main-command")).
baseDirectory.value / "src" / "main" / "contraband-scala",
sourceManaged in (Compile, generateContrabands) := baseDirectory.value / "src" / "main" / "contraband-scala",
contrabandFormatsForType in generateContrabands in Compile := ContrabandConfig.getFormats
).
configure(addSbtCompilerInterface, addSbtIO, addSbtUtilLogging, addSbtUtilCompletion, addSbtCompilerClasspath, addSbtLm)
)
.configure(addSbtCompilerInterface,
addSbtIO,
addSbtUtilLogging,
addSbtUtilCompletion,
addSbtCompilerClasspath,
addSbtLm)
// Fixes scope=Scope for Setting (core defined in collectionProj) to define the settings system used in build definitions
lazy val mainSettingsProj = (project in file("main-settings")).
dependsOn(commandProj, stdTaskProj).
settings(
lazy val mainSettingsProj = (project in file("main-settings"))
.dependsOn(commandProj, stdTaskProj)
.settings(
testedBaseSettings,
name := "Main Settings"
).
configure(addSbtUtilCache, addSbtUtilApplyMacro, addSbtCompilerInterface, addSbtUtilRelation,
addSbtUtilLogging, addSbtIO, addSbtUtilCompletion, addSbtCompilerClasspath, addSbtLm)
)
.configure(
addSbtUtilCache,
addSbtUtilApplyMacro,
addSbtCompilerInterface,
addSbtUtilRelation,
addSbtUtilLogging,
addSbtIO,
addSbtUtilCompletion,
addSbtCompilerClasspath,
addSbtLm
)
// The main integration project for sbt. It brings all of the projects together, configures them, and provides for overriding conventions.
lazy val mainProj = (project in file("main")).
dependsOn(actionsProj, mainSettingsProj, runProj, commandProj).
disablePlugins(SbtScalariform).
settings(
lazy val mainProj = (project in file("main"))
.dependsOn(actionsProj, mainSettingsProj, runProj, commandProj)
.settings(
testedBaseSettings,
name := "Main",
libraryDependencies ++= scalaXml.value ++ Seq(launcherInterface)
).
configure(addSbtCompilerInterface,
addSbtIO, addSbtUtilLogging, addSbtUtilLogic, addSbtLm, addSbtZincCompile)
)
.configure(addSbtCompilerInterface,
addSbtIO,
addSbtUtilLogging,
addSbtUtilLogic,
addSbtLm,
addSbtZincCompile)
// 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 mainProj'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)
lazy val sbtProj = (project in file("sbt")).
dependsOn(mainProj, scriptedSbtProj % "test->test").
settings(
lazy val sbtProj = (project in file("sbt"))
.dependsOn(mainProj, scriptedSbtProj % "test->test")
.settings(
baseSettings,
name := "sbt",
normalizedName := "sbt",
crossScalaVersions := Seq(baseScalaVersion),
crossPaths := false
).
configure(addSbtCompilerBridge)
)
.configure(addSbtCompilerBridge)
def scriptedTask: Def.Initialize[InputTask[Unit]] = Def.inputTask {
val result = scriptedSource(dir => (s: State) => Scripted.scriptedParser(dir)).parsed
@ -252,52 +284,88 @@ def scriptedTask: Def.Initialize[InputTask[Unit]] = Def.inputTask {
// that alternate repo to the running scripted test (in Scripted.scriptedpreScripted).
// (altLocalPublish in interfaceProj).value
// (altLocalPublish in compileInterfaceProj).value
Scripted.doScripted((sbtLaunchJar in bundledLauncherProj).value, (fullClasspath in scriptedSbtProj in Test).value,
Scripted.doScripted(
(sbtLaunchJar in bundledLauncherProj).value,
(fullClasspath in scriptedSbtProj in Test).value,
(scalaInstance in scriptedSbtProj).value,
scriptedSource.value, scriptedBufferLog.value, result, scriptedPrescripted.value, scriptedLaunchOpts.value)
scriptedSource.value,
scriptedBufferLog.value,
result,
scriptedPrescripted.value,
scriptedLaunchOpts.value
)
}
def scriptedUnpublishedTask: Def.Initialize[InputTask[Unit]] = Def.inputTask {
val result = scriptedSource(dir => (s: State) => Scripted.scriptedParser(dir)).parsed
Scripted.doScripted((sbtLaunchJar in bundledLauncherProj).value, (fullClasspath in scriptedSbtProj in Test).value,
Scripted.doScripted(
(sbtLaunchJar in bundledLauncherProj).value,
(fullClasspath in scriptedSbtProj in Test).value,
(scalaInstance in scriptedSbtProj).value,
scriptedSource.value, scriptedBufferLog.value, result, scriptedPrescripted.value, scriptedLaunchOpts.value)
scriptedSource.value,
scriptedBufferLog.value,
result,
scriptedPrescripted.value,
scriptedLaunchOpts.value
)
}
lazy val publishLauncher = TaskKey[Unit]("publish-launcher")
lazy val myProvided = config("provided") intransitive
def allProjects = Seq(
testingProj, testAgentProj, taskProj, stdTaskProj, runProj,
scriptedSbtProj, scriptedPluginProj, protocolProj,
actionsProj, commandProj, mainSettingsProj, mainProj, sbtProj, bundledLauncherProj)
def allProjects =
Seq(
testingProj,
testAgentProj,
taskProj,
stdTaskProj,
runProj,
scriptedSbtProj,
scriptedPluginProj,
protocolProj,
actionsProj,
commandProj,
mainSettingsProj,
mainProj,
sbtProj,
bundledLauncherProj
)
def projectsWithMyProvided = allProjects.map(p => p.copy(configurations = (p.configurations.filter(_ != Provided)) :+ myProvided))
def projectsWithMyProvided =
allProjects.map(p =>
p.copy(configurations = (p.configurations.filter(_ != Provided)) :+ myProvided))
lazy val nonRoots = projectsWithMyProvided.map(p => LocalProject(p.id))
def rootSettings = fullDocSettings ++
Util.publishPomSettings ++ otherRootSettings ++ Formatting.sbtFilesSettings ++
Transform.conscriptSettings(bundledLauncherProj)
def otherRootSettings = Seq(
scripted := scriptedTask.evaluated,
scriptedUnpublished := scriptedUnpublishedTask.evaluated,
scriptedSource := (sourceDirectory in sbtProj).value / "sbt-test",
// scriptedPrescripted := { addSbtAlternateResolver _ },
scriptedLaunchOpts := List("-XX:MaxPermSize=256M", "-Xmx1G"),
publishAll := { val _ = (publishLocal).all(ScopeFilter(inAnyProject)).value },
publishLocalBinAll := { val _ = (publishLocalBin).all(ScopeFilter(inAnyProject)).value },
aggregate in bintrayRelease := false
) ++ inConfig(Scripted.RepoOverrideTest)(Seq(
scriptedPrescripted := { _ => () },
scriptedLaunchOpts := {
List("-XX:MaxPermSize=256M", "-Xmx1G", "-Dsbt.override.build.repos=true",
s"""-Dsbt.repository.config=${ scriptedSource.value / "repo.config" }""")
},
scripted := scriptedTask.evaluated,
scriptedUnpublished := scriptedUnpublishedTask.evaluated,
scriptedSource := (sourceDirectory in sbtProj).value / "repo-override-test"
))
def rootSettings =
fullDocSettings ++
Util.publishPomSettings ++ otherRootSettings ++
Transform.conscriptSettings(bundledLauncherProj)
def otherRootSettings =
Seq(
scripted := scriptedTask.evaluated,
scriptedUnpublished := scriptedUnpublishedTask.evaluated,
scriptedSource := (sourceDirectory in sbtProj).value / "sbt-test",
// scriptedPrescripted := { addSbtAlternateResolver _ },
scriptedLaunchOpts := List("-XX:MaxPermSize=256M", "-Xmx1G"),
publishAll := { val _ = (publishLocal).all(ScopeFilter(inAnyProject)).value },
publishLocalBinAll := { val _ = (publishLocalBin).all(ScopeFilter(inAnyProject)).value },
aggregate in bintrayRelease := false
) ++ inConfig(Scripted.RepoOverrideTest)(
Seq(
scriptedPrescripted := { _ =>
()
},
scriptedLaunchOpts := {
List("-XX:MaxPermSize=256M",
"-Xmx1G",
"-Dsbt.override.build.repos=true",
s"""-Dsbt.repository.config=${scriptedSource.value / "repo.config"}""")
},
scripted := scriptedTask.evaluated,
scriptedUnpublished := scriptedUnpublishedTask.evaluated,
scriptedSource := (sourceDirectory in sbtProj).value / "repo-override-test"
))
// def addSbtAlternateResolver(scriptedRoot: File) = {
// val resolver = scriptedRoot / "project" / "AddResolverPlugin.scala"
@ -337,17 +405,20 @@ def fullDocSettings = Util.baseScalacOptions ++ Docs.settings ++ Sxr.settings ++
lazy val safeUnitTests = taskKey[Unit]("Known working tests (for both 2.10 and 2.11)")
lazy val safeProjects: ScopeFilter = ScopeFilter(
inProjects(mainSettingsProj, mainProj,
actionsProj, runProj, stdTaskProj),
inProjects(mainSettingsProj, mainProj, actionsProj, runProj, stdTaskProj),
inConfigurations(Test)
)
lazy val otherUnitTests = taskKey[Unit]("Unit test other projects")
lazy val otherProjects: ScopeFilter = ScopeFilter(
inProjects(
testingProj, testAgentProj, taskProj,
scriptedSbtProj, scriptedPluginProj,
commandProj, mainSettingsProj, mainProj,
sbtProj),
inProjects(testingProj,
testAgentProj,
taskProj,
scriptedSbtProj,
scriptedPluginProj,
commandProj,
mainSettingsProj,
mainProj,
sbtProj),
inConfigurations(Test)
)
@ -362,12 +433,19 @@ def customCommands: Seq[Setting[_]] = Seq(
otherUnitTests := {
test.all(otherProjects).value
},
commands += Command.command("scalafmtCheck") { state =>
sys.process.Process("git diff --name-only --exit-code").! match {
case 0 => // ok
case x => sys.error("git diff detected! Did you compile before committing?")
}
state
},
commands += Command.command("release-sbt-local") { state =>
"clean" ::
"so compile" ::
"so publishLocal" ::
"reload" ::
state
"so compile" ::
"so publishLocal" ::
"reload" ::
state
},
/** There are several complications with sbt's build.
* First is the fact that interface project is a Java-only project

View File

@ -4,7 +4,13 @@
package sbt
import sbt.internal.inc.javac.JavaTools
import sbt.internal.inc.{ AnalyzingCompiler, ComponentCompiler, ScalaInstance, ZincComponentManager, IncrementalCompilerImpl }
import sbt.internal.inc.{
AnalyzingCompiler,
ComponentCompiler,
ScalaInstance,
ZincComponentManager,
IncrementalCompilerImpl
}
import xsbti.{ Logger => _, _ }
import xsbti.compile.{ ClasspathOptions, Compilers, CompileResult, Inputs }
import java.io.File
@ -24,7 +30,9 @@ object Compiler {
}
private[this] def scalaCompilerBridgeSource(suffix: String): ModuleID =
ModuleID(xsbti.ArtifactInfo.SbtOrganization, s"compiler-bridge_$suffix", ComponentCompiler.incrementalVersion)
ModuleID(xsbti.ArtifactInfo.SbtOrganization,
s"compiler-bridge_$suffix",
ComponentCompiler.incrementalVersion)
.withConfigurations(Some("component"))
.sources()
@ -33,7 +41,8 @@ object Compiler {
private[sbt] def scalaCompilerBridgeSource2_12: ModuleID = scalaCompilerBridgeSource("2.12")
def compilers(
cpOptions: ClasspathOptions, ivyConfiguration: IvyConfiguration
cpOptions: ClasspathOptions,
ivyConfiguration: IvyConfiguration
)(implicit app: AppConfiguration, log: Logger): Compilers = {
val scalaProvider = app.provider.scalaProvider
val instance = ScalaInstance(scalaProvider.version, scalaProvider.launcher)
@ -43,8 +52,11 @@ object Compiler {
// TODO: Get java compiler
def compilers(
instance: ScalaInstance, cpOptions: ClasspathOptions, javaHome: Option[File],
ivyConfiguration: IvyConfiguration, sourcesModule: ModuleID
instance: ScalaInstance,
cpOptions: ClasspathOptions,
javaHome: Option[File],
ivyConfiguration: IvyConfiguration,
sourcesModule: ModuleID
)(implicit app: AppConfiguration, log: Logger): Compilers = {
val scalac = scalaCompiler(instance, cpOptions, javaHome, ivyConfiguration, sourcesModule)
val javac = JavaTools.directOrFork(instance, cpOptions, javaHome)
@ -52,12 +64,19 @@ object Compiler {
}
def scalaCompiler(
instance: ScalaInstance, cpOptions: ClasspathOptions, javaHome: Option[File],
ivyConfiguration: IvyConfiguration, sourcesModule: ModuleID
instance: ScalaInstance,
cpOptions: ClasspathOptions,
javaHome: Option[File],
ivyConfiguration: IvyConfiguration,
sourcesModule: ModuleID
)(implicit app: AppConfiguration, log: Logger): AnalyzingCompiler = {
val launcher = app.provider.scalaProvider.launcher
val componentManager = new ZincComponentManager(launcher.globalLock, app.provider.components, Option(launcher.ivyHome), log)
val provider = ComponentCompiler.interfaceProvider(componentManager, ivyConfiguration, sourcesModule)
val componentManager = new ZincComponentManager(launcher.globalLock,
app.provider.components,
Option(launcher.ivyHome),
log)
val provider =
ComponentCompiler.interfaceProvider(componentManager, ivyConfiguration, sourcesModule)
new AnalyzingCompiler(instance, provider, cpOptions, _ => (), None)
}
@ -66,5 +85,11 @@ object Compiler {
def compile(in: Inputs, log: Logger): CompileResult = compiler.compile(in, log)
private[sbt] def foldMappers[A](mappers: Seq[A => Option[A]]) =
mappers.foldRight({ p: A => p }) { (mapper, mappers) => { p: A => mapper(p).getOrElse(mappers(p)) } }
mappers.foldRight({ p: A =>
p
}) { (mapper, mappers) =>
{ p: A =>
mapper(p).getOrElse(mappers(p))
}
}
}

View File

@ -11,23 +11,36 @@ import xsbti.compile.{ Inputs, Compilers }
import scala.util.Try
final class Console(compiler: AnalyzingCompiler) {
/** Starts an interactive scala interpreter session with the given classpath.*/
def apply(classpath: Seq[File], log: Logger): Try[Unit] =
apply(classpath, Nil, "", "", log)
def apply(classpath: Seq[File], options: Seq[String], initialCommands: String, cleanupCommands: String, log: Logger): Try[Unit] =
def apply(classpath: Seq[File],
options: Seq[String],
initialCommands: String,
cleanupCommands: String,
log: Logger): Try[Unit] =
apply(classpath, options, initialCommands, cleanupCommands)(None, Nil)(log)
def apply(classpath: Seq[File], options: Seq[String], loader: ClassLoader, initialCommands: String, cleanupCommands: String)(bindings: (String, Any)*)(implicit log: Logger): Try[Unit] =
def apply(classpath: Seq[File],
options: Seq[String],
loader: ClassLoader,
initialCommands: String,
cleanupCommands: String)(bindings: (String, Any)*)(implicit log: Logger): Try[Unit] =
apply(classpath, options, initialCommands, cleanupCommands)(Some(loader), bindings)
def apply(classpath: Seq[File], options: Seq[String], initialCommands: String, cleanupCommands: String)(loader: Option[ClassLoader], bindings: Seq[(String, Any)])(implicit log: Logger): Try[Unit] =
{
def console0() = compiler.console(classpath, options, initialCommands, cleanupCommands, log)(loader, bindings)
// TODO: Fix JLine
//JLine.withJLine(Run.executeTrapExit(console0, log))
Run.executeTrapExit(console0, log)
}
def apply(classpath: Seq[File],
options: Seq[String],
initialCommands: String,
cleanupCommands: String)(loader: Option[ClassLoader], bindings: Seq[(String, Any)])(
implicit log: Logger): Try[Unit] = {
def console0() =
compiler.console(classpath, options, initialCommands, cleanupCommands, log)(loader, bindings)
// TODO: Fix JLine
//JLine.withJLine(Run.executeTrapExit(console0, log))
Run.executeTrapExit(console0, log)
}
}
object Console {
def apply(conf: Inputs): Console =

View File

@ -19,26 +19,67 @@ import sbt.internal.util.ManagedLogger
object Doc {
import RawCompileLike._
def scaladoc(label: String, cacheStoreFactory: CacheStoreFactory, compiler: AnalyzingCompiler): Gen =
def scaladoc(label: String,
cacheStoreFactory: CacheStoreFactory,
compiler: AnalyzingCompiler): Gen =
scaladoc(label, cacheStoreFactory, compiler, Seq())
def scaladoc(label: String, cacheStoreFactory: CacheStoreFactory, compiler: AnalyzingCompiler, fileInputOptions: Seq[String]): Gen =
cached(cacheStoreFactory, fileInputOptions, prepare(label + " Scala API documentation", compiler.doc))
def javadoc(label: String, cacheStoreFactory: CacheStoreFactory, doc: JavaTools, log: Logger, reporter: Reporter): Gen =
def scaladoc(label: String,
cacheStoreFactory: CacheStoreFactory,
compiler: AnalyzingCompiler,
fileInputOptions: Seq[String]): Gen =
cached(cacheStoreFactory,
fileInputOptions,
prepare(label + " Scala API documentation", compiler.doc))
def javadoc(label: String,
cacheStoreFactory: CacheStoreFactory,
doc: JavaTools,
log: Logger,
reporter: Reporter): Gen =
javadoc(label, cacheStoreFactory, doc, log, reporter, Seq())
def javadoc(label: String, cacheStoreFactory: CacheStoreFactory, doc: JavaTools, log: Logger, reporter: Reporter, fileInputOptions: Seq[String]): Gen =
cached(cacheStoreFactory, fileInputOptions, prepare(label + " Java API documentation", filterSources(
javaSourcesOnly,
(sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String], maxErrors: Int, log: Logger) => {
// doc.doc
???
}
)))
def javadoc(label: String,
cacheStoreFactory: CacheStoreFactory,
doc: JavaTools,
log: Logger,
reporter: Reporter,
fileInputOptions: Seq[String]): Gen =
cached(
cacheStoreFactory,
fileInputOptions,
prepare(
label + " Java API documentation",
filterSources(
javaSourcesOnly,
(sources: Seq[File],
classpath: Seq[File],
outputDirectory: File,
options: Seq[String],
maxErrors: Int,
log: Logger) => {
// doc.doc
???
}
)
)
)
val javaSourcesOnly: File => Boolean = _.getName.endsWith(".java")
private[sbt] final class Scaladoc(maximumErrors: Int, compiler: AnalyzingCompiler) extends Doc {
def apply(label: String, sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String], log: ManagedLogger): Unit = {
generate("Scala", label, compiler.doc, sources, classpath, outputDirectory, options, maximumErrors, log)
def apply(label: String,
sources: Seq[File],
classpath: Seq[File],
outputDirectory: File,
options: Seq[String],
log: ManagedLogger): Unit = {
generate("Scala",
label,
compiler.doc,
sources,
classpath,
outputDirectory,
options,
maximumErrors,
log)
}
}
}
@ -46,12 +87,21 @@ object Doc {
sealed trait Doc {
type Gen = (Seq[File], Seq[File], File, Seq[String], Int, ManagedLogger) => Unit
private[sbt] final def generate(variant: String, label: String, docf: Gen, sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String], maxErrors: Int, log: ManagedLogger): Unit = {
private[sbt] final def generate(variant: String,
label: String,
docf: Gen,
sources: Seq[File],
classpath: Seq[File],
outputDirectory: File,
options: Seq[String],
maxErrors: Int,
log: ManagedLogger): Unit = {
val logSnip = variant + " API documentation"
if (sources.isEmpty)
log.info("No sources available, skipping " + logSnip + "...")
else {
log.info("Generating " + logSnip + " for " + label + " sources to " + outputDirectory.absolutePath + "...")
log.info(
"Generating " + logSnip + " for " + label + " sources to " + outputDirectory.absolutePath + "...")
IO.delete(outputDirectory)
IO.createDirectory(outputDirectory)
docf(sources, classpath, outputDirectory, options, maxErrors, log)

View File

@ -18,24 +18,37 @@ object DotGraph {
apply(relations, outputDirectory, toString, toString)
}
def packages(relations: Relations, outputDirectory: File, sourceRoots: Iterable[File]): Unit = {
val packageOnly = (path: String) =>
{
val last = path.lastIndexOf(File.separatorChar.toInt)
val packagePath = (if (last > 0) path.substring(0, last) else path).trim
if (packagePath.isEmpty) "" else packagePath.replace(File.separatorChar, '.')
}
val packageOnly = (path: String) => {
val last = path.lastIndexOf(File.separatorChar.toInt)
val packagePath = (if (last > 0) path.substring(0, last) else path).trim
if (packagePath.isEmpty) "" else packagePath.replace(File.separatorChar, '.')
}
val toString = packageOnly compose fToString(sourceRoots)
apply(relations, outputDirectory, toString, toString)
}
def apply(relations: Relations, outputDir: File, sourceToString: File => String, externalToString: File => String): Unit = {
def apply(relations: Relations,
outputDir: File,
sourceToString: File => String,
externalToString: File => String): Unit = {
def file(name: String) = new File(outputDir, name)
IO.createDirectory(outputDir)
generateGraph(file("int-class-deps"), "dependencies", relations.internalClassDep, identity[String], identity[String])
generateGraph(file("binary-dependencies"), "externalDependencies", relations.libraryDep, externalToString, sourceToString)
generateGraph(file("int-class-deps"),
"dependencies",
relations.internalClassDep,
identity[String],
identity[String])
generateGraph(file("binary-dependencies"),
"externalDependencies",
relations.libraryDep,
externalToString,
sourceToString)
}
def generateGraph[K, V](file: File, graphName: String, relation: Relation[K, V],
keyToString: K => String, valueToString: V => String): Unit = {
def generateGraph[K, V](file: File,
graphName: String,
relation: Relation[K, V],
keyToString: K => String,
valueToString: V => String): Unit = {
import scala.collection.mutable.{ HashMap, HashSet }
val mappedGraph = new HashMap[String, HashSet[String]]
for ((key, values) <- relation.forwardMap; keyString = keyToString(key); value <- values)
@ -58,10 +71,9 @@ object DotGraph {
def sourceToString(roots: Iterable[File], source: File) =
relativized(roots, source).trim.stripSuffix(".scala").stripSuffix(".java")
private def relativized(roots: Iterable[File], path: File): String =
{
val relativized = roots.flatMap(root => IO.relativize(root, path))
val shortest = (Int.MaxValue /: relativized)(_ min _.length)
relativized.find(_.length == shortest).getOrElse(path.getName)
}
private def relativized(roots: Iterable[File], path: File): String = {
val relativized = roots.flatMap(root => IO.relativize(root, path))
val shortest = (Int.MaxValue /: relativized)(_ min _.length)
relativized.find(_.length == shortest).getOrElse(path.getName)
}
}

View File

@ -15,7 +15,13 @@ import sbt.ConcurrentRestrictions.Tag
import sbt.protocol.testing._
private[sbt] object ForkTests {
def apply(runners: Map[TestFramework, Runner], tests: List[TestDefinition], config: Execution, classpath: Seq[File], fork: ForkOptions, log: Logger, tag: Tag): Task[TestOutput] = {
def apply(runners: Map[TestFramework, Runner],
tests: List[TestDefinition],
config: Execution,
classpath: Seq[File],
fork: ForkOptions,
log: Logger,
tag: Tag): Task[TestOutput] = {
val opts = processOptions(config, tests, log)
import std.TaskExtra._
@ -32,7 +38,12 @@ private[sbt] object ForkTests {
}
}
private[this] def mainTestTask(runners: Map[TestFramework, Runner], opts: ProcessedOptions, classpath: Seq[File], fork: ForkOptions, log: Logger, parallel: Boolean): Task[TestOutput] =
private[this] def mainTestTask(runners: Map[TestFramework, Runner],
opts: ProcessedOptions,
classpath: Seq[File],
fork: ForkOptions,
log: Logger,
parallel: Boolean): Task[TestOutput] =
std.TaskExtra.task {
val server = new ServerSocket(0)
val testListeners = opts.testListeners flatMap {
@ -42,7 +53,8 @@ private[sbt] object ForkTests {
object Acceptor extends Runnable {
val resultsAcc = mutable.Map.empty[String, SuiteResult]
lazy val result = TestOutput(overall(resultsAcc.values.map(_.result)), resultsAcc.toMap, Iterable.empty)
lazy val result =
TestOutput(overall(resultsAcc.values.map(_.result)), resultsAcc.toMap, Iterable.empty)
def run(): Unit = {
val socket =
@ -50,7 +62,8 @@ private[sbt] object ForkTests {
server.accept()
} catch {
case e: java.net.SocketException =>
log.error("Could not accept connection from test agent: " + e.getClass + ": " + e.getMessage)
log.error(
"Could not accept connection from test agent: " + e.getClass + ": " + e.getMessage)
log.trace(e)
server.close()
return
@ -64,7 +77,12 @@ private[sbt] object ForkTests {
val config = new ForkConfiguration(log.ansiCodesSupported, parallel)
os.writeObject(config)
val taskdefs = opts.tests.map(t => new TaskDef(t.name, forkFingerprint(t.fingerprint), t.explicitlySpecified, t.selectors))
val taskdefs = opts.tests.map(
t =>
new TaskDef(t.name,
forkFingerprint(t.fingerprint),
t.explicitlySpecified,
t.selectors))
os.writeObject(taskdefs.toArray)
os.writeInt(runners.size)
@ -79,7 +97,8 @@ private[sbt] object ForkTests {
} catch {
case NonFatal(e) =>
def throwableToString(t: Throwable) = {
import java.io._; val sw = new StringWriter; t.printStackTrace(new PrintWriter(sw)); sw.toString
import java.io._; val sw = new StringWriter; t.printStackTrace(new PrintWriter(sw));
sw.toString
}
resultsAcc("Forked test harness failed: " + throwableToString(e)) = SuiteResult.Error
} finally {
@ -93,12 +112,20 @@ private[sbt] object ForkTests {
val acceptorThread = new Thread(Acceptor)
acceptorThread.start()
val fullCp = classpath ++: Seq(IO.classLocationFile[ForkMain], IO.classLocationFile[Framework])
val options = Seq("-classpath", fullCp mkString File.pathSeparator, classOf[ForkMain].getCanonicalName, server.getLocalPort.toString)
val fullCp = classpath ++: Seq(IO.classLocationFile[ForkMain],
IO.classLocationFile[Framework])
val options = Seq("-classpath",
fullCp mkString File.pathSeparator,
classOf[ForkMain].getCanonicalName,
server.getLocalPort.toString)
val ec = Fork.java(fork, options)
val result =
if (ec != 0)
TestOutput(TestResult.Error, Map("Running java with options " + options.mkString(" ") + " failed with exit code " + ec -> SuiteResult.Error), Iterable.empty)
TestOutput(TestResult.Error,
Map(
"Running java with options " + options
.mkString(" ") + " failed with exit code " + ec -> SuiteResult.Error),
Iterable.empty)
else {
// Need to wait acceptor thread to finish its business
acceptorThread.join()
@ -119,9 +146,14 @@ private[sbt] object ForkTests {
case _ => sys.error("Unknown fingerprint type: " + f.getClass)
}
}
private final class React(is: ObjectInputStream, os: ObjectOutputStream, log: Logger, listeners: Seq[TestReportListener], results: mutable.Map[String, SuiteResult]) {
private final class React(is: ObjectInputStream,
os: ObjectOutputStream,
log: Logger,
listeners: Seq[TestReportListener],
results: mutable.Map[String, SuiteResult]) {
import ForkTags._
@annotation.tailrec def react(): Unit = is.readObject match {
@annotation.tailrec
def react(): Unit = is.readObject match {
case `Done` =>
os.writeObject(Done); os.flush()
case Array(`Error`, s: String) =>

View File

@ -27,11 +27,10 @@ object Package {
}
final case class MainClass(mainClassName: String) extends PackageOption
final case class ManifestAttributes(attributes: (Attributes.Name, String)*) extends PackageOption
def ManifestAttributes(attributes: (String, String)*): ManifestAttributes =
{
val converted = for ((name, value) <- attributes) yield (new Attributes.Name(name), value)
new ManifestAttributes(converted: _*)
}
def ManifestAttributes(attributes: (String, String)*): ManifestAttributes = {
val converted = for ((name, value) <- attributes) yield (new Attributes.Name(name), value)
new ManifestAttributes(converted: _*)
}
def mergeAttributes(a1: Attributes, a2: Attributes) = a1.asScala ++= a2.asScala
// merges `mergeManifest` into `manifest` (mutating `manifest` in the process)
@ -46,7 +45,9 @@ object Package {
}
}
final class Configuration(val sources: Seq[(File, String)], val jar: File, val options: Seq[PackageOption])
final class Configuration(val sources: Seq[(File, String)],
val jar: File,
val options: Seq[PackageOption])
def apply(conf: Configuration, cacheStoreFactory: CacheStoreFactory, log: Logger): Unit = {
val manifest = new Manifest
val main = manifest.getMainAttributes
@ -60,15 +61,17 @@ object Package {
}
setVersion(main)
val cachedMakeJar = inputChanged(cacheStoreFactory make "inputs") { (inChanged, inputs: Map[File, String] :+: FilesInfo[ModifiedFileInfo] :+: Manifest :+: HNil) =>
import exists.format
val sources :+: _ :+: manifest :+: HNil = inputs
inputChanged(cacheStoreFactory make "output") { (outChanged, jar: PlainFileInfo) =>
if (inChanged || outChanged)
makeJar(sources.toSeq, jar.file, manifest, log)
else
log.debug("Jar uptodate: " + jar.file)
}
val cachedMakeJar = inputChanged(cacheStoreFactory make "inputs") {
(inChanged,
inputs: Map[File, String] :+: FilesInfo[ModifiedFileInfo] :+: Manifest :+: HNil) =>
import exists.format
val sources :+: _ :+: manifest :+: HNil = inputs
inputChanged(cacheStoreFactory make "output") { (outChanged, jar: PlainFileInfo) =>
if (inChanged || outChanged)
makeJar(sources.toSeq, jar.file, manifest, log)
else
log.debug("Jar uptodate: " + jar.file)
}
}
val map = conf.sources.toMap
@ -80,20 +83,27 @@ object Package {
if (main.getValue(version) eq null)
main.put(version, "1.0")
}
def addSpecManifestAttributes(name: String, version: String, orgName: String): PackageOption =
{
import Attributes.Name._
val attribKeys = Seq(SPECIFICATION_TITLE, SPECIFICATION_VERSION, SPECIFICATION_VENDOR)
val attribVals = Seq(name, version, orgName)
ManifestAttributes(attribKeys zip attribVals: _*)
}
def addImplManifestAttributes(name: String, version: String, homepage: Option[java.net.URL], org: String, orgName: String): PackageOption =
{
import Attributes.Name._
val attribKeys = Seq(IMPLEMENTATION_TITLE, IMPLEMENTATION_VERSION, IMPLEMENTATION_VENDOR, IMPLEMENTATION_VENDOR_ID)
val attribVals = Seq(name, version, orgName, org)
ManifestAttributes((attribKeys zip attribVals) ++ { homepage map (h => (IMPLEMENTATION_URL, h.toString)) }: _*)
}
def addSpecManifestAttributes(name: String, version: String, orgName: String): PackageOption = {
import Attributes.Name._
val attribKeys = Seq(SPECIFICATION_TITLE, SPECIFICATION_VERSION, SPECIFICATION_VENDOR)
val attribVals = Seq(name, version, orgName)
ManifestAttributes(attribKeys zip attribVals: _*)
}
def addImplManifestAttributes(name: String,
version: String,
homepage: Option[java.net.URL],
org: String,
orgName: String): PackageOption = {
import Attributes.Name._
val attribKeys = Seq(IMPLEMENTATION_TITLE,
IMPLEMENTATION_VERSION,
IMPLEMENTATION_VENDOR,
IMPLEMENTATION_VENDOR_ID)
val attribVals = Seq(name, version, orgName, org)
ManifestAttributes((attribKeys zip attribVals) ++ {
homepage map (h => (IMPLEMENTATION_URL, h.toString))
}: _*)
}
def makeJar(sources: Seq[(File, String)], jar: File, manifest: Manifest, log: Logger): Unit = {
val path = jar.getAbsolutePath
log.info("Packaging " + path + " ...")

View File

@ -23,43 +23,49 @@ import sbt.internal.util.ManagedLogger
object RawCompileLike {
type Gen = (Seq[File], Seq[File], File, Seq[String], Int, ManagedLogger) => Unit
private def optionFiles(options: Seq[String], fileInputOpts: Seq[String]): List[File] =
{
@annotation.tailrec
def loop(opt: List[String], result: List[File]): List[File] = {
opt.dropWhile(!fileInputOpts.contains(_)) match {
case List(_, fileOpt, tail @ _*) =>
{
val file = new File(fileOpt)
if (file.isFile) loop(tail.toList, file :: result)
else loop(tail.toList, result)
}
case Nil | List(_) => result
private def optionFiles(options: Seq[String], fileInputOpts: Seq[String]): List[File] = {
@annotation.tailrec
def loop(opt: List[String], result: List[File]): List[File] = {
opt.dropWhile(!fileInputOpts.contains(_)) match {
case List(_, fileOpt, tail @ _*) => {
val file = new File(fileOpt)
if (file.isFile) loop(tail.toList, file :: result)
else loop(tail.toList, result)
}
case Nil | List(_) => result
}
loop(options.toList, Nil)
}
loop(options.toList, Nil)
}
def cached(cacheStoreFactory: CacheStoreFactory, doCompile: Gen): Gen = cached(cacheStoreFactory, Seq(), doCompile)
def cached(cacheStoreFactory: CacheStoreFactory, fileInputOpts: Seq[String], doCompile: Gen): Gen = (sources, classpath, outputDirectory, options, maxErrors, log) =>
{
type Inputs = FilesInfo[HashFileInfo] :+: FilesInfo[ModifiedFileInfo] :+: Seq[File] :+: File :+: Seq[String] :+: Int :+: HNil
val inputs: Inputs = hash(sources.toSet ++ optionFiles(options, fileInputOpts)) :+: lastModified(classpath.toSet) :+: classpath :+: outputDirectory :+: options :+: maxErrors :+: HNil
def cached(cacheStoreFactory: CacheStoreFactory, doCompile: Gen): Gen =
cached(cacheStoreFactory, Seq(), doCompile)
def cached(cacheStoreFactory: CacheStoreFactory,
fileInputOpts: Seq[String],
doCompile: Gen): Gen =
(sources, classpath, outputDirectory, options, maxErrors, log) => {
type Inputs =
FilesInfo[HashFileInfo] :+: FilesInfo[ModifiedFileInfo] :+: Seq[File] :+: File :+: Seq[
String] :+: Int :+: HNil
val inputs
: Inputs = hash(sources.toSet ++ optionFiles(options, fileInputOpts)) :+: lastModified(
classpath.toSet) :+: classpath :+: outputDirectory :+: options :+: maxErrors :+: HNil
implicit val stringEquiv: Equiv[String] = defaultEquiv
implicit val fileEquiv: Equiv[File] = defaultEquiv
implicit val intEquiv: Equiv[Int] = defaultEquiv
val cachedComp = inputChanged(cacheStoreFactory make "inputs") { (inChanged, in: Inputs) =>
inputChanged(cacheStoreFactory make "output") { (outChanged, outputs: FilesInfo[PlainFileInfo]) =>
if (inChanged || outChanged)
doCompile(sources, classpath, outputDirectory, options, maxErrors, log)
else
log.debug("Uptodate: " + outputDirectory.getAbsolutePath)
inputChanged(cacheStoreFactory make "output") {
(outChanged, outputs: FilesInfo[PlainFileInfo]) =>
if (inChanged || outChanged)
doCompile(sources, classpath, outputDirectory, options, maxErrors, log)
else
log.debug("Uptodate: " + outputDirectory.getAbsolutePath)
}
}
cachedComp(inputs)(exists(outputDirectory.allPaths.get.toSet))
}
def prepare(description: String, doCompile: Gen): Gen = (sources, classpath, outputDirectory, options, maxErrors, log) =>
{
def prepare(description: String, doCompile: Gen): Gen =
(sources, classpath, outputDirectory, options, maxErrors, log) => {
if (sources.isEmpty)
log.info("No sources available, skipping " + description + "...")
else {
@ -70,15 +76,19 @@ object RawCompileLike {
log.info(description.capitalize + " successful.")
}
}
def filterSources(f: File => Boolean, doCompile: Gen): Gen = (sources, classpath, outputDirectory, options, maxErrors, log) =>
doCompile(sources filter f, classpath, outputDirectory, options, maxErrors, log)
def filterSources(f: File => Boolean, doCompile: Gen): Gen =
(sources, classpath, outputDirectory, options, maxErrors, log) =>
doCompile(sources filter f, classpath, outputDirectory, options, maxErrors, log)
def rawCompile(instance: ScalaInstance, cpOptions: ClasspathOptions): Gen = (sources, classpath, outputDirectory, options, maxErrors, log) =>
{
def rawCompile(instance: ScalaInstance, cpOptions: ClasspathOptions): Gen =
(sources, classpath, outputDirectory, options, maxErrors, log) => {
val compiler = new RawCompiler(instance, cpOptions, log)
compiler(sources, classpath, outputDirectory, options)
}
def compile(label: String, cacheStoreFactory: CacheStoreFactory, instance: ScalaInstance, cpOptions: ClasspathOptions): Gen =
def compile(label: String,
cacheStoreFactory: CacheStoreFactory,
instance: ScalaInstance,
cpOptions: ClasspathOptions): Gen =
cached(cacheStoreFactory, prepare(label + " sources", rawCompile(instance, cpOptions)))
val nop: Gen = (sources, classpath, outputDirectory, options, maxErrors, log) => ()

View File

@ -26,42 +26,44 @@ import sjsonnew.{ Builder, JsonFormat, Unbuilder, deserializationError }
* It is safe to use for its intended purpose: copying resources to a class output directory.
*/
object Sync {
def apply(store: CacheStore, inStyle: FileInfo.Style = FileInfo.lastModified, outStyle: FileInfo.Style = FileInfo.exists): Traversable[(File, File)] => Relation[File, File] =
mappings =>
{
val relation = Relation.empty ++ mappings
noDuplicateTargets(relation)
val currentInfo = relation._1s.map(s => (s, inStyle(s))).toMap
def apply(store: CacheStore,
inStyle: FileInfo.Style = FileInfo.lastModified,
outStyle: FileInfo.Style = FileInfo.exists)
: Traversable[(File, File)] => Relation[File, File] =
mappings => {
val relation = Relation.empty ++ mappings
noDuplicateTargets(relation)
val currentInfo = relation._1s.map(s => (s, inStyle(s))).toMap
val (previousRelation, previousInfo) = readInfo(store)(inStyle.format)
val removeTargets = previousRelation._2s -- relation._2s
val (previousRelation, previousInfo) = readInfo(store)(inStyle.format)
val removeTargets = previousRelation._2s -- relation._2s
def outofdate(source: File, target: File): Boolean =
!previousRelation.contains(source, target) ||
(previousInfo get source) != (currentInfo get source) ||
!target.exists ||
target.isDirectory != source.isDirectory
def outofdate(source: File, target: File): Boolean =
!previousRelation.contains(source, target) ||
(previousInfo get source) != (currentInfo get source) ||
!target.exists ||
target.isDirectory != source.isDirectory
val updates = relation filter outofdate
val updates = relation filter outofdate
val (cleanDirs, cleanFiles) = (updates._2s ++ removeTargets).partition(_.isDirectory)
val (cleanDirs, cleanFiles) = (updates._2s ++ removeTargets).partition(_.isDirectory)
IO.delete(cleanFiles)
IO.deleteIfEmpty(cleanDirs)
updates.all.foreach((copy _).tupled)
IO.delete(cleanFiles)
IO.deleteIfEmpty(cleanDirs)
updates.all.foreach((copy _).tupled)
writeInfo(store, relation, currentInfo)(inStyle.format)
relation
}
writeInfo(store, relation, currentInfo)(inStyle.format)
relation
}
def copy(source: File, target: File): Unit =
if (source.isFile)
IO.copyFile(source, target, true)
else if (!target.exists) // we don't want to update the last modified time of an existing directory
{
IO.createDirectory(target)
IO.copyLastModified(source, target)
}
{
IO.createDirectory(target)
IO.copyLastModified(source, target)
}
def noDuplicateTargets(relation: Relation[File, File]): Unit = {
val dups = relation.reverseMap.filter {
@ -75,7 +77,8 @@ object Sync {
sys.error("Duplicate mappings:" + dups.mkString)
}
implicit def relationFormat[A, B](implicit af: JsonFormat[Map[A, Set[B]]], bf: JsonFormat[Map[B, Set[A]]]): JsonFormat[Relation[A, B]] =
implicit def relationFormat[A, B](implicit af: JsonFormat[Map[A, Set[B]]],
bf: JsonFormat[Map[B, Set[A]]]): JsonFormat[Relation[A, B]] =
new JsonFormat[Relation[A, B]] {
def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): Relation[A, B] =
jsOpt match {
@ -98,12 +101,15 @@ object Sync {
}
def writeInfo[F <: FileInfo](store: CacheStore, relation: Relation[File, File], info: Map[File, F])(implicit infoFormat: JsonFormat[F]): Unit =
def writeInfo[F <: FileInfo](store: CacheStore,
relation: Relation[File, File],
info: Map[File, F])(implicit infoFormat: JsonFormat[F]): Unit =
store.write((relation, info))
type RelationInfo[F] = (Relation[File, File], Map[File, F])
def readInfo[F <: FileInfo](store: CacheStore)(implicit infoFormat: JsonFormat[F]): RelationInfo[F] =
def readInfo[F <: FileInfo](store: CacheStore)(
implicit infoFormat: JsonFormat[F]): RelationInfo[F] =
store.read(default = (Relation.empty[File, File], Map.empty[File, F]))
}

View File

@ -24,11 +24,13 @@ trait TestResultLogger {
def run(log: Logger, results: Output, taskName: String): Unit
/** Only allow invocation if certain criteria is met, else use another `TestResultLogger` (defaulting to nothing) . */
final def onlyIf(f: (Output, String) => Boolean, otherwise: TestResultLogger = TestResultLogger.Null) =
final def onlyIf(f: (Output, String) => Boolean,
otherwise: TestResultLogger = TestResultLogger.Null) =
TestResultLogger.choose(f, this, otherwise)
/** Allow invocation unless a certain predicate passes, in which case use another `TestResultLogger` (defaulting to nothing) . */
final def unless(f: (Output, String) => Boolean, otherwise: TestResultLogger = TestResultLogger.Null) =
final def unless(f: (Output, String) => Boolean,
otherwise: TestResultLogger = TestResultLogger.Null) =
TestResultLogger.choose(f, otherwise, this)
}
@ -118,16 +120,32 @@ object TestResultLogger {
results.summaries.size > 1 || results.summaries.headOption.forall(_.summaryText.isEmpty)
val printStandard = TestResultLogger((log, results, _) => {
val (skippedCount, errorsCount, passedCount, failuresCount, ignoredCount, canceledCount, pendingCount) =
val (skippedCount,
errorsCount,
passedCount,
failuresCount,
ignoredCount,
canceledCount,
pendingCount) =
results.events.foldLeft((0, 0, 0, 0, 0, 0, 0)) {
case ((skippedAcc, errorAcc, passedAcc, failureAcc, ignoredAcc, canceledAcc, pendingAcc), (name, testEvent)) =>
(skippedAcc + testEvent.skippedCount, errorAcc + testEvent.errorCount, passedAcc + testEvent.passedCount, failureAcc + testEvent.failureCount,
ignoredAcc + testEvent.ignoredCount, canceledAcc + testEvent.canceledCount, pendingAcc + testEvent.pendingCount)
case ((skippedAcc, errorAcc, passedAcc, failureAcc, ignoredAcc, canceledAcc, pendingAcc),
(name, testEvent)) =>
(skippedAcc + testEvent.skippedCount,
errorAcc + testEvent.errorCount,
passedAcc + testEvent.passedCount,
failureAcc + testEvent.failureCount,
ignoredAcc + testEvent.ignoredCount,
canceledAcc + testEvent.canceledCount,
pendingAcc + testEvent.pendingCount)
}
val totalCount = failuresCount + errorsCount + skippedCount + passedCount
val base = s"Total $totalCount, Failed $failuresCount, Errors $errorsCount, Passed $passedCount"
val base =
s"Total $totalCount, Failed $failuresCount, Errors $errorsCount, Passed $passedCount"
val otherCounts = Seq("Skipped" -> skippedCount, "Ignored" -> ignoredCount, "Canceled" -> canceledCount, "Pending" -> pendingCount)
val otherCounts = Seq("Skipped" -> skippedCount,
"Ignored" -> ignoredCount,
"Canceled" -> canceledCount,
"Pending" -> pendingCount)
val extra = otherCounts.filter(_._2 > 0).map { case (label, count) => s", $label $count" }
val postfix = base + extra.mkString
@ -155,7 +173,7 @@ object TestResultLogger {
show("Error during tests:", Level.Error, select(TestResult.Error))
})
val printNoTests = TestResultLogger((log, results, taskName) =>
log.info("No tests to run for " + taskName))
val printNoTests = TestResultLogger(
(log, results, taskName) => log.info("No tests to run for " + taskName))
}
}

View File

@ -12,7 +12,16 @@ import xsbti.api.Definition
import xsbti.compile.CompileAnalysis
import ConcurrentRestrictions.Tag
import testing.{ AnnotatedFingerprint, Fingerprint, Framework, SubclassFingerprint, Runner, TaskDef, SuiteSelector, Task => TestTask }
import testing.{
AnnotatedFingerprint,
Fingerprint,
Framework,
SubclassFingerprint,
Runner,
TaskDef,
SuiteSelector,
Task => TestTask
}
import scala.annotation.tailrec
import sbt.internal.util.ManagedLogger
@ -21,6 +30,7 @@ import sbt.protocol.testing.TestResult
sealed trait TestOption
object Tests {
/**
* The result of a test run.
*
@ -28,7 +38,9 @@ object Tests {
* @param events The result of each test group (suite) executed during this test run.
* @param summaries Explicit summaries directly provided by test frameworks. This may be empty, in which case a default summary will be generated.
*/
final case class Output(overall: TestResult, events: Map[String, SuiteResult], summaries: Iterable[Summary])
final case class Output(overall: TestResult,
events: Map[String, SuiteResult],
summaries: Iterable[Summary])
/**
* Summarizes a test run.
@ -88,7 +100,8 @@ object Tests {
* If None, the arguments will apply to all test frameworks.
* @param args The list of arguments to pass to the selected framework(s).
*/
final case class Argument(framework: Option[TestFramework], args: List[String]) extends TestOption
final case class Argument(framework: Option[TestFramework], args: List[String])
extends TestOption
/**
* Configures test execution.
@ -115,92 +128,134 @@ object Tests {
final case class Group(name: String, tests: Seq[TestDefinition], runPolicy: TestRunPolicy)
private[sbt] final class ProcessedOptions(
val tests: Seq[TestDefinition],
val setup: Seq[ClassLoader => Unit],
val cleanup: Seq[ClassLoader => Unit],
val testListeners: Seq[TestReportListener]
val tests: Seq[TestDefinition],
val setup: Seq[ClassLoader => Unit],
val cleanup: Seq[ClassLoader => Unit],
val testListeners: Seq[TestReportListener]
)
private[sbt] def processOptions(config: Execution, discovered: Seq[TestDefinition], log: Logger): ProcessedOptions =
{
import collection.mutable.{ HashSet, ListBuffer }
val testFilters = new ListBuffer[String => Boolean]
var orderedFilters = Seq[String => Boolean]()
val excludeTestsSet = new HashSet[String]
val setup, cleanup = new ListBuffer[ClassLoader => Unit]
val testListeners = new ListBuffer[TestReportListener]
val undefinedFrameworks = new ListBuffer[String]
private[sbt] def processOptions(config: Execution,
discovered: Seq[TestDefinition],
log: Logger): ProcessedOptions = {
import collection.mutable.{ HashSet, ListBuffer }
val testFilters = new ListBuffer[String => Boolean]
var orderedFilters = Seq[String => Boolean]()
val excludeTestsSet = new HashSet[String]
val setup, cleanup = new ListBuffer[ClassLoader => Unit]
val testListeners = new ListBuffer[TestReportListener]
val undefinedFrameworks = new ListBuffer[String]
for (option <- config.options) {
option match {
case Filter(include) => testFilters += include
case Filters(includes) => if (orderedFilters.nonEmpty) sys.error("Cannot define multiple ordered test filters.") else orderedFilters = includes
case Exclude(exclude) => excludeTestsSet ++= exclude
case Listeners(listeners) => testListeners ++= listeners
case Setup(setupFunction) => setup += setupFunction
case Cleanup(cleanupFunction) => cleanup += cleanupFunction
case a: Argument => // now handled by whatever constructs `runners`
}
}
if (excludeTestsSet.nonEmpty)
log.debug(excludeTestsSet.mkString("Excluding tests: \n\t", "\n\t", ""))
if (undefinedFrameworks.nonEmpty)
log.warn("Arguments defined for test frameworks that are not present:\n\t" + undefinedFrameworks.mkString("\n\t"))
def includeTest(test: TestDefinition) = !excludeTestsSet.contains(test.name) && testFilters.forall(filter => filter(test.name))
val filtered0 = discovered.filter(includeTest).toList.distinct
val tests = if (orderedFilters.isEmpty) filtered0 else orderedFilters.flatMap(f => filtered0.filter(d => f(d.name))).toList.distinct
val uniqueTests = distinctBy(tests)(_.name)
new ProcessedOptions(uniqueTests, setup.toList, cleanup.toList, testListeners.toList)
}
private[this] def distinctBy[T, K](in: Seq[T])(f: T => K): Seq[T] =
{
val seen = new collection.mutable.HashSet[K]
in.filter(t => seen.add(f(t)))
}
def apply(frameworks: Map[TestFramework, Framework], testLoader: ClassLoader, runners: Map[TestFramework, Runner], discovered: Seq[TestDefinition], config: Execution, log: ManagedLogger): Task[Output] =
{
val o = processOptions(config, discovered, log)
testTask(testLoader, frameworks, runners, o.tests, o.setup, o.cleanup, log, o.testListeners, config)
}
def testTask(loader: ClassLoader, frameworks: Map[TestFramework, Framework], runners: Map[TestFramework, Runner], tests: Seq[TestDefinition],
userSetup: Iterable[ClassLoader => Unit], userCleanup: Iterable[ClassLoader => Unit],
log: ManagedLogger, testListeners: Seq[TestReportListener], config: Execution): Task[Output] =
{
def fj(actions: Iterable[() => Unit]): Task[Unit] = nop.dependsOn(actions.toSeq.fork(_()): _*)
def partApp(actions: Iterable[ClassLoader => Unit]) = actions.toSeq map { a => () => a(loader) }
val (frameworkSetup, runnables, frameworkCleanup) =
TestFramework.testTasks(frameworks, runners, loader, tests, log, testListeners)
val setupTasks = fj(partApp(userSetup) :+ frameworkSetup)
val mainTasks =
if (config.parallel)
makeParallel(loader, runnables, setupTasks, config.tags) //.toSeq.join
else
makeSerial(loader, runnables, setupTasks, config.tags)
val taggedMainTasks = mainTasks.tagw(config.tags: _*)
taggedMainTasks map processResults flatMap { results =>
val cleanupTasks = fj(partApp(userCleanup) :+ frameworkCleanup(results.overall))
cleanupTasks map { _ => results }
for (option <- config.options) {
option match {
case Filter(include) => testFilters += include
case Filters(includes) =>
if (orderedFilters.nonEmpty) sys.error("Cannot define multiple ordered test filters.")
else orderedFilters = includes
case Exclude(exclude) => excludeTestsSet ++= exclude
case Listeners(listeners) => testListeners ++= listeners
case Setup(setupFunction) => setup += setupFunction
case Cleanup(cleanupFunction) => cleanup += cleanupFunction
case a: Argument => // now handled by whatever constructs `runners`
}
}
if (excludeTestsSet.nonEmpty)
log.debug(excludeTestsSet.mkString("Excluding tests: \n\t", "\n\t", ""))
if (undefinedFrameworks.nonEmpty)
log.warn(
"Arguments defined for test frameworks that are not present:\n\t" + undefinedFrameworks
.mkString("\n\t"))
def includeTest(test: TestDefinition) =
!excludeTestsSet.contains(test.name) && testFilters.forall(filter => filter(test.name))
val filtered0 = discovered.filter(includeTest).toList.distinct
val tests =
if (orderedFilters.isEmpty) filtered0
else orderedFilters.flatMap(f => filtered0.filter(d => f(d.name))).toList.distinct
val uniqueTests = distinctBy(tests)(_.name)
new ProcessedOptions(uniqueTests, setup.toList, cleanup.toList, testListeners.toList)
}
private[this] def distinctBy[T, K](in: Seq[T])(f: T => K): Seq[T] = {
val seen = new collection.mutable.HashSet[K]
in.filter(t => seen.add(f(t)))
}
def apply(frameworks: Map[TestFramework, Framework],
testLoader: ClassLoader,
runners: Map[TestFramework, Runner],
discovered: Seq[TestDefinition],
config: Execution,
log: ManagedLogger): Task[Output] = {
val o = processOptions(config, discovered, log)
testTask(testLoader,
frameworks,
runners,
o.tests,
o.setup,
o.cleanup,
log,
o.testListeners,
config)
}
def testTask(loader: ClassLoader,
frameworks: Map[TestFramework, Framework],
runners: Map[TestFramework, Runner],
tests: Seq[TestDefinition],
userSetup: Iterable[ClassLoader => Unit],
userCleanup: Iterable[ClassLoader => Unit],
log: ManagedLogger,
testListeners: Seq[TestReportListener],
config: Execution): Task[Output] = {
def fj(actions: Iterable[() => Unit]): Task[Unit] = nop.dependsOn(actions.toSeq.fork(_()): _*)
def partApp(actions: Iterable[ClassLoader => Unit]) = actions.toSeq map { a => () =>
a(loader)
}
val (frameworkSetup, runnables, frameworkCleanup) =
TestFramework.testTasks(frameworks, runners, loader, tests, log, testListeners)
val setupTasks = fj(partApp(userSetup) :+ frameworkSetup)
val mainTasks =
if (config.parallel)
makeParallel(loader, runnables, setupTasks, config.tags) //.toSeq.join
else
makeSerial(loader, runnables, setupTasks, config.tags)
val taggedMainTasks = mainTasks.tagw(config.tags: _*)
taggedMainTasks map processResults flatMap { results =>
val cleanupTasks = fj(partApp(userCleanup) :+ frameworkCleanup(results.overall))
cleanupTasks map { _ =>
results
}
}
}
type TestRunnable = (String, TestFunction)
private def createNestedRunnables(loader: ClassLoader, testFun: TestFunction, nestedTasks: Seq[TestTask]): Seq[(String, TestFunction)] =
private def createNestedRunnables(loader: ClassLoader,
testFun: TestFunction,
nestedTasks: Seq[TestTask]): Seq[(String, TestFunction)] =
nestedTasks.view.zipWithIndex map {
case (nt, idx) =>
val testFunDef = testFun.taskDef
(testFunDef.fullyQualifiedName, TestFramework.createTestFunction(loader, new TaskDef(testFunDef.fullyQualifiedName + "-" + idx, testFunDef.fingerprint, testFunDef.explicitlySpecified, testFunDef.selectors), testFun.runner, nt))
(testFunDef.fullyQualifiedName,
TestFramework.createTestFunction(loader,
new TaskDef(testFunDef.fullyQualifiedName + "-" + idx,
testFunDef.fingerprint,
testFunDef.explicitlySpecified,
testFunDef.selectors),
testFun.runner,
nt))
}
def makeParallel(loader: ClassLoader, runnables: Iterable[TestRunnable], setupTasks: Task[Unit], tags: Seq[(Tag, Int)]): Task[Map[String, SuiteResult]] =
def makeParallel(loader: ClassLoader,
runnables: Iterable[TestRunnable],
setupTasks: Task[Unit],
tags: Seq[(Tag, Int)]): Task[Map[String, SuiteResult]] =
toTasks(loader, runnables.toSeq, tags).dependsOn(setupTasks)
def toTasks(loader: ClassLoader, runnables: Seq[TestRunnable], tags: Seq[(Tag, Int)]): Task[Map[String, SuiteResult]] = {
def toTasks(loader: ClassLoader,
runnables: Seq[TestRunnable],
tags: Seq[(Tag, Int)]): Task[Map[String, SuiteResult]] = {
val tasks = runnables.map { case (name, test) => toTask(loader, name, test, tags) }
tasks.join.map(_.foldLeft(Map.empty[String, SuiteResult]) {
case (sum, e) =>
@ -212,7 +267,10 @@ object Tests {
})
}
def toTask(loader: ClassLoader, name: String, fun: TestFunction, tags: Seq[(Tag, Int)]): Task[Map[String, SuiteResult]] = {
def toTask(loader: ClassLoader,
name: String,
fun: TestFunction,
tags: Seq[(Tag, Int)]): Task[Map[String, SuiteResult]] = {
val base = task { (name, fun.apply()) }
val taggedBase = base.tagw(tags: _*).tag(fun.tags.map(ConcurrentRestrictions.Tag(_)): _*)
taggedBase flatMap {
@ -229,21 +287,24 @@ object Tests {
}
}
def makeSerial(loader: ClassLoader, runnables: Seq[TestRunnable], setupTasks: Task[Unit], tags: Seq[(Tag, Int)]): Task[List[(String, SuiteResult)]] =
{
@tailrec
def processRunnable(runnableList: List[TestRunnable], acc: List[(String, SuiteResult)]): List[(String, SuiteResult)] =
runnableList match {
case hd :: rst =>
val testFun = hd._2
val (result, nestedTasks) = testFun.apply()
val nestedRunnables = createNestedRunnables(loader, testFun, nestedTasks)
processRunnable(nestedRunnables.toList ::: rst, (hd._1, result) :: acc)
case Nil => acc
}
def makeSerial(loader: ClassLoader,
runnables: Seq[TestRunnable],
setupTasks: Task[Unit],
tags: Seq[(Tag, Int)]): Task[List[(String, SuiteResult)]] = {
@tailrec
def processRunnable(runnableList: List[TestRunnable],
acc: List[(String, SuiteResult)]): List[(String, SuiteResult)] =
runnableList match {
case hd :: rst =>
val testFun = hd._2
val (result, nestedTasks) = testFun.apply()
val nestedRunnables = createNestedRunnables(loader, testFun, nestedTasks)
processRunnable(nestedRunnables.toList ::: rst, (hd._1, result) :: acc)
case Nil => acc
}
task { processRunnable(runnables.toList, List.empty) } dependsOn (setupTasks)
}
task { processRunnable(runnables.toList, List.empty) } dependsOn (setupTasks)
}
def processResults(results: Iterable[(String, SuiteResult)]): Output =
Output(overall(results.map(_._2.result)), results.toMap, Iterable.empty)
@ -257,24 +318,34 @@ object Tests {
def foldTasks(results: Seq[Task[Output]], parallel: Boolean): Task[Output] =
if (results.isEmpty)
task { Output(TestResult.Passed, Map.empty, Nil) }
else if (parallel)
task { Output(TestResult.Passed, Map.empty, Nil) } else if (parallel)
reduced(results.toIndexedSeq, {
case (Output(v1, m1, _), Output(v2, m2, _)) => Output(if (severity(v1) < severity(v2)) v2 else v1, m1 ++ m2, Iterable.empty)
case (Output(v1, m1, _), Output(v2, m2, _)) =>
Output(if (severity(v1) < severity(v2)) v2 else v1, m1 ++ m2, Iterable.empty)
})
else {
def sequence(tasks: List[Task[Output]], acc: List[Output]): Task[List[Output]] = tasks match {
case Nil => task(acc.reverse)
case hd :: tl => hd flatMap { out => sequence(tl, out :: acc) }
}
def sequence(tasks: List[Task[Output]], acc: List[Output]): Task[List[Output]] =
tasks match {
case Nil => task(acc.reverse)
case hd :: tl =>
hd flatMap { out =>
sequence(tl, out :: acc)
}
}
sequence(results.toList, List()) map { ress =>
val (rs, ms) = ress.unzip { e => (e.overall, e.events) }
val (rs, ms) = ress.unzip { e =>
(e.overall, e.events)
}
Output(overall(rs), ms reduce (_ ++ _), Iterable.empty)
}
}
def overall(results: Iterable[TestResult]): TestResult =
((TestResult.Passed: TestResult) /: results) { (acc, result) => if (severity(acc) < severity(result)) result else acc }
def discover(frameworks: Seq[Framework], analysis: CompileAnalysis, log: Logger): (Seq[TestDefinition], Set[String]) =
((TestResult.Passed: TestResult) /: results) { (acc, result) =>
if (severity(acc) < severity(result)) result else acc
}
def discover(frameworks: Seq[Framework],
analysis: CompileAnalysis,
log: Logger): (Seq[TestDefinition], Set[String]) =
discover(frameworks flatMap TestFramework.getFingerprints, allDefs(analysis), log)
def allDefs(analysis: CompileAnalysis) = analysis match {
@ -290,27 +361,37 @@ object Tests {
all
}.toSeq
}
def discover(fingerprints: Seq[Fingerprint], definitions: Seq[Definition], log: Logger): (Seq[TestDefinition], Set[String]) =
{
val subclasses = fingerprints collect { case sub: SubclassFingerprint => (sub.superclassName, sub.isModule, sub) };
val annotations = fingerprints collect { case ann: AnnotatedFingerprint => (ann.annotationName, ann.isModule, ann) };
log.debug("Subclass fingerprints: " + subclasses)
log.debug("Annotation fingerprints: " + annotations)
def discover(fingerprints: Seq[Fingerprint],
definitions: Seq[Definition],
log: Logger): (Seq[TestDefinition], Set[String]) = {
val subclasses = fingerprints collect {
case sub: SubclassFingerprint => (sub.superclassName, sub.isModule, sub)
};
val annotations = fingerprints collect {
case ann: AnnotatedFingerprint => (ann.annotationName, ann.isModule, ann)
};
log.debug("Subclass fingerprints: " + subclasses)
log.debug("Annotation fingerprints: " + annotations)
def firsts[A, B, C](s: Seq[(A, B, C)]): Set[A] = s.map(_._1).toSet
def defined(in: Seq[(String, Boolean, Fingerprint)], names: Set[String], IsModule: Boolean): Seq[Fingerprint] =
in collect { case (name, IsModule, print) if names(name) => print }
def firsts[A, B, C](s: Seq[(A, B, C)]): Set[A] = s.map(_._1).toSet
def defined(in: Seq[(String, Boolean, Fingerprint)],
names: Set[String],
IsModule: Boolean): Seq[Fingerprint] =
in collect { case (name, IsModule, print) if names(name) => print }
def toFingerprints(d: Discovered): Seq[Fingerprint] =
defined(subclasses, d.baseClasses, d.isModule) ++
defined(annotations, d.annotations, d.isModule)
def toFingerprints(d: Discovered): Seq[Fingerprint] =
defined(subclasses, d.baseClasses, d.isModule) ++
defined(annotations, d.annotations, d.isModule)
val discovered = Discovery(firsts(subclasses), firsts(annotations))(definitions)
// TODO: To pass in correct explicitlySpecified and selectors
val tests = for ((df, di) <- discovered; fingerprint <- toFingerprints(di)) yield new TestDefinition(df.name, fingerprint, false, Array(new SuiteSelector))
val mains = discovered collect { case (df, di) if di.hasMain => df.name }
(tests, mains.toSet)
}
val discovered = Discovery(firsts(subclasses), firsts(annotations))(definitions)
// TODO: To pass in correct explicitlySpecified and selectors
val tests = for ((df, di) <- discovered; fingerprint <- toFingerprints(di))
yield new TestDefinition(df.name, fingerprint, false, Array(new SuiteSelector))
val mains = discovered collect { case (df, di) if di.hasMain => df.name }
(tests, mains.toSet)
}
}
final class TestsFailedException extends RuntimeException("Tests unsuccessful") with FeedbackProvidedException
final class TestsFailedException
extends RuntimeException("Tests unsuccessful")
with FeedbackProvidedException

View File

@ -26,7 +26,10 @@ final class EvalImports(val strings: Seq[(String, Int)], val srcName: String)
* the module from that class loader. `generated` contains the compiled classes and cache files related
* to the expression. The name of the auto-generated module wrapping the expression is `enclosingModule`.
*/
final class EvalResult(val tpe: String, val getValue: ClassLoader => Any, val generated: Seq[File], val enclosingModule: String)
final class EvalResult(val tpe: String,
val getValue: ClassLoader => Any,
val generated: Seq[File],
val enclosingModule: String)
/**
* The result of evaluating a group of Scala definitions. The definitions are wrapped in an auto-generated,
@ -35,7 +38,10 @@ final class EvalResult(val tpe: String, val getValue: ClassLoader => Any, val ge
* from the classpath that the definitions were compiled against. The list of vals with the requested types is `valNames`.
* The values for these may be obtained by providing the parent class loader to `values` as is done with `loader`.
*/
final class EvalDefinitions(val loader: ClassLoader => ClassLoader, val generated: Seq[File], val enclosingModule: String, val valNames: Seq[String]) {
final class EvalDefinitions(val loader: ClassLoader => ClassLoader,
val generated: Seq[File],
val enclosingModule: String,
val valNames: Seq[String]) {
def values(parent: ClassLoader): Seq[Any] = {
val module = getModule(enclosingModule, loader(parent))
for (n <- valNames) yield module.getClass.getMethod(n).invoke(module)
@ -44,26 +50,31 @@ final class EvalDefinitions(val loader: ClassLoader => ClassLoader, val generate
final class EvalException(msg: String) extends RuntimeException(msg)
// not thread safe, since it reuses a Global instance
final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Settings => Reporter, backing: Option[File]) {
def this(mkReporter: Settings => Reporter, backing: Option[File]) = this(Nil, IO.classLocationFile[Product] :: Nil, mkReporter, backing)
final class Eval(optionsNoncp: Seq[String],
classpath: Seq[File],
mkReporter: Settings => Reporter,
backing: Option[File]) {
def this(mkReporter: Settings => Reporter, backing: Option[File]) =
this(Nil, IO.classLocationFile[Product] :: Nil, mkReporter, backing)
def this() = this(s => new ConsoleReporter(s), None)
backing.foreach(IO.createDirectory)
val classpathString = Path.makeString(classpath ++ backing.toList)
val options = "-cp" +: classpathString +: optionsNoncp
lazy val settings =
{
val s = new Settings(println)
new CompilerCommand(options.toList, s) // this side-effects on Settings..
s
}
lazy val settings = {
val s = new Settings(println)
new CompilerCommand(options.toList, s) // this side-effects on Settings..
s
}
lazy val reporter = mkReporter(settings)
/**
* Subclass of Global which allows us to mutate currentRun from outside.
* See for rationale https://issues.scala-lang.org/browse/SI-8794
*/
final class EvalGlobal(settings: Settings, reporter: Reporter) extends Global(settings, reporter) {
final class EvalGlobal(settings: Settings, reporter: Reporter)
extends Global(settings, reporter) {
override def currentRun: Run = curRun
var curRun: Run = null
}
@ -78,116 +89,135 @@ final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Se
private[this] var toUnlinkLater = List[Symbol]()
private[this] def unlink(sym: Symbol) = sym.owner.info.decls.unlink(sym)
def eval(expression: String, imports: EvalImports = noImports, tpeName: Option[String] = None, srcName: String = "<setting>", line: Int = DefaultStartLine): EvalResult =
{
val ev = new EvalType[String] {
def makeUnit = mkUnit(srcName, line, expression)
def unlink = true
def unitBody(unit: CompilationUnit, importTrees: Seq[Tree], moduleName: String): Tree = {
val (parser, tree) = parse(unit, settingErrorStrings, _.expr())
val tpt: Tree = expectedType(tpeName)
augment(parser, importTrees, tree, tpt, moduleName)
}
def extra(run: Run, unit: CompilationUnit) = enteringPhase(run.typerPhase.next) { (new TypeExtractor).getType(unit.body) }
def read(file: File) = IO.read(file)
def write(value: String, f: File) = IO.write(f, value)
def extraHash = ""
def eval(expression: String,
imports: EvalImports = noImports,
tpeName: Option[String] = None,
srcName: String = "<setting>",
line: Int = DefaultStartLine): EvalResult = {
val ev = new EvalType[String] {
def makeUnit = mkUnit(srcName, line, expression)
def unlink = true
def unitBody(unit: CompilationUnit, importTrees: Seq[Tree], moduleName: String): Tree = {
val (parser, tree) = parse(unit, settingErrorStrings, _.expr())
val tpt: Tree = expectedType(tpeName)
augment(parser, importTrees, tree, tpt, moduleName)
}
val i = evalCommon(expression :: Nil, imports, tpeName, ev)
val value = (cl: ClassLoader) => getValue[Any](i.enclosingModule, i.loader(cl))
new EvalResult(i.extra, value, i.generated, i.enclosingModule)
def extra(run: Run, unit: CompilationUnit) = enteringPhase(run.typerPhase.next) {
(new TypeExtractor).getType(unit.body)
}
def read(file: File) = IO.read(file)
def write(value: String, f: File) = IO.write(f, value)
def extraHash = ""
}
def evalDefinitions(definitions: Seq[(String, scala.Range)], imports: EvalImports, srcName: String, file: Option[File], valTypes: Seq[String]): EvalDefinitions =
{
require(definitions.nonEmpty, "Definitions to evaluate cannot be empty.")
val ev = new EvalType[Seq[String]] {
lazy val (fullUnit, defUnits) = mkDefsUnit(srcName, definitions)
def makeUnit = fullUnit
def unlink = false
def unitBody(unit: CompilationUnit, importTrees: Seq[Tree], moduleName: String): Tree = {
val fullParser = new syntaxAnalyzer.UnitParser(unit)
val trees = defUnits flatMap parseDefinitions
syntheticModule(fullParser, importTrees, trees.toList, moduleName)
}
def extra(run: Run, unit: CompilationUnit) = {
enteringPhase(run.typerPhase.next) { (new ValExtractor(valTypes.toSet)).getVals(unit.body) }
}
def read(file: File) = IO.readLines(file)
def write(value: Seq[String], file: File) = IO.writeLines(file, value)
def extraHash = file match {
case Some(f) => f.getAbsolutePath
case None => ""
val i = evalCommon(expression :: Nil, imports, tpeName, ev)
val value = (cl: ClassLoader) => getValue[Any](i.enclosingModule, i.loader(cl))
new EvalResult(i.extra, value, i.generated, i.enclosingModule)
}
def evalDefinitions(definitions: Seq[(String, scala.Range)],
imports: EvalImports,
srcName: String,
file: Option[File],
valTypes: Seq[String]): EvalDefinitions = {
require(definitions.nonEmpty, "Definitions to evaluate cannot be empty.")
val ev = new EvalType[Seq[String]] {
lazy val (fullUnit, defUnits) = mkDefsUnit(srcName, definitions)
def makeUnit = fullUnit
def unlink = false
def unitBody(unit: CompilationUnit, importTrees: Seq[Tree], moduleName: String): Tree = {
val fullParser = new syntaxAnalyzer.UnitParser(unit)
val trees = defUnits flatMap parseDefinitions
syntheticModule(fullParser, importTrees, trees.toList, moduleName)
}
def extra(run: Run, unit: CompilationUnit) = {
enteringPhase(run.typerPhase.next) {
(new ValExtractor(valTypes.toSet)).getVals(unit.body)
}
}
val i = evalCommon(definitions.map(_._1), imports, Some(""), ev)
new EvalDefinitions(i.loader, i.generated, i.enclosingModule, i.extra)
def read(file: File) = IO.readLines(file)
def write(value: Seq[String], file: File) = IO.writeLines(file, value)
def extraHash = file match {
case Some(f) => f.getAbsolutePath
case None => ""
}
}
val i = evalCommon(definitions.map(_._1), imports, Some(""), ev)
new EvalDefinitions(i.loader, i.generated, i.enclosingModule, i.extra)
}
private[this] def evalCommon[T](content: Seq[String],
imports: EvalImports,
tpeName: Option[String],
ev: EvalType[T]): EvalIntermediate[T] = {
import Eval._
// TODO - We also encode the source of the setting into the hash to avoid conflicts where the exact SAME setting
// is defined in multiple evaluated instances with a backing. This leads to issues with finding a previous
// value on the classpath when compiling.
val hash = Hash.toHex(
Hash(bytes(
stringSeqBytes(content) :: optBytes(backing)(fileExistsBytes) :: stringSeqBytes(options) ::
seqBytes(classpath)(fileModifiedBytes) :: stringSeqBytes(imports.strings.map(_._1)) :: optBytes(
tpeName)(bytes) ::
bytes(ev.extraHash) :: Nil)))
val moduleName = makeModuleName(hash)
lazy val unit = {
reporter.reset
ev.makeUnit
}
lazy val run = new Run {
override def units = (unit :: Nil).iterator
}
def unlinkAll(): Unit =
for ((sym, _) <- run.symSource) if (ev.unlink) unlink(sym) else toUnlinkLater ::= sym
val (extra, loader) = backing match {
case Some(back) if classExists(back, moduleName) =>
val loader = (parent: ClassLoader) => new URLClassLoader(Array(back.toURI.toURL), parent)
val extra = ev.read(cacheFile(back, moduleName))
(extra, loader)
case _ =>
try { compileAndLoad(run, unit, imports, backing, moduleName, ev) } finally { unlinkAll() }
}
private[this] def evalCommon[T](content: Seq[String], imports: EvalImports, tpeName: Option[String], ev: EvalType[T]): EvalIntermediate[T] =
{
import Eval._
// TODO - We also encode the source of the setting into the hash to avoid conflicts where the exact SAME setting
// is defined in multiple evaluated instances with a backing. This leads to issues with finding a previous
// value on the classpath when compiling.
val hash = Hash.toHex(Hash(bytes(stringSeqBytes(content) :: optBytes(backing)(fileExistsBytes) :: stringSeqBytes(options) ::
seqBytes(classpath)(fileModifiedBytes) :: stringSeqBytes(imports.strings.map(_._1)) :: optBytes(tpeName)(bytes) ::
bytes(ev.extraHash) :: Nil)))
val moduleName = makeModuleName(hash)
lazy val unit = {
reporter.reset
ev.makeUnit
}
lazy val run = new Run {
override def units = (unit :: Nil).iterator
}
def unlinkAll(): Unit = for ((sym, _) <- run.symSource) if (ev.unlink) unlink(sym) else toUnlinkLater ::= sym
val (extra, loader) = backing match {
case Some(back) if classExists(back, moduleName) =>
val loader = (parent: ClassLoader) => new URLClassLoader(Array(back.toURI.toURL), parent)
val extra = ev.read(cacheFile(back, moduleName))
(extra, loader)
case _ =>
try { compileAndLoad(run, unit, imports, backing, moduleName, ev) }
finally { unlinkAll() }
}
val generatedFiles = getGeneratedFiles(backing, moduleName)
new EvalIntermediate(extra, loader, generatedFiles, moduleName)
}
val generatedFiles = getGeneratedFiles(backing, moduleName)
new EvalIntermediate(extra, loader, generatedFiles, moduleName)
}
// location of the cached type or definition information
private[this] def cacheFile(base: File, moduleName: String): File = new File(base, moduleName + ".cache")
private[this] def compileAndLoad[T](run: Run, unit: CompilationUnit, imports: EvalImports, backing: Option[File], moduleName: String, ev: EvalType[T]): (T, ClassLoader => ClassLoader) =
{
global.curRun = run
run.currentUnit = unit
val dir = outputDirectory(backing)
settings.outputDirs setSingleOutput dir
private[this] def cacheFile(base: File, moduleName: String): File =
new File(base, moduleName + ".cache")
private[this] def compileAndLoad[T](run: Run,
unit: CompilationUnit,
imports: EvalImports,
backing: Option[File],
moduleName: String,
ev: EvalType[T]): (T, ClassLoader => ClassLoader) = {
global.curRun = run
run.currentUnit = unit
val dir = outputDirectory(backing)
settings.outputDirs setSingleOutput dir
val importTrees = parseImports(imports)
unit.body = ev.unitBody(unit, importTrees, moduleName)
val importTrees = parseImports(imports)
unit.body = ev.unitBody(unit, importTrees, moduleName)
def compile(phase: Phase): Unit =
{
globalPhase = phase
if (phase == null || phase == phase.next || reporter.hasErrors)
()
else {
enteringPhase(phase) { phase.run }
compile(phase.next)
}
}
compile(run.namerPhase)
checkError("Type error in expression")
val extra = ev.extra(run, unit)
for (f <- backing) ev.write(extra, cacheFile(f, moduleName))
val loader = (parent: ClassLoader) => new AbstractFileClassLoader(dir, parent)
(extra, loader)
def compile(phase: Phase): Unit = {
globalPhase = phase
if (phase == null || phase == phase.next || reporter.hasErrors)
()
else {
enteringPhase(phase) { phase.run }
compile(phase.next)
}
}
compile(run.namerPhase)
checkError("Type error in expression")
val extra = ev.extra(run, unit)
for (f <- backing) ev.write(extra, cacheFile(f, moduleName))
val loader = (parent: ClassLoader) => new AbstractFileClassLoader(dir, parent)
(extra, loader)
}
private[this] def expectedType(tpeName: Option[String]): Tree =
tpeName match {
case Some(tpe) => parseType(tpe)
@ -195,43 +225,55 @@ final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Se
}
private[this] def outputDirectory(backing: Option[File]): AbstractFile =
backing match { case None => new VirtualDirectory("<virtual>", None); case Some(dir) => new PlainFile(dir) }
backing match {
case None => new VirtualDirectory("<virtual>", None); case Some(dir) => new PlainFile(dir)
}
def load(dir: AbstractFile, moduleName: String): ClassLoader => Any = parent => getValue[Any](moduleName, new AbstractFileClassLoader(dir, parent))
def loadPlain(dir: File, moduleName: String): ClassLoader => Any = parent => getValue[Any](moduleName, new URLClassLoader(Array(dir.toURI.toURL), parent))
def load(dir: AbstractFile, moduleName: String): ClassLoader => Any =
parent => getValue[Any](moduleName, new AbstractFileClassLoader(dir, parent))
def loadPlain(dir: File, moduleName: String): ClassLoader => Any =
parent => getValue[Any](moduleName, new URLClassLoader(Array(dir.toURI.toURL), parent))
//wrap tree in object objectName { def WrapValName = <tree> }
def augment(parser: global.syntaxAnalyzer.UnitParser, imports: Seq[Tree], tree: Tree, tpt: Tree, objectName: String): Tree =
{
val method = DefDef(NoMods, newTermName(WrapValName), Nil, Nil, tpt, tree)
syntheticModule(parser, imports, method :: Nil, objectName)
}
private[this] def syntheticModule(parser: global.syntaxAnalyzer.UnitParser, imports: Seq[Tree], definitions: List[Tree], objectName: String): Tree =
{
val emptyTypeName = nme.EMPTY.toTypeName
def emptyPkg = parser.atPos(0, 0, 0) { Ident(nme.EMPTY_PACKAGE_NAME) }
def emptyInit = DefDef(
NoMods,
nme.CONSTRUCTOR,
Nil,
List(Nil),
TypeTree(),
Block(List(Apply(Select(Super(This(emptyTypeName), emptyTypeName), nme.CONSTRUCTOR), Nil)), Literal(Constant(())))
)
def augment(parser: global.syntaxAnalyzer.UnitParser,
imports: Seq[Tree],
tree: Tree,
tpt: Tree,
objectName: String): Tree = {
val method = DefDef(NoMods, newTermName(WrapValName), Nil, Nil, tpt, tree)
syntheticModule(parser, imports, method :: Nil, objectName)
}
private[this] def syntheticModule(parser: global.syntaxAnalyzer.UnitParser,
imports: Seq[Tree],
definitions: List[Tree],
objectName: String): Tree = {
val emptyTypeName = nme.EMPTY.toTypeName
def emptyPkg = parser.atPos(0, 0, 0) { Ident(nme.EMPTY_PACKAGE_NAME) }
def emptyInit = DefDef(
NoMods,
nme.CONSTRUCTOR,
Nil,
List(Nil),
TypeTree(),
Block(List(Apply(Select(Super(This(emptyTypeName), emptyTypeName), nme.CONSTRUCTOR), Nil)),
Literal(Constant(())))
)
def moduleBody = Template(List(gen.scalaAnyRefConstr), noSelfType, emptyInit :: definitions)
def moduleDef = ModuleDef(NoMods, newTermName(objectName), moduleBody)
parser.makePackaging(0, emptyPkg, (imports :+ moduleDef).toList)
}
def moduleBody = Template(List(gen.scalaAnyRefConstr), noSelfType, emptyInit :: definitions)
def moduleDef = ModuleDef(NoMods, newTermName(objectName), moduleBody)
parser.makePackaging(0, emptyPkg, (imports :+ moduleDef).toList)
}
private[this] final class TypeExtractor extends Traverser {
private[this] var result = ""
def getType(t: Tree) = { result = ""; traverse(t); result }
override def traverse(tree: Tree): Unit = tree match {
case d: DefDef if d.symbol.nameString == WrapValName => result = d.symbol.tpe.finalResultType.toString
case _ => super.traverse(tree)
case d: DefDef if d.symbol.nameString == WrapValName =>
result = d.symbol.tpe.finalResultType.toString
case _ => super.traverse(tree)
}
}
/** Tree traverser that obtains the names of vals in a top-level module whose type is a subtype of one of `types`.*/
private[this] final class ValExtractor(tpes: Set[String]) extends Traverser {
private[this] var vals = List[String]()
@ -242,15 +284,20 @@ final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Se
}
}
override def traverse(tree: Tree): Unit = tree match {
case ValDef(_, n, actualTpe, _) if isTopLevelModule(tree.symbol.owner) && isAcceptableType(actualTpe.tpe) =>
case ValDef(_, n, actualTpe, _)
if isTopLevelModule(tree.symbol.owner) && isAcceptableType(actualTpe.tpe) =>
vals ::= n.dropLocal.encoded
case _ => super.traverse(tree)
}
}
// inlined implemented of Symbol.isTopLevelModule that was removed in e5b050814deb2e7e1d6d05511d3a6cb6b013b549
private[this] def isTopLevelModule(s: Symbol): Boolean = s.hasFlag(reflect.internal.Flags.MODULE) && s.owner.isPackageClass
private[this] def isTopLevelModule(s: Symbol): Boolean =
s.hasFlag(reflect.internal.Flags.MODULE) && s.owner.isPackageClass
private[this] final class EvalIntermediate[T](val extra: T, val loader: ClassLoader => ClassLoader, val generated: Seq[File], val enclosingModule: String)
private[this] final class EvalIntermediate[T](val extra: T,
val loader: ClassLoader => ClassLoader,
val generated: Seq[File],
val enclosingModule: String)
private[this] def classExists(dir: File, name: String) = (new File(dir, name + ".class")).exists
// TODO: use the code from Analyzer
@ -264,7 +311,10 @@ final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Se
(s contains moduleName)
}
private[this] class ParseErrorStrings(val base: String, val extraBlank: String, val missingBlank: String, val extraSemi: String)
private[this] class ParseErrorStrings(val base: String,
val extraBlank: String,
val missingBlank: String,
val extraSemi: String)
private[this] def definitionErrorStrings = new ParseErrorStrings(
base = "Error parsing definition.",
extraBlank = " Ensure that there are no blank lines within a definition.",
@ -275,67 +325,67 @@ final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Se
base = "Error parsing expression.",
extraBlank = " Ensure that there are no blank lines within a setting.",
missingBlank = " Ensure that settings are separated by blank lines.",
extraSemi = " Note that settings are expressions and do not end with semicolons. (Semicolons are fine within {} blocks, however.)"
extraSemi =
" Note that settings are expressions and do not end with semicolons. (Semicolons are fine within {} blocks, however.)"
)
/**
* Parses the provided compilation `unit` according to `f` and then performs checks on the final parser state
* to catch errors that are common when the content is embedded in a blank-line-delimited format.
*/
private[this] def parse[T](unit: CompilationUnit, errors: ParseErrorStrings, f: syntaxAnalyzer.UnitParser => T): (syntaxAnalyzer.UnitParser, T) =
{
val parser = new syntaxAnalyzer.UnitParser(unit)
private[this] def parse[T](unit: CompilationUnit,
errors: ParseErrorStrings,
f: syntaxAnalyzer.UnitParser => T): (syntaxAnalyzer.UnitParser, T) = {
val parser = new syntaxAnalyzer.UnitParser(unit)
val tree = f(parser)
val extra = parser.in.token match {
case EOF => errors.extraBlank
case _ => ""
}
checkError(errors.base + extra)
parser.accept(EOF)
val extra2 = parser.in.token match {
case SEMI => errors.extraSemi
case NEWLINE | NEWLINES => errors.missingBlank
case _ => ""
}
checkError(errors.base + extra2)
(parser, tree)
val tree = f(parser)
val extra = parser.in.token match {
case EOF => errors.extraBlank
case _ => ""
}
private[this] def parseType(tpe: String): Tree =
{
val tpeParser = new syntaxAnalyzer.UnitParser(mkUnit("<expected-type>", DefaultStartLine, tpe))
val tpt0: Tree = tpeParser.typ()
tpeParser.accept(EOF)
checkError("Error parsing expression type.")
tpt0
checkError(errors.base + extra)
parser.accept(EOF)
val extra2 = parser.in.token match {
case SEMI => errors.extraSemi
case NEWLINE | NEWLINES => errors.missingBlank
case _ => ""
}
checkError(errors.base + extra2)
(parser, tree)
}
private[this] def parseType(tpe: String): Tree = {
val tpeParser = new syntaxAnalyzer.UnitParser(mkUnit("<expected-type>", DefaultStartLine, tpe))
val tpt0: Tree = tpeParser.typ()
tpeParser.accept(EOF)
checkError("Error parsing expression type.")
tpt0
}
private[this] def parseImports(imports: EvalImports): Seq[Tree] =
imports.strings flatMap { case (s, line) => parseImport(mkUnit(imports.srcName, line, s)) }
private[this] def parseImport(importUnit: CompilationUnit): Seq[Tree] =
{
val parser = new syntaxAnalyzer.UnitParser(importUnit)
val trees: Seq[Tree] = parser.importClause()
parser.accept(EOF)
checkError("Error parsing imports for expression.")
trees
}
private[this] def parseImport(importUnit: CompilationUnit): Seq[Tree] = {
val parser = new syntaxAnalyzer.UnitParser(importUnit)
val trees: Seq[Tree] = parser.importClause()
parser.accept(EOF)
checkError("Error parsing imports for expression.")
trees
}
private[this] def parseDefinitions(du: CompilationUnit): Seq[Tree] =
parse(du, definitionErrorStrings, parseDefinitions)._2
/** Parses one or more definitions (defs, vals, lazy vals, classes, traits, modules). */
private[this] def parseDefinitions(parser: syntaxAnalyzer.UnitParser): Seq[Tree] =
{
val defs = ListBuffer[Tree]()
do {
defs ++= parser.nonLocalDefOrDcl
parser.acceptStatSepOpt()
} while (!parser.isStatSeqEnd)
defs.toList
}
private[this] def parseDefinitions(parser: syntaxAnalyzer.UnitParser): Seq[Tree] = {
val defs = ListBuffer[Tree]()
do {
defs ++= parser.nonLocalDefOrDcl
parser.acceptStatSepOpt()
} while (!parser.isStatSeqEnd)
defs.toList
}
private[this] trait EvalType[T] {
/** Extracts additional information after the compilation unit is evaluated.*/
def extra(run: Run, unit: CompilationUnit): T
@ -369,47 +419,55 @@ final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Se
val DefaultStartLine = 0
private[this] def makeModuleName(hash: String): String = "$" + Hash.halve(hash)
private[this] def noImports = new EvalImports(Nil, "")
private[this] def mkUnit(srcName: String, firstLine: Int, s: String) = new CompilationUnit(new EvalSourceFile(srcName, firstLine, s))
private[this] def checkError(label: String) = if (reporter.hasErrors) throw new EvalException(label)
private[this] def mkUnit(srcName: String, firstLine: Int, s: String) =
new CompilationUnit(new EvalSourceFile(srcName, firstLine, s))
private[this] def checkError(label: String) =
if (reporter.hasErrors) throw new EvalException(label)
private[this] final class EvalSourceFile(name: String, startLine: Int, contents: String) extends BatchSourceFile(name, contents) {
private[this] final class EvalSourceFile(name: String, startLine: Int, contents: String)
extends BatchSourceFile(name, contents) {
override def lineToOffset(line: Int): Int = super.lineToOffset((line - startLine) max 0)
override def offsetToLine(offset: Int): Int = super.offsetToLine(offset) + startLine
}
/**
* Constructs a CompilationUnit for each definition, which can be used to independently parse the definition into a Tree.
* Additionally, a CompilationUnit for the combined definitions is constructed for use by combined compilation after parsing.
*/
private[this] def mkDefsUnit(srcName: String, definitions: Seq[(String, scala.Range)]): (CompilationUnit, Seq[CompilationUnit]) =
{
def fragmentUnit(content: String, lineMap: Array[Int]) = new CompilationUnit(fragmentSourceFile(srcName, content, lineMap))
private[this] def mkDefsUnit(
srcName: String,
definitions: Seq[(String, scala.Range)]): (CompilationUnit, Seq[CompilationUnit]) = {
def fragmentUnit(content: String, lineMap: Array[Int]) =
new CompilationUnit(fragmentSourceFile(srcName, content, lineMap))
import collection.mutable.ListBuffer
val lines = new ListBuffer[Int]()
val defs = new ListBuffer[CompilationUnit]()
val fullContent = new java.lang.StringBuilder()
for ((defString, range) <- definitions) {
defs += fragmentUnit(defString, range.toArray)
fullContent.append(defString)
lines ++= range
fullContent.append("\n\n")
lines ++= (range.end :: range.end :: Nil)
}
val fullUnit = fragmentUnit(fullContent.toString, lines.toArray)
(fullUnit, defs.toSeq)
import collection.mutable.ListBuffer
val lines = new ListBuffer[Int]()
val defs = new ListBuffer[CompilationUnit]()
val fullContent = new java.lang.StringBuilder()
for ((defString, range) <- definitions) {
defs += fragmentUnit(defString, range.toArray)
fullContent.append(defString)
lines ++= range
fullContent.append("\n\n")
lines ++= (range.end :: range.end :: Nil)
}
val fullUnit = fragmentUnit(fullContent.toString, lines.toArray)
(fullUnit, defs.toSeq)
}
/**
* Source file that can map the offset in the file to and from line numbers that may discontinuous.
* The values in `lineMap` must be ordered, but need not be consecutive.
*/
private[this] def fragmentSourceFile(srcName: String, content: String, lineMap: Array[Int]) = new BatchSourceFile(srcName, content) {
override def lineToOffset(line: Int): Int = super.lineToOffset(lineMap.indexWhere(_ == line) max 0)
override def offsetToLine(offset: Int): Int = index(lineMap, super.offsetToLine(offset))
// the SourceFile attribute is populated from this method, so we are required to only return the name
override def toString = new File(srcName).getName
private[this] def index(a: Array[Int], i: Int): Int = if (i < 0 || i >= a.length) 0 else a(i)
}
private[this] def fragmentSourceFile(srcName: String, content: String, lineMap: Array[Int]) =
new BatchSourceFile(srcName, content) {
override def lineToOffset(line: Int): Int =
super.lineToOffset(lineMap.indexWhere(_ == line) max 0)
override def offsetToLine(offset: Int): Int = index(lineMap, super.offsetToLine(offset))
// the SourceFile attribute is populated from this method, so we are required to only return the name
override def toString = new File(srcName).getName
private[this] def index(a: Array[Int], i: Int): Int = if (i < 0 || i >= a.length) 0 else a(i)
}
}
private[sbt] object Eval {
def optBytes[T](o: Option[T])(f: T => Array[Byte]): Array[Byte] = seqBytes(o.toSeq)(f)
@ -417,7 +475,8 @@ private[sbt] object Eval {
def seqBytes[T](s: Seq[T])(f: T => Array[Byte]): Array[Byte] = bytes(s map f)
def bytes(b: Seq[Array[Byte]]): Array[Byte] = bytes(b.length) ++ b.flatten.toArray[Byte]
def bytes(b: Boolean): Array[Byte] = Array[Byte](if (b) 1 else 0)
def filesModifiedBytes(fs: Array[File]): Array[Byte] = if (fs eq null) filesModifiedBytes(Array[File]()) else seqBytes(fs)(fileModifiedBytes)
def filesModifiedBytes(fs: Array[File]): Array[Byte] =
if (fs eq null) filesModifiedBytes(Array[File]()) else seqBytes(fs)(fileModifiedBytes)
def fileModifiedBytes(f: File): Array[Byte] =
(if (f.isDirectory) filesModifiedBytes(f listFiles classDirFilter) else bytes(f.lastModified)) ++
bytes(f.getAbsolutePath)
@ -426,18 +485,16 @@ private[sbt] object Eval {
bytes(f.getAbsolutePath)
def bytes(s: String): Array[Byte] = s getBytes "UTF-8"
def bytes(l: Long): Array[Byte] =
{
val buffer = ByteBuffer.allocate(8)
buffer.putLong(l)
buffer.array
}
def bytes(i: Int): Array[Byte] =
{
val buffer = ByteBuffer.allocate(4)
buffer.putInt(i)
buffer.array
}
def bytes(l: Long): Array[Byte] = {
val buffer = ByteBuffer.allocate(8)
buffer.putLong(l)
buffer.array
}
def bytes(i: Int): Array[Byte] = {
val buffer = ByteBuffer.allocate(4)
buffer.putInt(i)
buffer.array
}
/** The name of the synthetic val in the synthetic module that an expression is assigned to. */
final val WrapValName = "$sbtdef"
@ -446,20 +503,18 @@ private[sbt] object Eval {
* Gets the value of the expression wrapped in module `objectName`, which is accessible via `loader`.
* The module name should not include the trailing `$`.
*/
def getValue[T](objectName: String, loader: ClassLoader): T =
{
val module = getModule(objectName, loader)
val accessor = module.getClass.getMethod(WrapValName)
val value = accessor.invoke(module)
value.asInstanceOf[T]
}
def getValue[T](objectName: String, loader: ClassLoader): T = {
val module = getModule(objectName, loader)
val accessor = module.getClass.getMethod(WrapValName)
val value = accessor.invoke(module)
value.asInstanceOf[T]
}
/** Gets the top-level module `moduleName` from the provided class `loader`. The module name should not include the trailing `$`.*/
def getModule(moduleName: String, loader: ClassLoader): Any =
{
val clazz = Class.forName(moduleName + "$", true, loader)
clazz.getField("MODULE$").get(null)
}
def getModule(moduleName: String, loader: ClassLoader): Any = {
val clazz = Class.forName(moduleName + "$", true, loader)
clazz.getField("MODULE$").get(null)
}
private val classDirFilter: FileFilter = DirectoryFilter || GlobFilter("*.class")
}

View File

@ -31,15 +31,19 @@ class CacheIvyTest extends Properties("CacheIvy") {
content = converter.toJsonUnsafe(value)
}
private def testCache[T: JsonFormat, U](f: (SingletonCache[T], CacheStore) => U)(implicit cache: SingletonCache[T]): U = {
private def testCache[T: JsonFormat, U](f: (SingletonCache[T], CacheStore) => U)(
implicit cache: SingletonCache[T]): U = {
val store = new InMemoryStore(Converter)
f(cache, store)
}
private def cachePreservesEquality[T: JsonFormat](m: T, eq: (T, T) => Prop, str: T => String): Prop = testCache[T, Prop] { (cache, store) =>
cache.write(store, m)
val out = cache.read(store)
eq(out, m) :| s"Expected: ${str(m)}" :| s"Got: ${str(out)}"
private def cachePreservesEquality[T: JsonFormat](m: T,
eq: (T, T) => Prop,
str: T => String): Prop = testCache[T, Prop] {
(cache, store) =>
cache.write(store, m)
val out = cache.read(store)
eq(out, m) :| s"Expected: ${str(m)}" :| s"Got: ${str(out)}"
}
implicit val arbExclusionRule: Arbitrary[ExclusionRule] = Arbitrary(
@ -77,11 +81,22 @@ class CacheIvyTest extends Properties("CacheIvy") {
inclusions <- Gen.listOf(arbitrary[InclusionRule])
extraAttributes <- Gen.mapOf(arbitrary[(String, String)])
crossVersion <- arbitrary[CrossVersion]
} yield ModuleID(
organization = o, name = n, revision = r, configurations = cs, isChanging = isChanging, isTransitive = isTransitive,
isForce = isForce, explicitArtifacts = explicitArtifacts.toVector, inclusions = inclusions.toVector, exclusions = exclusions.toVector,
extraAttributes = extraAttributes, crossVersion = crossVersion, branchName = branch
)
} yield
ModuleID(
organization = o,
name = n,
revision = r,
configurations = cs,
isChanging = isChanging,
isTransitive = isTransitive,
isForce = isForce,
explicitArtifacts = explicitArtifacts.toVector,
inclusions = inclusions.toVector,
exclusions = exclusions.toVector,
extraAttributes = extraAttributes,
crossVersion = crossVersion,
branchName = branch
)
}
property("moduleIDFormat") = forAll { (m: ModuleID) =>

View File

@ -16,22 +16,23 @@ class EvalTest extends Properties("eval") {
property("inferred integer") = forAll { (i: Int) =>
val result = eval.eval(i.toString)
(label("Value", value(result)) |: (value(result) == i)) &&
(label("Type", value(result)) |: (result.tpe == IntType)) &&
(label("Files", result.generated) |: (result.generated.isEmpty))
(label("Type", value(result)) |: (result.tpe == IntType)) &&
(label("Files", result.generated) |: (result.generated.isEmpty))
}
property("explicit integer") = forAll { (i: Int) =>
val result = eval.eval(i.toString, tpeName = Some(IntType))
(label("Value", value(result)) |: (value(result) == i)) &&
(label("Type", result.tpe) |: (result.tpe == IntType)) &&
(label("Files", result.generated) |: (result.generated.isEmpty))
(label("Type", result.tpe) |: (result.tpe == IntType)) &&
(label("Files", result.generated) |: (result.generated.isEmpty))
}
property("type mismatch") = forAll { (i: Int, l: Int) =>
val line = math.abs(l)
val src = "mismatch"
throws(classOf[RuntimeException])(eval.eval(i.toString, tpeName = Some(BooleanType), line = line, srcName = src)) &&
hasErrors(line + 1, src)
throws(classOf[RuntimeException])(
eval.eval(i.toString, tpeName = Some(BooleanType), line = line, srcName = src)) &&
hasErrors(line + 1, src)
}
property("backed local class") = forAll { (i: Int) =>
@ -40,8 +41,8 @@ class EvalTest extends Properties("eval") {
val result = eval.eval(local(i))
val v = value(result).asInstanceOf[{ def i: Int }].i
(label("Value", v) |: (v == i)) &&
(label("Type", result.tpe) |: (result.tpe == LocalType)) &&
(label("Files", result.generated) |: result.generated.nonEmpty)
(label("Type", result.tpe) |: (result.tpe == LocalType)) &&
(label("Files", result.generated) |: result.generated.nonEmpty)
}
}
@ -62,35 +63,38 @@ val p = {
property("val test") = secure {
val defs = (ValTestContent, 1 to 7) :: Nil
val res = eval.evalDefinitions(defs, new EvalImports(Nil, ""), "<defs>", None, "scala.Int" :: Nil)
val res =
eval.evalDefinitions(defs, new EvalImports(Nil, ""), "<defs>", None, "scala.Int" :: Nil)
label("Val names", res.valNames) |: (res.valNames.toSet == ValTestNames)
}
property("explicit import") = forAll(testImport("import math.abs" :: Nil))
property("wildcard import") = forAll(testImport("import math._" :: Nil))
property("comma-separated imports") = forAll(testImport("import annotation._, math._, meta._" :: Nil))
property("multiple imports") = forAll(testImport("import annotation._" :: "import math._" :: "import meta._" :: Nil))
property("comma-separated imports") = forAll(
testImport("import annotation._, math._, meta._" :: Nil))
property("multiple imports") = forAll(
testImport("import annotation._" :: "import math._" :: "import meta._" :: Nil))
private[this] def testImport(imports: Seq[String]): Int => Prop = i =>
value(eval.eval("abs(" + i + ")", new EvalImports(imports.zipWithIndex, "imp"))) == math.abs(i)
private[this] def testImport(imports: Seq[String]): Int => Prop =
i =>
value(eval.eval("abs(" + i + ")", new EvalImports(imports.zipWithIndex, "imp"))) == math.abs(
i)
private[this] def local(i: Int) = "{ class ETest(val i: Int); new ETest(" + i + ") }"
val LocalType = "AnyRef{val i: Int}"
private[this] def value(r: EvalResult) = r.getValue(getClass.getClassLoader)
private[this] def hasErrors(line: Int, src: String) =
{
val is = reporter.infos
("Has errors" |: is.nonEmpty) &&
all(is.toSeq.map(validPosition(line, src)): _*)
}
private[this] def validPosition(line: Int, src: String)(i: Info) =
{
val nme = i.pos.source.file.name
(label("Severity", i.severity) |: (i.severity == ERROR)) &&
(label("Line", i.pos.line) |: (i.pos.line == line)) &&
(label("Name", nme) |: (nme == src))
}
private[this] def hasErrors(line: Int, src: String) = {
val is = reporter.infos
("Has errors" |: is.nonEmpty) &&
all(is.toSeq.map(validPosition(line, src)): _*)
}
private[this] def validPosition(line: Int, src: String)(i: Info) = {
val nme = i.pos.source.file.name
(label("Severity", i.severity) |: (i.severity == ERROR)) &&
(label("Line", i.pos.line) |: (i.pos.line == line)) &&
(label("Name", nme) |: (nme == src))
}
val IntType = "Int"
val BooleanType = "Boolean"

View File

@ -16,7 +16,9 @@ object BasicCommandStrings {
/** The command name to terminate the program.*/
val TerminateAction: String = Exit
def helpBrief = (HelpCommand, s"Displays this help message or prints detailed help on requested commands (run '$HelpCommand <command>').")
def helpBrief =
(HelpCommand,
s"Displays this help message or prints detailed help on requested commands (run '$HelpCommand <command>').")
def helpDetailed = s"""$HelpCommand
Prints a help summary.
@ -30,26 +32,29 @@ $HelpCommand <regular expression>
Searches the help according to the provided regular expression.
"""
def CompletionsDetailed = "Displays a list of completions for the given argument string (run 'completions <string>')."
def CompletionsDetailed =
"Displays a list of completions for the given argument string (run 'completions <string>')."
def CompletionsBrief = (CompletionsCommand, CompletionsDetailed)
def templateBrief = (TemplateCommand, "Creates a new sbt build.")
def templateDetailed = TemplateCommand + """ [--options] <template>
Create a new sbt build based on the given template."""
def HistoryHelpBrief = (HistoryCommands.Start -> "History command help. Lists and describes all history commands.")
def historyHelp = Help(Nil, (HistoryHelpBrief +: HistoryCommands.descriptions).toMap, Set(HistoryCommands.Start))
def HistoryHelpBrief =
(HistoryCommands.Start -> "History command help. Lists and describes all history commands.")
def historyHelp =
Help(Nil, (HistoryHelpBrief +: HistoryCommands.descriptions).toMap, Set(HistoryCommands.Start))
def exitBrief = "Terminates the build."
def logLevelHelp =
{
val levels = Level.values.toSeq
val levelList = levels.mkString(", ")
val brief = ("<log-level>", "Sets the logging level to 'log-level'. Valid levels: " + levelList)
val detailed = levels.map(l => (l.toString, logLevelDetail(l))).toMap
Help(brief, detailed)
}
def logLevelHelp = {
val levels = Level.values.toSeq
val levelList = levels.mkString(", ")
val brief =
("<log-level>", "Sets the logging level to 'log-level'. Valid levels: " + levelList)
val detailed = levels.map(l => (l.toString, logLevelDetail(l))).toMap
Help(brief, detailed)
}
private[this] def logLevelDetail(level: Level.Value): String =
s"""$level
@ -70,11 +75,12 @@ $HelpCommand <regular expression>
private[sbt] def isEarlyCommand(s: String): Boolean = {
val levelOptions = Level.values.toSeq map { "-" + _ }
(s.startsWith(EarlyCommand + "(") && s.endsWith(")")) ||
(levelOptions contains s)
(levelOptions contains s)
}
val EarlyCommand = "early"
val EarlyCommandBrief = (s"$EarlyCommand(<command>)", "Schedules a command to run before other commands on startup.")
val EarlyCommandBrief =
(s"$EarlyCommand(<command>)", "Schedules a command to run before other commands on startup.")
val EarlyCommandDetailed =
s"""$EarlyCommand(<command>)
@ -117,7 +123,9 @@ $HelpCommand <regular expression>
and is useful when working with development versions of sbt or Scala."""
def Multi = ";"
def MultiBrief = (Multi + " <command> (" + Multi + " <command>)*", "Runs the provided semicolon-separated commands.")
def MultiBrief =
(Multi + " <command> (" + Multi + " <command>)*",
"Runs the provided semicolon-separated commands.")
def MultiDetailed =
Multi + " command1 " + Multi + """ command2 ...
@ -150,7 +158,8 @@ $AliasCommand name=
Removes the alias for `name`."""
def Shell = "shell"
def ShellDetailed = "Provides an interactive prompt and network server from which commands can be run."
def ShellDetailed =
"Provides an interactive prompt and network server from which commands can be run."
def OldShell = "oldshell"
def OldShellDetailed = "Provides an interactive prompt from which commands can be run."
@ -168,7 +177,8 @@ $AliasCommand name=
def ClearOnFailure = "--"
def FailureWall = "---"
def OnFailureDeprecated = deprecatedAlias(OnFailure, BasicCommandStrings.OnFailure)
def ClearOnFailureDeprecated = deprecatedAlias(ClearOnFailure, BasicCommandStrings.ClearOnFailure)
def ClearOnFailureDeprecated =
deprecatedAlias(ClearOnFailure, BasicCommandStrings.ClearOnFailure)
def FailureWallDeprecated = deprecatedAlias(FailureWall, BasicCommandStrings.FailureWall)
private[this] def deprecatedAlias(oldName: String, newName: String): String =
s"The `$oldName` command is deprecated in favor of `$newName` and will be removed in 0.14.0"

View File

@ -28,9 +28,27 @@ import scala.util.control.NonFatal
object BasicCommands {
lazy val allBasicCommands: Seq[Command] = Seq(
nop, ignore, help, completionsCommand, multi, ifLast, append, setOnFailure, clearOnFailure,
stashOnFailure, popOnFailure, reboot, call, early, exit, continuous, history, oldshell, client,
read, alias
nop,
ignore,
help,
completionsCommand,
multi,
ifLast,
append,
setOnFailure,
clearOnFailure,
stashOnFailure,
popOnFailure,
reboot,
call,
early,
exit,
continuous,
history,
oldshell,
client,
read,
alias
) ++ compatCommands
def nop: Command = Command.custom(s => success(() => s))
@ -51,24 +69,26 @@ object BasicCommands {
def help: Command = Command.make(HelpCommand, helpBrief, helpDetailed)(helpParser)
def helpParser(s: State): Parser[() => State] =
{
val h = (Help.empty /: s.definedCommands)((a, b) =>
a ++ (try b.help(s) catch { case NonFatal(_) => Help.empty }))
val helpCommands = h.detail.keySet
val spacedArg = singleArgument(helpCommands).?
applyEffect(spacedArg)(runHelp(s, h))
}
def helpParser(s: State): Parser[() => State] = {
val h = (Help.empty /: s.definedCommands)(
(a, b) =>
a ++ (try b.help(s)
catch { case NonFatal(_) => Help.empty }))
val helpCommands = h.detail.keySet
val spacedArg = singleArgument(helpCommands).?
applyEffect(spacedArg)(runHelp(s, h))
}
def runHelp(s: State, h: Help)(arg: Option[String]): State =
{
val message = try Help.message(h, arg) catch { case NonFatal(ex) => ex.toString }
System.out.println(message)
s
}
def runHelp(s: State, h: Help)(arg: Option[String]): State = {
val message = try Help.message(h, arg)
catch { case NonFatal(ex) => ex.toString }
System.out.println(message)
s
}
def completionsCommand: Command =
Command(CompletionsCommand, CompletionsBrief, CompletionsDetailed)(completionsParser)(runCompletions(_)(_))
Command(CompletionsCommand, CompletionsBrief, CompletionsDetailed)(completionsParser)(
runCompletions(_)(_))
def completionsParser(state: State): Parser[String] = {
val notQuoted = (NotQuoted ~ any.*) map { case (nq, s) => nq ++ s }
@ -77,21 +97,21 @@ object BasicCommands {
}
def runCompletions(state: State)(input: String): State = {
Parser.completions(state.combinedParser, input, 9).get map {
c => if (c.isEmpty) input else input + c.append
Parser.completions(state.combinedParser, input, 9).get map { c =>
if (c.isEmpty) input else input + c.append
} foreach { c =>
System.out.println("[completions] " + c.replaceAll("\n", " "))
}
state
}
def multiParser(s: State): Parser[List[String]] =
{
val nonSemi = token(charClass(_ != ';').+, hide = const(true))
val semi = token(';' ~> OptSpace)
val part = semi flatMap (_ => matched((s.combinedParser & nonSemi) | nonSemi) <~ token(OptSpace))
(part map (_.trim)).+ map (_.toList)
}
def multiParser(s: State): Parser[List[String]] = {
val nonSemi = token(charClass(_ != ';').+, hide = const(true))
val semi = token(';' ~> OptSpace)
val part = semi flatMap (_ =>
matched((s.combinedParser & nonSemi) | nonSemi) <~ token(OptSpace))
(part map (_.trim)).+ map (_.toList)
}
def multiApplied(s: State): Parser[() => State] =
Command.applyEffect(multiParser(s))(_ ::: s)
@ -104,12 +124,13 @@ object BasicCommands {
def combinedLax(s: State, any: Parser[_]): Parser[String] =
matched(s.combinedParser | token(any, hide = const(true)))
def ifLast: Command = Command(IfLast, Help.more(IfLast, IfLastDetailed))(otherCommandParser)((s, arg) =>
if (s.remainingCommands.isEmpty) arg :: s else s)
def ifLast: Command =
Command(IfLast, Help.more(IfLast, IfLastDetailed))(otherCommandParser)((s, arg) =>
if (s.remainingCommands.isEmpty) arg :: s else s)
def append: Command =
Command(AppendCommand, Help.more(AppendCommand, AppendLastDetailed))(otherCommandParser)((s, arg) =>
s.copy(remainingCommands = s.remainingCommands :+ Exec(arg, s.source)))
Command(AppendCommand, Help.more(AppendCommand, AppendLastDetailed))(otherCommandParser)(
(s, arg) => s.copy(remainingCommands = s.remainingCommands :+ Exec(arg, s.source)))
def setOnFailure: Command =
Command(OnFailure, Help.more(OnFailure, OnFailureDetailed))(otherCommandParser)((s, arg) =>
@ -120,8 +141,10 @@ object BasicCommands {
s.log.warn(Compat.ClearOnFailureDeprecated)
s.copy(onFailure = None)
},
Command.arb(s => token(Compat.OnFailure, hide = const(true))
.flatMap(_ => otherCommandParser(s))) { (s, arg) =>
Command.arb(
s =>
token(Compat.OnFailure, hide = const(true))
.flatMap(_ => otherCommandParser(s))) { (s, arg) =>
s.log.warn(Compat.OnFailureDeprecated)
s.copy(onFailure = Some(Exec(arg, s.source)))
},
@ -133,43 +156,48 @@ object BasicCommands {
def clearOnFailure: Command = Command.command(ClearOnFailure)(s => s.copy(onFailure = None))
def stashOnFailure: Command = Command.command(StashOnFailure)(s =>
s.copy(onFailure = None).update(OnFailureStack)(s.onFailure :: _.toList.flatten))
def stashOnFailure: Command =
Command.command(StashOnFailure)(s =>
s.copy(onFailure = None).update(OnFailureStack)(s.onFailure :: _.toList.flatten))
def popOnFailure: Command = Command.command(PopOnFailure) { s =>
val stack = s.get(OnFailureStack).getOrElse(Nil)
val updated = if (stack.isEmpty) s.remove(OnFailureStack) else s.put(OnFailureStack, stack.tail)
val updated =
if (stack.isEmpty) s.remove(OnFailureStack) else s.put(OnFailureStack, stack.tail)
updated.copy(onFailure = stack.headOption.flatten)
}
def reboot: Command =
Command(RebootCommand, Help.more(RebootCommand, RebootDetailed))(rebootParser)((s, full) => s reboot full)
Command(RebootCommand, Help.more(RebootCommand, RebootDetailed))(rebootParser)((s, full) =>
s reboot full)
def rebootParser(s: State): Parser[Boolean] = token(Space ~> "full" ^^^ true) ?? false
def call: Command = Command(ApplyCommand, Help.more(ApplyCommand, ApplyDetailed))(_ => callParser) {
case (state, (cp, args)) =>
val parentLoader = getClass.getClassLoader
def argsStr = args mkString ", "
def cpStr = cp mkString File.pathSeparator
def fromCpStr = if (cp.isEmpty) "" else s" from $cpStr"
state.log info s"Applying State transformations $argsStr$fromCpStr"
val loader = if (cp.isEmpty) parentLoader else toLoader(cp.map(f => new File(f)), parentLoader)
val loaded = args.map(arg => ModuleUtilities.getObject(arg, loader).asInstanceOf[State => State])
(state /: loaded)((s, obj) => obj(s))
}
def call: Command =
Command(ApplyCommand, Help.more(ApplyCommand, ApplyDetailed))(_ => callParser) {
case (state, (cp, args)) =>
val parentLoader = getClass.getClassLoader
def argsStr = args mkString ", "
def cpStr = cp mkString File.pathSeparator
def fromCpStr = if (cp.isEmpty) "" else s" from $cpStr"
state.log info s"Applying State transformations $argsStr$fromCpStr"
val loader =
if (cp.isEmpty) parentLoader else toLoader(cp.map(f => new File(f)), parentLoader)
val loaded =
args.map(arg => ModuleUtilities.getObject(arg, loader).asInstanceOf[State => State])
(state /: loaded)((s, obj) => obj(s))
}
def callParser: Parser[(Seq[String], Seq[String])] =
token(Space) ~> ((classpathOptionParser ?? Nil) ~ rep1sep(className, token(Space)))
private[this] def className: Parser[String] =
{
val base = StringBasic & not('-' ~> any.*, "Class name cannot start with '-'.")
def single(s: String) = Completions.single(Completion.displayOnly(s))
val compl = TokenCompletions.fixed((seen, _) =>
if (seen.startsWith("-")) Completions.nil else single("<class name>"))
token(base, compl)
}
private[this] def className: Parser[String] = {
val base = StringBasic & not('-' ~> any.*, "Class name cannot start with '-'.")
def single(s: String) = Completions.single(Completion.displayOnly(s))
val compl = TokenCompletions.fixed((seen, _) =>
if (seen.startsWith("-")) Completions.nil else single("<class name>"))
token(base, compl)
}
private[this] def classpathOptionParser: Parser[Seq[String]] =
token(("-cp" | "-classpath") ~> Space) ~> classpathStrings <~ token(Space)
@ -180,11 +208,12 @@ object BasicCommands {
def exit: Command = Command.command(TerminateAction, exitBrief, exitBrief)(_ exit true)
def continuous: Command =
Command(ContinuousExecutePrefix, continuousBriefHelp, continuousDetail)(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)
}
Command(ContinuousExecutePrefix, continuousBriefHelp, continuousDetail)(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 = Command.custom(historyParser, BasicCommandStrings.historyHelp)
@ -209,16 +238,19 @@ object BasicCommands {
val line = reader.readLine(prompt)
line match {
case Some(line) =>
val newState = s.copy(
onFailure = Some(Exec(Shell, None)),
remainingCommands = Exec(line, s.source) +: Exec(OldShell, None) +: s.remainingCommands
).setInteractive(true)
val newState = s
.copy(
onFailure = Some(Exec(Shell, None)),
remainingCommands = Exec(line, s.source) +: Exec(OldShell, None) +: s.remainingCommands
)
.setInteractive(true)
if (line.trim.isEmpty) newState else newState.clearGlobalLog
case None => s.setInteractive(false)
}
}
def client: Command = Command(Client, Help.more(Client, ClientDetailed))(_ => clientParser)(runClient)
def client: Command =
Command(Client, Help.more(Client, ClientDetailed))(_ => clientParser)(runClient)
def clientParser: Parser[Seq[String]] =
(token(Space) ~> repsep(StringBasic, token(Space))) | (token(EOF) map (_ => Nil))
@ -233,14 +265,14 @@ object BasicCommands {
"exit" :: s0.copy(remainingCommands = Nil)
}
def read: Command = Command(ReadCommand, Help.more(ReadCommand, ReadDetailed))(readParser)(doRead(_)(_))
def read: Command =
Command(ReadCommand, Help.more(ReadCommand, ReadDetailed))(readParser)(doRead(_)(_))
def readParser(s: State): Parser[Either[Int, Seq[File]]] =
{
val files = (token(Space) ~> fileParser(s.baseDir)).+
val portAndSuccess = token(OptSpace) ~> Port
portAndSuccess || files
}
def readParser(s: State): Parser[Either[Int, Seq[File]]] = {
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 {
@ -267,24 +299,24 @@ object BasicCommands {
}
}
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)
}
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 = Command(AliasCommand, Help.more(AliasCommand, 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).failOnException | any.+.string
OptSpace ~> (name ~ (assign ~> to.?).?).?
}(runAlias)
def alias: Command =
Command(AliasCommand, Help.more(AliasCommand, 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).failOnException | any.+.string
OptSpace ~> (name ~ (assign ~> to.?).?).?
}(runAlias)
def runAlias(s: State, args: Option[(String, Option[Option[String]])]): State =
args match {
@ -336,20 +368,23 @@ object BasicCommands {
s.definedCommands.flatMap(c => getAlias(c).filter(tupled(pred)))
def newAlias(name: String, value: String): Command =
Command.make(name, (name, s"'$value'"), s"Alias of '$value'")(aliasBody(name, value))
Command
.make(name, (name, s"'$value'"), s"Alias of '$value'")(aliasBody(name, value))
.tag(CommandAliasKey, (name, value))
def aliasBody(name: String, value: String)(state: State): Parser[() => State] = {
val aliasRemoved = removeAlias(state, name)
// apply the alias value to the commands of `state` except for the alias to avoid recursion (#933)
val partiallyApplied = Parser(Command.combine(aliasRemoved.definedCommands)(aliasRemoved))(value)
val partiallyApplied =
Parser(Command.combine(aliasRemoved.definedCommands)(aliasRemoved))(value)
val arg = matched(partiallyApplied & (success(()) | (SpaceClass ~ any.*)))
// by scheduling the expanded alias instead of directly executing,
// we get errors on the expanded string (#598)
arg.map(str => () => (value + str) :: state)
}
def delegateToAlias(name: String, orElse: Parser[() => State])(state: State): Parser[() => State] =
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)

View File

@ -6,15 +6,37 @@ import sbt.internal.inc.classpath.ClassLoaderCache
import sbt.librarymanagement.ModuleID
object BasicKeys {
val historyPath = AttributeKey[Option[File]]("history", "The location where command line history is persisted.", 40)
val shellPrompt = AttributeKey[State => String]("shell-prompt", "The function that constructs the command prompt from the current build state.", 10000)
val historyPath = AttributeKey[Option[File]](
"history",
"The location where command line history is persisted.",
40)
val shellPrompt = AttributeKey[State => String](
"shell-prompt",
"The function that constructs the command prompt from the current build state.",
10000)
val watch = AttributeKey[Watched]("watch", "Continuous execution configuration.", 1000)
val serverPort = AttributeKey[Int]("server-port", "The port number used by server command.", 10000)
private[sbt] val interactive = AttributeKey[Boolean]("interactive", "True if commands are currently being entered from an interactive environment.", 10)
private[sbt] val classLoaderCache = AttributeKey[ClassLoaderCache]("class-loader-cache", "Caches class loaders based on the classpath entries and last modified times.", 10)
private[sbt] val OnFailureStack = AttributeKey[List[Option[Exec]]]("on-failure-stack", "Stack that remembers on-failure handlers.", 10)
private[sbt] val explicitGlobalLogLevels = AttributeKey[Boolean]("explicit-global-log-levels", "True if the global logging levels were explicitly set by the user.", 10)
private[sbt] val templateResolverInfos = AttributeKey[Seq[TemplateResolverInfo]]("templateResolverInfos", "List of template resolver infos.", 1000)
val serverPort =
AttributeKey[Int]("server-port", "The port number used by server command.", 10000)
private[sbt] val interactive = AttributeKey[Boolean](
"interactive",
"True if commands are currently being entered from an interactive environment.",
10)
private[sbt] val classLoaderCache = AttributeKey[ClassLoaderCache](
"class-loader-cache",
"Caches class loaders based on the classpath entries and last modified times.",
10)
private[sbt] val OnFailureStack = AttributeKey[List[Option[Exec]]](
"on-failure-stack",
"Stack that remembers on-failure handlers.",
10)
private[sbt] val explicitGlobalLogLevels = AttributeKey[Boolean](
"explicit-global-log-levels",
"True if the global logging levels were explicitly set by the user.",
10)
private[sbt] val templateResolverInfos = AttributeKey[Seq[TemplateResolverInfo]](
"templateResolverInfos",
"List of template resolver infos.",
1000)
}
case class TemplateResolverInfo(module: ModuleID, implementationClass: String)

View File

@ -27,7 +27,6 @@ private[sbt] final class SimpleCommand(
val name: String,
private[sbt] val help0: Help,
val parser: State => Parser[() => State],
val tags: AttributeMap
) extends Command {
@ -42,7 +41,9 @@ private[sbt] final class SimpleCommand(
}
private[sbt] final class ArbitraryCommand(
val parser: State => Parser[() => State], val help: State => Help, val tags: AttributeMap
val parser: State => Parser[() => State],
val help: State => Help,
val tags: AttributeMap
) extends Command {
def tag[T](key: AttributeKey[T], value: T): ArbitraryCommand =
new ArbitraryCommand(parser, help, tags.put(key, value))
@ -56,16 +57,19 @@ object Command {
def make(name: String, help: Help = Help.empty)(parser: State => Parser[() => State]): Command =
new SimpleCommand(name, help, parser, AttributeMap.empty)
def make(name: String, briefHelp: (String, String), detail: String)(parser: State => Parser[() => State]): Command =
def make(name: String, briefHelp: (String, String), detail: String)(
parser: State => Parser[() => State]): Command =
make(name, Help(name, briefHelp, detail))(parser)
// General command construction
/** Construct a command with the given name, parser and effect. */
def apply[T](name: String, help: Help = Help.empty)(parser: State => Parser[T])(effect: (State, T) => State): Command =
def apply[T](name: String, help: Help = Help.empty)(parser: State => Parser[T])(
effect: (State, T) => State): Command =
make(name, help)(applyEffect(parser)(effect))
def apply[T](name: String, briefHelp: (String, String), detail: String)(parser: State => Parser[T])(effect: (State, T) => State): Command =
def apply[T](name: String, briefHelp: (String, String), detail: String)(
parser: State => Parser[T])(effect: (State, T) => State): Command =
apply(name, Help(name, briefHelp, detail))(parser)(effect)
// No-argument command construction
@ -83,16 +87,19 @@ object Command {
def single(name: String, help: Help = Help.empty)(f: (State, String) => State): Command =
make(name, help)(state => token(trimmed(spacedAny(name)) map apply1(f, state)))
def single(name: String, briefHelp: (String, String), detail: String)(f: (State, String) => State): Command =
def single(name: String, briefHelp: (String, String), detail: String)(
f: (State, String) => State): Command =
single(name, Help(name, briefHelp, detail))(f)
// Multi-argument command construction
/** Construct a multi-argument command with the given name, tab completion display and effect. */
def args(name: String, display: String, help: Help = Help.empty)(f: (State, Seq[String]) => State): Command =
def args(name: String, display: String, help: Help = Help.empty)(
f: (State, Seq[String]) => State): Command =
make(name, help)(state => spaceDelimited(display) map apply1(f, state))
def args(name: String, briefHelp: (String, String), detail: String, display: String)(f: (State, Seq[String]) => State): Command =
def args(name: String, briefHelp: (String, String), detail: String, display: String)(
f: (State, Seq[String]) => State): Command =
args(name, display, Help(name, briefHelp, detail))(f)
// create ArbitraryCommand
@ -103,7 +110,8 @@ object Command {
def custom(parser: State => Parser[() => State], help: Help = Help.empty): Command =
customHelp(parser, const(help))
def arb[T](parser: State => Parser[T], help: Help = Help.empty)(effect: (State, T) => State): Command =
def arb[T](parser: State => Parser[T], help: Help = Help.empty)(
effect: (State, T) => State): Command =
custom(applyEffect(parser)(effect), help)
// misc Command object utilities
@ -112,15 +120,18 @@ object Command {
def applyEffect[T](p: Parser[T])(f: T => State): Parser[() => State] = p map (t => () => f(t))
def applyEffect[T](parser: State => Parser[T])(effect: (State, T) => State): State => Parser[() => State] =
def applyEffect[T](parser: State => Parser[T])(
effect: (State, T) => State): State => Parser[() => State] =
s => applyEffect(parser(s))(t => effect(s, t))
def combine(cmds: Seq[Command]): State => Parser[() => State] = {
val (simple, arbs) = separateCommands(cmds)
state => (simpleParser(simple)(state) /: arbs.map(_ parser state))(_ | _)
state =>
(simpleParser(simple)(state) /: arbs.map(_ parser state))(_ | _)
}
private[this] def separateCommands(cmds: Seq[Command]): (Seq[SimpleCommand], Seq[ArbitraryCommand]) =
private[this] def separateCommands(
cmds: Seq[Command]): (Seq[SimpleCommand], Seq[ArbitraryCommand]) =
Util.separate(cmds) { case s: SimpleCommand => Left(s); case a: ArbitraryCommand => Right(a) }
private[this] def apply1[A, B, C](f: (A, B) => C, a: A): B => () => C = b => () => f(a, b)
@ -130,15 +141,18 @@ object Command {
private[this] def argParser(sc: SimpleCommand): State => Parser[() => State] = {
def usageError = s"${sc.name} usage:" + Help.message(sc.help0, None)
s => (Parser.softFailure(usageError, definitive = true): Parser[() => State]) | sc.parser(s)
s =>
(Parser.softFailure(usageError, definitive = true): Parser[() => State]) | sc.parser(s)
}
def simpleParser(commandMap: Map[String, State => Parser[() => State]]): State => Parser[() => State] =
state => token(OpOrID examples commandMap.keys.toSet) flatMap (id =>
(commandMap get id) match {
case None => failure(invalidValue("command", commandMap.keys)(id))
case Some(c) => c(state)
})
def simpleParser(
commandMap: Map[String, State => Parser[() => State]]): State => Parser[() => State] =
state =>
token(OpOrID examples commandMap.keys.toSet) flatMap (id =>
(commandMap get id) match {
case None => failure(invalidValue("command", commandMap.keys)(id))
case Some(c) => c(state)
})
def invalidValue(label: String, allowed: Iterable[String])(value: String): String =
s"Not a valid $label: $value" + similar(value, allowed)
@ -148,12 +162,22 @@ object Command {
if (suggested.isEmpty) "" else suggested.mkString(" (similar: ", ", ", ")")
}
def suggestions(a: String, bs: Seq[String], maxDistance: Int = 3, maxSuggestions: Int = 3): Seq[String] =
def suggestions(a: String,
bs: Seq[String],
maxDistance: Int = 3,
maxSuggestions: Int = 3): Seq[String] =
bs map (b => (b, distance(a, b))) filter (_._2 <= maxDistance) sortBy (_._2) take (maxSuggestions) map (_._1)
def distance(a: String, b: String): Int =
EditDistance.levenshtein(a, b, insertCost = 1, deleteCost = 1, subCost = 2, transposeCost = 1,
matchCost = -1, caseCost = 1, transpositions = true)
EditDistance.levenshtein(a,
b,
insertCost = 1,
deleteCost = 1,
subCost = 2,
transposeCost = 1,
matchCost = -1,
caseCost = 1,
transpositions = true)
def spacedAny(name: String): Parser[String] = spacedC(name, any)
@ -169,9 +193,12 @@ trait Help {
}
private final class Help0(
val brief: Seq[(String, String)], val detail: Map[String, String], val more: Set[String]
val brief: Seq[(String, String)],
val detail: Map[String, String],
val more: Set[String]
) extends Help {
def ++(h: Help): Help = new Help0(Help0.this.brief ++ h.brief, Help0.this.detail ++ h.detail, more ++ h.more)
def ++(h: Help): Help =
new Help0(Help0.this.brief ++ h.brief, Help0.this.detail ++ h.detail, more ++ h.more)
}
object Help {
@ -186,10 +213,13 @@ object Help {
def apply(briefHelp: Seq[(String, String)], detailedHelp: Map[String, String]): Help =
apply(briefHelp, detailedHelp, Set.empty[String])
def apply(briefHelp: Seq[(String, String)], detailedHelp: Map[String, String], more: Set[String]): Help =
def apply(briefHelp: Seq[(String, String)],
detailedHelp: Map[String, String],
more: Set[String]): Help =
new Help0(briefHelp, detailedHelp, more)
def more(name: String, detailedHelp: String): Help = apply(Nil, Map(name -> detailedHelp), Set(name))
def more(name: String, detailedHelp: String): Help =
apply(Nil, Map(name -> detailedHelp), Set(name))
def briefDetail(help: Seq[(String, String)]): Help = apply(help, help.toMap)
def briefOnly(help: Seq[(String, String)]): Help = apply(help, Map.empty[String, String])
def detailOnly(help: Seq[(String, String)]): Help = apply(Nil, help.toMap)

View File

@ -10,8 +10,11 @@ import sbt.internal.util.complete.DefaultParsers._
import sbt.io.IO
object CommandUtil {
def readLines(files: Seq[File]): Seq[String] = files flatMap (IO.readLines(_)) flatMap processLine
def processLine(s: String): Option[String] = { val s2 = s.trim; if (ignoreLine(s2)) None else Some(s2) }
def readLines(files: Seq[File]): Seq[String] =
files flatMap (IO.readLines(_)) flatMap processLine
def processLine(s: String): Option[String] = {
val s2 = s.trim; if (ignoreLine(s2)) None else Some(s2)
}
def ignoreLine(s: String): Boolean = s.isEmpty || s.startsWith("#")
private def canRead = (_: File).canRead
@ -23,10 +26,12 @@ object CommandUtil {
try state.configuration.provider.scalaProvider.launcher.bootDirectory
catch { case _: NoSuchMethodError => new File(".").getAbsoluteFile }
def aligned(pre: String, sep: String, in: Seq[(String, String)]): Seq[String] = if (in.isEmpty) Nil else {
val width = in.iterator.map(_._1.length).max
for ((a, b) <- in) yield pre + fill(a, width) + sep + b
}
def aligned(pre: String, sep: String, in: Seq[(String, String)]): Seq[String] =
if (in.isEmpty) Nil
else {
val width = in.iterator.map(_._1.length).max
for ((a, b) <- in) yield pre + fill(a, width) + sep + b
}
def fill(s: String, size: Int): String = s + " " * math.max(size - s.length, 0)
@ -37,42 +42,41 @@ object CommandUtil {
case Some(nav) => f(nav)
}
def singleArgument(exampleStrings: Set[String]): Parser[String] =
{
val arg = (NotSpaceClass ~ any.*) map { case (ns, s) => (ns +: s).mkString }
token(Space) ~> token(arg examples exampleStrings)
}
def singleArgument(exampleStrings: Set[String]): Parser[String] = {
val arg = (NotSpaceClass ~ any.*) map { case (ns, s) => (ns +: s).mkString }
token(Space) ~> token(arg examples exampleStrings)
}
def detail(selected: String, detailMap: Map[String, String]): String =
detailMap.get(selected) match {
case Some(exactDetail) => exactDetail
case None => try {
val details = searchHelp(selected, detailMap)
if (details.isEmpty)
"No matches for regular expression '" + selected + "'."
else
layoutDetails(details)
} catch {
case pse: PatternSyntaxException =>
sys.error("Invalid regular expression (java.util.regex syntax).\n" + pse.getMessage)
}
case None =>
try {
val details = searchHelp(selected, detailMap)
if (details.isEmpty)
"No matches for regular expression '" + selected + "'."
else
layoutDetails(details)
} catch {
case pse: PatternSyntaxException =>
sys.error("Invalid regular expression (java.util.regex syntax).\n" + pse.getMessage)
}
}
def searchHelp(selected: String, detailMap: Map[String, String]): Map[String, String] =
{
val pattern = Pattern.compile(selected, HelpPatternFlags)
detailMap flatMap {
case (k, v) =>
val contentMatches = Highlight.showMatches(pattern)(v)
val keyMatches = Highlight.showMatches(pattern)(k)
val keyString = Highlight.bold(keyMatches getOrElse k)
val contentString = contentMatches getOrElse v
if (keyMatches.isDefined || contentMatches.isDefined)
(keyString, contentString) :: Nil
else
Nil
}
def searchHelp(selected: String, detailMap: Map[String, String]): Map[String, String] = {
val pattern = Pattern.compile(selected, HelpPatternFlags)
detailMap flatMap {
case (k, v) =>
val contentMatches = Highlight.showMatches(pattern)(v)
val keyMatches = Highlight.showMatches(pattern)(k)
val keyString = Highlight.bold(keyMatches getOrElse k)
val contentString = contentMatches getOrElse v
if (keyMatches.isDefined || contentMatches.isDefined)
(keyString, contentString) :: Nil
else
Nil
}
}
def layoutDetails(details: Map[String, String]): String =
details.map { case (k, v) => k + "\n\n " + v } mkString ("\n", "\n\n", "\n")

View File

@ -7,17 +7,17 @@ import sbt.internal.util.ConsoleAppender
object Highlight {
def showMatches(pattern: Pattern)(line: String): Option[String] =
{
val matcher = pattern.matcher(line)
if (ConsoleAppender.formatEnabled) {
// ANSI codes like \033[39m (normal text color) don't work on Windows
val highlighted = matcher.replaceAll(scala.Console.RED + "$0" + RESET)
if (highlighted == line) None else Some(highlighted)
} else if (matcher.find)
Some(line)
else
None
}
def bold(s: String) = if (ConsoleAppender.formatEnabled) BOLD + s.replace(RESET, RESET + BOLD) + RESET else s
def showMatches(pattern: Pattern)(line: String): Option[String] = {
val matcher = pattern.matcher(line)
if (ConsoleAppender.formatEnabled) {
// ANSI codes like \033[39m (normal text color) don't work on Windows
val highlighted = matcher.replaceAll(scala.Console.RED + "$0" + RESET)
if (highlighted == line) None else Some(highlighted)
} else if (matcher.find)
Some(line)
else
None
}
def bold(s: String) =
if (ConsoleAppender.formatEnabled) BOLD + s.replace(RESET, RESET + BOLD) + RESET else s
}

View File

@ -8,10 +8,21 @@ import java.io.File
final case class Exit(code: Int) extends xsbti.Exit {
require(code >= 0)
}
final case class Reboot(scalaVersion: String, argsList: Seq[String], app: xsbti.ApplicationID, baseDirectory: File) extends xsbti.Reboot {
final case class Reboot(scalaVersion: String,
argsList: Seq[String],
app: xsbti.ApplicationID,
baseDirectory: File)
extends xsbti.Reboot {
def arguments = argsList.toArray
}
final case class ApplicationID(groupID: String, name: String, version: String, mainClass: String, components: Seq[String], crossVersionedValue: xsbti.CrossValue, extra: Seq[File]) extends xsbti.ApplicationID {
final case class ApplicationID(groupID: String,
name: String,
version: String,
mainClass: String,
components: Seq[String],
crossVersionedValue: xsbti.CrossValue,
extra: Seq[File])
extends xsbti.ApplicationID {
def mainComponents = components.toArray
def classpathExtra = extra.toArray
def crossVersioned = crossVersionedValue != xsbti.CrossValue.Disabled
@ -20,5 +31,11 @@ object ApplicationID {
def apply(delegate: xsbti.ApplicationID, newVersion: String): ApplicationID =
apply(delegate).copy(version = newVersion)
def apply(delegate: xsbti.ApplicationID): ApplicationID =
ApplicationID(delegate.groupID, delegate.name, delegate.version, delegate.mainClass, delegate.mainComponents, delegate.crossVersionedValue, delegate.classpathExtra)
}
ApplicationID(delegate.groupID,
delegate.name,
delegate.version,
delegate.mainClass,
delegate.mainComponents,
delegate.crossVersionedValue,
delegate.classpathExtra)
}

View File

@ -6,7 +6,14 @@ package sbt
import java.io.File
import java.util.concurrent.Callable
import sbt.util.Logger
import sbt.internal.util.{ AttributeKey, AttributeMap, ErrorHandling, ExitHook, ExitHooks, GlobalLogging }
import sbt.internal.util.{
AttributeKey,
AttributeMap,
ErrorHandling,
ExitHook,
ExitHooks,
GlobalLogging
}
import sbt.internal.util.complete.HistoryCommands
import sbt.internal.inc.classpath.ClassLoaderCache
import sbt.BasicCommandStrings.Shell
@ -91,11 +98,13 @@ trait StateOps {
/** Sets the next command processing action to be to rotate the global log and continue executing commands.*/
def clearGlobalLog: State
/** Sets the next command processing action to be to keep the previous log and continue executing commands. */
def keepLastLog: State
/** Sets the next command processing action to be to exit with a zero exit code if `ok` is true and a nonzero exit code if `ok` if false.*/
def exit(ok: Boolean): State
/** Marks the currently executing command as failing. This triggers failure handling by the command processor. See also `State.onFailure`*/
def fail: State
@ -109,17 +118,22 @@ trait StateOps {
/** Registers `newCommands` as available commands. */
def ++(newCommands: Seq[Command]): State
/** Registers `newCommand` as an available command. */
def +(newCommand: Command): State
/** Gets the value associated with `key` from the custom attributes map.*/
def get[T](key: AttributeKey[T]): Option[T]
/** Sets the value associated with `key` in the custom attributes map.*/
def put[T](key: AttributeKey[T], value: T): State
/** Removes the `key` and any associated value from the custom attributes map.*/
def remove(key: AttributeKey[_]): State
/** Sets the value associated with `key` in the custom attributes map by transforming the current value.*/
def update[T](key: AttributeKey[T])(f: Option[T] => T): State
/** Returns true if `key` exists in the custom attributes map, false if it does not exist.*/
def has(key: AttributeKey[_]): Boolean
@ -134,11 +148,13 @@ trait StateOps {
/** Runs any defined exitHooks and then clears them.*/
def runExitHooks(): State
/** Registers a new exit hook, which will run when sbt exits or restarts.*/
def addExitHook(f: => Unit): State
/** An advisory flag that is `true` if this application will execute commands based on user input.*/
def interactive: Boolean
/** Changes the advisory `interactive` flag. */
def setInteractive(flag: Boolean): State
@ -150,17 +166,22 @@ trait StateOps {
}
object State {
/** Indicates where command execution should resume after a failure.*/
val FailureWall = BasicCommandStrings.FailureWall
/** Represents the next action for the command processor.*/
sealed trait Next
/** Indicates that the command processor should process the next command.*/
object Continue extends Next
/** Indicates that the application should exit with the given result.*/
final class Return(val result: xsbti.MainResult) extends Next
/** Indicates that global logging should be rotated.*/
final object ClearGlobalLog extends Next
/** Indicates that the previous log file should be preserved instead of discarded.*/
final object KeepLastLog extends Next
@ -170,30 +191,33 @@ object State {
* @param maxSize the maximum number of commands to keep, or 0 to keep an unlimited number.
*/
final class History private[State] (val executed: Seq[Exec], val maxSize: Int) {
/** Adds `command` as the most recently executed command.*/
def ::(command: Exec): History =
{
val prependTo = if (maxSize > 0 && executed.size >= maxSize) executed.take(maxSize - 1) else executed
new History(command +: prependTo, maxSize)
}
def ::(command: Exec): History = {
val prependTo =
if (maxSize > 0 && executed.size >= maxSize) executed.take(maxSize - 1) else executed
new History(command +: prependTo, maxSize)
}
/** Changes the maximum number of commands kept, adjusting the current history if necessary.*/
def setMaxSize(size: Int): History =
new History(if (size <= 0) executed else executed.take(size), size)
def currentOption: Option[Exec] = executed.headOption
def previous: Option[Exec] = executed.drop(1).headOption
}
/** Constructs an empty command History with a default, finite command limit.*/
def newHistory = new History(Vector.empty, HistoryCommands.MaxLines)
def defaultReload(state: State): Reboot =
{
val app = state.configuration.provider
new Reboot(
app.scalaProvider.version,
state.remainingCommands map { case e: Exec => e.commandLine },
app.id, state.configuration.baseDirectory
)
}
def defaultReload(state: State): Reboot = {
val app = state.configuration.provider
new Reboot(
app.scalaProvider.version,
state.remainingCommands map { case e: Exec => e.commandLine },
app.id,
state.configuration.baseDirectory
)
}
/** Provides operations and transformations on State. */
implicit def stateOps(s: State): StateOps = new StateOps {
@ -204,22 +228,33 @@ object State {
}
def isInteractive = System.console() != null
def hasInput = System.console().reader().ready()
def hasShellCmd = s.definedCommands exists { case c: SimpleCommand => c.name == Shell; case _ => false }
def hasShellCmd = s.definedCommands exists {
case c: SimpleCommand => c.name == Shell; case _ => false
}
s.remainingCommands match {
case List() => if (isInteractive && hasInput && hasShellCmd) doX(Exec(Shell, s.source), Nil) else exit(true)
case List() =>
if (isInteractive && hasInput && hasShellCmd) doX(Exec(Shell, s.source), Nil)
else exit(true)
case List(x, xs @ _*) => doX(x, xs.toList)
}
}
def :::(newCommands: List[String]): State = ++:(newCommands map { Exec(_, s.source) })
def ++:(newCommands: List[Exec]): State = s.copy(remainingCommands = newCommands ::: s.remainingCommands)
def ++:(newCommands: List[Exec]): State =
s.copy(remainingCommands = newCommands ::: s.remainingCommands)
def ::(command: String): State = +:(Exec(command, s.source))
def +:(command: Exec): State = (command :: Nil) ++: this
def ++(newCommands: Seq[Command]): State = s.copy(definedCommands = (s.definedCommands ++ newCommands).distinct)
def ++(newCommands: Seq[Command]): State =
s.copy(definedCommands = (s.definedCommands ++ newCommands).distinct)
def +(newCommand: Command): State = this ++ (newCommand :: Nil)
def baseDir: File = s.configuration.baseDirectory
def setNext(n: Next) = s.copy(next = n)
def continue = setNext(Continue)
def reboot(full: Boolean) = { runExitHooks(); throw new xsbti.FullReload((s.remainingCommands map { case e: Exec => e.commandLine }).toArray, full) }
def reboot(full: Boolean) = {
runExitHooks();
throw new xsbti.FullReload(
(s.remainingCommands map { case e: Exec => e.commandLine }).toArray,
full)
}
def reload = runExitHooks().setNext(new Return(defaultReload(s)))
def clearGlobalLog = setNext(ClearGlobalLog)
def keepLastLog = setNext(KeepLastLog)
@ -231,16 +266,16 @@ object State {
def remove(key: AttributeKey[_]) = s.copy(attributes = s.attributes remove key)
def log = s.globalLogging.full
def handleError(t: Throwable): State = handleException(t, s, log)
def fail =
{
import BasicCommandStrings.Compat.{ FailureWall => CompatFailureWall }
val remaining =
s.remainingCommands.dropWhile(c => c.commandLine != FailureWall && c.commandLine != CompatFailureWall)
if (remaining.isEmpty)
applyOnFailure(s, Nil, exit(ok = false))
else
applyOnFailure(s, remaining, s.copy(remainingCommands = remaining))
}
def fail = {
import BasicCommandStrings.Compat.{ FailureWall => CompatFailureWall }
val remaining =
s.remainingCommands.dropWhile(c =>
c.commandLine != FailureWall && c.commandLine != CompatFailureWall)
if (remaining.isEmpty)
applyOnFailure(s, Nil, exit(ok = false))
else
applyOnFailure(s, remaining, s.copy(remainingCommands = remaining))
}
private[this] def applyOnFailure(s: State, remaining: List[Exec], noHandler: => State): State =
s.onFailure match {
case Some(c) => s.copy(remainingCommands = c +: remaining, onFailure = None)
@ -254,27 +289,30 @@ object State {
s.copy(exitHooks = Set.empty)
}
def locked[T](file: File)(t: => T): T =
s.configuration.provider.scalaProvider.launcher.globalLock.apply(file, new Callable[T] { def call = t })
s.configuration.provider.scalaProvider.launcher.globalLock.apply(file, new Callable[T] {
def call = t
})
def interactive = getBoolean(s, BasicKeys.interactive, false)
def setInteractive(i: Boolean) = s.put(BasicKeys.interactive, i)
def classLoaderCache: ClassLoaderCache = s get BasicKeys.classLoaderCache getOrElse newClassLoaderCache
def classLoaderCache: ClassLoaderCache =
s get BasicKeys.classLoaderCache getOrElse newClassLoaderCache
def initializeClassLoaderCache = s.put(BasicKeys.classLoaderCache, newClassLoaderCache)
private[this] def newClassLoaderCache = new ClassLoaderCache(s.configuration.provider.scalaProvider.launcher.topLoader)
private[this] def newClassLoaderCache =
new ClassLoaderCache(s.configuration.provider.scalaProvider.launcher.topLoader)
}
import ExceptionCategory._
private[sbt] def handleException(t: Throwable, s: State, log: Logger): State =
{
ExceptionCategory(t) match {
case AlreadyHandled => ()
case m: MessageOnly => log.error(m.message)
case f: Full => logFullException(f.exception, log)
}
s.fail
private[sbt] def handleException(t: Throwable, s: State, log: Logger): State = {
ExceptionCategory(t) match {
case AlreadyHandled => ()
case m: MessageOnly => log.error(m.message)
case f: Full => logFullException(f.exception, log)
}
s.fail
}
private[sbt] def logFullException(e: Throwable, log: Logger): Unit = {
log.trace(e)
log.error(ErrorHandling reducedToString e)

View File

@ -14,22 +14,27 @@ import sbt.internal.util.AttributeKey
import sbt.internal.util.Types.const
trait Watched {
/** The files watched when an action is run with a preceeding ~ */
def watchPaths(s: State): Seq[File] = Nil
def terminateWatch(key: Int): Boolean = Watched.isEnter(key)
/**
* The time in milliseconds between checking for changes. The actual time between the last change made to a file and the
* execution time is between `pollInterval` and `pollInterval*2`.
*/
def pollInterval: Int = Watched.PollDelayMillis
/** The message to show when triggered execution waits for sources to change.*/
private[sbt] def watchingMessage(s: WatchState): String = Watched.defaultWatchingMessage(s)
/** The message to show before an action is run. */
private[sbt] def triggeredMessage(s: WatchState): String = Watched.defaultTriggeredMessage(s)
}
object Watched {
val defaultWatchingMessage: WatchState => String = _.count + ". Waiting for source changes... (press enter to interrupt)"
val defaultWatchingMessage
: WatchState => String = _.count + ". Waiting for source changes... (press enter to interrupt)"
val defaultTriggeredMessage: WatchState => String = const("")
val clearWhenTriggered: WatchState => String = const(clearScreen)
def clearScreen: String = "\u001b[2J\u001b[0;0H"
@ -50,36 +55,40 @@ object Watched {
def isEnter(key: Int): Boolean = key == 10 || key == 13
def printIfDefined(msg: String) = if (!msg.isEmpty) System.out.println(msg)
def executeContinuously(watched: Watched, s: State, next: String, repeat: String): State =
{
@tailrec def shouldTerminate: Boolean =
(System.in.available > 0) && (watched.terminateWatch(System.in.read()) || shouldTerminate)
val sourcesFinder = PathFinder { watched watchPaths s }
val watchState = s get ContinuousState getOrElse WatchState.empty
def executeContinuously(watched: Watched, s: State, next: String, repeat: String): State = {
@tailrec def shouldTerminate: Boolean =
(System.in.available > 0) && (watched.terminateWatch(System.in.read()) || shouldTerminate)
val sourcesFinder = PathFinder { watched watchPaths s }
val watchState = s get ContinuousState getOrElse WatchState.empty
if (watchState.count > 0)
printIfDefined(watched watchingMessage watchState)
if (watchState.count > 0)
printIfDefined(watched watchingMessage watchState)
val (triggered, newWatchState) =
try {
val (triggered, newWatchState) = SourceModificationWatch.watch(sourcesFinder, watched.pollInterval, watchState)(shouldTerminate)
(triggered, newWatchState)
} catch {
case e: Exception =>
val log = s.log
log.error("Error occurred obtaining files to watch. Terminating continuous execution...")
State.handleException(e, s, log)
(false, watchState)
}
if (triggered) {
printIfDefined(watched triggeredMessage newWatchState)
(ClearOnFailure :: next :: FailureWall :: repeat :: s).put(ContinuousState, newWatchState)
} else {
while (System.in.available() > 0) System.in.read()
s.put(ContinuousState, WatchState.empty)
val (triggered, newWatchState) =
try {
val (triggered, newWatchState) =
SourceModificationWatch.watch(sourcesFinder, watched.pollInterval, watchState)(
shouldTerminate)
(triggered, newWatchState)
} catch {
case e: Exception =>
val log = s.log
log.error(
"Error occurred obtaining files to watch. Terminating continuous execution...")
State.handleException(e, s, log)
(false, watchState)
}
if (triggered) {
printIfDefined(watched triggeredMessage newWatchState)
(ClearOnFailure :: next :: FailureWall :: repeat :: s).put(ContinuousState, newWatchState)
} else {
while (System.in.available() > 0) System.in.read()
s.put(ContinuousState, WatchState.empty)
}
val ContinuousState = AttributeKey[WatchState]("watch state", "Internal: tracks state for continuous execution.")
val Configuration = AttributeKey[Watched]("watched-configuration", "Configures continuous execution.")
}
val ContinuousState =
AttributeKey[WatchState]("watch state", "Internal: tracks state for continuous execution.")
val Configuration =
AttributeKey[Watched]("watched-configuration", "Configures continuous execution.")
}

View File

@ -48,23 +48,21 @@ class NetworkClient(arguments: List[String]) { self =>
new ServerConnection(socket) {
override def onEvent(event: EventMessage): Unit = self.onEvent(event)
override def onLogEntry(event: StringEvent): Unit = self.onLogEntry(event)
override def onShutdown(): Unit =
{
running.set(false)
}
override def onShutdown(): Unit = {
running.set(false)
}
}
}
def onLogEntry(event: StringEvent): Unit =
{
val level = event.level match {
case "debug" => Level.Debug
case "info" => Level.Info
case "warn" => Level.Warn
case "error" => Level.Error
}
console.appendLog(level, event.message)
def onLogEntry(event: StringEvent): Unit = {
val level = event.level match {
case "debug" => Level.Debug
case "info" => Level.Info
case "warn" => Level.Warn
case "error" => Level.Error
}
console.appendLog(level, event.message)
}
def onEvent(event: EventMessage): Unit =
event match {
@ -84,41 +82,39 @@ class NetworkClient(arguments: List[String]) { self =>
case e => println(e.toString)
}
def start(): Unit =
{
val reader = JLine.simple(None, JLine.HandleCONT, injectThreadSleep = true)
while (running.get) {
reader.readLine("> ", None) match {
case Some("exit") =>
running.set(false)
case Some(s) =>
val execId = UUID.randomUUID.toString
publishCommand(ExecCommand(s, execId))
lock.synchronized {
pendingExecIds += execId
}
while (pendingExecIds contains execId) {
Thread.sleep(100)
}
case _ => //
}
def start(): Unit = {
val reader = JLine.simple(None, JLine.HandleCONT, injectThreadSleep = true)
while (running.get) {
reader.readLine("> ", None) match {
case Some("exit") =>
running.set(false)
case Some(s) =>
val execId = UUID.randomUUID.toString
publishCommand(ExecCommand(s, execId))
lock.synchronized {
pendingExecIds += execId
}
while (pendingExecIds contains execId) {
Thread.sleep(100)
}
case _ => //
}
}
}
def publishCommand(command: CommandMessage): Unit =
{
val bytes = Serialization.serializeCommand(command)
try {
connection.publish(bytes)
} catch {
case e: SocketException =>
// log.debug(e.getMessage)
// toDel += client
}
lock.synchronized {
status.set("Processing")
}
def publishCommand(command: CommandMessage): Unit = {
val bytes = Serialization.serializeCommand(command)
try {
connection.publish(bytes)
} catch {
case e: SocketException =>
// log.debug(e.getMessage)
// toDel += client
}
lock.synchronized {
status.set("Processing")
}
}
}
object NetworkClient {

View File

@ -35,16 +35,18 @@ abstract class ServerConnection(connection: Socket) {
val chunk = buffer.take(delimPos)
buffer = buffer.drop(delimPos + 1)
Serialization.deserializeEvent(chunk).fold(
{ errorDesc =>
val s = new String(chunk.toArray, "UTF-8")
println(s"Got invalid chunk from server: $s \n" + errorDesc)
},
_ match {
case event: EventMessage => onEvent(event)
case event: StringEvent => onLogEntry(event)
}
)
Serialization
.deserializeEvent(chunk)
.fold(
{ errorDesc =>
val s = new String(chunk.toArray, "UTF-8")
println(s"Got invalid chunk from server: $s \n" + errorDesc)
},
_ match {
case event: EventMessage => onEvent(event)
case event: StringEvent => onLogEntry(event)
}
)
}
} catch {

View File

@ -18,8 +18,10 @@ private[sbt] sealed trait ServerInstance {
}
private[sbt] object Server {
def start(host: String, port: Int, onIncomingSocket: Socket => Unit,
/*onIncomingCommand: CommandMessage => Unit,*/ log: Logger): ServerInstance =
def start(host: String,
port: Int,
onIncomingSocket: Socket => Unit,
/*onIncomingCommand: CommandMessage => Unit,*/ log: Logger): ServerInstance =
new ServerInstance {
// val lock = new AnyRef {}

View File

@ -16,44 +16,37 @@ object IPC {
def client[T](port: Int)(f: IPC => T): T =
ipc(new Socket(loopback, port))(f)
def pullServer[T](f: Server => T): T =
{
val server = makeServer
try { f(new Server(server)) }
finally { server.close() }
}
def pullServer[T](f: Server => T): T = {
val server = makeServer
try { f(new Server(server)) } finally { server.close() }
}
def unmanagedServer: Server = new Server(makeServer)
def makeServer: ServerSocket =
{
val random = new java.util.Random
def nextPort = random.nextInt(portMax - portMin + 1) + portMin
def createServer(attempts: Int): ServerSocket =
if (attempts > 0)
try { new ServerSocket(nextPort, 1, loopback) }
catch { case NonFatal(e) => createServer(attempts - 1) }
else
sys.error("Could not connect to socket: maximum attempts exceeded")
createServer(10)
}
def makeServer: ServerSocket = {
val random = new java.util.Random
def nextPort = random.nextInt(portMax - portMin + 1) + portMin
def createServer(attempts: Int): ServerSocket =
if (attempts > 0)
try { new ServerSocket(nextPort, 1, loopback) } catch {
case NonFatal(e) => createServer(attempts - 1)
} else
sys.error("Could not connect to socket: maximum attempts exceeded")
createServer(10)
}
def server[T](f: IPC => Option[T]): T = serverImpl(makeServer, f)
def server[T](port: Int)(f: IPC => Option[T]): T =
serverImpl(new ServerSocket(port, 1, loopback), f)
private def serverImpl[T](server: ServerSocket, f: IPC => Option[T]): T =
{
def listen(): T =
{
ipc(server.accept())(f) match {
case Some(done) => done
case None => listen()
}
}
try { listen() }
finally { server.close() }
private def serverImpl[T](server: ServerSocket, f: IPC => Option[T]): T = {
def listen(): T = {
ipc(server.accept())(f) match {
case Some(done) => done
case None => listen()
}
}
try { listen() } finally { server.close() }
}
private def ipc[T](s: Socket)(f: IPC => T): T =
try { f(new IPC(s)) }
finally { s.close() }
try { f(new IPC(s)) } finally { s.close() }
final class Server private[IPC] (s: ServerSocket) {
def port = s.getLocalPort

View File

@ -8,40 +8,54 @@ import Def.Initialize
import reflect.internal.annotations.compileTimeOnly
object Append {
@implicitNotFound(msg = "No implicit for Append.Value[${A}, ${B}] found,\n so ${B} cannot be appended to ${A}")
@implicitNotFound(
msg = "No implicit for Append.Value[${A}, ${B}] found,\n so ${B} cannot be appended to ${A}")
trait Value[A, B] {
def appendValue(a: A, b: B): A
}
@implicitNotFound(msg = "No implicit for Append.Values[${A}, ${B}] found,\n so ${B} cannot be appended to ${A}")
@implicitNotFound(
msg = "No implicit for Append.Values[${A}, ${B}] found,\n so ${B} cannot be appended to ${A}")
trait Values[A, -B] {
def appendValues(a: A, b: B): A
}
trait Sequence[A, -B, T] extends Value[A, T] with Values[A, B]
implicit def appendSeq[T, V <: T]: Sequence[Seq[T], Seq[V], V] = new Sequence[Seq[T], Seq[V], V] {
def appendValues(a: Seq[T], b: Seq[V]): Seq[T] = a ++ b
def appendValue(a: Seq[T], b: V): Seq[T] = a :+ b
}
implicit def appendSeqImplicit[T, V](implicit ev: V => T): Sequence[Seq[T], Seq[V], V] = new Sequence[Seq[T], Seq[V], V] {
def appendValues(a: Seq[T], b: Seq[V]): Seq[T] = a ++ (b map { x => (x: T) })
def appendValue(a: Seq[T], b: V): Seq[T] = a :+ (b: T)
}
implicit def appendSeq[T, V <: T]: Sequence[Seq[T], Seq[V], V] =
new Sequence[Seq[T], Seq[V], V] {
def appendValues(a: Seq[T], b: Seq[V]): Seq[T] = a ++ b
def appendValue(a: Seq[T], b: V): Seq[T] = a :+ b
}
implicit def appendSeqImplicit[T, V](implicit ev: V => T): Sequence[Seq[T], Seq[V], V] =
new Sequence[Seq[T], Seq[V], V] {
def appendValues(a: Seq[T], b: Seq[V]): Seq[T] =
a ++ (b map { x =>
(x: T)
})
def appendValue(a: Seq[T], b: V): Seq[T] = a :+ (b: T)
}
@compileTimeOnly("This can be used in += only.")
implicit def appendTaskValueSeq[T, V <: T]: Value[Seq[Task[T]], Initialize[Task[V]]] = new Value[Seq[Task[T]], Initialize[Task[V]]] {
def appendValue(a: Seq[Task[T]], b: Initialize[Task[V]]): Seq[Task[T]] = ???
}
implicit def appendTaskValueSeq[T, V <: T]: Value[Seq[Task[T]], Initialize[Task[V]]] =
new Value[Seq[Task[T]], Initialize[Task[V]]] {
def appendValue(a: Seq[Task[T]], b: Initialize[Task[V]]): Seq[Task[T]] = ???
}
@compileTimeOnly("This can be used in += only.")
implicit def appendTaskKeySeq[T, V <: T]: Value[Seq[Task[T]], TaskKey[V]] = new Value[Seq[Task[T]], TaskKey[V]] {
def appendValue(a: Seq[Task[T]], b: TaskKey[V]): Seq[Task[T]] = ???
}
implicit def appendList[T, V <: T]: Sequence[List[T], List[V], V] = new Sequence[List[T], List[V], V] {
def appendValues(a: List[T], b: List[V]): List[T] = a ::: b
def appendValue(a: List[T], b: V): List[T] = a :+ b
}
implicit def appendListImplicit[T, V](implicit ev: V => T): Sequence[List[T], List[V], V] = new Sequence[List[T], List[V], V] {
def appendValues(a: List[T], b: List[V]): List[T] = a ::: (b map { x => (x: T) })
def appendValue(a: List[T], b: V): List[T] = a :+ (b: T)
}
implicit def appendTaskKeySeq[T, V <: T]: Value[Seq[Task[T]], TaskKey[V]] =
new Value[Seq[Task[T]], TaskKey[V]] {
def appendValue(a: Seq[Task[T]], b: TaskKey[V]): Seq[Task[T]] = ???
}
implicit def appendList[T, V <: T]: Sequence[List[T], List[V], V] =
new Sequence[List[T], List[V], V] {
def appendValues(a: List[T], b: List[V]): List[T] = a ::: b
def appendValue(a: List[T], b: V): List[T] = a :+ b
}
implicit def appendListImplicit[T, V](implicit ev: V => T): Sequence[List[T], List[V], V] =
new Sequence[List[T], List[V], V] {
def appendValues(a: List[T], b: List[V]): List[T] =
a ::: (b map { x =>
(x: T)
})
def appendValue(a: List[T], b: V): List[T] = a :+ (b: T)
}
implicit def appendString: Value[String, String] = new Value[String, String] {
def appendValue(a: String, b: String) = a + b
}
@ -54,18 +68,21 @@ object Append {
implicit def appendDouble = new Value[Double, Double] {
def appendValue(a: Double, b: Double) = a + b
}
implicit def appendClasspath: Sequence[Classpath, Seq[File], File] = new Sequence[Classpath, Seq[File], File] {
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
}
implicit def appendClasspath: Sequence[Classpath, Seq[File], File] =
new Sequence[Classpath, Seq[File], File] {
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
}
implicit def appendOption[T]: Sequence[Seq[T], Option[T], Option[T]] =
new Sequence[Seq[T], Option[T], Option[T]] {
def appendValue(a: Seq[T], b: Option[T]): Seq[T] = b.fold(a)(a :+ _)

View File

@ -2,5 +2,6 @@ package sbt
final case class ConfigKey(name: String)
object ConfigKey {
implicit def configurationToKey(c: sbt.librarymanagement.Configuration): ConfigKey = ConfigKey(c.name)
implicit def configurationToKey(c: sbt.librarymanagement.Configuration): ConfigKey =
ConfigKey(c.name)
}

View File

@ -17,35 +17,55 @@ object Def extends Init[Scope] with TaskMacroExtra {
val triggeredBy = AttributeKey[Seq[Task[_]]]("triggered-by")
val runBefore = AttributeKey[Seq[Task[_]]]("run-before")
val resolvedScoped = SettingKey[ScopedKey[_]]("resolved-scoped", "The ScopedKey for the referencing setting or task.", KeyRanks.DSetting)
private[sbt] val taskDefinitionKey = AttributeKey[ScopedKey[_]]("task-definition-key", "Internal: used to map a task back to its ScopedKey.", Invisible)
val resolvedScoped = SettingKey[ScopedKey[_]](
"resolved-scoped",
"The ScopedKey for the referencing setting or task.",
KeyRanks.DSetting)
private[sbt] val taskDefinitionKey = AttributeKey[ScopedKey[_]](
"task-definition-key",
"Internal: used to map a task back to its ScopedKey.",
Invisible)
lazy val showFullKey: Show[ScopedKey[_]] = showFullKey(None)
def showFullKey(keyNameColor: Option[String]): Show[ScopedKey[_]] =
Show[ScopedKey[_]]((key: ScopedKey[_]) => displayFull(key, keyNameColor))
def showRelativeKey(current: ProjectRef, multi: Boolean, keyNameColor: Option[String] = None): Show[ScopedKey[_]] =
Show[ScopedKey[_]]((key: ScopedKey[_]) =>
Scope.display(key.scope, colored(key.key.label, keyNameColor), ref => displayRelative(current, multi, ref)))
def showRelativeKey(current: ProjectRef,
multi: Boolean,
keyNameColor: Option[String] = None): Show[ScopedKey[_]] =
Show[ScopedKey[_]](
(key: ScopedKey[_]) =>
Scope.display(key.scope,
colored(key.key.label, keyNameColor),
ref => displayRelative(current, multi, ref)))
def showBuildRelativeKey(currentBuild: URI, multi: Boolean, keyNameColor: Option[String] = None): Show[ScopedKey[_]] =
Show[ScopedKey[_]]((key: ScopedKey[_]) =>
Scope.display(key.scope, colored(key.key.label, keyNameColor), ref => displayBuildRelative(currentBuild, multi, ref)))
def showBuildRelativeKey(currentBuild: URI,
multi: Boolean,
keyNameColor: Option[String] = None): Show[ScopedKey[_]] =
Show[ScopedKey[_]](
(key: ScopedKey[_]) =>
Scope.display(key.scope,
colored(key.key.label, keyNameColor),
ref => displayBuildRelative(currentBuild, multi, ref)))
def displayRelative(current: ProjectRef, multi: Boolean, project: Reference): String = project match {
case BuildRef(current.build) => "{.}/"
case `current` => if (multi) current.project + "/" else ""
case ProjectRef(current.build, x) => x + "/"
case _ => Reference.display(project) + "/"
}
def displayBuildRelative(currentBuild: URI, multi: Boolean, project: Reference): String = project match {
case BuildRef(`currentBuild`) => "{.}/"
case ProjectRef(`currentBuild`, x) => x + "/"
case _ => Reference.display(project) + "/"
}
def displayRelative(current: ProjectRef, multi: Boolean, project: Reference): String =
project match {
case BuildRef(current.build) => "{.}/"
case `current` => if (multi) current.project + "/" else ""
case ProjectRef(current.build, x) => x + "/"
case _ => Reference.display(project) + "/"
}
def displayBuildRelative(currentBuild: URI, multi: Boolean, project: Reference): String =
project match {
case BuildRef(`currentBuild`) => "{.}/"
case ProjectRef(`currentBuild`, x) => x + "/"
case _ => Reference.display(project) + "/"
}
def displayFull(scoped: ScopedKey[_]): String = displayFull(scoped, None)
def displayFull(scoped: ScopedKey[_], keyNameColor: Option[String]): String = Scope.display(scoped.scope, colored(scoped.key.label, keyNameColor))
def displayMasked(scoped: ScopedKey[_], mask: ScopeMask): String = Scope.displayMasked(scoped.scope, scoped.key.label, mask)
def displayFull(scoped: ScopedKey[_], keyNameColor: Option[String]): String =
Scope.display(scoped.scope, colored(scoped.key.label, keyNameColor))
def displayMasked(scoped: ScopedKey[_], mask: ScopeMask): String =
Scope.displayMasked(scoped.scope, scoped.key.label, mask)
def colored(s: String, color: Option[String]): String = color match {
case Some(c) => c + s + scala.Console.RESET
@ -54,10 +74,16 @@ object Def extends Init[Scope] with TaskMacroExtra {
override def deriveAllowed[T](s: Setting[T], allowDynamic: Boolean): Option[String] =
super.deriveAllowed(s, allowDynamic) orElse
(if (s.key.scope != ThisScope) Some(s"Scope cannot be defined for ${definedSettingString(s)}") else None) orElse
s.dependencies.find(k => k.scope != ThisScope).map(k => s"Scope cannot be defined for dependency ${k.key.label} of ${definedSettingString(s)}")
(if (s.key.scope != ThisScope)
Some(s"Scope cannot be defined for ${definedSettingString(s)}")
else None) orElse
s.dependencies
.find(k => k.scope != ThisScope)
.map(k =>
s"Scope cannot be defined for dependency ${k.key.label} of ${definedSettingString(s)}")
override def intersect(s1: Scope, s2: Scope)(implicit delegates: Scope => Seq[Scope]): Option[Scope] =
override def intersect(s1: Scope, s2: Scope)(
implicit delegates: Scope => Seq[Scope]): Option[Scope] =
if (s2 == GlobalScope) Some(s1) // s1 is more specific
else if (s1 == GlobalScope) Some(s2) // s2 is more specific
else super.intersect(s1, s2)
@ -71,7 +97,8 @@ object Def extends Init[Scope] with TaskMacroExtra {
* A default Parser for splitting input into space-separated arguments.
* `argLabel` is an optional, fixed label shown for an argument during tab completion.
*/
def spaceDelimited(argLabel: String = "<arg>"): Parser[Seq[String]] = sbt.internal.util.complete.Parsers.spaceDelimited(argLabel)
def spaceDelimited(argLabel: String = "<arg>"): Parser[Seq[String]] =
sbt.internal.util.complete.Parsers.spaceDelimited(argLabel)
/** Lifts the result of a setting initialization into a Task. */
def toITask[T](i: Initialize[T]): Initialize[Task[T]] = map(i)(std.TaskExtra.inlineTask)
@ -81,7 +108,12 @@ object Def extends Init[Scope] with TaskMacroExtra {
def toIParser[T](p: Initialize[InputTask[T]]): Initialize[State => Parser[Task[T]]] = p(_.parser)
import language.experimental.macros
import std.TaskMacro.{ inputTaskMacroImpl, inputTaskDynMacroImpl, taskDynMacroImpl, taskMacroImpl }
import std.TaskMacro.{
inputTaskMacroImpl,
inputTaskDynMacroImpl,
taskDynMacroImpl,
taskMacroImpl
}
import std.SettingMacro.{ settingDynMacroImpl, settingMacroImpl }
import std.{ InputEvaluated, MacroPrevious, MacroValue, MacroTaskValue, ParserInput }
@ -90,7 +122,8 @@ object Def extends Init[Scope] with TaskMacroExtra {
def setting[T](t: T): Def.Initialize[T] = macro settingMacroImpl[T]
def settingDyn[T](t: Def.Initialize[T]): Def.Initialize[T] = macro settingDynMacroImpl[T]
def inputTask[T](t: T): Def.Initialize[InputTask[T]] = macro inputTaskMacroImpl[T]
def inputTaskDyn[T](t: Def.Initialize[Task[T]]): Def.Initialize[InputTask[T]] = macro inputTaskDynMacroImpl[T]
def inputTaskDyn[T](t: Def.Initialize[Task[T]]): Def.Initialize[InputTask[T]] =
macro inputTaskDynMacroImpl[T]
// The following conversions enable the types Initialize[T], Initialize[Task[T]], and Task[T] to
// be used in task and setting macros as inputs with an ultimate result of type T
@ -110,17 +143,24 @@ object Def extends Init[Scope] with TaskMacroExtra {
def taskKey[T](description: String): TaskKey[T] = macro std.KeyMacro.taskKeyImpl[T]
def inputKey[T](description: String): InputKey[T] = macro std.KeyMacro.inputKeyImpl[T]
private[sbt] def dummy[T: Manifest](name: String, description: String): (TaskKey[T], Task[T]) = (TaskKey[T](name, description, DTask), dummyTask(name))
private[sbt] def dummyTask[T](name: String): Task[T] =
{
import std.TaskExtra.{ task => newTask, _ }
val base: Task[T] = newTask(sys.error("Dummy task '" + name + "' did not get converted to a full task.")) named name
base.copy(info = base.info.set(isDummyTask, true))
}
private[sbt] def isDummy(t: Task[_]): Boolean = t.info.attributes.get(isDummyTask) getOrElse false
private[sbt] val isDummyTask = AttributeKey[Boolean]("is-dummy-task", "Internal: used to identify dummy tasks. sbt injects values for these tasks at the start of task execution.", Invisible)
private[sbt] def dummy[T: Manifest](name: String, description: String): (TaskKey[T], Task[T]) =
(TaskKey[T](name, description, DTask), dummyTask(name))
private[sbt] def dummyTask[T](name: String): Task[T] = {
import std.TaskExtra.{ task => newTask, _ }
val base: Task[T] = newTask(
sys.error("Dummy task '" + name + "' did not get converted to a full task.")) named name
base.copy(info = base.info.set(isDummyTask, true))
}
private[sbt] def isDummy(t: Task[_]): Boolean =
t.info.attributes.get(isDummyTask) getOrElse false
private[sbt] val isDummyTask = AttributeKey[Boolean](
"is-dummy-task",
"Internal: used to identify dummy tasks. sbt injects values for these tasks at the start of task execution.",
Invisible)
private[sbt] val (stateKey, dummyState) = dummy[State]("state", "Current build state.")
private[sbt] val (streamsManagerKey, dummyStreamsManager) = Def.dummy[std.Streams[ScopedKey[_]]]("streams-manager", "Streams manager, which provides streams for different contexts.")
private[sbt] val (streamsManagerKey, dummyStreamsManager) = Def.dummy[std.Streams[ScopedKey[_]]](
"streams-manager",
"Streams manager, which provides streams for different contexts.")
}
// these need to be mixed into the sbt package object because the target doesn't involve Initialize or anything in Def
trait TaskMacroExtra {

View File

@ -7,11 +7,18 @@ sealed trait DelegateIndex {
// def extra(ref: ProjectRef, e: AttributeMap): Seq[ScopeAxis[AttributeMap]]
}
private final class DelegateIndex0(refs: Map[ProjectRef, ProjectDelegates]) extends DelegateIndex {
def project(ref: ProjectRef): Seq[ScopeAxis[ResolvedReference]] = refs.get(ref) match { case Some(pd) => pd.refs; case None => Nil }
def project(ref: ProjectRef): Seq[ScopeAxis[ResolvedReference]] = refs.get(ref) match {
case Some(pd) => pd.refs; case None => Nil
}
def config(ref: ProjectRef, conf: ConfigKey): Seq[ScopeAxis[ConfigKey]] =
refs.get(ref) match {
case Some(pd) => pd.confs.get(conf) match { case Some(cs) => cs; case None => Select(conf) :: Global :: Nil }
case None => Select(conf) :: Global :: Nil
case Some(pd) =>
pd.confs.get(conf) match {
case Some(cs) => cs; case None => Select(conf) :: Global :: Nil
}
case None => Select(conf) :: Global :: Nil
}
}
private final class ProjectDelegates(val ref: ProjectRef, val refs: Seq[ScopeAxis[ResolvedReference]], val confs: Map[ConfigKey, Seq[ScopeAxis[ConfigKey]]])
private final class ProjectDelegates(val ref: ProjectRef,
val refs: Seq[ScopeAxis[ResolvedReference]],
val confs: Map[ConfigKey, Seq[ScopeAxis[ConfigKey]]])

View File

@ -14,12 +14,13 @@ final class InputTask[T] private (val parser: State => Parser[Task[T]]) {
def partialInput(in: String): InputTask[T] =
new InputTask[T](s => Parser(parser(s))(in))
def fullInput(in: String): InputTask[T] = new InputTask[T](s =>
Parser.parse(in, parser(s)) match {
case Right(v) => Parser.success(v)
case Left(msg) =>
val indented = msg.lines.map(" " + _).mkString("\n")
Parser.failure(s"Invalid programmatic input:\n$indented")
def fullInput(in: String): InputTask[T] =
new InputTask[T](s =>
Parser.parse(in, parser(s)) match {
case Right(v) => Parser.success(v)
case Left(msg) =>
val indented = msg.lines.map(" " + _).mkString("\n")
Parser.failure(s"Invalid programmatic input:\n$indented")
})
}
@ -54,49 +55,60 @@ object InputTask {
def free[I, T](p: State => Parser[I])(c: I => Task[T]): InputTask[T] = free(s => p(s) map c)
def separate[I, T](p: State => Parser[I])(action: Initialize[I => Task[T]]): Initialize[InputTask[T]] =
def separate[I, T](p: State => Parser[I])(
action: Initialize[I => Task[T]]): Initialize[InputTask[T]] =
separate(Def value p)(action)
def separate[I, T](p: Initialize[State => Parser[I]])(action: Initialize[I => Task[T]]): Initialize[InputTask[T]] =
def separate[I, T](p: Initialize[State => Parser[I]])(
action: Initialize[I => Task[T]]): Initialize[InputTask[T]] =
p.zipWith(action)((parser, act) => free(parser)(act))
/** Constructs an InputTask that accepts no user input. */
def createFree[T](action: Initialize[Task[T]]): Initialize[InputTask[T]] =
action { tsk => free(emptyParser)(const(tsk)) }
action { tsk =>
free(emptyParser)(const(tsk))
}
/**
* Constructs an InputTask from:
* a) a Parser constructed using other Settings, but not Tasks
* b) a dynamically constructed Task that uses Settings, Tasks, and the result of parsing.
*/
def createDyn[I, T](p: Initialize[State => Parser[I]])(action: Initialize[Task[I => Initialize[Task[T]]]]): Initialize[InputTask[T]] =
def createDyn[I, T](p: Initialize[State => Parser[I]])(
action: Initialize[Task[I => Initialize[Task[T]]]]): Initialize[InputTask[T]] =
separate(p)(std.FullInstance.flattenFun[I, T](action))
/** A dummy parser that consumes no input and produces nothing useful (unit).*/
def emptyParser: State => Parser[Unit] = Types.const(sbt.internal.util.complete.DefaultParsers.success(()))
def emptyParser: State => Parser[Unit] =
Types.const(sbt.internal.util.complete.DefaultParsers.success(()))
/** Implementation detail that is public because it is used by a macro.*/
def parserAsInput[T](p: Parser[T]): Initialize[State => Parser[T]] = Def.valueStrict(Types.const(p))
def parserAsInput[T](p: Parser[T]): Initialize[State => Parser[T]] =
Def.valueStrict(Types.const(p))
/** Implementation detail that is public because it is used by a macro.*/
def initParserAsInput[T](i: Initialize[Parser[T]]): Initialize[State => Parser[T]] = i(Types.const)
def initParserAsInput[T](i: Initialize[Parser[T]]): Initialize[State => Parser[T]] =
i(Types.const)
@deprecated("Use another InputTask constructor or the `Def.inputTask` macro.", "0.13.0")
def apply[I, T](p: Initialize[State => Parser[I]])(action: TaskKey[I] => Initialize[Task[T]]): Initialize[InputTask[T]] =
{
val dummyKey = localKey[Task[I]]
val (marker, dummy) = dummyTask[I]
val it = action(TaskKey(dummyKey)) mapConstant subResultForDummy(dummyKey, dummy)
val act = it { tsk => (value: I) => subForDummy(marker, value, tsk) }
separate(p)(act)
def apply[I, T](p: Initialize[State => Parser[I]])(
action: TaskKey[I] => Initialize[Task[T]]): Initialize[InputTask[T]] = {
val dummyKey = localKey[Task[I]]
val (marker, dummy) = dummyTask[I]
val it = action(TaskKey(dummyKey)) mapConstant subResultForDummy(dummyKey, dummy)
val act = it { tsk => (value: I) =>
subForDummy(marker, value, tsk)
}
separate(p)(act)
}
/**
* The proper solution is to have a Manifest context bound and accept slight source incompatibility,
* The affected InputTask construction methods are all deprecated and so it is better to keep complete
* compatibility. Because the AttributeKey is local, it uses object equality and the manifest is not used.
*/
private[this] def localKey[T]: AttributeKey[T] = AttributeKey.local[Unit].asInstanceOf[AttributeKey[T]]
private[this] def localKey[T]: AttributeKey[T] =
AttributeKey.local[Unit].asInstanceOf[AttributeKey[T]]
private[this] def subResultForDummy[I](dummyKey: AttributeKey[Task[I]], dummyTask: Task[I]) =
new (ScopedKey ~> Option) {
@ -109,35 +121,34 @@ object InputTask {
None
}
private[this] def dummyTask[I]: (AttributeKey[Option[I]], Task[I]) =
{
val key = localKey[Option[I]]
val f: () => I = () => sys.error(s"Internal sbt error: InputTask stub was not substituted properly.")
val t: Task[I] = Task(Info[I]().set(key, None), Pure(f, false))
(key, t)
}
private[this] def dummyTask[I]: (AttributeKey[Option[I]], Task[I]) = {
val key = localKey[Option[I]]
val f: () => I = () =>
sys.error(s"Internal sbt error: InputTask stub was not substituted properly.")
val t: Task[I] = Task(Info[I]().set(key, None), Pure(f, false))
(key, t)
}
private[this] def subForDummy[I, T](marker: AttributeKey[Option[I]], value: I, task: Task[T]): Task[T] =
{
val seen = new java.util.IdentityHashMap[Task[_], Task[_]]
lazy val f: Task ~> Task = new (Task ~> Task) {
def apply[A](t: Task[A]): Task[A] =
{
val t0 = seen.get(t)
if (t0 == null) {
val newAction =
if (t.info.get(marker).isDefined)
Pure(() => value.asInstanceOf[A], inline = true)
else
t.work.mapTask(f)
val newTask = Task(t.info, newAction)
seen.put(t, newTask)
newTask
} else
t0.asInstanceOf[Task[A]]
}
private[this] def subForDummy[I, T](marker: AttributeKey[Option[I]],
value: I,
task: Task[T]): Task[T] = {
val seen = new java.util.IdentityHashMap[Task[_], Task[_]]
lazy val f: Task ~> Task = new (Task ~> Task) {
def apply[A](t: Task[A]): Task[A] = {
val t0 = seen.get(t)
if (t0 == null) {
val newAction =
if (t.info.get(marker).isDefined)
Pure(() => value.asInstanceOf[A], inline = true)
else
t.work.mapTask(f)
val newTask = Task(t.info, newAction)
seen.put(t, newTask)
newTask
} else
t0.asInstanceOf[Task[A]]
}
f(task)
}
f(task)
}
}

View File

@ -12,13 +12,16 @@ import sjsonnew.JsonFormat
*/
private[sbt] final class Previous(streams: Streams, referenced: IMap[ScopedTaskKey, Referenced]) {
private[this] val map = referenced.mapValues(toValue)
private[this] def toValue = new (Referenced ~> ReferencedValue) { def apply[T](x: Referenced[T]) = new ReferencedValue(x) }
private[this] def toValue = new (Referenced ~> ReferencedValue) {
def apply[T](x: Referenced[T]) = new ReferencedValue(x)
}
private[this] final class ReferencedValue[T](referenced: Referenced[T]) {
import referenced.{ stamped, task }
lazy val previousValue: Option[T] = {
val in = streams(task).getInput(task, StreamName)
try read(in, stamped) finally in.close()
try read(in, stamped)
finally in.close()
}
}
@ -28,7 +31,7 @@ private[sbt] final class Previous(streams: Streams, referenced: IMap[ScopedTaskK
}
object Previous {
import sjsonnew.BasicJsonProtocol.StringJsonFormat
private[sbt]type ScopedTaskKey[T] = ScopedKey[Task[T]]
private[sbt] type ScopedTaskKey[T] = ScopedKey[Task[T]]
private type Streams = sbt.std.Streams[ScopedKey[_]]
/** The stream where the task value is persisted. */
@ -40,8 +43,14 @@ object Previous {
def setTask(newTask: ScopedKey[Task[T]]) = new Referenced(newTask, format)
}
private[sbt] val references = SettingKey[References]("previous-references", "Collects all static references to previous values of tasks.", KeyRanks.Invisible)
private[sbt] val cache = TaskKey[Previous]("previous-cache", "Caches previous values of tasks read from disk for the duration of a task execution.", KeyRanks.Invisible)
private[sbt] val references = SettingKey[References](
"previous-references",
"Collects all static references to previous values of tasks.",
KeyRanks.Invisible)
private[sbt] val cache = TaskKey[Previous](
"previous-cache",
"Caches previous values of tasks read from disk for the duration of a task execution.",
KeyRanks.Invisible)
/** Records references to previous task value. This should be completely populated after settings finish loading. */
private[sbt] final class References {
@ -56,20 +65,22 @@ object Previous {
}
/** Persists values of tasks t where there is some task referencing it via t.previous. */
private[sbt] def complete(referenced: References, results: RMap[Task, Result], streams: Streams): Unit =
{
val map = referenced.getReferences
def impl[T](key: ScopedKey[_], result: T): Unit =
for (i <- map.get(key.asInstanceOf[ScopedTaskKey[T]])) {
val out = streams.apply(i.task).getOutput(StreamName)
try write(out, i.stamped, result) finally out.close()
}
private[sbt] def complete(referenced: References,
results: RMap[Task, Result],
streams: Streams): Unit = {
val map = referenced.getReferences
def impl[T](key: ScopedKey[_], result: T): Unit =
for (i <- map.get(key.asInstanceOf[ScopedTaskKey[T]])) {
val out = streams.apply(i.task).getOutput(StreamName)
try write(out, i.stamped, result)
finally out.close()
}
for {
results.TPair(Task(info, _), Value(result)) <- results.toTypedSeq
key <- info.attributes get Def.taskDefinitionKey
} impl(key, result)
}
for {
results.TPair(Task(info, _), Value(result)) <- results.toTypedSeq
key <- info.attributes get Def.taskDefinitionKey
} impl(key, result)
}
private def read[T](input: Input, format: JsonFormat[T]): Option[T] =
try Some(input.read()(format))
@ -80,14 +91,13 @@ object Previous {
catch { case e: Exception => () }
/** Public as a macro implementation detail. Do not call directly. */
def runtime[T](skey: TaskKey[T])(implicit format: JsonFormat[T]): Initialize[Task[Option[T]]] =
{
val inputs = (cache in Global) zip Def.validated(skey, selfRefOk = true) zip (references in Global)
inputs {
case ((prevTask, resolved), refs) =>
refs.recordReference(resolved, format) // always evaluated on project load
import std.TaskExtra._
prevTask.map(_ get resolved) // evaluated if this task is evaluated
}
def runtime[T](skey: TaskKey[T])(implicit format: JsonFormat[T]): Initialize[Task[Option[T]]] = {
val inputs = (cache in Global) zip Def.validated(skey, selfRefOk = true) zip (references in Global)
inputs {
case ((prevTask, resolved), refs) =>
refs.recordReference(resolved, format) // always evaluated on project load
import std.TaskExtra._
prevTask.map(_ get resolved) // evaluated if this task is evaluated
}
}
}

View File

@ -18,21 +18,30 @@ sealed trait ResolvedReference extends Reference
/** Identifies a build. */
sealed trait BuildReference extends Reference
/** Identifies the build for the current context. */
final case object ThisBuild extends BuildReference
/** Uniquely identifies a build by a URI. */
final case class BuildRef(build: URI) extends BuildReference with ResolvedReference
/** Identifies a project. */
sealed trait ProjectReference extends Reference
/** Uniquely references a project by a URI and a project identifier String. */
final case class ProjectRef(build: URI, project: String) extends ProjectReference with ResolvedReference
final case class ProjectRef(build: URI, project: String)
extends ProjectReference
with ResolvedReference
/** Identifies a project in the current build context. */
final case class LocalProject(project: String) extends ProjectReference
/** Identifies the root project in the specified build. */
final case class RootProject(build: URI) extends ProjectReference
/** Identifies the root project in the current build context. */
final case object LocalRootProject extends ProjectReference
/** Identifies the project for the current context. */
final case object ThisProject extends ProjectReference
@ -40,18 +49,20 @@ object ProjectRef {
def apply(base: File, id: String): ProjectRef = ProjectRef(IO toURI base, id)
}
object RootProject {
/** Reference to the root project at 'base'.*/
def apply(base: File): RootProject = RootProject(IO toURI base)
}
object Reference {
implicit val resolvedReferenceOrdering: Ordering[ResolvedReference] = new Ordering[ResolvedReference] {
def compare(a: ResolvedReference, b: ResolvedReference): Int = (a, b) match {
case (ba: BuildRef, bb: BuildRef) => buildRefOrdering.compare(ba, bb)
case (pa: ProjectRef, pb: ProjectRef) => projectRefOrdering.compare(pa, pb)
case (_: BuildRef, _: ProjectRef) => -1
case (_: ProjectRef, _: BuildRef) => 1
implicit val resolvedReferenceOrdering: Ordering[ResolvedReference] =
new Ordering[ResolvedReference] {
def compare(a: ResolvedReference, b: ResolvedReference): Int = (a, b) match {
case (ba: BuildRef, bb: BuildRef) => buildRefOrdering.compare(ba, bb)
case (pa: ProjectRef, pb: ProjectRef) => projectRefOrdering.compare(pa, pb)
case (_: BuildRef, _: ProjectRef) => -1
case (_: ProjectRef, _: BuildRef) => 1
}
}
}
implicit val buildRefOrdering: Ordering[BuildRef] = new Ordering[BuildRef] {
def compare(a: BuildRef, b: BuildRef): Int = a.build compareTo b.build
}
@ -87,6 +98,7 @@ object Reference {
case BuildRef(b) => b
case ProjectRef(b, _) => b
}
/** Extracts the build URI from a Reference if one has been explicitly defined.*/
def uri(ref: Reference): Option[URI] = ref match {
case RootProject(b) => Some(b)

View File

@ -3,20 +3,23 @@ package sbt
import scala.annotation.implicitNotFound
object Remove {
@implicitNotFound(msg = "No implicit for Remove.Value[${A}, ${B}] found,\n so ${B} cannot be removed from ${A}")
@implicitNotFound(
msg = "No implicit for Remove.Value[${A}, ${B}] found,\n so ${B} cannot be removed from ${A}")
trait Value[A, B] extends Any {
def removeValue(a: A, b: B): A
}
@implicitNotFound(msg = "No implicit for Remove.Values[${A}, ${B}] found,\n so ${B} cannot be removed from ${A}")
@implicitNotFound(msg =
"No implicit for Remove.Values[${A}, ${B}] found,\n so ${B} cannot be removed from ${A}")
trait Values[A, -B] extends Any {
def removeValues(a: A, b: B): A
}
trait Sequence[A, -B, T] extends Value[A, T] with Values[A, B]
implicit def removeSeq[T, V <: T]: Sequence[Seq[T], Seq[V], V] = new Sequence[Seq[T], Seq[V], V] {
def removeValue(a: Seq[T], b: V): Seq[T] = a filterNot b.==
def removeValues(a: Seq[T], b: Seq[V]): Seq[T] = a diff b
}
implicit def removeSeq[T, V <: T]: Sequence[Seq[T], Seq[V], V] =
new Sequence[Seq[T], Seq[V], V] {
def removeValue(a: Seq[T], b: V): Seq[T] = a filterNot b.==
def removeValues(a: Seq[T], b: Seq[V]): Seq[T] = a diff b
}
implicit def removeOption[T]: Sequence[Seq[T], Option[T], Option[T]] =
new Sequence[Seq[T], Option[T], Option[T]] {
def removeValue(a: Seq[T], b: Option[T]): Seq[T] = b.fold(a)(a filterNot _.==)

View File

@ -9,11 +9,18 @@ import sbt.internal.util.{ AttributeKey, AttributeMap, Dag }
import sbt.io.IO
final case class Scope(project: ScopeAxis[Reference], config: ScopeAxis[ConfigKey], task: ScopeAxis[AttributeKey[_]], extra: ScopeAxis[AttributeMap]) {
def in(project: Reference, config: ConfigKey): Scope = copy(project = Select(project), config = Select(config))
def in(config: ConfigKey, task: AttributeKey[_]): Scope = copy(config = Select(config), task = Select(task))
def in(project: Reference, task: AttributeKey[_]): Scope = copy(project = Select(project), task = Select(task))
def in(project: Reference, config: ConfigKey, task: AttributeKey[_]): Scope = copy(project = Select(project), config = Select(config), task = Select(task))
final case class Scope(project: ScopeAxis[Reference],
config: ScopeAxis[ConfigKey],
task: ScopeAxis[AttributeKey[_]],
extra: ScopeAxis[AttributeMap]) {
def in(project: Reference, config: ConfigKey): Scope =
copy(project = Select(project), config = Select(config))
def in(config: ConfigKey, task: AttributeKey[_]): Scope =
copy(config = Select(config), task = Select(task))
def in(project: Reference, task: AttributeKey[_]): Scope =
copy(project = Select(project), task = Select(task))
def in(project: Reference, config: ConfigKey, task: AttributeKey[_]): Scope =
copy(project = Select(project), config = Select(config), task = Select(task))
def in(project: Reference): Scope = copy(project = Select(project))
def in(config: ConfigKey): Scope = copy(config = Select(config))
def in(task: AttributeKey[_]): Scope = copy(task = Select(task))
@ -28,8 +35,14 @@ object Scope {
def resolveBuildScope(thisScope: Scope, current: URI): Scope => Scope =
buildResolve(current) compose replaceThis(thisScope) compose subThisProject
def replaceThis(thisScope: Scope): Scope => Scope = (scope: Scope) =>
Scope(subThis(thisScope.project, scope.project), subThis(thisScope.config, scope.config), subThis(thisScope.task, scope.task), subThis(thisScope.extra, scope.extra))
def replaceThis(thisScope: Scope): Scope => Scope =
(scope: Scope) =>
Scope(
subThis(thisScope.project, scope.project),
subThis(thisScope.config, scope.config),
subThis(thisScope.task, scope.task),
subThis(thisScope.extra, scope.extra)
)
def subThis[T](sub: ScopeAxis[T], into: ScopeAxis[T]): ScopeAxis[T] =
if (into == This) sub else into
@ -49,11 +62,10 @@ object Scope {
case _ => scope.copy(task = Select(key))
}
def mapReference(f: Reference => Reference): Scope => Scope =
{
case Scope(Select(ref), a, b, c) => Scope(Select(f(ref)), a, b, c)
case x => x
}
def mapReference(f: Reference => Reference): Scope => Scope = {
case Scope(Select(ref), a, b, c) => Scope(Select(f(ref)), a, b, c)
case x => x
}
def resolveProject(uri: URI, rootProject: URI => String): Scope => Scope =
mapReference(ref => resolveReference(uri, rootProject, ref))
def buildResolve(uri: URI): Scope => Scope =
@ -75,7 +87,8 @@ object Scope {
case LocalProject(id) => ProjectRef(current, id)
case RootProject(uri) => RootProject(resolveBuild(current, uri))
case ProjectRef(uri, id) => ProjectRef(resolveBuild(current, uri), id)
case ThisProject => RootProject(current) // Is this right? It was an inexhaustive match before..
case ThisProject =>
RootProject(current) // Is this right? It was an inexhaustive match before..
}
def resolveBuild(current: URI, uri: URI): URI =
if (!uri.isAbsolute && current.isOpaque && uri.getSchemeSpecificPart == ".")
@ -83,20 +96,25 @@ object Scope {
else
IO.directoryURI(current resolve uri)
def resolveReference(current: URI, rootProject: URI => String, ref: Reference): ResolvedReference =
def resolveReference(current: URI,
rootProject: URI => String,
ref: Reference): ResolvedReference =
ref match {
case br: BuildReference => resolveBuildRef(current, br)
case pr: ProjectReference => resolveProjectRef(current, rootProject, pr)
}
def resolveProjectRef(current: URI, rootProject: URI => String, ref: ProjectReference): ProjectRef =
def resolveProjectRef(current: URI,
rootProject: URI => String,
ref: ProjectReference): ProjectRef =
ref match {
case LocalRootProject => ProjectRef(current, rootProject(current))
case LocalProject(id) => ProjectRef(current, id)
case RootProject(uri) =>
val res = resolveBuild(current, uri); ProjectRef(res, rootProject(res))
case ProjectRef(uri, id) => ProjectRef(resolveBuild(current, uri), id)
case ThisProject => ProjectRef(current, rootProject(current)) // Is this right? It was an inexhaustive match before..
case ThisProject =>
ProjectRef(current, rootProject(current)) // Is this right? It was an inexhaustive match before..
}
def resolveBuildRef(current: URI, ref: BuildReference): BuildRef =
ref match {
@ -105,18 +123,23 @@ object Scope {
}
def display(config: ConfigKey): String = config.name + ":"
def display(scope: Scope, sep: String): String = displayMasked(scope, sep, showProject, ScopeMask())
def displayMasked(scope: Scope, sep: String, mask: ScopeMask): String = displayMasked(scope, sep, showProject, mask)
def display(scope: Scope, sep: String, showProject: Reference => String): String = displayMasked(scope, sep, showProject, ScopeMask())
def displayMasked(scope: Scope, sep: String, showProject: Reference => String, mask: ScopeMask): String =
{
import scope.{ project, config, task, extra }
val configPrefix = config.foldStrict(display, "*:", ".:")
val taskPrefix = task.foldStrict(_.label + "::", "", ".::")
val extras = extra.foldStrict(_.entries.map(_.toString).toList, Nil, Nil)
val postfix = if (extras.isEmpty) "" else extras.mkString("(", ", ", ")")
mask.concatShow(projectPrefix(project, showProject), configPrefix, taskPrefix, sep, postfix)
}
def display(scope: Scope, sep: String): String =
displayMasked(scope, sep, showProject, ScopeMask())
def displayMasked(scope: Scope, sep: String, mask: ScopeMask): String =
displayMasked(scope, sep, showProject, mask)
def display(scope: Scope, sep: String, showProject: Reference => String): String =
displayMasked(scope, sep, showProject, ScopeMask())
def displayMasked(scope: Scope,
sep: String,
showProject: Reference => String,
mask: ScopeMask): String = {
import scope.{ project, config, task, extra }
val configPrefix = config.foldStrict(display, "*:", ".:")
val taskPrefix = task.foldStrict(_.label + "::", "", ".::")
val extras = extra.foldStrict(_.entries.map(_.toString).toList, Nil, Nil)
val postfix = if (extras.isEmpty) "" else extras.mkString("(", ", ", ")")
mask.concatShow(projectPrefix(project, showProject), configPrefix, taskPrefix, sep, postfix)
}
def equal(a: Scope, b: Scope, mask: ScopeMask): Boolean =
(!mask.project || a.project == b.project) &&
@ -124,106 +147,121 @@ object Scope {
(!mask.task || a.task == b.task) &&
(!mask.extra || a.extra == b.extra)
def projectPrefix(project: ScopeAxis[Reference], show: Reference => String = showProject): String = project.foldStrict(show, "*/", "./")
def projectPrefix(project: ScopeAxis[Reference],
show: Reference => String = showProject): String =
project.foldStrict(show, "*/", "./")
def showProject = (ref: Reference) => Reference.display(ref) + "/"
def transformTaskName(s: String) =
{
val parts = s.split("-+")
(parts.take(1) ++ parts.drop(1).map(_.capitalize)).mkString
}
def transformTaskName(s: String) = {
val parts = s.split("-+")
(parts.take(1) ++ parts.drop(1).map(_.capitalize)).mkString
}
// *Inherit functions should be immediate delegates and not include argument itself. Transitivity will be provided by this method
def delegates[Proj](
refs: Seq[(ProjectRef, Proj)],
configurations: Proj => Seq[ConfigKey],
resolve: Reference => ResolvedReference,
rootProject: URI => String,
projectInherit: ProjectRef => Seq[ProjectRef],
configInherit: (ResolvedReference, ConfigKey) => Seq[ConfigKey],
taskInherit: AttributeKey[_] => Seq[AttributeKey[_]],
extraInherit: (ResolvedReference, AttributeMap) => Seq[AttributeMap]
): Scope => Seq[Scope] =
{
val index = delegates(refs, configurations, projectInherit, configInherit)
scope => indexedDelegates(resolve, index, rootProject, taskInherit, extraInherit)(scope)
}
refs: Seq[(ProjectRef, Proj)],
configurations: Proj => Seq[ConfigKey],
resolve: Reference => ResolvedReference,
rootProject: URI => String,
projectInherit: ProjectRef => Seq[ProjectRef],
configInherit: (ResolvedReference, ConfigKey) => Seq[ConfigKey],
taskInherit: AttributeKey[_] => Seq[AttributeKey[_]],
extraInherit: (ResolvedReference, AttributeMap) => Seq[AttributeMap]
): Scope => Seq[Scope] = {
val index = delegates(refs, configurations, projectInherit, configInherit)
scope =>
indexedDelegates(resolve, index, rootProject, taskInherit, extraInherit)(scope)
}
def indexedDelegates(
resolve: Reference => ResolvedReference,
index: DelegateIndex,
rootProject: URI => String,
taskInherit: AttributeKey[_] => Seq[AttributeKey[_]],
extraInherit: (ResolvedReference, AttributeMap) => Seq[AttributeMap]
)(rawScope: Scope): Seq[Scope] =
{
val scope = Scope.replaceThis(GlobalScope)(rawScope)
resolve: Reference => ResolvedReference,
index: DelegateIndex,
rootProject: URI => String,
taskInherit: AttributeKey[_] => Seq[AttributeKey[_]],
extraInherit: (ResolvedReference, AttributeMap) => Seq[AttributeMap]
)(rawScope: Scope): Seq[Scope] = {
val scope = Scope.replaceThis(GlobalScope)(rawScope)
def nonProjectScopes(resolvedProj: ResolvedReference)(px: ScopeAxis[ResolvedReference]) =
{
val p = px.toOption getOrElse resolvedProj
val configProj = p match { case pr: ProjectRef => pr; case br: BuildRef => ProjectRef(br.build, rootProject(br.build)) }
val cLin = scope.config match { case Select(conf) => index.config(configProj, conf); case _ => withGlobalAxis(scope.config) }
val tLin = scope.task match { case t @ Select(task) => linearize(t)(taskInherit); case _ => withGlobalAxis(scope.task) }
val eLin = withGlobalAxis(scope.extra)
for (c <- cLin; t <- tLin; e <- eLin) yield Scope(px, c, t, e)
}
scope.project match {
case Global | This => globalProjectDelegates(scope)
case Select(proj) =>
val resolvedProj = resolve(proj)
val projAxes: Seq[ScopeAxis[ResolvedReference]] =
resolvedProj match {
case pr: ProjectRef => index.project(pr)
case br: BuildRef => Select(br) :: Global :: Nil
}
projAxes flatMap nonProjectScopes(resolvedProj)
def nonProjectScopes(resolvedProj: ResolvedReference)(px: ScopeAxis[ResolvedReference]) = {
val p = px.toOption getOrElse resolvedProj
val configProj = p match {
case pr: ProjectRef => pr; case br: BuildRef => ProjectRef(br.build, rootProject(br.build))
}
val cLin = scope.config match {
case Select(conf) => index.config(configProj, conf); case _ => withGlobalAxis(scope.config)
}
val tLin = scope.task match {
case t @ Select(task) => linearize(t)(taskInherit); case _ => withGlobalAxis(scope.task)
}
val eLin = withGlobalAxis(scope.extra)
for (c <- cLin; t <- tLin; e <- eLin) yield Scope(px, c, t, e)
}
scope.project match {
case Global | This => globalProjectDelegates(scope)
case Select(proj) =>
val resolvedProj = resolve(proj)
val projAxes: Seq[ScopeAxis[ResolvedReference]] =
resolvedProj match {
case pr: ProjectRef => index.project(pr)
case br: BuildRef => Select(br) :: Global :: Nil
}
projAxes flatMap nonProjectScopes(resolvedProj)
}
}
def withGlobalAxis[T](base: ScopeAxis[T]): Seq[ScopeAxis[T]] = if (base.isSelect) base :: Global :: Nil else Global :: Nil
def withGlobalScope(base: Scope): Seq[Scope] = if (base == GlobalScope) GlobalScope :: Nil else base :: GlobalScope :: Nil
def withGlobalAxis[T](base: ScopeAxis[T]): Seq[ScopeAxis[T]] =
if (base.isSelect) base :: Global :: Nil else Global :: Nil
def withGlobalScope(base: Scope): Seq[Scope] =
if (base == GlobalScope) GlobalScope :: Nil else base :: GlobalScope :: Nil
def withRawBuilds(ps: Seq[ScopeAxis[ProjectRef]]): Seq[ScopeAxis[ResolvedReference]] =
ps ++ (ps flatMap rawBuild).distinct :+ Global
def rawBuild(ps: ScopeAxis[ProjectRef]): Seq[ScopeAxis[BuildRef]] = ps match { case Select(ref) => Select(BuildRef(ref.build)) :: Nil; case _ => Nil }
def rawBuild(ps: ScopeAxis[ProjectRef]): Seq[ScopeAxis[BuildRef]] = ps match {
case Select(ref) => Select(BuildRef(ref.build)) :: Nil; case _ => Nil
}
def delegates[Proj](
refs: Seq[(ProjectRef, Proj)],
configurations: Proj => Seq[ConfigKey],
projectInherit: ProjectRef => Seq[ProjectRef],
configInherit: (ResolvedReference, ConfigKey) => Seq[ConfigKey]
): DelegateIndex =
{
val pDelegates = refs map {
case (ref, project) =>
(ref, delegateIndex(ref, configurations(project))(projectInherit, configInherit))
} toMap;
new DelegateIndex0(pDelegates)
refs: Seq[(ProjectRef, Proj)],
configurations: Proj => Seq[ConfigKey],
projectInherit: ProjectRef => Seq[ProjectRef],
configInherit: (ResolvedReference, ConfigKey) => Seq[ConfigKey]
): DelegateIndex = {
val pDelegates = refs map {
case (ref, project) =>
(ref, delegateIndex(ref, configurations(project))(projectInherit, configInherit))
} toMap;
new DelegateIndex0(pDelegates)
}
private[this] def delegateIndex(ref: ProjectRef, confs: Seq[ConfigKey])(
projectInherit: ProjectRef => Seq[ProjectRef],
configInherit: (ResolvedReference, ConfigKey) => Seq[ConfigKey]): ProjectDelegates = {
val refDelegates = withRawBuilds(linearize(Select(ref), false)(projectInherit))
val configs = confs map { c =>
axisDelegates(configInherit, ref, c)
}
private[this] def delegateIndex(ref: ProjectRef, confs: Seq[ConfigKey])(projectInherit: ProjectRef => Seq[ProjectRef], configInherit: (ResolvedReference, ConfigKey) => Seq[ConfigKey]): ProjectDelegates =
{
val refDelegates = withRawBuilds(linearize(Select(ref), false)(projectInherit))
val configs = confs map { c => axisDelegates(configInherit, ref, c) }
new ProjectDelegates(ref, refDelegates, configs.toMap)
}
def axisDelegates[T](direct: (ResolvedReference, T) => Seq[T], ref: ResolvedReference, init: T): (T, Seq[ScopeAxis[T]]) =
new ProjectDelegates(ref, refDelegates, configs.toMap)
}
def axisDelegates[T](direct: (ResolvedReference, T) => Seq[T],
ref: ResolvedReference,
init: T): (T, Seq[ScopeAxis[T]]) =
(init, linearize(Select(init))(direct(ref, _)))
def linearize[T](axis: ScopeAxis[T], appendGlobal: Boolean = true)(inherit: T => Seq[T]): Seq[ScopeAxis[T]] =
def linearize[T](axis: ScopeAxis[T], appendGlobal: Boolean = true)(
inherit: T => Seq[T]): Seq[ScopeAxis[T]] =
axis match {
case Select(x) => topologicalSort[T](x, appendGlobal)(inherit)
case Global | This => if (appendGlobal) Global :: Nil else Nil
}
def topologicalSort[T](node: T, appendGlobal: Boolean)(dependencies: T => Seq[T]): Seq[ScopeAxis[T]] =
{
val o = Dag.topologicalSortUnchecked(node)(dependencies).map(Select.apply)
if (appendGlobal) o ::: Global :: Nil else o
}
def topologicalSort[T](node: T, appendGlobal: Boolean)(
dependencies: T => Seq[T]): Seq[ScopeAxis[T]] = {
val o = Dag.topologicalSortUnchecked(node)(dependencies).map(Select.apply)
if (appendGlobal) o ::: Global :: Nil else o
}
def globalProjectDelegates(scope: Scope): Seq[Scope] =
if (scope == GlobalScope)
GlobalScope :: Nil
else
for (c <- withGlobalAxis(scope.config); t <- withGlobalAxis(scope.task); e <- withGlobalAxis(scope.extra)) yield Scope(Global, c, t, e)
for (c <- withGlobalAxis(scope.config); t <- withGlobalAxis(scope.task);
e <- withGlobalAxis(scope.extra)) yield Scope(Global, c, t, e)
}

View File

@ -1,15 +1,17 @@
package sbt
/** Specifies the Scope axes that should be used for an operation. `true` indicates an axis should be used. */
final case class ScopeMask(project: Boolean = true, config: Boolean = true, task: Boolean = true, extra: Boolean = true) {
def concatShow(p: String, c: String, t: String, sep: String, x: String): String =
{
val sb = new StringBuilder
if (project) sb.append(p)
if (config) sb.append(c)
if (task) sb.append(t)
sb.append(sep)
if (extra) sb.append(x)
sb.toString
}
final case class ScopeMask(project: Boolean = true,
config: Boolean = true,
task: Boolean = true,
extra: Boolean = true) {
def concatShow(p: String, c: String, t: String, sep: String, x: String): String = {
val sb = new StringBuilder
if (project) sb.append(p)
if (config) sb.append(c)
if (task) sb.append(t)
sb.append(sep)
if (extra) sb.append(x)
sb.toString
}
}

View File

@ -13,7 +13,6 @@ import sbt.Def.{ Initialize, KeyedInitialize, ScopedKey, Setting, setting }
import std.TaskExtra.{ task => mktask, _ }
/** An abstraction on top of Settings for build configuration and task definition. */
sealed trait Scoped { def scope: Scope; val key: AttributeKey[_] }
/** A common type for SettingKey and TaskKey so that both can be used as inputs to tasks.*/
@ -27,30 +26,46 @@ sealed trait ScopedTaskable[T] extends Scoped {
* The name and the type are represented by a value of type `AttributeKey[T]`.
* Instances are constructed using the companion object.
*/
sealed abstract class SettingKey[T] extends ScopedTaskable[T] with KeyedInitialize[T] with Scoped.ScopingSetting[SettingKey[T]] with Scoped.DefinableSetting[T] {
sealed abstract class SettingKey[T]
extends ScopedTaskable[T]
with KeyedInitialize[T]
with Scoped.ScopingSetting[SettingKey[T]]
with Scoped.DefinableSetting[T] {
val key: AttributeKey[T]
final def toTask: Initialize[Task[T]] = this apply inlineTask
final def scopedKey: ScopedKey[T] = ScopedKey(scope, key)
final def in(scope: Scope): SettingKey[T] = Scoped.scopedSetting(Scope.replaceThis(this.scope)(scope), this.key)
final def in(scope: Scope): SettingKey[T] =
Scoped.scopedSetting(Scope.replaceThis(this.scope)(scope), this.key)
final def :=(v: T): Setting[T] = macro std.TaskMacro.settingAssignMacroImpl[T]
final def +=[U](v: U)(implicit a: Append.Value[T, U]): Setting[T] = macro std.TaskMacro.settingAppend1Impl[T, U]
final def ++=[U](vs: U)(implicit a: Append.Values[T, U]): Setting[T] = macro std.TaskMacro.settingAppendNImpl[T, U]
final def <+=[V](v: Initialize[V])(implicit a: Append.Value[T, V]): Setting[T] = macro std.TaskMacro.fakeSettingAppend1Position[T, V]
final def <++=[V](vs: Initialize[V])(implicit a: Append.Values[T, V]): Setting[T] = macro std.TaskMacro.fakeSettingAppendNPosition[T, V]
final def -=[U](v: U)(implicit r: Remove.Value[T, U]): Setting[T] = macro std.TaskMacro.settingRemove1Impl[T, U]
final def --=[U](vs: U)(implicit r: Remove.Values[T, U]): Setting[T] = macro std.TaskMacro.settingRemoveNImpl[T, U]
final def +=[U](v: U)(implicit a: Append.Value[T, U]): Setting[T] =
macro std.TaskMacro.settingAppend1Impl[T, U]
final def ++=[U](vs: U)(implicit a: Append.Values[T, U]): Setting[T] =
macro std.TaskMacro.settingAppendNImpl[T, U]
final def <+=[V](v: Initialize[V])(implicit a: Append.Value[T, V]): Setting[T] =
macro std.TaskMacro.fakeSettingAppend1Position[T, V]
final def <++=[V](vs: Initialize[V])(implicit a: Append.Values[T, V]): Setting[T] =
macro std.TaskMacro.fakeSettingAppendNPosition[T, V]
final def -=[U](v: U)(implicit r: Remove.Value[T, U]): Setting[T] =
macro std.TaskMacro.settingRemove1Impl[T, U]
final def --=[U](vs: U)(implicit r: Remove.Values[T, U]): Setting[T] =
macro std.TaskMacro.settingRemoveNImpl[T, U]
final def ~=(f: T => T): Setting[T] = macro std.TaskMacro.settingTransformPosition[T]
final def append1[V](v: Initialize[V], source: SourcePosition)(implicit a: Append.Value[T, V]): Setting[T] = make(v, source)(a.appendValue)
final def appendN[V](vs: Initialize[V], source: SourcePosition)(implicit a: Append.Values[T, V]): Setting[T] = make(vs, source)(a.appendValues)
final def append1[V](v: Initialize[V], source: SourcePosition)(
implicit a: Append.Value[T, V]): Setting[T] = make(v, source)(a.appendValue)
final def appendN[V](vs: Initialize[V], source: SourcePosition)(
implicit a: Append.Values[T, V]): Setting[T] = make(vs, source)(a.appendValues)
final def remove1[V](v: Initialize[V], source: SourcePosition)(implicit r: Remove.Value[T, V]): Setting[T] = make(v, source)(r.removeValue)
final def removeN[V](vs: Initialize[V], source: SourcePosition)(implicit r: Remove.Values[T, V]): Setting[T] = make(vs, source)(r.removeValues)
final def remove1[V](v: Initialize[V], source: SourcePosition)(
implicit r: Remove.Value[T, V]): Setting[T] = make(v, source)(r.removeValue)
final def removeN[V](vs: Initialize[V], source: SourcePosition)(
implicit r: Remove.Values[T, V]): Setting[T] = make(vs, source)(r.removeValues)
final def transform(f: T => T, source: SourcePosition): Setting[T] = set(scopedKey(f), source)
protected[this] def make[S](other: Initialize[S], source: SourcePosition)(f: (T, S) => T): Setting[T] =
protected[this] def make[S](other: Initialize[S], source: SourcePosition)(
f: (T, S) => T): Setting[T] =
set((this, other)(f), source)
}
@ -60,26 +75,42 @@ sealed abstract class SettingKey[T] extends ScopedTaskable[T] with KeyedInitiali
* The name and the type are represented by a value of type `AttributeKey[Task[T]]`.
* Instances are constructed using the companion object.
*/
sealed abstract class TaskKey[T] extends ScopedTaskable[T] with KeyedInitialize[Task[T]] with Scoped.ScopingSetting[TaskKey[T]] with Scoped.DefinableTask[T] {
sealed abstract class TaskKey[T]
extends ScopedTaskable[T]
with KeyedInitialize[Task[T]]
with Scoped.ScopingSetting[TaskKey[T]]
with Scoped.DefinableTask[T] {
val key: AttributeKey[Task[T]]
def toTask: Initialize[Task[T]] = this
def scopedKey: ScopedKey[Task[T]] = ScopedKey(scope, key)
def in(scope: Scope): TaskKey[T] = Scoped.scopedTask(Scope.replaceThis(this.scope)(scope), this.key)
def in(scope: Scope): TaskKey[T] =
Scoped.scopedTask(Scope.replaceThis(this.scope)(scope), this.key)
def +=[U](v: U)(implicit a: Append.Value[T, U]): Setting[Task[T]] = macro std.TaskMacro.taskAppend1Impl[T, U]
def ++=[U](vs: U)(implicit a: Append.Values[T, U]): Setting[Task[T]] = macro std.TaskMacro.taskAppendNImpl[T, U]
def <+=[V](v: Initialize[Task[V]])(implicit a: Append.Value[T, V]): Setting[Task[T]] = macro std.TaskMacro.fakeTaskAppend1Position[T, V]
def <++=[V](vs: Initialize[Task[V]])(implicit a: Append.Values[T, V]): Setting[Task[T]] = macro std.TaskMacro.fakeTaskAppendNPosition[T, V]
final def -=[U](v: U)(implicit r: Remove.Value[T, U]): Setting[Task[T]] = macro std.TaskMacro.taskRemove1Impl[T, U]
final def --=[U](vs: U)(implicit r: Remove.Values[T, U]): Setting[Task[T]] = macro std.TaskMacro.taskRemoveNImpl[T, U]
def +=[U](v: U)(implicit a: Append.Value[T, U]): Setting[Task[T]] =
macro std.TaskMacro.taskAppend1Impl[T, U]
def ++=[U](vs: U)(implicit a: Append.Values[T, U]): Setting[Task[T]] =
macro std.TaskMacro.taskAppendNImpl[T, U]
def <+=[V](v: Initialize[Task[V]])(implicit a: Append.Value[T, V]): Setting[Task[T]] =
macro std.TaskMacro.fakeTaskAppend1Position[T, V]
def <++=[V](vs: Initialize[Task[V]])(implicit a: Append.Values[T, V]): Setting[Task[T]] =
macro std.TaskMacro.fakeTaskAppendNPosition[T, V]
final def -=[U](v: U)(implicit r: Remove.Value[T, U]): Setting[Task[T]] =
macro std.TaskMacro.taskRemove1Impl[T, U]
final def --=[U](vs: U)(implicit r: Remove.Values[T, U]): Setting[Task[T]] =
macro std.TaskMacro.taskRemoveNImpl[T, U]
def append1[V](v: Initialize[Task[V]], source: SourcePosition)(implicit a: Append.Value[T, V]): Setting[Task[T]] = make(v, source)(a.appendValue)
def appendN[V](vs: Initialize[Task[V]], source: SourcePosition)(implicit a: Append.Values[T, V]): Setting[Task[T]] = make(vs, source)(a.appendValues)
def append1[V](v: Initialize[Task[V]], source: SourcePosition)(
implicit a: Append.Value[T, V]): Setting[Task[T]] = make(v, source)(a.appendValue)
def appendN[V](vs: Initialize[Task[V]], source: SourcePosition)(
implicit a: Append.Values[T, V]): Setting[Task[T]] = make(vs, source)(a.appendValues)
final def remove1[V](v: Initialize[Task[V]], source: SourcePosition)(implicit r: Remove.Value[T, V]): Setting[Task[T]] = make(v, source)(r.removeValue)
final def removeN[V](vs: Initialize[Task[V]], source: SourcePosition)(implicit r: Remove.Values[T, V]): Setting[Task[T]] = make(vs, source)(r.removeValues)
final def remove1[V](v: Initialize[Task[V]], source: SourcePosition)(
implicit r: Remove.Value[T, V]): Setting[Task[T]] = make(v, source)(r.removeValue)
final def removeN[V](vs: Initialize[Task[V]], source: SourcePosition)(
implicit r: Remove.Values[T, V]): Setting[Task[T]] = make(vs, source)(r.removeValues)
private[this] def make[S](other: Initialize[Task[S]], source: SourcePosition)(f: (T, S) => T): Setting[Task[T]] =
private[this] def make[S](other: Initialize[Task[S]], source: SourcePosition)(
f: (T, S) => T): Setting[Task[T]] =
set((this, other)((a, b) => (a, b) map f.tupled), source)
}
@ -90,20 +121,27 @@ sealed abstract class TaskKey[T] extends ScopedTaskable[T] with KeyedInitialize[
* The name and the type are represented by a value of type `AttributeKey[InputTask[T]]`.
* Instances are constructed using the companion object.
*/
sealed trait InputKey[T] extends Scoped with KeyedInitialize[InputTask[T]] with Scoped.ScopingSetting[InputKey[T]] with Scoped.DefinableSetting[InputTask[T]] {
sealed trait InputKey[T]
extends Scoped
with KeyedInitialize[InputTask[T]]
with Scoped.ScopingSetting[InputKey[T]]
with Scoped.DefinableSetting[InputTask[T]] {
val key: AttributeKey[InputTask[T]]
def scopedKey: ScopedKey[InputTask[T]] = ScopedKey(scope, key)
def in(scope: Scope): InputKey[T] = Scoped.scopedInput(Scope.replaceThis(this.scope)(scope), this.key)
def in(scope: Scope): InputKey[T] =
Scoped.scopedInput(Scope.replaceThis(this.scope)(scope), this.key)
final def :=(v: T): Setting[InputTask[T]] = macro std.TaskMacro.inputTaskAssignMacroImpl[T]
final def ~=(f: T => T): Setting[InputTask[T]] = macro std.TaskMacro.itaskTransformPosition[T]
final def transform(f: T => T, source: SourcePosition): Setting[InputTask[T]] = set(scopedKey(_ mapTask { _ map f }), source)
final def transform(f: T => T, source: SourcePosition): Setting[InputTask[T]] =
set(scopedKey(_ mapTask { _ map f }), source)
}
/** Methods and types related to constructing settings, including keys, scopes, and initializations. */
object Scoped {
implicit def taskScopedToKey[T](s: TaskKey[T]): ScopedKey[Task[T]] = ScopedKey(s.scope, s.key)
implicit def inputScopedToKey[T](s: InputKey[T]): ScopedKey[InputTask[T]] = ScopedKey(s.scope, s.key)
implicit def inputScopedToKey[T](s: InputKey[T]): ScopedKey[InputTask[T]] =
ScopedKey(s.scope, s.key)
/**
* Mixin trait for adding convenience vocabulary associated with specifying the [[Scope]] of a setting.
@ -129,13 +167,22 @@ object Scoped {
def in(c: ConfigKey, t: Scoped): ResultType = in(This, Select(c), Select(t.key))
def in(p: Reference, c: ConfigKey): ResultType = in(Select(p), Select(c), This)
def in(p: Reference, t: Scoped): ResultType = in(Select(p), This, Select(t.key))
def in(p: Reference, c: ConfigKey, t: Scoped): ResultType = in(Select(p), Select(c), Select(t.key))
def in(p: ScopeAxis[Reference], c: ScopeAxis[ConfigKey], t: ScopeAxis[AttributeKey[_]]): ResultType = in(Scope(p, c, t, This))
def in(p: Reference, c: ConfigKey, t: Scoped): ResultType =
in(Select(p), Select(c), Select(t.key))
def in(p: ScopeAxis[Reference],
c: ScopeAxis[ConfigKey],
t: ScopeAxis[AttributeKey[_]]): ResultType = in(Scope(p, c, t, This))
}
def scopedSetting[T](s: Scope, k: AttributeKey[T]): SettingKey[T] = new SettingKey[T] { val scope = s; val key = k }
def scopedInput[T](s: Scope, k: AttributeKey[InputTask[T]]): InputKey[T] = new InputKey[T] { val scope = s; val key = k }
def scopedTask[T](s: Scope, k: AttributeKey[Task[T]]): TaskKey[T] = new TaskKey[T] { val scope = s; val key = k }
def scopedSetting[T](s: Scope, k: AttributeKey[T]): SettingKey[T] = new SettingKey[T] {
val scope = s; val key = k
}
def scopedInput[T](s: Scope, k: AttributeKey[InputTask[T]]): InputKey[T] = new InputKey[T] {
val scope = s; val key = k
}
def scopedTask[T](s: Scope, k: AttributeKey[Task[T]]): TaskKey[T] = new TaskKey[T] {
val scope = s; val key = k
}
/**
* Mixin trait for adding convenience vocabulary associated with applying a setting to a configuration item.
@ -145,13 +192,16 @@ object Scoped {
private[sbt] final def :==(app: S): Setting[S] = macro std.TaskMacro.settingAssignPure[S]
final def <<=(app: Initialize[S]): Setting[S] = macro std.TaskMacro.fakeSettingAssignPosition[S]
final def <<=(app: Initialize[S]): Setting[S] =
macro std.TaskMacro.fakeSettingAssignPosition[S]
/** Internally used function for setting a value along with the `.sbt` file location where it is defined. */
final def set(app: Initialize[S], source: SourcePosition): Setting[S] = setting(scopedKey, app, source)
final def set(app: Initialize[S], source: SourcePosition): Setting[S] =
setting(scopedKey, app, source)
/** From the given [[Settings]], extract the value bound to this key. */
final def get(settings: Settings[Scope]): Option[S] = settings.get(scopedKey.scope, scopedKey.key)
final def get(settings: Settings[Scope]): Option[S] =
settings.get(scopedKey.scope, scopedKey.key)
/**
* Creates an [[Def.Initialize]] with value [[scala.None]] if there was no previous definition of this key,
@ -187,21 +237,29 @@ object Scoped {
sealed trait DefinableTask[S] { self: TaskKey[S] =>
private[sbt] def :==(app: S): Setting[Task[S]] = macro std.TaskMacro.taskAssignPositionPure[S]
private[sbt] def ::=(app: Task[S]): Setting[Task[S]] = macro std.TaskMacro.taskAssignPositionT[S]
private[sbt] def ::=(app: Task[S]): Setting[Task[S]] =
macro std.TaskMacro.taskAssignPositionT[S]
def :=(v: S): Setting[Task[S]] = macro std.TaskMacro.taskAssignMacroImpl[S]
def ~=(f: S => S): Setting[Task[S]] = macro std.TaskMacro.taskTransformPosition[S]
def <<=(app: Initialize[Task[S]]): Setting[Task[S]] = macro std.TaskMacro.fakeItaskAssignPosition[S]
def set(app: Initialize[Task[S]], source: SourcePosition): Setting[Task[S]] = Def.setting(scopedKey, app, source)
def transform(f: S => S, source: SourcePosition): Setting[Task[S]] = set(scopedKey(_ map f), source)
def <<=(app: Initialize[Task[S]]): Setting[Task[S]] =
macro std.TaskMacro.fakeItaskAssignPosition[S]
def set(app: Initialize[Task[S]], source: SourcePosition): Setting[Task[S]] =
Def.setting(scopedKey, app, source)
def transform(f: S => S, source: SourcePosition): Setting[Task[S]] =
set(scopedKey(_ map f), source)
@deprecated("No longer needed with new task syntax and SettingKey inheriting from Initialize.", "0.13.2")
@deprecated("No longer needed with new task syntax and SettingKey inheriting from Initialize.",
"0.13.2")
def task: SettingKey[Task[S]] = scopedSetting(scope, key)
def get(settings: Settings[Scope]): Option[Task[S]] = settings.get(scope, key)
def ? : Initialize[Task[Option[S]]] = Def.optional(scopedKey) { case None => mktask { None }; case Some(t) => t map some.fn }
def ? : Initialize[Task[Option[S]]] = Def.optional(scopedKey) {
case None => mktask { None }; case Some(t) => t map some.fn
}
def ??[T >: S](or: => T): Initialize[Task[T]] = Def.optional(scopedKey)(_ getOrElse mktask(or))
def or[T >: S](i: Initialize[Task[T]]): Initialize[Task[T]] = (this.? zipWith i)((x, y) => (x, y) map { case (a, b) => a getOrElse b })
def or[T >: S](i: Initialize[Task[T]]): Initialize[Task[T]] =
(this.? zipWith i)((x, y) => (x, y) map { case (a, b) => a getOrElse b })
}
final class RichInitializeTask[S](i: Initialize[Task[S]]) extends RichInitTaskBase[S, Task] {
@ -213,19 +271,25 @@ object Scoped {
def failure: Initialize[Task[Incomplete]] = i(_.failure)
def result: Initialize[Task[Result[S]]] = i(_.result)
def xtriggeredBy[T](tasks: Initialize[Task[T]]*): Initialize[Task[S]] = nonLocal(tasks, Def.triggeredBy)
def triggeredBy[T](tasks: Initialize[Task[T]]*): Initialize[Task[S]] = nonLocal(tasks, Def.triggeredBy)
def runBefore[T](tasks: Initialize[Task[T]]*): Initialize[Task[S]] = nonLocal(tasks, Def.runBefore)
def xtriggeredBy[T](tasks: Initialize[Task[T]]*): Initialize[Task[S]] =
nonLocal(tasks, Def.triggeredBy)
def triggeredBy[T](tasks: Initialize[Task[T]]*): Initialize[Task[S]] =
nonLocal(tasks, Def.triggeredBy)
def runBefore[T](tasks: Initialize[Task[T]]*): Initialize[Task[S]] =
nonLocal(tasks, Def.runBefore)
private[this] def nonLocal(tasks: Seq[AnyInitTask], key: AttributeKey[Seq[Task[_]]]): Initialize[Task[S]] =
private[this] def nonLocal(tasks: Seq[AnyInitTask],
key: AttributeKey[Seq[Task[_]]]): Initialize[Task[S]] =
(Initialize.joinAny[Task](tasks), i)((ts, i) => i.copy(info = i.info.set(key, ts)))
}
final class RichInitializeInputTask[S](i: Initialize[InputTask[S]]) extends RichInitTaskBase[S, InputTask] {
final class RichInitializeInputTask[S](i: Initialize[InputTask[S]])
extends RichInitTaskBase[S, InputTask] {
protected def onTask[T](f: Task[S] => Task[T]): Initialize[InputTask[T]] = i(_ mapTask f)
def dependsOn(tasks: AnyInitTask*): Initialize[InputTask[S]] =
(i, Initialize.joinAny[Task](tasks))((thisTask, deps) => thisTask.mapTask(_.dependsOn(deps: _*)))
(i, Initialize.joinAny[Task](tasks))((thisTask, deps) =>
thisTask.mapTask(_.dependsOn(deps: _*)))
}
sealed abstract class RichInitTaskBase[S, R[_]] {
@ -242,16 +306,24 @@ object Scoped {
def tag(tags: Tag*): Initialize[R[S]] = onTask(_.tag(tags: _*))
def tagw(tags: (Tag, Int)*): Initialize[R[S]] = onTask(_.tagw(tags: _*))
@deprecated("Use the `result` method to create a task that returns the full Result of this task. Then, call `flatMap` on the new task.", "0.13.0")
@deprecated(
"Use the `result` method to create a task that returns the full Result of this task. Then, call `flatMap` on the new task.",
"0.13.0")
def flatMapR[T](f: Result[S] => Task[T]): Initialize[R[T]] = onTask(_ flatMapR f)
@deprecated("Use the `result` method to create a task that returns the full Result of this task. Then, call `map` on the new task.", "0.13.0")
@deprecated(
"Use the `result` method to create a task that returns the full Result of this task. Then, call `map` on the new task.",
"0.13.0")
def mapR[T](f: Result[S] => T): Initialize[R[T]] = onTask(_ mapR f)
@deprecated("Use the `failure` method to create a task that returns Incomplete when this task fails and then call `flatMap` on the new task.", "0.13.0")
@deprecated(
"Use the `failure` method to create a task that returns Incomplete when this task fails and then call `flatMap` on the new task.",
"0.13.0")
def flatFailure[T](f: Incomplete => Task[T]): Initialize[R[T]] = flatMapR(f compose failM)
@deprecated("Use the `failure` method to create a task that returns Incomplete when this task fails and then call `map` on the new task.", "0.13.0")
@deprecated(
"Use the `failure` method to create a task that returns Incomplete when this task fails and then call `map` on the new task.",
"0.13.0")
def mapFailure[T](f: Incomplete => T): Initialize[R[T]] = mapR(f compose failM)
}
@ -264,9 +336,12 @@ object Scoped {
}
implicit def richAnyTaskSeq(in: Seq[AnyInitTask]): RichAnyTaskSeq = new RichAnyTaskSeq(in)
final class RichAnyTaskSeq(keys: Seq[AnyInitTask]) {
def dependOn: Initialize[Task[Unit]] = Initialize.joinAny[Task](keys).apply(deps => nop.dependsOn(deps: _*))
def dependOn: Initialize[Task[Unit]] =
Initialize.joinAny[Task](keys).apply(deps => nop.dependsOn(deps: _*))
}
// format: off
// this is the least painful arrangement I came up with
@deprecated("The sbt 0.10 style DSL is deprecated: '(k1, k2) map { (x, y) => ... }' should now be '{ val x = k1.value; val y = k2.value }'.\nSee http://www.scala-sbt.org/0.13/docs/Migrating-from-sbt-012x.html", "0.13.13") implicit def t2ToTable2[A, B](t2: (ScopedTaskable[A], ScopedTaskable[B])): RichTaskable2[A, B] = new RichTaskable2(t2)
@deprecated("The sbt 0.10 style DSL is deprecated: '(k1, k2) map { (x, y) => ... }' should now be '{ val x = k1.value; val y = k2.value }'.\nSee http://www.scala-sbt.org/0.13/docs/Migrating-from-sbt-012x.html", "0.13.13") implicit def t3ToTable3[A, B, C](t3: (ScopedTaskable[A], ScopedTaskable[B], ScopedTaskable[C])): RichTaskable3[A, B, C] = new RichTaskable3(t3)
@ -279,12 +354,18 @@ object Scoped {
@deprecated("The sbt 0.10 style DSL is deprecated: '(k1, k2) map { (x, y) => ... }' should now be '{ val x = k1.value; val y = k2.value }'.\nSee http://www.scala-sbt.org/0.13/docs/Migrating-from-sbt-012x.html", "0.13.13") implicit def t10ToTable10[A, B, C, D, E, F, G, H, I, J](t10: (ScopedTaskable[A], ScopedTaskable[B], ScopedTaskable[C], ScopedTaskable[D], ScopedTaskable[E], ScopedTaskable[F], ScopedTaskable[G], ScopedTaskable[H], ScopedTaskable[I], ScopedTaskable[J])): RichTaskable10[A, B, C, D, E, F, G, H, I, J] = new RichTaskable10(t10)
@deprecated("The sbt 0.10 style DSL is deprecated: '(k1, k2) map { (x, y) => ... }' should now be '{ val x = k1.value; val y = k2.value }'.\nSee http://www.scala-sbt.org/0.13/docs/Migrating-from-sbt-012x.html", "0.13.13") implicit def t11ToTable11[A, B, C, D, E, F, G, H, I, J, K](t11: (ScopedTaskable[A], ScopedTaskable[B], ScopedTaskable[C], ScopedTaskable[D], ScopedTaskable[E], ScopedTaskable[F], ScopedTaskable[G], ScopedTaskable[H], ScopedTaskable[I], ScopedTaskable[J], ScopedTaskable[K])): RichTaskable11[A, B, C, D, E, F, G, H, I, J, K] = new RichTaskable11(t11)
sealed abstract class RichTaskables[K[L[x]]]( final val keys: K[ScopedTaskable])(implicit a: AList[K]) {
// format: on
sealed abstract class RichTaskables[K[L[x]]](final val keys: K[ScopedTaskable])(
implicit a: AList[K]) {
type App[T] = Initialize[Task[T]]
type Fun[M[_], Ret]
protected def convert[M[_], Ret](f: Fun[M, Ret]): K[M] => Ret
private[this] val inputs: K[App] = a.transform(keys, new (ScopedTaskable ~> App) { def apply[T](in: ScopedTaskable[T]): App[T] = in.toTask })
private[this] def onTasks[T](f: K[Task] => Task[T]): App[T] = Def.app[({ type l[L[x]] = K[(L Task)#l] })#l, Task[T]](inputs)(f)(AList.asplit[K, Task](a))
private[this] val inputs: K[App] = a.transform(keys, new (ScopedTaskable ~> App) {
def apply[T](in: ScopedTaskable[T]): App[T] = in.toTask
})
private[this] def onTasks[T](f: K[Task] => Task[T]): App[T] =
Def.app[({ type l[L[x]] = K[(L Task)#l] })#l, Task[T]](inputs)(f)(AList.asplit[K, Task](a))
def flatMap[T](f: Fun[Id, Task[T]]): App[T] = onTasks(_.flatMap(convert(f)))
def flatMapR[T](f: Fun[Result, Task[T]]): App[T] = onTasks(_.flatMapR(convert(f)))
@ -293,6 +374,9 @@ object Scoped {
def flatFailure[T](f: Seq[Incomplete] => Task[T]): App[T] = onTasks(_ flatFailure f)
def mapFailure[T](f: Seq[Incomplete] => T): App[T] = onTasks(_ mapFailure f)
}
// format: off
type ST[X] = ScopedTaskable[X]
final class RichTaskable2[A, B](t2: (ST[A], ST[B])) extends RichTaskables[AList.T2K[A, B]#l](t2)(AList.tuple2[A, B]) {
type Fun[M[_], Ret] = (M[A], M[B]) => Ret
@ -412,20 +496,32 @@ object Scoped {
def identity = apply(mkTuple11)
}
private[sbt] def extendScoped(s1: Scoped, ss: Seq[Scoped]): Seq[AttributeKey[_]] = s1.key +: ss.map(_.key)
// format: on
private[sbt] def extendScoped(s1: Scoped, ss: Seq[Scoped]): Seq[AttributeKey[_]] =
s1.key +: ss.map(_.key)
}
import Scoped.extendScoped
/** Constructs InputKeys, which are associated with input tasks to define a setting.*/
object InputKey {
def apply[T: Manifest](label: String, description: String = "", rank: Int = KeyRanks.DefaultInputRank): InputKey[T] =
def apply[T: Manifest](label: String,
description: String = "",
rank: Int = KeyRanks.DefaultInputRank): InputKey[T] =
apply(AttributeKey[InputTask[T]](label, description, rank))
def apply[T: Manifest](label: String, description: String, extend1: Scoped, extendN: Scoped*): InputKey[T] =
def apply[T: Manifest](label: String,
description: String,
extend1: Scoped,
extendN: Scoped*): InputKey[T] =
apply(label, description, KeyRanks.DefaultInputRank, extend1, extendN: _*)
def apply[T: Manifest](label: String, description: String, rank: Int, extend1: Scoped, extendN: Scoped*): InputKey[T] =
def apply[T: Manifest](label: String,
description: String,
rank: Int,
extend1: Scoped,
extendN: Scoped*): InputKey[T] =
apply(AttributeKey[InputTask[T]](label, description, extendScoped(extend1, extendN), rank))
def apply[T](akey: AttributeKey[InputTask[T]]): InputKey[T] =
@ -434,13 +530,22 @@ object InputKey {
/** Constructs TaskKeys, which are associated with tasks to define a setting.*/
object TaskKey {
def apply[T: Manifest](label: String, description: String = "", rank: Int = KeyRanks.DefaultTaskRank): TaskKey[T] =
def apply[T: Manifest](label: String,
description: String = "",
rank: Int = KeyRanks.DefaultTaskRank): TaskKey[T] =
apply(AttributeKey[Task[T]](label, description, rank))
def apply[T: Manifest](label: String, description: String, extend1: Scoped, extendN: Scoped*): TaskKey[T] =
def apply[T: Manifest](label: String,
description: String,
extend1: Scoped,
extendN: Scoped*): TaskKey[T] =
apply(AttributeKey[Task[T]](label, description, extendScoped(extend1, extendN)))
def apply[T: Manifest](label: String, description: String, rank: Int, extend1: Scoped, extendN: Scoped*): TaskKey[T] =
def apply[T: Manifest](label: String,
description: String,
rank: Int,
extend1: Scoped,
extendN: Scoped*): TaskKey[T] =
apply(AttributeKey[Task[T]](label, description, extendScoped(extend1, extendN), rank))
def apply[T](akey: AttributeKey[Task[T]]): TaskKey[T] =
@ -451,13 +556,22 @@ object TaskKey {
/** Constructs SettingKeys, which are associated with a value to define a basic setting.*/
object SettingKey {
def apply[T: Manifest: OptJsonWriter](label: String, description: String = "", rank: Int = KeyRanks.DefaultSettingRank): SettingKey[T] =
def apply[T: Manifest: OptJsonWriter](label: String,
description: String = "",
rank: Int = KeyRanks.DefaultSettingRank): SettingKey[T] =
apply(AttributeKey[T](label, description, rank))
def apply[T: Manifest: OptJsonWriter](label: String, description: String, extend1: Scoped, extendN: Scoped*): SettingKey[T] =
def apply[T: Manifest: OptJsonWriter](label: String,
description: String,
extend1: Scoped,
extendN: Scoped*): SettingKey[T] =
apply(AttributeKey[T](label, description, extendScoped(extend1, extendN)))
def apply[T: Manifest: OptJsonWriter](label: String, description: String, rank: Int, extend1: Scoped, extendN: Scoped*): SettingKey[T] =
def apply[T: Manifest: OptJsonWriter](label: String,
description: String,
rank: Int,
extend1: Scoped,
extendN: Scoped*): SettingKey[T] =
apply(AttributeKey[T](label, description, extendScoped(extend1, extendN), rank))
def apply[T](akey: AttributeKey[T]): SettingKey[T] =

View File

@ -33,7 +33,8 @@ object ParserConvert extends Convert {
/** Convert instance for plain `Task`s not within the settings system. */
object TaskConvert extends Convert {
def apply[T: c.WeakTypeTag](c: blackbox.Context)(nme: String, in: c.Tree): Converted[c.type] =
if (nme == InputWrapper.WrapTaskName) Converted.Success[c.type](in) else Converted.NotApplicable[c.type]
if (nme == InputWrapper.WrapTaskName) Converted.Success[c.type](in)
else Converted.NotApplicable[c.type]
}
/** Converts an input `Tree` of type `Initialize[T]`, `Initialize[Task[T]]`, or `Task[T]` into a `Tree` of type `Initialize[Task[T]]`.*/

View File

@ -22,39 +22,52 @@ object InputWrapper {
private[std] final val WrapInputName = "wrapInputTask_\u2603\u2603"
private[std] final val WrapPreviousName = "wrapPrevious_\u2603\u2603"
@compileTimeOnly("`value` can only be called on a task within a task definition macro, such as :=, +=, ++=, or Def.task.")
@compileTimeOnly(
"`value` can only be called on a task within a task definition macro, such as :=, +=, ++=, or Def.task.")
def wrapTask_\u2603\u2603[T](in: Any): T = implDetailError
@compileTimeOnly("`value` can only be used within a task or setting macro, such as :=, +=, ++=, Def.task, or Def.setting.")
@compileTimeOnly(
"`value` can only be used within a task or setting macro, such as :=, +=, ++=, Def.task, or Def.setting.")
def wrapInit_\u2603\u2603[T](in: Any): T = implDetailError
@compileTimeOnly("`value` can only be called on a task within a task definition macro, such as :=, +=, ++=, or Def.task.")
@compileTimeOnly(
"`value` can only be called on a task within a task definition macro, such as :=, +=, ++=, or Def.task.")
def wrapInitTask_\u2603\u2603[T](in: Any): T = implDetailError
@compileTimeOnly("`value` can only be called on an input task within a task definition macro, such as := or Def.inputTask.")
@compileTimeOnly(
"`value` can only be called on an input task within a task definition macro, such as := or Def.inputTask.")
def wrapInputTask_\u2603\u2603[T](in: Any): T = implDetailError
@compileTimeOnly("`value` can only be called on an input task within a task definition macro, such as := or Def.inputTask.")
@compileTimeOnly(
"`value` can only be called on an input task within a task definition macro, such as := or Def.inputTask.")
def wrapInitInputTask_\u2603\u2603[T](in: Any): T = implDetailError
@compileTimeOnly("`previous` can only be called on a task within a task or input task definition macro, such as :=, +=, ++=, Def.task, or Def.inputTask.")
@compileTimeOnly(
"`previous` can only be called on a task within a task or input task definition macro, such as :=, +=, ++=, Def.task, or Def.inputTask.")
def wrapPrevious_\u2603\u2603[T](in: Any): T = implDetailError
private[this] def implDetailError = sys.error("This method is an implementation detail and should not be referenced.")
private[this] def implDetailError =
sys.error("This method is an implementation detail and should not be referenced.")
private[std] def wrapTask[T: c.WeakTypeTag](c: blackbox.Context)(ts: c.Expr[Any], pos: c.Position): c.Expr[T] =
private[std] def wrapTask[T: c.WeakTypeTag](c: blackbox.Context)(ts: c.Expr[Any],
pos: c.Position): c.Expr[T] =
wrapImpl[T, InputWrapper.type](c, InputWrapper, WrapTaskName)(ts, pos)
private[std] def wrapInit[T: c.WeakTypeTag](c: blackbox.Context)(ts: c.Expr[Any], pos: c.Position): c.Expr[T] =
private[std] def wrapInit[T: c.WeakTypeTag](c: blackbox.Context)(ts: c.Expr[Any],
pos: c.Position): c.Expr[T] =
wrapImpl[T, InputWrapper.type](c, InputWrapper, WrapInitName)(ts, pos)
private[std] def wrapInitTask[T: c.WeakTypeTag](c: blackbox.Context)(ts: c.Expr[Any], pos: c.Position): c.Expr[T] =
private[std] def wrapInitTask[T: c.WeakTypeTag](
c: blackbox.Context)(ts: c.Expr[Any], pos: c.Position): c.Expr[T] =
wrapImpl[T, InputWrapper.type](c, InputWrapper, WrapInitTaskName)(ts, pos)
private[std] def wrapInitInputTask[T: c.WeakTypeTag](c: blackbox.Context)(ts: c.Expr[Any], pos: c.Position): c.Expr[T] =
private[std] def wrapInitInputTask[T: c.WeakTypeTag](
c: blackbox.Context)(ts: c.Expr[Any], pos: c.Position): c.Expr[T] =
wrapImpl[T, InputWrapper.type](c, InputWrapper, WrapInitInputName)(ts, pos)
private[std] def wrapInputTask[T: c.WeakTypeTag](c: blackbox.Context)(ts: c.Expr[Any], pos: c.Position): c.Expr[T] =
private[std] def wrapInputTask[T: c.WeakTypeTag](
c: blackbox.Context)(ts: c.Expr[Any], pos: c.Position): c.Expr[T] =
wrapImpl[T, InputWrapper.type](c, InputWrapper, WrapInputName)(ts, pos)
private[std] def wrapPrevious[T: c.WeakTypeTag](c: blackbox.Context)(ts: c.Expr[Any], pos: c.Position): c.Expr[Option[T]] =
private[std] def wrapPrevious[T: c.WeakTypeTag](
c: blackbox.Context)(ts: c.Expr[Any], pos: c.Position): c.Expr[Option[T]] =
wrapImpl[Option[T], InputWrapper.type](c, InputWrapper, WrapPreviousName)(ts, pos)
/**
@ -63,44 +76,52 @@ object InputWrapper {
*
* `c.universe.reify { <s>.<wrapName>[T](ts.splice) }`
*/
def wrapImpl[T: c.WeakTypeTag, S <: AnyRef with Singleton](c: blackbox.Context, s: S, wrapName: String)(ts: c.Expr[Any], pos: c.Position)(implicit it: c.TypeTag[s.type]): c.Expr[T] =
{
import c.universe.{ Apply => ApplyTree, _ }
import internal.decorators._
val util = new ContextUtil[c.type](c)
val iw = util.singleton(s)
val tpe = c.weakTypeOf[T]
val nme = TermName(wrapName).encodedName
val sel = Select(Ident(iw), nme)
sel.setPos(pos) // need to set the position on Select, because that is where the compileTimeOnly check looks
val tree = ApplyTree(TypeApply(sel, TypeTree(tpe) :: Nil), ts.tree :: Nil)
tree.setPos(ts.tree.pos)
// JZ: I'm not sure why we need to do this. Presumably a caller is wrapping this tree in a
// typed tree *before* handing the whole thing back to the macro engine. One must never splice
// untyped trees under typed trees, as the type checker doesn't descend if `tree.tpe == null`.
//
// #1031 The previous attempt to fix this just set the type on `tree`, which worked in cases when the
// call to `.value` was inside a the task macro and eliminated before the end of the typer phase.
// But, if a "naked" call to `.value` left the typer, the superaccessors phase would freak out when
// if hit the untyped trees, before we could get to refchecks and the desired @compileTimeOnly warning.
val typedTree = c.typecheck(tree)
c.Expr[T](typedTree)
}
def wrapImpl[T: c.WeakTypeTag, S <: AnyRef with Singleton](
c: blackbox.Context,
s: S,
wrapName: String)(ts: c.Expr[Any], pos: c.Position)(
implicit it: c.TypeTag[s.type]): c.Expr[T] = {
import c.universe.{ Apply => ApplyTree, _ }
import internal.decorators._
val util = new ContextUtil[c.type](c)
val iw = util.singleton(s)
val tpe = c.weakTypeOf[T]
val nme = TermName(wrapName).encodedName
val sel = Select(Ident(iw), nme)
sel.setPos(pos) // need to set the position on Select, because that is where the compileTimeOnly check looks
val tree = ApplyTree(TypeApply(sel, TypeTree(tpe) :: Nil), ts.tree :: Nil)
tree.setPos(ts.tree.pos)
// JZ: I'm not sure why we need to do this. Presumably a caller is wrapping this tree in a
// typed tree *before* handing the whole thing back to the macro engine. One must never splice
// untyped trees under typed trees, as the type checker doesn't descend if `tree.tpe == null`.
//
// #1031 The previous attempt to fix this just set the type on `tree`, which worked in cases when the
// call to `.value` was inside a the task macro and eliminated before the end of the typer phase.
// But, if a "naked" call to `.value` left the typer, the superaccessors phase would freak out when
// if hit the untyped trees, before we could get to refchecks and the desired @compileTimeOnly warning.
val typedTree = c.typecheck(tree)
c.Expr[T](typedTree)
}
def valueMacroImpl[T: c.WeakTypeTag](c: blackbox.Context): c.Expr[T] =
ContextUtil.selectMacroImpl[T](c) { (ts, pos) =>
ts.tree.tpe match {
case tpe if tpe <:< c.weakTypeOf[Initialize[T]] =>
if (c.weakTypeOf[T] <:< c.weakTypeOf[InputTask[_]]) {
c.abort(pos, """`value` is removed from input tasks. Use `evaluated` or `inputTaskValue`.
|See http://www.scala-sbt.org/1.0/docs/Input-Tasks.html for more details.""".stripMargin)
c.abort(
pos,
"""`value` is removed from input tasks. Use `evaluated` or `inputTaskValue`.
|See http://www.scala-sbt.org/1.0/docs/Input-Tasks.html for more details.""".stripMargin
)
}
InputWrapper.wrapInit[T](c)(ts, pos)
case tpe if tpe <:< c.weakTypeOf[Initialize[Task[T]]] => InputWrapper.wrapInitTask[T](c)(ts, pos)
case tpe if tpe <:< c.weakTypeOf[Task[T]] => InputWrapper.wrapTask[T](c)(ts, pos)
case tpe if tpe <:< c.weakTypeOf[InputTask[T]] => InputWrapper.wrapInputTask[T](c)(ts, pos)
case tpe if tpe <:< c.weakTypeOf[Initialize[InputTask[T]]] => InputWrapper.wrapInitInputTask[T](c)(ts, pos)
case tpe => unexpectedType(c)(pos, tpe)
case tpe if tpe <:< c.weakTypeOf[Initialize[Task[T]]] =>
InputWrapper.wrapInitTask[T](c)(ts, pos)
case tpe if tpe <:< c.weakTypeOf[Task[T]] => InputWrapper.wrapTask[T](c)(ts, pos)
case tpe if tpe <:< c.weakTypeOf[InputTask[T]] => InputWrapper.wrapInputTask[T](c)(ts, pos)
case tpe if tpe <:< c.weakTypeOf[Initialize[InputTask[T]]] =>
InputWrapper.wrapInitInputTask[T](c)(ts, pos)
case tpe => unexpectedType(c)(pos, tpe)
}
}
def inputTaskValueMacroImpl[T: c.WeakTypeTag](c: blackbox.Context): c.Expr[InputTask[T]] =
@ -115,51 +136,60 @@ object InputWrapper {
else
unexpectedType(c)(pos, tpe)
}
/** Translates <task: TaskKey[T]>.previous(format) to Previous.runtime(<task>)(format).value*/
def previousMacroImpl[T: c.WeakTypeTag](c: blackbox.Context)(format: c.Expr[sjsonnew.JsonFormat[T]]): c.Expr[Option[T]] =
{
import c.universe._
c.macroApplication match {
case a @ Apply(Select(Apply(_, t :: Nil), tp), fmt) =>
if (t.tpe <:< c.weakTypeOf[TaskKey[T]]) {
val tsTyped = c.Expr[TaskKey[T]](t)
val newTree = c.universe.reify { Previous.runtime[T](tsTyped.splice)(format.splice) }
wrapPrevious[T](c)(newTree, a.pos)
} else
unexpectedType(c)(a.pos, t.tpe)
case x => ContextUtil.unexpectedTree(x)
}
def previousMacroImpl[T: c.WeakTypeTag](c: blackbox.Context)(
format: c.Expr[sjsonnew.JsonFormat[T]]): c.Expr[Option[T]] = {
import c.universe._
c.macroApplication match {
case a @ Apply(Select(Apply(_, t :: Nil), tp), fmt) =>
if (t.tpe <:< c.weakTypeOf[TaskKey[T]]) {
val tsTyped = c.Expr[TaskKey[T]](t)
val newTree = c.universe.reify { Previous.runtime[T](tsTyped.splice)(format.splice) }
wrapPrevious[T](c)(newTree, a.pos)
} else
unexpectedType(c)(a.pos, t.tpe)
case x => ContextUtil.unexpectedTree(x)
}
}
private def unexpectedType(c: blackbox.Context)(pos: c.Position, tpe: c.Type) =
c.abort(pos, s"Internal sbt error. Unexpected type ${tpe.widen}")
}
sealed abstract class MacroTaskValue[T] {
@compileTimeOnly("`taskValue` can only be used within a setting macro, such as :=, +=, ++=, or Def.setting.")
@compileTimeOnly(
"`taskValue` can only be used within a setting macro, such as :=, +=, ++=, or Def.setting.")
def taskValue: Task[T] = macro InputWrapper.taskValueMacroImpl[T]
}
sealed abstract class MacroValue[T] {
@compileTimeOnly("`value` can only be used within a task or setting macro, such as :=, +=, ++=, Def.task, or Def.setting.")
@compileTimeOnly(
"`value` can only be used within a task or setting macro, such as :=, +=, ++=, Def.task, or Def.setting.")
def value: T = macro InputWrapper.valueMacroImpl[T]
}
sealed abstract class ParserInput[T] {
@compileTimeOnly("`parsed` can only be used within an input task macro, such as := or Def.inputTask.")
@compileTimeOnly(
"`parsed` can only be used within an input task macro, such as := or Def.inputTask.")
def parsed: T = macro ParserInput.parsedMacroImpl[T]
}
sealed abstract class InputEvaluated[T] {
@compileTimeOnly("`evaluated` can only be used within an input task macro, such as := or Def.inputTask.")
@compileTimeOnly(
"`evaluated` can only be used within an input task macro, such as := or Def.inputTask.")
def evaluated: T = macro InputWrapper.valueMacroImpl[T]
@compileTimeOnly("`inputTaskValue` can only be used within an input task macro, such as := or Def.inputTask.")
@compileTimeOnly(
"`inputTaskValue` can only be used within an input task macro, such as := or Def.inputTask.")
def inputTaskValue: InputTask[T] = macro InputWrapper.inputTaskValueMacroImpl[T]
}
sealed abstract class ParserInputTask[T] {
@compileTimeOnly("`parsed` can only be used within an input task macro, such as := or Def.inputTask.")
@compileTimeOnly(
"`parsed` can only be used within an input task macro, such as := or Def.inputTask.")
def parsed: Task[T] = macro ParserInput.parsedInputMacroImpl[T]
}
sealed abstract class MacroPrevious[T] {
@compileTimeOnly("`previous` can only be used within a task macro, such as :=, +=, ++=, or Def.task.")
def previous(implicit format: sjsonnew.JsonFormat[T]): Option[T] = macro InputWrapper.previousMacroImpl[T]
@compileTimeOnly(
"`previous` can only be used within a task macro, such as :=, +=, ++=, or Def.task.")
def previous(implicit format: sjsonnew.JsonFormat[T]): Option[T] =
macro InputWrapper.previousMacroImpl[T]
}
/** Implementation detail. The wrap method temporarily holds the input parser (as a Tree, at compile time) until the input task macro processes it. */
@ -170,25 +200,34 @@ object ParserInput {
private[std] val WrapName = "parser_\u2603\u2603"
private[std] val WrapInitName = "initParser_\u2603\u2603"
@compileTimeOnly("`parsed` can only be used within an input task macro, such as := or Def.inputTask.")
def parser_\u2603\u2603[T](i: Any): T = sys.error("This method is an implementation detail and should not be referenced.")
@compileTimeOnly(
"`parsed` can only be used within an input task macro, such as := or Def.inputTask.")
def parser_\u2603\u2603[T](i: Any): T =
sys.error("This method is an implementation detail and should not be referenced.")
@compileTimeOnly("`parsed` can only be used within an input task macro, such as := or Def.inputTask.")
def initParser_\u2603\u2603[T](i: Any): T = sys.error("This method is an implementation detail and should not be referenced.")
@compileTimeOnly(
"`parsed` can only be used within an input task macro, such as := or Def.inputTask.")
def initParser_\u2603\u2603[T](i: Any): T =
sys.error("This method is an implementation detail and should not be referenced.")
private[std] def wrap[T: c.WeakTypeTag](c: blackbox.Context)(ts: c.Expr[Any], pos: c.Position): c.Expr[T] =
private[std] def wrap[T: c.WeakTypeTag](c: blackbox.Context)(ts: c.Expr[Any],
pos: c.Position): c.Expr[T] =
InputWrapper.wrapImpl[T, ParserInput.type](c, ParserInput, WrapName)(ts, pos)
private[std] def wrapInit[T: c.WeakTypeTag](c: blackbox.Context)(ts: c.Expr[Any], pos: c.Position): c.Expr[T] =
private[std] def wrapInit[T: c.WeakTypeTag](c: blackbox.Context)(ts: c.Expr[Any],
pos: c.Position): c.Expr[T] =
InputWrapper.wrapImpl[T, ParserInput.type](c, ParserInput, WrapInitName)(ts, pos)
private[std] def inputParser[T: c.WeakTypeTag](c: blackbox.Context)(t: c.Expr[InputTask[T]]): c.Expr[State => Parser[Task[T]]] = c.universe.reify(t.splice.parser)
private[std] def inputParser[T: c.WeakTypeTag](c: blackbox.Context)(
t: c.Expr[InputTask[T]]): c.Expr[State => Parser[Task[T]]] =
c.universe.reify(t.splice.parser)
def parsedInputMacroImpl[T: c.WeakTypeTag](c: blackbox.Context): c.Expr[Task[T]] =
ContextUtil.selectMacroImpl[Task[T]](c) { (p, pos) =>
p.tree.tpe match {
case tpe if tpe <:< c.weakTypeOf[InputTask[T]] => wrapInputTask[T](c)(p.tree, pos)
case tpe if tpe <:< c.weakTypeOf[Initialize[InputTask[T]]] => wrapInitInputTask[T](c)(p.tree, pos)
case tpe => unexpectedType(c)(pos, tpe, "parsedInputMacroImpl")
case tpe if tpe <:< c.weakTypeOf[InputTask[T]] => wrapInputTask[T](c)(p.tree, pos)
case tpe if tpe <:< c.weakTypeOf[Initialize[InputTask[T]]] =>
wrapInitInputTask[T](c)(p.tree, pos)
case tpe => unexpectedType(c)(pos, tpe, "parsedInputMacroImpl")
}
}
@ -197,7 +236,8 @@ object ParserInput {
wrap[Task[T]](c)(inputParser(c)(e), pos)
}
private def wrapInitInputTask[T: c.WeakTypeTag](c: blackbox.Context)(tree: c.Tree, pos: c.Position) = {
private def wrapInitInputTask[T: c.WeakTypeTag](c: blackbox.Context)(tree: c.Tree,
pos: c.Position) = {
val e = c.Expr[Initialize[InputTask[T]]](tree)
wrapInit[Task[T]](c)(c.universe.reify { Def.toIParser(e.splice) }, pos)
}
@ -206,9 +246,10 @@ object ParserInput {
def parsedMacroImpl[T: c.WeakTypeTag](c: blackbox.Context): c.Expr[T] =
ContextUtil.selectMacroImpl[T](c) { (p, pos) =>
p.tree.tpe match {
case tpe if tpe <:< c.weakTypeOf[Parser[T]] => wrapParser[T](c)(p.tree, pos)
case tpe if tpe <:< c.weakTypeOf[State => Parser[T]] => wrap[T](c)(p, pos)
case tpe if tpe <:< c.weakTypeOf[Initialize[Parser[T]]] => wrapInitParser[T](c)(p.tree, pos)
case tpe if tpe <:< c.weakTypeOf[Parser[T]] => wrapParser[T](c)(p.tree, pos)
case tpe if tpe <:< c.weakTypeOf[State => Parser[T]] => wrap[T](c)(p, pos)
case tpe if tpe <:< c.weakTypeOf[Initialize[Parser[T]]] =>
wrapInitParser[T](c)(p.tree, pos)
case tpe if tpe <:< c.weakTypeOf[Initialize[State => Parser[T]]] => wrapInit[T](c)(p, pos)
case tpe => unexpectedType(c)(pos, tpe, "parsedMacroImpl")
}
@ -219,7 +260,8 @@ object ParserInput {
wrap[T](c)(c.universe.reify { Def.toSParser(e.splice) }, pos)
}
private def wrapInitParser[T: c.WeakTypeTag](c: blackbox.Context)(tree: c.Tree, pos: c.Position) = {
private def wrapInitParser[T: c.WeakTypeTag](c: blackbox.Context)(tree: c.Tree,
pos: c.Position) = {
val e = c.Expr[Initialize[Parser[T]]](tree)
val es = c.universe.reify { Def.toISParser(e.splice) }
wrapInit[T](c)(es, pos)

View File

@ -7,32 +7,38 @@ import scala.reflect.macros._
import sbt.util.OptJsonWriter
private[sbt] object KeyMacro {
def settingKeyImpl[T: c.WeakTypeTag](c: blackbox.Context)(description: c.Expr[String]): c.Expr[SettingKey[T]] =
def settingKeyImpl[T: c.WeakTypeTag](c: blackbox.Context)(
description: c.Expr[String]): c.Expr[SettingKey[T]] =
keyImpl2[T, SettingKey[T]](c) { (name, mf, ojw) =>
c.universe.reify { SettingKey[T](name.splice, description.splice)(mf.splice, ojw.splice) }
}
def taskKeyImpl[T: c.WeakTypeTag](c: blackbox.Context)(description: c.Expr[String]): c.Expr[TaskKey[T]] =
def taskKeyImpl[T: c.WeakTypeTag](c: blackbox.Context)(
description: c.Expr[String]): c.Expr[TaskKey[T]] =
keyImpl[T, TaskKey[T]](c) { (name, mf) =>
c.universe.reify { TaskKey[T](name.splice, description.splice)(mf.splice) }
}
def inputKeyImpl[T: c.WeakTypeTag](c: blackbox.Context)(description: c.Expr[String]): c.Expr[InputKey[T]] =
def inputKeyImpl[T: c.WeakTypeTag](c: blackbox.Context)(
description: c.Expr[String]): c.Expr[InputKey[T]] =
keyImpl[T, InputKey[T]](c) { (name, mf) =>
c.universe.reify { InputKey[T](name.splice, description.splice)(mf.splice) }
}
def keyImpl[T: c.WeakTypeTag, S: c.WeakTypeTag](c: blackbox.Context)(
f: (c.Expr[String], c.Expr[Manifest[T]]) => c.Expr[S]
f: (c.Expr[String], c.Expr[Manifest[T]]) => c.Expr[S]
): c.Expr[S] =
f(getName(c), getImplicit[Manifest[T]](c))
private def keyImpl2[T: c.WeakTypeTag, S: c.WeakTypeTag](c: blackbox.Context)(
f: (c.Expr[String], c.Expr[Manifest[T]], c.Expr[OptJsonWriter[T]]) => c.Expr[S]
f: (c.Expr[String], c.Expr[Manifest[T]], c.Expr[OptJsonWriter[T]]) => c.Expr[S]
): c.Expr[S] =
f(getName(c), getImplicit[Manifest[T]](c), getImplicit[OptJsonWriter[T]](c))
private def getName[S: c.WeakTypeTag, T: c.WeakTypeTag](c: blackbox.Context): c.Expr[String] = {
import c.universe._
val enclosingValName = definingValName(c, methodName => s"""$methodName must be directly assigned to a val, such as `val x = $methodName[Int]("description")`.""")
val enclosingValName = definingValName(
c,
methodName =>
s"""$methodName must be directly assigned to a val, such as `val x = $methodName[Int]("description")`.""")
c.Expr[String](Literal(Constant(enclosingValName)))
}
@ -41,26 +47,30 @@ private[sbt] object KeyMacro {
c.Expr[T](c.inferImplicitValue(weakTypeOf[T]))
}
def definingValName(c: blackbox.Context, invalidEnclosingTree: String => String): String =
{
import c.universe.{ Apply => ApplyTree, _ }
val methodName = c.macroApplication.symbol.name
def processName(n: Name): String = n.decodedName.toString.trim // trim is not strictly correct, but macros don't expose the API necessary
@tailrec def enclosingVal(trees: List[c.Tree]): String =
{
trees match {
case vd @ ValDef(_, name, _, _) :: ts => processName(name)
case (_: ApplyTree | _: Select | _: TypeApply) :: xs => enclosingVal(xs)
// lazy val x: X = <methodName> has this form for some reason (only when the explicit type is present, though)
case Block(_, _) :: DefDef(mods, name, _, _, _, _) :: xs if mods.hasFlag(Flag.LAZY) => processName(name)
case _ =>
c.error(c.enclosingPosition, invalidEnclosingTree(methodName.decodedName.toString))
"<error>"
}
}
enclosingVal(enclosingTrees(c).toList)
def definingValName(c: blackbox.Context, invalidEnclosingTree: String => String): String = {
import c.universe.{ Apply => ApplyTree, _ }
val methodName = c.macroApplication.symbol.name
def processName(n: Name): String =
n.decodedName.toString.trim // trim is not strictly correct, but macros don't expose the API necessary
@tailrec def enclosingVal(trees: List[c.Tree]): String = {
trees match {
case vd @ ValDef(_, name, _, _) :: ts => processName(name)
case (_: ApplyTree | _: Select | _: TypeApply) :: xs => enclosingVal(xs)
// lazy val x: X = <methodName> has this form for some reason (only when the explicit type is present, though)
case Block(_, _) :: DefDef(mods, name, _, _, _, _) :: xs if mods.hasFlag(Flag.LAZY) =>
processName(name)
case _ =>
c.error(c.enclosingPosition, invalidEnclosingTree(methodName.decodedName.toString))
"<error>"
}
}
enclosingVal(enclosingTrees(c).toList)
}
def enclosingTrees(c: blackbox.Context): Seq[c.Tree] =
c.asInstanceOf[reflect.macros.runtime.Context].callsiteTyper.context.enclosingContextChain.map(_.tree.asInstanceOf[c.Tree])
c.asInstanceOf[reflect.macros.runtime.Context]
.callsiteTyper
.context
.enclosingContextChain
.map(_.tree.asInstanceOf[c.Tree])
}

View File

@ -8,7 +8,8 @@ import sbt.internal.util.appmacro.{ Convert, Converted, Instance, MixedBuilder,
object InitializeInstance extends MonadInstance {
type M[x] = Initialize[x]
def app[K[L[x]], Z](in: K[Initialize], f: K[Id] => Z)(implicit a: AList[K]): Initialize[Z] = Def.app[K, Z](in)(f)(a)
def app[K[L[x]], Z](in: K[Initialize], f: K[Id] => Z)(implicit a: AList[K]): Initialize[Z] =
Def.app[K, Z](in)(f)(a)
def map[S, T](in: Initialize[S], f: S => T): Initialize[T] = Def.map(in)(f)
def flatten[T](in: Initialize[Initialize[T]]): Initialize[T] = Def.bind(in)(idFun[Initialize[T]])
def pure[T](t: () => T): Initialize[T] = Def.pure(t)
@ -25,23 +26,30 @@ object InitializeConvert extends Convert {
case _ => Converted.NotApplicable
}
private def convert[T: c.WeakTypeTag](c: blackbox.Context)(in: c.Tree): Converted[c.type] =
{
val i = c.Expr[Initialize[T]](in)
val t = c.universe.reify(i.splice).tree
Converted.Success(t)
}
private def convert[T: c.WeakTypeTag](c: blackbox.Context)(in: c.Tree): Converted[c.type] = {
val i = c.Expr[Initialize[T]](in)
val t = c.universe.reify(i.splice).tree
Converted.Success(t)
}
private def failTask[C <: blackbox.Context with Singleton](c: C)(pos: c.Position): Converted[c.type] =
private def failTask[C <: blackbox.Context with Singleton](c: C)(
pos: c.Position): Converted[c.type] =
Converted.Failure(pos, "A setting cannot depend on a task")
private def failPrevious[C <: blackbox.Context with Singleton](c: C)(pos: c.Position): Converted[c.type] =
private def failPrevious[C <: blackbox.Context with Singleton](c: C)(
pos: c.Position): Converted[c.type] =
Converted.Failure(pos, "A setting cannot depend on a task's previous value.")
}
object SettingMacro {
def settingMacroImpl[T: c.WeakTypeTag](c: blackbox.Context)(t: c.Expr[T]): c.Expr[Initialize[T]] =
Instance.contImpl[T, Id](c, InitializeInstance, InitializeConvert, MixedBuilder)(Left(t), Instance.idTransform[c.type])
def settingMacroImpl[T: c.WeakTypeTag](c: blackbox.Context)(
t: c.Expr[T]): c.Expr[Initialize[T]] =
Instance.contImpl[T, Id](c, InitializeInstance, InitializeConvert, MixedBuilder)(
Left(t),
Instance.idTransform[c.type])
def settingDynMacroImpl[T: c.WeakTypeTag](c: blackbox.Context)(t: c.Expr[Initialize[T]]): c.Expr[Initialize[T]] =
Instance.contImpl[T, Id](c, InitializeInstance, InitializeConvert, MixedBuilder)(Right(t), Instance.idTransform[c.type])
def settingDynMacroImpl[T: c.WeakTypeTag](c: blackbox.Context)(
t: c.Expr[Initialize[T]]): c.Expr[Initialize[T]] =
Instance.contImpl[T, Id](c, InitializeInstance, InitializeConvert, MixedBuilder)(
Right(t),
Instance.idTransform[c.type])
}

View File

@ -38,23 +38,29 @@ object ParserInstance extends Instance {
}
/** Composes the Task and Initialize Instances to provide an Instance for [T] Initialize[Task[T]].*/
object FullInstance extends Instance.Composed[Initialize, Task](InitializeInstance, TaskInstance) with MonadInstance {
object FullInstance
extends Instance.Composed[Initialize, Task](InitializeInstance, TaskInstance)
with MonadInstance {
type SS = sbt.internal.util.Settings[Scope]
val settingsData = TaskKey[SS]("settings-data", "Provides access to the project data for the build.", KeyRanks.DTask)
val settingsData = TaskKey[SS]("settings-data",
"Provides access to the project data for the build.",
KeyRanks.DTask)
def flatten[T](in: Initialize[Task[Initialize[Task[T]]]]): Initialize[Task[T]] = {
import Scoped._
(in, settingsData, Def.capturedTransformations) { (a: Task[Initialize[Task[T]]], data: Task[SS], f) =>
import TaskExtra.multT2Task
(a, data) flatMap { case (a, d) => f(a) evaluate d }
(in, settingsData, Def.capturedTransformations) {
(a: Task[Initialize[Task[T]]], data: Task[SS], f) =>
import TaskExtra.multT2Task
(a, data) flatMap { case (a, d) => f(a) evaluate d }
}
}
def flattenFun[S, T](in: Initialize[Task[S => Initialize[Task[T]]]]): Initialize[S => Task[T]] = {
import Scoped._
(in, settingsData, Def.capturedTransformations) { (a: Task[S => Initialize[Task[T]]], data: Task[SS], f) => (s: S) =>
import TaskExtra.multT2Task
(a, data) flatMap { case (af, d) => f(af(s)) evaluate d }
(in, settingsData, Def.capturedTransformations) {
(a: Task[S => Initialize[Task[T]]], data: Task[SS], f) => (s: S) =>
import TaskExtra.multT2Task
(a, data) flatMap { case (af, d) => f(af(s)) evaluate d }
}
}
}
@ -68,241 +74,273 @@ object TaskMacro {
final val TransformInitName = "transform"
final val InputTaskCreateDynName = "createDyn"
final val InputTaskCreateFreeName = "createFree"
final val append1Migration = "`<+=` operator is deprecated. Try `lhs += { x.value }`\n or see http://www.scala-sbt.org/0.13/docs/Migrating-from-sbt-012x.html."
final val appendNMigration = "`<++=` operator is deprecated. Try `lhs ++= { x.value }`\n or see http://www.scala-sbt.org/0.13/docs/Migrating-from-sbt-012x.html."
final val append1Migration =
"`<+=` operator is deprecated. Try `lhs += { x.value }`\n or see http://www.scala-sbt.org/0.13/docs/Migrating-from-sbt-012x.html."
final val appendNMigration =
"`<++=` operator is deprecated. Try `lhs ++= { x.value }`\n or see http://www.scala-sbt.org/0.13/docs/Migrating-from-sbt-012x.html."
final val assignMigration =
"""`<<=` operator is deprecated. Use `key := { x.value }` or `key ~= (old => { newValue })`.
|See http://www.scala-sbt.org/0.13/docs/Migrating-from-sbt-012x.html""".stripMargin
def taskMacroImpl[T: c.WeakTypeTag](c: blackbox.Context)(t: c.Expr[T]): c.Expr[Initialize[Task[T]]] =
Instance.contImpl[T, Id](c, FullInstance, FullConvert, MixedBuilder)(Left(t), Instance.idTransform[c.type])
def taskMacroImpl[T: c.WeakTypeTag](c: blackbox.Context)(
t: c.Expr[T]): c.Expr[Initialize[Task[T]]] =
Instance.contImpl[T, Id](c, FullInstance, FullConvert, MixedBuilder)(
Left(t),
Instance.idTransform[c.type])
def taskDynMacroImpl[T: c.WeakTypeTag](c: blackbox.Context)(t: c.Expr[Initialize[Task[T]]]): c.Expr[Initialize[Task[T]]] =
Instance.contImpl[T, Id](c, FullInstance, FullConvert, MixedBuilder)(Right(t), Instance.idTransform[c.type])
def taskDynMacroImpl[T: c.WeakTypeTag](c: blackbox.Context)(
t: c.Expr[Initialize[Task[T]]]): c.Expr[Initialize[Task[T]]] =
Instance.contImpl[T, Id](c, FullInstance, FullConvert, MixedBuilder)(
Right(t),
Instance.idTransform[c.type])
/** Implementation of := macro for settings. */
def settingAssignMacroImpl[T: c.WeakTypeTag](c: blackbox.Context)(v: c.Expr[T]): c.Expr[Setting[T]] =
{
val init = SettingMacro.settingMacroImpl[T](c)(v)
val assign = transformMacroImpl(c)(init.tree)(AssignInitName)
c.Expr[Setting[T]](assign)
}
def settingAssignMacroImpl[T: c.WeakTypeTag](c: blackbox.Context)(
v: c.Expr[T]): c.Expr[Setting[T]] = {
val init = SettingMacro.settingMacroImpl[T](c)(v)
val assign = transformMacroImpl(c)(init.tree)(AssignInitName)
c.Expr[Setting[T]](assign)
}
/** Implementation of := macro for tasks. */
def taskAssignMacroImpl[T: c.WeakTypeTag](c: blackbox.Context)(v: c.Expr[T]): c.Expr[Setting[Task[T]]] =
{
val init = taskMacroImpl[T](c)(v)
val assign = transformMacroImpl(c)(init.tree)(AssignInitName)
c.Expr[Setting[Task[T]]](assign)
}
def taskAssignMacroImpl[T: c.WeakTypeTag](c: blackbox.Context)(
v: c.Expr[T]): c.Expr[Setting[Task[T]]] = {
val init = taskMacroImpl[T](c)(v)
val assign = transformMacroImpl(c)(init.tree)(AssignInitName)
c.Expr[Setting[Task[T]]](assign)
}
// Error macros (Restligeist)
// These macros are there just so we can fail old operators like `<<=` and provide useful migration information.
def fakeSettingAssignPosition[T: c.WeakTypeTag](c: blackbox.Context)(app: c.Expr[Initialize[T]]): c.Expr[Setting[T]] =
def fakeSettingAssignPosition[T: c.WeakTypeTag](c: blackbox.Context)(
app: c.Expr[Initialize[T]]): c.Expr[Setting[T]] =
ContextUtil.selectMacroImpl[Setting[T]](c) { (ts, pos) =>
c.abort(pos, assignMigration)
}
def fakeSettingAppend1Position[S: c.WeakTypeTag, V: c.WeakTypeTag](c: blackbox.Context)(v: c.Expr[Initialize[V]])(a: c.Expr[Append.Value[S, V]]): c.Expr[Setting[S]] =
def fakeSettingAppend1Position[S: c.WeakTypeTag, V: c.WeakTypeTag](c: blackbox.Context)(
v: c.Expr[Initialize[V]])(a: c.Expr[Append.Value[S, V]]): c.Expr[Setting[S]] =
ContextUtil.selectMacroImpl[Setting[S]](c) { (ts, pos) =>
c.abort(pos, append1Migration)
}
def fakeSettingAppendNPosition[S: c.WeakTypeTag, V: c.WeakTypeTag](c: blackbox.Context)(vs: c.Expr[Initialize[V]])(a: c.Expr[Append.Values[S, V]]): c.Expr[Setting[S]] =
def fakeSettingAppendNPosition[S: c.WeakTypeTag, V: c.WeakTypeTag](c: blackbox.Context)(
vs: c.Expr[Initialize[V]])(a: c.Expr[Append.Values[S, V]]): c.Expr[Setting[S]] =
ContextUtil.selectMacroImpl[Setting[S]](c) { (ts, pos) =>
c.abort(pos, appendNMigration)
}
def fakeItaskAssignPosition[T: c.WeakTypeTag](c: blackbox.Context)(app: c.Expr[Initialize[Task[T]]]): c.Expr[Setting[Task[T]]] =
def fakeItaskAssignPosition[T: c.WeakTypeTag](c: blackbox.Context)(
app: c.Expr[Initialize[Task[T]]]): c.Expr[Setting[Task[T]]] =
ContextUtil.selectMacroImpl[Setting[Task[T]]](c) { (ts, pos) =>
c.abort(pos, assignMigration)
}
def fakeTaskAppend1Position[S: c.WeakTypeTag, V: c.WeakTypeTag](c: blackbox.Context)(v: c.Expr[Initialize[Task[V]]])(a: c.Expr[Append.Value[S, V]]): c.Expr[Setting[Task[S]]] =
def fakeTaskAppend1Position[S: c.WeakTypeTag, V: c.WeakTypeTag](c: blackbox.Context)(
v: c.Expr[Initialize[Task[V]]])(a: c.Expr[Append.Value[S, V]]): c.Expr[Setting[Task[S]]] =
ContextUtil.selectMacroImpl[Setting[Task[S]]](c) { (ts, pos) =>
c.abort(pos, append1Migration)
}
def fakeTaskAppendNPosition[S: c.WeakTypeTag, V: c.WeakTypeTag](c: blackbox.Context)(vs: c.Expr[Initialize[Task[V]]])(a: c.Expr[Append.Values[S, V]]): c.Expr[Setting[Task[S]]] =
def fakeTaskAppendNPosition[S: c.WeakTypeTag, V: c.WeakTypeTag](c: blackbox.Context)(
vs: c.Expr[Initialize[Task[V]]])(a: c.Expr[Append.Values[S, V]]): c.Expr[Setting[Task[S]]] =
ContextUtil.selectMacroImpl[Setting[Task[S]]](c) { (ts, pos) =>
c.abort(pos, appendNMigration)
}
/* Implementations of <<= macro variations for tasks and settings. These just get the source position of the call site.*/
def itaskAssignPosition[T: c.WeakTypeTag](c: blackbox.Context)(app: c.Expr[Initialize[Task[T]]]): c.Expr[Setting[Task[T]]] =
def itaskAssignPosition[T: c.WeakTypeTag](c: blackbox.Context)(
app: c.Expr[Initialize[Task[T]]]): c.Expr[Setting[Task[T]]] =
settingAssignPosition(c)(app)
def taskAssignPositionT[T: c.WeakTypeTag](c: blackbox.Context)(app: c.Expr[Task[T]]): c.Expr[Setting[Task[T]]] =
def taskAssignPositionT[T: c.WeakTypeTag](c: blackbox.Context)(
app: c.Expr[Task[T]]): c.Expr[Setting[Task[T]]] =
itaskAssignPosition(c)(c.universe.reify { Def.valueStrict(app.splice) })
def taskAssignPositionPure[T: c.WeakTypeTag](c: blackbox.Context)(app: c.Expr[T]): c.Expr[Setting[Task[T]]] =
def taskAssignPositionPure[T: c.WeakTypeTag](c: blackbox.Context)(
app: c.Expr[T]): c.Expr[Setting[Task[T]]] =
taskAssignPositionT(c)(c.universe.reify { TaskExtra.constant(app.splice) })
def taskTransformPosition[S: c.WeakTypeTag](c: blackbox.Context)(f: c.Expr[S => S]): c.Expr[Setting[Task[S]]] =
def taskTransformPosition[S: c.WeakTypeTag](c: blackbox.Context)(
f: c.Expr[S => S]): c.Expr[Setting[Task[S]]] =
c.Expr[Setting[Task[S]]](transformMacroImpl(c)(f.tree)(TransformInitName))
def settingTransformPosition[S: c.WeakTypeTag](c: blackbox.Context)(f: c.Expr[S => S]): c.Expr[Setting[S]] =
def settingTransformPosition[S: c.WeakTypeTag](c: blackbox.Context)(
f: c.Expr[S => S]): c.Expr[Setting[S]] =
c.Expr[Setting[S]](transformMacroImpl(c)(f.tree)(TransformInitName))
def itaskTransformPosition[S: c.WeakTypeTag](c: blackbox.Context)(f: c.Expr[S => S]): c.Expr[Setting[S]] =
def itaskTransformPosition[S: c.WeakTypeTag](c: blackbox.Context)(
f: c.Expr[S => S]): c.Expr[Setting[S]] =
c.Expr[Setting[S]](transformMacroImpl(c)(f.tree)(TransformInitName))
def settingAssignPure[T: c.WeakTypeTag](c: blackbox.Context)(app: c.Expr[T]): c.Expr[Setting[T]] =
def settingAssignPure[T: c.WeakTypeTag](c: blackbox.Context)(
app: c.Expr[T]): c.Expr[Setting[T]] =
settingAssignPosition(c)(c.universe.reify { Def.valueStrict(app.splice) })
def settingAssignPosition[T: c.WeakTypeTag](c: blackbox.Context)(app: c.Expr[Initialize[T]]): c.Expr[Setting[T]] =
def settingAssignPosition[T: c.WeakTypeTag](c: blackbox.Context)(
app: c.Expr[Initialize[T]]): c.Expr[Setting[T]] =
c.Expr[Setting[T]](transformMacroImpl(c)(app.tree)(AssignInitName))
/** Implementation of := macro for tasks. */
def inputTaskAssignMacroImpl[T: c.WeakTypeTag](c: blackbox.Context)(v: c.Expr[T]): c.Expr[Setting[InputTask[T]]] =
{
val init = inputTaskMacroImpl[T](c)(v)
val assign = transformMacroImpl(c)(init.tree)(AssignInitName)
c.Expr[Setting[InputTask[T]]](assign)
}
def inputTaskAssignMacroImpl[T: c.WeakTypeTag](c: blackbox.Context)(
v: c.Expr[T]): c.Expr[Setting[InputTask[T]]] = {
val init = inputTaskMacroImpl[T](c)(v)
val assign = transformMacroImpl(c)(init.tree)(AssignInitName)
c.Expr[Setting[InputTask[T]]](assign)
}
/** Implementation of += macro for tasks. */
def taskAppend1Impl[T: c.WeakTypeTag, U: c.WeakTypeTag](c: blackbox.Context)(v: c.Expr[U])(a: c.Expr[Append.Value[T, U]]): c.Expr[Setting[Task[T]]] =
{
val init = taskMacroImpl[U](c)(v)
val append = appendMacroImpl(c)(init.tree, a.tree)(Append1InitName)
c.Expr[Setting[Task[T]]](append)
}
def taskAppend1Impl[T: c.WeakTypeTag, U: c.WeakTypeTag](c: blackbox.Context)(v: c.Expr[U])(
a: c.Expr[Append.Value[T, U]]): c.Expr[Setting[Task[T]]] = {
val init = taskMacroImpl[U](c)(v)
val append = appendMacroImpl(c)(init.tree, a.tree)(Append1InitName)
c.Expr[Setting[Task[T]]](append)
}
/** Implementation of += macro for settings. */
def settingAppend1Impl[T: c.WeakTypeTag, U: c.WeakTypeTag](c: blackbox.Context)(v: c.Expr[U])(a: c.Expr[Append.Value[T, U]]): c.Expr[Setting[T]] =
{
import c.universe._
val ttpe = c.weakTypeOf[T]
val typeArgs = ttpe.typeArgs
v.tree.tpe match {
// To allow Initialize[Task[A]] in the position of += RHS, we're going to call "taskValue" automatically.
case tpe if typeArgs.nonEmpty && (tpe weak_<:< c.weakTypeOf[Initialize[_]]) =>
c.macroApplication match {
case Apply(Apply(TypeApply(Select(preT, nmeT), targs), _), _) =>
val tree = Apply(TypeApply(Select(preT, TermName("+=").encodedName), TypeTree(typeArgs.head) :: Nil), Select(v.tree, TermName("taskValue").encodedName) :: Nil)
c.Expr[Setting[T]](tree)
case x => ContextUtil.unexpectedTree(x)
}
case _ =>
val init = SettingMacro.settingMacroImpl[U](c)(v)
val append = appendMacroImpl(c)(init.tree, a.tree)(Append1InitName)
c.Expr[Setting[T]](append)
}
def settingAppend1Impl[T: c.WeakTypeTag, U: c.WeakTypeTag](c: blackbox.Context)(v: c.Expr[U])(
a: c.Expr[Append.Value[T, U]]): c.Expr[Setting[T]] = {
import c.universe._
val ttpe = c.weakTypeOf[T]
val typeArgs = ttpe.typeArgs
v.tree.tpe match {
// To allow Initialize[Task[A]] in the position of += RHS, we're going to call "taskValue" automatically.
case tpe if typeArgs.nonEmpty && (tpe weak_<:< c.weakTypeOf[Initialize[_]]) =>
c.macroApplication match {
case Apply(Apply(TypeApply(Select(preT, nmeT), targs), _), _) =>
val tree = Apply(
TypeApply(Select(preT, TermName("+=").encodedName), TypeTree(typeArgs.head) :: Nil),
Select(v.tree, TermName("taskValue").encodedName) :: Nil)
c.Expr[Setting[T]](tree)
case x => ContextUtil.unexpectedTree(x)
}
case _ =>
val init = SettingMacro.settingMacroImpl[U](c)(v)
val append = appendMacroImpl(c)(init.tree, a.tree)(Append1InitName)
c.Expr[Setting[T]](append)
}
}
/** Implementation of ++= macro for tasks. */
def taskAppendNImpl[T: c.WeakTypeTag, U: c.WeakTypeTag](c: blackbox.Context)(vs: c.Expr[U])(a: c.Expr[Append.Values[T, U]]): c.Expr[Setting[Task[T]]] =
{
val init = taskMacroImpl[U](c)(vs)
val append = appendMacroImpl(c)(init.tree, a.tree)(AppendNInitName)
c.Expr[Setting[Task[T]]](append)
}
def taskAppendNImpl[T: c.WeakTypeTag, U: c.WeakTypeTag](c: blackbox.Context)(vs: c.Expr[U])(
a: c.Expr[Append.Values[T, U]]): c.Expr[Setting[Task[T]]] = {
val init = taskMacroImpl[U](c)(vs)
val append = appendMacroImpl(c)(init.tree, a.tree)(AppendNInitName)
c.Expr[Setting[Task[T]]](append)
}
/** Implementation of ++= macro for settings. */
def settingAppendNImpl[T: c.WeakTypeTag, U: c.WeakTypeTag](c: blackbox.Context)(vs: c.Expr[U])(a: c.Expr[Append.Values[T, U]]): c.Expr[Setting[T]] =
{
val init = SettingMacro.settingMacroImpl[U](c)(vs)
val append = appendMacroImpl(c)(init.tree, a.tree)(AppendNInitName)
c.Expr[Setting[T]](append)
}
def settingAppendNImpl[T: c.WeakTypeTag, U: c.WeakTypeTag](c: blackbox.Context)(vs: c.Expr[U])(
a: c.Expr[Append.Values[T, U]]): c.Expr[Setting[T]] = {
val init = SettingMacro.settingMacroImpl[U](c)(vs)
val append = appendMacroImpl(c)(init.tree, a.tree)(AppendNInitName)
c.Expr[Setting[T]](append)
}
/** Implementation of -= macro for tasks. */
def taskRemove1Impl[T: c.WeakTypeTag, U: c.WeakTypeTag](c: blackbox.Context)(v: c.Expr[U])(r: c.Expr[Remove.Value[T, U]]): c.Expr[Setting[Task[T]]] =
{
val init = taskMacroImpl[U](c)(v)
val remove = removeMacroImpl(c)(init.tree, r.tree)(Remove1InitName)
c.Expr[Setting[Task[T]]](remove)
}
def taskRemove1Impl[T: c.WeakTypeTag, U: c.WeakTypeTag](c: blackbox.Context)(v: c.Expr[U])(
r: c.Expr[Remove.Value[T, U]]): c.Expr[Setting[Task[T]]] = {
val init = taskMacroImpl[U](c)(v)
val remove = removeMacroImpl(c)(init.tree, r.tree)(Remove1InitName)
c.Expr[Setting[Task[T]]](remove)
}
/** Implementation of -= macro for settings. */
def settingRemove1Impl[T: c.WeakTypeTag, U: c.WeakTypeTag](c: blackbox.Context)(v: c.Expr[U])(r: c.Expr[Remove.Value[T, U]]): c.Expr[Setting[T]] =
{
val init = SettingMacro.settingMacroImpl[U](c)(v)
val remove = removeMacroImpl(c)(init.tree, r.tree)(Remove1InitName)
c.Expr[Setting[T]](remove)
}
def settingRemove1Impl[T: c.WeakTypeTag, U: c.WeakTypeTag](c: blackbox.Context)(v: c.Expr[U])(
r: c.Expr[Remove.Value[T, U]]): c.Expr[Setting[T]] = {
val init = SettingMacro.settingMacroImpl[U](c)(v)
val remove = removeMacroImpl(c)(init.tree, r.tree)(Remove1InitName)
c.Expr[Setting[T]](remove)
}
/** Implementation of --= macro for tasks. */
def taskRemoveNImpl[T: c.WeakTypeTag, U: c.WeakTypeTag](c: blackbox.Context)(vs: c.Expr[U])(r: c.Expr[Remove.Values[T, U]]): c.Expr[Setting[Task[T]]] =
{
val init = taskMacroImpl[U](c)(vs)
val remove = removeMacroImpl(c)(init.tree, r.tree)(RemoveNInitName)
c.Expr[Setting[Task[T]]](remove)
}
def taskRemoveNImpl[T: c.WeakTypeTag, U: c.WeakTypeTag](c: blackbox.Context)(vs: c.Expr[U])(
r: c.Expr[Remove.Values[T, U]]): c.Expr[Setting[Task[T]]] = {
val init = taskMacroImpl[U](c)(vs)
val remove = removeMacroImpl(c)(init.tree, r.tree)(RemoveNInitName)
c.Expr[Setting[Task[T]]](remove)
}
/** Implementation of --= macro for settings. */
def settingRemoveNImpl[T: c.WeakTypeTag, U: c.WeakTypeTag](c: blackbox.Context)(vs: c.Expr[U])(r: c.Expr[Remove.Values[T, U]]): c.Expr[Setting[T]] =
{
val init = SettingMacro.settingMacroImpl[U](c)(vs)
val remove = removeMacroImpl(c)(init.tree, r.tree)(RemoveNInitName)
c.Expr[Setting[T]](remove)
}
def settingRemoveNImpl[T: c.WeakTypeTag, U: c.WeakTypeTag](c: blackbox.Context)(vs: c.Expr[U])(
r: c.Expr[Remove.Values[T, U]]): c.Expr[Setting[T]] = {
val init = SettingMacro.settingMacroImpl[U](c)(vs)
val remove = removeMacroImpl(c)(init.tree, r.tree)(RemoveNInitName)
c.Expr[Setting[T]](remove)
}
private[this] def appendMacroImpl(c: blackbox.Context)(init: c.Tree, append: c.Tree)(newName: String): c.Tree =
{
import c.universe._
private[this] def appendMacroImpl(c: blackbox.Context)(init: c.Tree, append: c.Tree)(
newName: String): c.Tree = {
import c.universe._
c.macroApplication match {
case Apply(Apply(TypeApply(Select(preT, nmeT), targs), _), _) =>
Apply(Apply(TypeApply(Select(preT, TermName(newName).encodedName), targs),
init :: sourcePosition(c).tree :: Nil),
append :: Nil)
case x => ContextUtil.unexpectedTree(x)
}
}
private[this] def removeMacroImpl(c: blackbox.Context)(init: c.Tree, remove: c.Tree)(
newName: String): c.Tree = {
import c.universe._
c.macroApplication match {
case Apply(Apply(TypeApply(Select(preT, nmeT), targs), _), r) =>
Apply(Apply(TypeApply(Select(preT, TermName(newName).encodedName), targs),
init :: sourcePosition(c).tree :: Nil),
r)
case x => ContextUtil.unexpectedTree(x)
}
}
private[this] def transformMacroImpl(c: blackbox.Context)(init: c.Tree)(
newName: String): c.Tree = {
import c.universe._
val target =
c.macroApplication match {
case Apply(Apply(TypeApply(Select(preT, nmeT), targs), _), _) =>
Apply(Apply(TypeApply(Select(preT, TermName(newName).encodedName), targs), init :: sourcePosition(c).tree :: Nil), append :: Nil)
case x => ContextUtil.unexpectedTree(x)
case Apply(Select(prefix, _), _) => prefix
case x => ContextUtil.unexpectedTree(x)
}
}
Apply.apply(Select(target, TermName(newName).encodedName),
init :: sourcePosition(c).tree :: Nil)
}
private[this] def removeMacroImpl(c: blackbox.Context)(init: c.Tree, remove: c.Tree)(newName: String): c.Tree =
{
import c.universe._
c.macroApplication match {
case Apply(Apply(TypeApply(Select(preT, nmeT), targs), _), r) =>
Apply(Apply(TypeApply(Select(preT, TermName(newName).encodedName), targs), init :: sourcePosition(c).tree :: Nil), r)
case x => ContextUtil.unexpectedTree(x)
}
}
private[this] def sourcePosition(c: blackbox.Context): c.Expr[SourcePosition] = {
import c.universe.reify
val pos = c.enclosingPosition
if (!pos.isInstanceOf[UndefinedPosition] && pos.line >= 0 && pos.source != null) {
val f = pos.source.file
val name = constant[String](c, settingSource(c, f.path, f.name))
val line = constant[Int](c, pos.line)
reify { LinePosition(name.splice, line.splice) }
} else
reify { NoPosition }
}
private[this] def transformMacroImpl(c: blackbox.Context)(init: c.Tree)(newName: String): c.Tree =
{
import c.universe._
val target =
c.macroApplication match {
case Apply(Select(prefix, _), _) => prefix
case x => ContextUtil.unexpectedTree(x)
}
Apply.apply(Select(target, TermName(newName).encodedName), init :: sourcePosition(c).tree :: Nil)
}
private[this] def sourcePosition(c: blackbox.Context): c.Expr[SourcePosition] =
{
import c.universe.reify
val pos = c.enclosingPosition
if (!pos.isInstanceOf[UndefinedPosition] && pos.line >= 0 && pos.source != null) {
val f = pos.source.file
val name = constant[String](c, settingSource(c, f.path, f.name))
val line = constant[Int](c, pos.line)
reify { LinePosition(name.splice, line.splice) }
} else
reify { NoPosition }
}
private[this] def settingSource(c: blackbox.Context, path: String, name: String): String =
{
@tailrec def inEmptyPackage(s: c.Symbol): Boolean = s != c.universe.NoSymbol && (
s.owner == c.mirror.EmptyPackage || s.owner == c.mirror.EmptyPackageClass || inEmptyPackage(s.owner)
)
c.internal.enclosingOwner match {
case ec if !ec.isStatic => name
case ec if inEmptyPackage(ec) => path
case ec => s"(${ec.fullName}) $name"
}
private[this] def settingSource(c: blackbox.Context, path: String, name: String): String = {
@tailrec def inEmptyPackage(s: c.Symbol): Boolean = s != c.universe.NoSymbol && (
s.owner == c.mirror.EmptyPackage || s.owner == c.mirror.EmptyPackageClass || inEmptyPackage(
s.owner)
)
c.internal.enclosingOwner match {
case ec if !ec.isStatic => name
case ec if inEmptyPackage(ec) => path
case ec => s"(${ec.fullName}) $name"
}
}
private[this] def constant[T: c.TypeTag](c: blackbox.Context, t: T): c.Expr[T] = {
import c.universe._
c.Expr[T](Literal(Constant(t)))
}
def inputTaskMacroImpl[T: c.WeakTypeTag](c: blackbox.Context)(t: c.Expr[T]): c.Expr[Initialize[InputTask[T]]] =
def inputTaskMacroImpl[T: c.WeakTypeTag](c: blackbox.Context)(
t: c.Expr[T]): c.Expr[Initialize[InputTask[T]]] =
inputTaskMacro0[T](c)(t)
def inputTaskDynMacroImpl[T: c.WeakTypeTag](c: blackbox.Context)(t: c.Expr[Initialize[Task[T]]]): c.Expr[Initialize[InputTask[T]]] =
def inputTaskDynMacroImpl[T: c.WeakTypeTag](c: blackbox.Context)(
t: c.Expr[Initialize[Task[T]]]): c.Expr[Initialize[InputTask[T]]] =
inputTaskDynMacro0[T](c)(t)
private[this] def inputTaskMacro0[T: c.WeakTypeTag](c: blackbox.Context)(t: c.Expr[T]): c.Expr[Initialize[InputTask[T]]] =
private[this] def inputTaskMacro0[T: c.WeakTypeTag](c: blackbox.Context)(
t: c.Expr[T]): c.Expr[Initialize[InputTask[T]]] =
iInitializeMacro(c)(t) { et =>
val pt = iParserMacro(c)(et) { pt =>
iTaskMacro(c)(pt)
@ -310,145 +348,153 @@ object TaskMacro {
c.universe.reify { InputTask.make(pt.splice) }
}
private[this] def iInitializeMacro[M[_], T](c: blackbox.Context)(t: c.Expr[T])(f: c.Expr[T] => c.Expr[M[T]])(implicit tt: c.WeakTypeTag[T], mt: c.WeakTypeTag[M[T]]): c.Expr[Initialize[M[T]]] =
{
val inner: Transform[c.type, M] = new Transform[c.type, M] { def apply(in: c.Tree): c.Tree = f(c.Expr[T](in)).tree }
val cond = c.Expr[T](conditionInputTaskTree(c)(t.tree))
Instance.contImpl[T, M](c, InitializeInstance, InputInitConvert, MixedBuilder)(Left(cond), inner)
private[this] def iInitializeMacro[M[_], T](c: blackbox.Context)(t: c.Expr[T])(
f: c.Expr[T] => c.Expr[M[T]])(implicit tt: c.WeakTypeTag[T],
mt: c.WeakTypeTag[M[T]]): c.Expr[Initialize[M[T]]] = {
val inner: Transform[c.type, M] = new Transform[c.type, M] {
def apply(in: c.Tree): c.Tree = f(c.Expr[T](in)).tree
}
val cond = c.Expr[T](conditionInputTaskTree(c)(t.tree))
Instance
.contImpl[T, M](c, InitializeInstance, InputInitConvert, MixedBuilder)(Left(cond), inner)
}
private[this] def conditionInputTaskTree(c: blackbox.Context)(t: c.Tree): c.Tree = {
import c.universe._
import InputWrapper._
def wrapInitTask[T: c.WeakTypeTag](tree: Tree) = {
val e = c.Expr[Initialize[Task[T]]](tree)
wrapTask[T](c)(wrapInit[Task[T]](c)(e, tree.pos), tree.pos).tree
}
def wrapInitParser[T: c.WeakTypeTag](tree: Tree) = {
val e = c.Expr[Initialize[State => Parser[T]]](tree)
ParserInput.wrap[T](c)(wrapInit[State => Parser[T]](c)(e, tree.pos), tree.pos).tree
}
def wrapInitInput[T: c.WeakTypeTag](tree: Tree) = {
val e = c.Expr[Initialize[InputTask[T]]](tree)
wrapInput[T](wrapInit[InputTask[T]](c)(e, tree.pos).tree)
}
def wrapInput[T: c.WeakTypeTag](tree: Tree) = {
val e = c.Expr[InputTask[T]](tree)
val p = ParserInput.wrap[Task[T]](c)(ParserInput.inputParser(c)(e), tree.pos)
wrapTask[T](c)(p, tree.pos).tree
}
private[this] def conditionInputTaskTree(c: blackbox.Context)(t: c.Tree): c.Tree =
{
import c.universe._
import InputWrapper._
def wrapInitTask[T: c.WeakTypeTag](tree: Tree) =
{
val e = c.Expr[Initialize[Task[T]]](tree)
wrapTask[T](c)(wrapInit[Task[T]](c)(e, tree.pos), tree.pos).tree
}
def wrapInitParser[T: c.WeakTypeTag](tree: Tree) =
{
val e = c.Expr[Initialize[State => Parser[T]]](tree)
ParserInput.wrap[T](c)(wrapInit[State => Parser[T]](c)(e, tree.pos), tree.pos).tree
}
def wrapInitInput[T: c.WeakTypeTag](tree: Tree) =
{
val e = c.Expr[Initialize[InputTask[T]]](tree)
wrapInput[T](wrapInit[InputTask[T]](c)(e, tree.pos).tree)
}
def wrapInput[T: c.WeakTypeTag](tree: Tree) =
{
val e = c.Expr[InputTask[T]](tree)
val p = ParserInput.wrap[Task[T]](c)(ParserInput.inputParser(c)(e), tree.pos)
wrapTask[T](c)(p, tree.pos).tree
}
def expand(nme: String, tpe: Type, tree: Tree): Converted[c.type] = nme match {
case WrapInitTaskName => Converted.Success(wrapInitTask(tree)(c.WeakTypeTag(tpe)))
case ParserInput.WrapInitName => Converted.Success(wrapInitParser(tree)(c.WeakTypeTag(tpe)))
case WrapInitInputName => Converted.Success(wrapInitInput(tree)(c.WeakTypeTag(tpe)))
case WrapInputName => Converted.Success(wrapInput(tree)(c.WeakTypeTag(tpe)))
case _ => Converted.NotApplicable
}
val util = ContextUtil[c.type](c)
util.transformWrappers(t, (nme, tpe, tree, original) => expand(nme, tpe, tree))
}
def expand(nme: String, tpe: Type, tree: Tree): Converted[c.type] = nme match {
case WrapInitTaskName => Converted.Success(wrapInitTask(tree)(c.WeakTypeTag(tpe)))
case ParserInput.WrapInitName => Converted.Success(wrapInitParser(tree)(c.WeakTypeTag(tpe)))
case WrapInitInputName => Converted.Success(wrapInitInput(tree)(c.WeakTypeTag(tpe)))
case WrapInputName => Converted.Success(wrapInput(tree)(c.WeakTypeTag(tpe)))
case _ => Converted.NotApplicable
private[this] def iParserMacro[M[_], T](c: blackbox.Context)(t: c.Expr[T])(
f: c.Expr[T] => c.Expr[M[T]])(implicit tt: c.WeakTypeTag[T],
mt: c.WeakTypeTag[M[T]]): c.Expr[State => Parser[M[T]]] = {
val inner: Transform[c.type, M] = new Transform[c.type, M] {
def apply(in: c.Tree): c.Tree = f(c.Expr[T](in)).tree
}
Instance.contImpl[T, M](c, ParserInstance, ParserConvert, MixedBuilder)(Left(t), inner)
}
private[this] def iTaskMacro[T: c.WeakTypeTag](c: blackbox.Context)(
t: c.Expr[T]): c.Expr[Task[T]] =
Instance
.contImpl[T, Id](c, TaskInstance, TaskConvert, MixedBuilder)(Left(t), Instance.idTransform)
private[this] def inputTaskDynMacro0[T: c.WeakTypeTag](c: blackbox.Context)(
t: c.Expr[Initialize[Task[T]]]): c.Expr[Initialize[InputTask[T]]] = {
import c.universe.{ Apply => ApplyTree, _ }
import internal.decorators._
val tag = implicitly[c.WeakTypeTag[T]]
val util = ContextUtil[c.type](c)
val it = Ident(util.singleton(InputTask))
val isParserWrapper = InitParserConvert.asPredicate(c)
val isTaskWrapper = FullConvert.asPredicate(c)
val isAnyWrapper = (n: String, tpe: Type, tr: Tree) =>
isParserWrapper(n, tpe, tr) || isTaskWrapper(n, tpe, tr)
val ttree = t.tree
val defs = util.collectDefs(ttree, isAnyWrapper)
val checkQual = util.checkReferences(defs, isAnyWrapper)
// the Symbol for the anonymous function passed to the appropriate Instance.map/flatMap/pure method
// this Symbol needs to be known up front so that it can be used as the owner of synthetic vals
val functionSym = util.functionSymbol(ttree.pos)
var result: Option[(Tree, Type, ValDef)] = None
// original is the Tree being replaced. It is needed for preserving attributes.
def subWrapper(tpe: Type, qual: Tree, original: Tree): Tree =
if (result.isDefined) {
c.error(
qual.pos,
"Implementation restriction: a dynamic InputTask can only have a single input parser.")
EmptyTree
} else {
qual.foreach(checkQual)
val vd = util.freshValDef(tpe, qual.symbol.pos, functionSym) // val $x: <tpe>
result = Some((qual, tpe, vd))
val tree = util.refVal(original, vd) // $x
tree.setPos(qual.pos) // position needs to be set so that wrapKey passes the position onto the wrapper
assert(tree.tpe != null, "Null type: " + tree)
tree.setType(tpe)
tree
}
val util = ContextUtil[c.type](c)
util.transformWrappers(t, (nme, tpe, tree, original) => expand(nme, tpe, tree))
// Tree for InputTask.<name>[<tpeA>, <tpeB>](arg1)(arg2)
def inputTaskCreate(name: String, tpeA: Type, tpeB: Type, arg1: Tree, arg2: Tree) = {
val typedApp = TypeApply(util.select(it, name), TypeTree(tpeA) :: TypeTree(tpeB) :: Nil)
val app = ApplyTree(ApplyTree(typedApp, arg1 :: Nil), arg2 :: Nil)
c.Expr[Initialize[InputTask[T]]](app)
}
private[this] def iParserMacro[M[_], T](c: blackbox.Context)(t: c.Expr[T])(f: c.Expr[T] => c.Expr[M[T]])(implicit tt: c.WeakTypeTag[T], mt: c.WeakTypeTag[M[T]]): c.Expr[State => Parser[M[T]]] =
{
val inner: Transform[c.type, M] = new Transform[c.type, M] { def apply(in: c.Tree): c.Tree = f(c.Expr[T](in)).tree }
Instance.contImpl[T, M](c, ParserInstance, ParserConvert, MixedBuilder)(Left(t), inner)
// Tree for InputTask.createFree[<tpe>](arg1)
def inputTaskCreateFree(tpe: Type, arg: Tree) = {
val typedApp = TypeApply(util.select(it, InputTaskCreateFreeName), TypeTree(tpe) :: Nil)
val app = ApplyTree(typedApp, arg :: Nil)
c.Expr[Initialize[InputTask[T]]](app)
}
def expandTask[I: WeakTypeTag](dyn: Boolean, tx: Tree): c.Expr[Initialize[Task[I]]] =
if (dyn)
taskDynMacroImpl[I](c)(c.Expr[Initialize[Task[I]]](tx))
else
taskMacroImpl[I](c)(c.Expr[I](tx))
def wrapTag[I: WeakTypeTag]: WeakTypeTag[Initialize[Task[I]]] = weakTypeTag
private[this] def iTaskMacro[T: c.WeakTypeTag](c: blackbox.Context)(t: c.Expr[T]): c.Expr[Task[T]] =
Instance.contImpl[T, Id](c, TaskInstance, TaskConvert, MixedBuilder)(Left(t), Instance.idTransform)
private[this] def inputTaskDynMacro0[T: c.WeakTypeTag](c: blackbox.Context)(t: c.Expr[Initialize[Task[T]]]): c.Expr[Initialize[InputTask[T]]] =
{
import c.universe.{ Apply => ApplyTree, _ }
import internal.decorators._
val tag = implicitly[c.WeakTypeTag[T]]
val util = ContextUtil[c.type](c)
val it = Ident(util.singleton(InputTask))
val isParserWrapper = InitParserConvert.asPredicate(c)
val isTaskWrapper = FullConvert.asPredicate(c)
val isAnyWrapper = (n: String, tpe: Type, tr: Tree) => isParserWrapper(n, tpe, tr) || isTaskWrapper(n, tpe, tr)
val ttree = t.tree
val defs = util.collectDefs(ttree, isAnyWrapper)
val checkQual = util.checkReferences(defs, isAnyWrapper)
// the Symbol for the anonymous function passed to the appropriate Instance.map/flatMap/pure method
// this Symbol needs to be known up front so that it can be used as the owner of synthetic vals
val functionSym = util.functionSymbol(ttree.pos)
var result: Option[(Tree, Type, ValDef)] = None
// original is the Tree being replaced. It is needed for preserving attributes.
def subWrapper(tpe: Type, qual: Tree, original: Tree): Tree =
if (result.isDefined) {
c.error(qual.pos, "Implementation restriction: a dynamic InputTask can only have a single input parser.")
EmptyTree
} else {
qual.foreach(checkQual)
val vd = util.freshValDef(tpe, qual.symbol.pos, functionSym) // val $x: <tpe>
result = Some((qual, tpe, vd))
val tree = util.refVal(original, vd) // $x
tree.setPos(qual.pos) // position needs to be set so that wrapKey passes the position onto the wrapper
assert(tree.tpe != null, "Null type: " + tree)
tree.setType(tpe)
tree
}
// Tree for InputTask.<name>[<tpeA>, <tpeB>](arg1)(arg2)
def inputTaskCreate(name: String, tpeA: Type, tpeB: Type, arg1: Tree, arg2: Tree) =
{
val typedApp = TypeApply(util.select(it, name), TypeTree(tpeA) :: TypeTree(tpeB) :: Nil)
val app = ApplyTree(ApplyTree(typedApp, arg1 :: Nil), arg2 :: Nil)
c.Expr[Initialize[InputTask[T]]](app)
}
// Tree for InputTask.createFree[<tpe>](arg1)
def inputTaskCreateFree(tpe: Type, arg: Tree) =
{
val typedApp = TypeApply(util.select(it, InputTaskCreateFreeName), TypeTree(tpe) :: Nil)
val app = ApplyTree(typedApp, arg :: Nil)
c.Expr[Initialize[InputTask[T]]](app)
}
def expandTask[I: WeakTypeTag](dyn: Boolean, tx: Tree): c.Expr[Initialize[Task[I]]] =
if (dyn)
taskDynMacroImpl[I](c)(c.Expr[Initialize[Task[I]]](tx))
else
taskMacroImpl[I](c)(c.Expr[I](tx))
def wrapTag[I: WeakTypeTag]: WeakTypeTag[Initialize[Task[I]]] = weakTypeTag
def sub(name: String, tpe: Type, qual: Tree, selection: Tree): Converted[c.type] =
{
val tag = c.WeakTypeTag[T](tpe)
InitParserConvert(c)(name, qual)(tag) transform { tree =>
subWrapper(tpe, tree, selection)
}
}
val tx = util.transformWrappers(ttree, (n, tpe, tree, replace) => sub(n, tpe, tree, replace))
result match {
case Some((p, tpe, param)) =>
val fCore = util.createFunction(param :: Nil, tx, functionSym)
val bodyTpe = wrapTag(tag).tpe
val fTpe = util.functionType(tpe :: Nil, bodyTpe)
val fTag = c.WeakTypeTag[Any](fTpe) // don't know the actual type yet, so use Any
val fInit = expandTask(false, fCore)(fTag).tree
inputTaskCreate(InputTaskCreateDynName, tpe, tag.tpe, p, fInit)
case None =>
val init = expandTask[T](true, tx).tree
inputTaskCreateFree(tag.tpe, init)
def sub(name: String, tpe: Type, qual: Tree, selection: Tree): Converted[c.type] = {
val tag = c.WeakTypeTag[T](tpe)
InitParserConvert(c)(name, qual)(tag) transform { tree =>
subWrapper(tpe, tree, selection)
}
}
val tx = util.transformWrappers(ttree, (n, tpe, tree, replace) => sub(n, tpe, tree, replace))
result match {
case Some((p, tpe, param)) =>
val fCore = util.createFunction(param :: Nil, tx, functionSym)
val bodyTpe = wrapTag(tag).tpe
val fTpe = util.functionType(tpe :: Nil, bodyTpe)
val fTag = c.WeakTypeTag[Any](fTpe) // don't know the actual type yet, so use Any
val fInit = expandTask(false, fCore)(fTag).tree
inputTaskCreate(InputTaskCreateDynName, tpe, tag.tpe, p, fInit)
case None =>
val init = expandTask[T](true, tx).tree
inputTaskCreateFree(tag.tpe, init)
}
}
}
object PlainTaskMacro {
def task[T](t: T): Task[T] = macro taskImpl[T]
def taskImpl[T: c.WeakTypeTag](c: blackbox.Context)(t: c.Expr[T]): c.Expr[Task[T]] =
Instance.contImpl[T, Id](c, TaskInstance, TaskConvert, MixedBuilder)(Left(t), Instance.idTransform[c.type])
Instance.contImpl[T, Id](c, TaskInstance, TaskConvert, MixedBuilder)(
Left(t),
Instance.idTransform[c.type])
def taskDyn[T](t: Task[T]): Task[T] = macro taskDynImpl[T]
def taskDynImpl[T: c.WeakTypeTag](c: blackbox.Context)(t: c.Expr[Task[T]]): c.Expr[Task[T]] =
Instance.contImpl[T, Id](c, TaskInstance, TaskConvert, MixedBuilder)(Right(t), Instance.idTransform[c.type])
Instance.contImpl[T, Id](c, TaskInstance, TaskConvert, MixedBuilder)(
Right(t),
Instance.idTransform[c.type])
}

View File

@ -52,7 +52,9 @@ object Assign {
bk ++= Seq(z.value)
)*/
val zz = Def.task { mk.value + tk.value + mk.value + tk.value + mk.value + tk.value + mk.value + tk.value + mk.value + tk.value + mk.value + tk.value }
val zz = Def.task {
mk.value + tk.value + mk.value + tk.value + mk.value + tk.value + mk.value + tk.value + mk.value + tk.value + mk.value + tk.value
}
import DefaultParsers._
val p = Def.setting { name.value ~> Space ~> ID }
@ -61,7 +63,7 @@ object Assign {
name := "asdf",
tk := (math.random * 1000).toInt,
isk := dummys.value.parsed // should not compile: cannot use a task to define the parser
// ik := { if( tsk.parsed.value == "blue") tk.value else mk.value }
// ik := { if( tsk.parsed.value == "blue") tk.value else mk.value }
)
val it1 = Def.inputTask {

View File

@ -7,20 +7,25 @@ import sbt.internal.util.complete._
import java.io.File
abstract class BackgroundJobService extends Closeable {
/**
* Launch a background job which is a function that runs inside another thread;
* killing the job will interrupt() the thread. If your thread blocks on a process,
* then you should get an InterruptedException while blocking on the process, and
* then you could process.destroy() for example.
*/
def runInBackground(spawningTask: ScopedKey[_], state: State)(start: (Logger, File) => Unit): JobHandle
def runInBackground(spawningTask: ScopedKey[_], state: State)(
start: (Logger, File) => Unit): JobHandle
/** Same as shutown. */
def close(): Unit
/** Shuts down all background jobs. */
def shutdown(): Unit
def jobs: Vector[JobHandle]
def stop(job: JobHandle): Unit
def waitFor(job: JobHandle): Unit
/** Copies classpath to temporary directories. */
def copyClasspath(products: Classpath, full: Classpath, workingDirectory: File): Classpath
}
@ -28,12 +33,15 @@ abstract class BackgroundJobService extends Closeable {
object BackgroundJobService {
private[sbt] def jobIdParser: (State, Seq[JobHandle]) => Parser[Seq[JobHandle]] = {
import DefaultParsers._
(state, handles) => {
val stringIdParser: Parser[Seq[String]] = Space ~> token(NotSpace examples handles.map(_.id.toString).toSet, description = "<job id>").+
stringIdParser.map { strings =>
strings.map(Integer.parseInt(_)).flatMap(id => handles.find(_.id == id))
(state, handles) =>
{
val stringIdParser: Parser[Seq[String]] = Space ~> token(
NotSpace examples handles.map(_.id.toString).toSet,
description = "<job id>").+
stringIdParser.map { strings =>
strings.map(Integer.parseInt(_)).flatMap(id => handles.find(_.id == id))
}
}
}
}
}

View File

@ -10,11 +10,22 @@ import sbt.io.{ GlobFilter, Path }
import sbt.internal.util.AttributeKey
object BuildPaths {
val globalBaseDirectory = AttributeKey[File]("global-base-directory", "The base directory for global sbt configuration and staging.", DSetting)
val globalPluginsDirectory = AttributeKey[File]("global-plugins-directory", "The base directory for global sbt plugins.", DSetting)
val globalSettingsDirectory = AttributeKey[File]("global-settings-directory", "The base directory for global sbt settings.", DSetting)
val stagingDirectory = AttributeKey[File]("staging-directory", "The directory for staging remote projects.", DSetting)
val dependencyBaseDirectory = AttributeKey[File]("dependency-base-directory", "The base directory for caching dependency resolution.", DSetting)
val globalBaseDirectory = AttributeKey[File](
"global-base-directory",
"The base directory for global sbt configuration and staging.",
DSetting)
val globalPluginsDirectory = AttributeKey[File]("global-plugins-directory",
"The base directory for global sbt plugins.",
DSetting)
val globalSettingsDirectory = AttributeKey[File]("global-settings-directory",
"The base directory for global sbt settings.",
DSetting)
val stagingDirectory =
AttributeKey[File]("staging-directory", "The directory for staging remote projects.", DSetting)
val dependencyBaseDirectory = AttributeKey[File](
"dependency-base-directory",
"The base directory for caching dependency resolution.",
DSetting)
import sbt.io.syntax._
@ -37,29 +48,36 @@ object BuildPaths {
fileSetting(stagingDirectory, StagingProperty, defaultStaging(globalBase))(state)
def getGlobalPluginsDirectory(state: State, globalBase: File): File =
fileSetting(globalPluginsDirectory, GlobalPluginsProperty, defaultGlobalPlugins(globalBase))(state)
fileSetting(globalPluginsDirectory, GlobalPluginsProperty, defaultGlobalPlugins(globalBase))(
state)
def getGlobalSettingsDirectory(state: State, globalBase: File): File =
fileSetting(globalSettingsDirectory, GlobalSettingsProperty, globalBase)(state)
def getDependencyDirectory(state: State, globalBase: File): File =
fileSetting(dependencyBaseDirectory, DependencyBaseProperty, defaultDependencyBase(globalBase))(state)
fileSetting(dependencyBaseDirectory,
DependencyBaseProperty,
defaultDependencyBase(globalBase))(state)
private[this] def fileSetting(stateKey: AttributeKey[File], property: String, default: File)(state: State): File =
private[this] def fileSetting(stateKey: AttributeKey[File], property: String, default: File)(
state: State): File =
getFileSetting(stateKey, property, default)(state)
def getFileSetting(stateKey: AttributeKey[File], property: String, default: => File)(state: State): File =
def getFileSetting(stateKey: AttributeKey[File], property: String, default: => File)(
state: State): File =
state get stateKey orElse getFileProperty(property) getOrElse default
def getFileProperty(name: String): Option[File] = Option(System.getProperty(name)) flatMap { path =>
if (path.isEmpty) None else Some(new File(path))
def getFileProperty(name: String): Option[File] = Option(System.getProperty(name)) flatMap {
path =>
if (path.isEmpty) None else Some(new File(path))
}
def defaultVersionedGlobalBase(sbtVersion: String): File = defaultGlobalBase / sbtVersion
def defaultGlobalBase = Path.userHome / ConfigDirectoryName
private[this] def binarySbtVersion(state: State): String =
sbt.internal.librarymanagement.cross.CrossVersionUtil.binarySbtVersion(state.configuration.provider.id.version)
sbt.internal.librarymanagement.cross.CrossVersionUtil
.binarySbtVersion(state.configuration.provider.id.version)
private[this] def defaultStaging(globalBase: File) = globalBase / "staging"
private[this] def defaultGlobalPlugins(globalBase: File) = globalBase / PluginsDirectoryName
private[this] def defaultDependencyBase(globalBase: File) = globalBase / "dependency"
@ -81,7 +99,8 @@ object BuildPaths {
final val GlobalSettingsProperty = "sbt.global.settings"
final val DependencyBaseProperty = "sbt.dependency.base"
def crossPath(base: File, instance: xsbti.compile.ScalaInstance): File = base / ("scala_" + instance.version)
def crossPath(base: File, instance: xsbti.compile.ScalaInstance): File =
base / ("scala_" + instance.version)
private[this] def globalDirTransitionWarning(unversioned: File, versioned: File): String =
s"""The global sbt directory is now versioned and is located at $versioned.

View File

@ -13,7 +13,8 @@ private[sbt] trait BuildSyntax {
def enablePlugins(ps: AutoPlugin*): DslEntry = DslEntry.DslEnablePlugins(ps)
def disablePlugins(ps: AutoPlugin*): DslEntry = DslEntry.DslDisablePlugins(ps)
def configs(cs: Configuration*): DslEntry = DslEntry.DslConfigs(cs)
def dependsOn(deps: Eval[ClasspathDep[ProjectReference]]*): DslEntry = DslEntry.DslDependsOn(deps)
def dependsOn(deps: Eval[ClasspathDep[ProjectReference]]*): DslEntry =
DslEntry.DslDependsOn(deps)
// avoid conflict with `sbt.Keys.aggregate`
def aggregateProjects(refs: Eval[ProjectReference]*): DslEntry = DslEntry.DslAggregate(refs)
}

View File

@ -8,7 +8,14 @@ import sbt.internal.util.complete.{ DefaultParsers, Parser }
import sbt.internal.util.AttributeKey
import DefaultParsers._
import Def.{ ScopedKey, Setting }
import sbt.internal.CommandStrings.{ CrossCommand, CrossRestoreSessionCommand, SwitchCommand, crossHelp, crossRestoreSessionHelp, switchHelp }
import sbt.internal.CommandStrings.{
CrossCommand,
CrossRestoreSessionCommand,
SwitchCommand,
crossHelp,
crossRestoreSessionHelp,
switchHelp
}
import java.io.File
import sbt.internal.inc.ScalaInstance
@ -24,7 +31,8 @@ object Cross {
def force: Boolean
}
private case class NamedScalaVersion(name: String, force: Boolean) extends ScalaVersion
private case class ScalaHomeVersion(home: File, resolveVersion: Option[String], force: Boolean) extends ScalaVersion
private case class ScalaHomeVersion(home: File, resolveVersion: Option[String], force: Boolean)
extends ScalaVersion
private def switchParser(state: State): Parser[Switch] = {
import DefaultParsers._
@ -36,9 +44,11 @@ object Cross {
val force = arg.endsWith("!")
val versionArg = if (force) arg.dropRight(1) else arg
versionArg.split("=", 2) match {
case Array(home) if new File(home).exists() => ScalaHomeVersion(new File(home), None, force)
case Array(v) => NamedScalaVersion(v, force)
case Array(v, home) => ScalaHomeVersion(new File(home), Some(v).filterNot(_.isEmpty), force)
case Array(home) if new File(home).exists() =>
ScalaHomeVersion(new File(home), None, force)
case Array(v) => NamedScalaVersion(v, force)
case Array(v, home) =>
ScalaHomeVersion(new File(home), Some(v).filterNot(_.isEmpty), force)
}
}
val spacedVersion = if (spacePresent) version else version & spacedFirst(SwitchCommand)
@ -50,7 +60,9 @@ object Cross {
}
}
token(SwitchCommand ~> OptSpace) flatMap { sp => versionAndCommand(sp.nonEmpty) }
token(SwitchCommand ~> OptSpace) flatMap { sp =>
versionAndCommand(sp.nonEmpty)
}
}
private case class CrossArgs(command: String, verbose: Boolean)
@ -62,10 +74,11 @@ object Cross {
} & spacedFirst(CrossCommand)
}
private def crossRestoreSessionParser(state: State): Parser[String] = token(CrossRestoreSessionCommand)
private def crossRestoreSessionParser(state: State): Parser[String] =
token(CrossRestoreSessionCommand)
private def requireSession[T](p: State => Parser[T]): State => Parser[T] = s =>
if (s get sessionSettings isEmpty) failure("No project loaded") else p(s)
private def requireSession[T](p: State => Parser[T]): State => Parser[T] =
s => if (s get sessionSettings isEmpty) failure("No project loaded") else p(s)
private def resolveAggregates(extracted: Extracted): Seq[ProjectRef] = {
import extracted._
@ -134,7 +147,8 @@ object Cross {
s"$SwitchCommand $verbose $version" :: projects.map(project => s"$project/$aggCommand")
case (version, projects) =>
// First switch scala version, then use the all command to run the command on each project concurrently
Seq(s"$SwitchCommand $verbose $version", projects.map(_ + "/" + aggCommand).mkString("all ", " ", ""))
Seq(s"$SwitchCommand $verbose $version",
projects.map(_ + "/" + aggCommand).mkString("all ", " ", ""))
}
allCommands.toList ::: CrossRestoreSessionCommand :: captureCurrentSession(state, x)
@ -158,7 +172,9 @@ object Cross {
state.get(CapturedSession) match {
case Some(rawAppend) =>
val restoredSession = extracted.session.copy(rawAppend = rawAppend)
BuiltinCommands.reapply(restoredSession, extracted.structure, state).remove(CapturedSession)
BuiltinCommands
.reapply(restoredSession, extracted.structure, state)
.remove(CapturedSession)
case None => state
}
}
@ -191,7 +207,8 @@ object Cross {
val binaryVersion = CrossVersion.binaryScalaVersion(version)
def logSwitchInfo(included: Seq[(ProjectRef, Seq[String])], excluded: Seq[(ProjectRef, Seq[String])]) = {
def logSwitchInfo(included: Seq[(ProjectRef, Seq[String])],
excluded: Seq[(ProjectRef, Seq[String])]) = {
instance.foreach {
case (home, instance) =>
@ -206,7 +223,8 @@ object Cross {
state.log.info(s"Excluded ${excluded.size} projects, run ++ $version -v for more details.")
}
def detailedLog(msg: => String) = if (switch.verbose) state.log.info(msg) else state.log.debug(msg)
def detailedLog(msg: => String) =
if (switch.verbose) state.log.info(msg) else state.log.debug(msg)
def logProject: (ProjectRef, Seq[String]) => Unit = (proj, scalaVersions) => {
val current = if (proj == currentRef) "*" else " "
@ -219,14 +237,16 @@ object Cross {
}
val projects: Seq[Reference] = {
val projectScalaVersions = structure.allProjectRefs.map(proj => proj -> crossVersions(x, proj))
val projectScalaVersions =
structure.allProjectRefs.map(proj => proj -> crossVersions(x, proj))
if (switch.version.force) {
logSwitchInfo(projectScalaVersions, Nil)
structure.allProjectRefs ++ structure.units.keys.map(BuildRef.apply)
} else {
val (included, excluded) = projectScalaVersions.partition {
case (proj, scalaVersions) => scalaVersions.exists(v => CrossVersion.binaryScalaVersion(v) == binaryVersion)
case (proj, scalaVersions) =>
scalaVersions.exists(v => CrossVersion.binaryScalaVersion(v) == binaryVersion)
}
logSwitchInfo(included, excluded)
included.map(_._1)
@ -236,23 +256,28 @@ object Cross {
setScalaVersionForProjects(version, instance, projects, state, x)
}
private def setScalaVersionForProjects(version: String, instance: Option[(File, ScalaInstance)],
projects: Seq[Reference], state: State, extracted: Extracted): State = {
private def setScalaVersionForProjects(version: String,
instance: Option[(File, ScalaInstance)],
projects: Seq[Reference],
state: State,
extracted: Extracted): State = {
import extracted._
val newSettings = projects.flatMap { project =>
val scope = Scope(Select(project), Global, Global, Global)
instance match {
case Some((home, inst)) => Seq(
scalaVersion in scope := version,
scalaHome in scope := Some(home),
scalaInstance in scope := inst
)
case None => Seq(
scalaVersion in scope := version,
scalaHome in scope := None
)
case Some((home, inst)) =>
Seq(
scalaVersion in scope := version,
scalaHome in scope := Some(home),
scalaInstance in scope := inst
)
case None =>
Seq(
scalaVersion in scope := version,
scalaHome in scope := None
)
}
}
@ -260,7 +285,9 @@ object Cross {
// Filter out any old scala version settings that were added, this is just for hygiene.
val filteredRawAppend = session.rawAppend.filter(_.key match {
case ScopedKey(Scope(Select(ref), Global, Global, Global), key) if filterKeys.contains(key) && projects.contains(ref) => false
case ScopedKey(Scope(Select(ref), Global, Global, Global), key)
if filterKeys.contains(key) && projects.contains(ref) =>
false
case _ => true
})

File diff suppressed because it is too large Load Diff

View File

@ -34,6 +34,7 @@ import TaskName._
* invoking `cancelAndShutdown()` allows you to cancel the current task execution.
*/
trait RunningTaskEngine {
/** Attempts to kill and shutdown the running task engine.*/
def cancelAndShutdown(): Unit
}
@ -47,6 +48,7 @@ trait RunningTaskEngine {
* All methods on this API are expected to be called from the same thread.
*/
trait TaskCancellationStrategy {
/** The state used by this task. */
type State
@ -63,6 +65,7 @@ trait TaskCancellationStrategy {
}
object TaskCancellationStrategy {
/** An empty handler that does not cancel tasks. */
object Null extends TaskCancellationStrategy {
type State = Unit
@ -105,35 +108,41 @@ sealed trait EvaluateTaskConfig {
}
object EvaluateTaskConfig {
/** Raw constructor for EvaluateTaskConfig. */
def apply(
restrictions: Seq[Tags.Rule],
checkCycles: Boolean,
progressReporter: ExecuteProgress[Task],
cancelStrategy: TaskCancellationStrategy,
forceGarbageCollection: Boolean,
minForcegcInterval: Duration
restrictions: Seq[Tags.Rule],
checkCycles: Boolean,
progressReporter: ExecuteProgress[Task],
cancelStrategy: TaskCancellationStrategy,
forceGarbageCollection: Boolean,
minForcegcInterval: Duration
): EvaluateTaskConfig =
DefaultEvaluateTaskConfig(
restrictions, checkCycles, progressReporter, cancelStrategy, forceGarbageCollection, minForcegcInterval
restrictions,
checkCycles,
progressReporter,
cancelStrategy,
forceGarbageCollection,
minForcegcInterval
)
private[this] case class DefaultEvaluateTaskConfig(
restrictions: Seq[Tags.Rule],
checkCycles: Boolean,
progressReporter: ExecuteProgress[Task],
cancelStrategy: TaskCancellationStrategy,
forceGarbageCollection: Boolean,
minForcegcInterval: Duration
restrictions: Seq[Tags.Rule],
checkCycles: Boolean,
progressReporter: ExecuteProgress[Task],
cancelStrategy: TaskCancellationStrategy,
forceGarbageCollection: Boolean,
minForcegcInterval: Duration
) extends EvaluateTaskConfig
}
final case class PluginData(
dependencyClasspath: Seq[Attributed[File]],
definitionClasspath: Seq[Attributed[File]],
resolvers: Option[Seq[Resolver]],
report: Option[UpdateReport],
scalacOptions: Seq[String]
dependencyClasspath: Seq[Attributed[File]],
definitionClasspath: Seq[Attributed[File]],
resolvers: Option[Seq[Resolver]],
report: Option[UpdateReport],
scalacOptions: Seq[String]
) {
val classpath: Seq[Attributed[File]] = definitionClasspath ++ dependencyClasspath
}
@ -159,28 +168,31 @@ object EvaluateTask {
val SystemProcessors = Runtime.getRuntime.availableProcessors
def extractedTaskConfig(extracted: Extracted, structure: BuildStructure, state: State): EvaluateTaskConfig =
{
val rs = restrictions(extracted, structure)
val canceller = cancelStrategy(extracted, structure, state)
val progress = executeProgress(extracted, structure, state)
val fgc = forcegc(extracted, structure)
val mfi = minForcegcInterval(extracted, structure)
EvaluateTaskConfig(rs, false, progress, canceller, fgc, mfi)
}
def extractedTaskConfig(extracted: Extracted,
structure: BuildStructure,
state: State): EvaluateTaskConfig = {
val rs = restrictions(extracted, structure)
val canceller = cancelStrategy(extracted, structure, state)
val progress = executeProgress(extracted, structure, state)
val fgc = forcegc(extracted, structure)
val mfi = minForcegcInterval(extracted, structure)
EvaluateTaskConfig(rs, false, progress, canceller, fgc, mfi)
}
def defaultRestrictions(maxWorkers: Int) = Tags.limitAll(maxWorkers) :: Nil
def defaultRestrictions(extracted: Extracted, structure: BuildStructure): Seq[Tags.Rule] =
Tags.limitAll(maxWorkers(extracted, structure)) :: Nil
def restrictions(state: State): Seq[Tags.Rule] =
{
val extracted = Project.extract(state)
restrictions(extracted, extracted.structure)
}
def restrictions(state: State): Seq[Tags.Rule] = {
val extracted = Project.extract(state)
restrictions(extracted, extracted.structure)
}
def restrictions(extracted: Extracted, structure: BuildStructure): Seq[Tags.Rule] =
getSetting(Keys.concurrentRestrictions, defaultRestrictions(extracted, structure), extracted, structure)
getSetting(Keys.concurrentRestrictions,
defaultRestrictions(extracted, structure),
extracted,
structure)
def maxWorkers(extracted: Extracted, structure: BuildStructure): Int =
if (getSetting(Keys.parallelExecution, true, extracted, structure))
@ -191,12 +203,22 @@ object EvaluateTask {
def cancelable(extracted: Extracted, structure: BuildStructure): Boolean =
getSetting(Keys.cancelable, false, extracted, structure)
def cancelStrategy(extracted: Extracted, structure: BuildStructure, state: State): TaskCancellationStrategy =
getSetting(Keys.taskCancelStrategy, { (_: State) => TaskCancellationStrategy.Null }, extracted, structure)(state)
def cancelStrategy(extracted: Extracted,
structure: BuildStructure,
state: State): TaskCancellationStrategy =
getSetting(Keys.taskCancelStrategy, { (_: State) =>
TaskCancellationStrategy.Null
}, extracted, structure)(state)
private[sbt] def executeProgress(extracted: Extracted, structure: BuildStructure, state: State): ExecuteProgress[Task] = {
private[sbt] def executeProgress(extracted: Extracted,
structure: BuildStructure,
state: State): ExecuteProgress[Task] = {
import Types.const
val maker: State => Keys.TaskProgress = getSetting(Keys.executeProgress, const(new Keys.TaskProgress(defaultProgress)), extracted, structure)
val maker: State => Keys.TaskProgress = getSetting(
Keys.executeProgress,
const(new Keys.TaskProgress(defaultProgress)),
extracted,
structure)
maker(state).progress
}
// TODO - Should this pull from Global or from the project itself?
@ -204,9 +226,15 @@ object EvaluateTask {
getSetting(Keys.forcegc in Global, GCUtil.defaultForceGarbageCollection, extracted, structure)
// TODO - Should this pull from Global or from the project itself?
private[sbt] def minForcegcInterval(extracted: Extracted, structure: BuildStructure): Duration =
getSetting(Keys.minForcegcInterval in Global, GCUtil.defaultMinForcegcInterval, extracted, structure)
getSetting(Keys.minForcegcInterval in Global,
GCUtil.defaultMinForcegcInterval,
extracted,
structure)
def getSetting[T](key: SettingKey[T], default: T, extracted: Extracted, structure: BuildStructure): T =
def getSetting[T](key: SettingKey[T],
default: T,
extracted: Extracted,
structure: BuildStructure): T =
key in extracted.currentRef get structure.data getOrElse default
def injectSettings: Seq[Setting[_]] = Seq(
@ -215,39 +243,55 @@ object EvaluateTask {
(executionRoots in GlobalScope) ::= dummyRoots
)
def evalPluginDef(log: Logger)(pluginDef: BuildStructure, state: State): PluginData =
{
val root = ProjectRef(pluginDef.root, Load.getRootProject(pluginDef.units)(pluginDef.root))
val pluginKey = pluginData
val config = extractedTaskConfig(Project.extract(state), pluginDef, state)
val evaluated = apply(pluginDef, ScopedKey(pluginKey.scope, pluginKey.key), state, root, config)
val (newS, result) = evaluated getOrElse sys.error("Plugin data does not exist for plugin definition at " + pluginDef.root)
Project.runUnloadHooks(newS) // discard states
processResult(result, log)
}
def evalPluginDef(log: Logger)(pluginDef: BuildStructure, state: State): PluginData = {
val root = ProjectRef(pluginDef.root, Load.getRootProject(pluginDef.units)(pluginDef.root))
val pluginKey = pluginData
val config = extractedTaskConfig(Project.extract(state), pluginDef, state)
val evaluated =
apply(pluginDef, ScopedKey(pluginKey.scope, pluginKey.key), state, root, config)
val (newS, result) = evaluated getOrElse sys.error(
"Plugin data does not exist for plugin definition at " + pluginDef.root)
Project.runUnloadHooks(newS) // discard states
processResult(result, log)
}
/**
* Evaluates `taskKey` and returns the new State and the result of the task wrapped in Some.
* If the task is not defined, None is returned. The provided task key is resolved against the current project `ref`.
* Task execution is configured according to settings defined in the loaded project.
*/
def apply[T](structure: BuildStructure, taskKey: ScopedKey[Task[T]], state: State, ref: ProjectRef): Option[(State, Result[T])] =
apply[T](structure, taskKey, state, ref, extractedTaskConfig(Project.extract(state), structure, state))
def apply[T](structure: BuildStructure,
taskKey: ScopedKey[Task[T]],
state: State,
ref: ProjectRef): Option[(State, Result[T])] =
apply[T](structure,
taskKey,
state,
ref,
extractedTaskConfig(Project.extract(state), structure, state))
/**
* Evaluates `taskKey` and returns the new State and the result of the task wrapped in Some.
* If the task is not defined, None is returned. The provided task key is resolved against the current project `ref`.
* `config` configures concurrency and canceling of task execution.
*/
def apply[T](structure: BuildStructure, taskKey: ScopedKey[Task[T]], state: State, ref: ProjectRef, config: EvaluateTaskConfig): Option[(State, Result[T])] = {
def apply[T](structure: BuildStructure,
taskKey: ScopedKey[Task[T]],
state: State,
ref: ProjectRef,
config: EvaluateTaskConfig): Option[(State, Result[T])] = {
withStreams(structure, state) { str =>
for ((task, toNode) <- getTask(structure, taskKey, state, str, ref)) yield runTask(task, state, str, structure.index.triggers, config)(toNode)
for ((task, toNode) <- getTask(structure, taskKey, state, str, ref))
yield runTask(task, state, str, structure.index.triggers, config)(toNode)
}
}
def logIncResult(result: Result[_], state: State, streams: Streams) = result match { case Inc(i) => logIncomplete(i, state, streams); case _ => () }
def logIncResult(result: Result[_], state: State, streams: Streams) = result match {
case Inc(i) => logIncomplete(i, state, streams); case _ => ()
}
def logIncomplete(result: Incomplete, state: State, streams: Streams): Unit = {
val all = Incomplete linearize result
val keyed = for (Incomplete(Some(key: ScopedKey[_]), _, msg, _, ex) <- all) yield (key, msg, ex)
val keyed = for (Incomplete(Some(key: ScopedKey[_]), _, msg, _, ex) <- all)
yield (key, msg, ex)
import ExceptionCategory._
for ((key, msg, Some(ex)) <- keyed) {
@ -266,82 +310,102 @@ object EvaluateTask {
log.error("(" + display.show(key) + ") " + msgString)
}
}
private[this] def contextDisplay(state: State, highlight: Boolean) = Project.showContextKey(state, if (highlight) Some(RED) else None)
private[this] def contextDisplay(state: State, highlight: Boolean) =
Project.showContextKey(state, if (highlight) Some(RED) else None)
def suppressedMessage(key: ScopedKey[_])(implicit display: Show[ScopedKey[_]]): String =
"Stack trace suppressed. Run 'last %s' for the full log.".format(display.show(key))
def getStreams(key: ScopedKey[_], streams: Streams): TaskStreams =
streams(ScopedKey(Project.fillTaskAxis(key).scope, Keys.streams.key))
def withStreams[T](structure: BuildStructure, state: State)(f: Streams => T): T =
{
val str = std.Streams.closeable(structure.streams(state))
try { f(str) } finally { str.close() }
def withStreams[T](structure: BuildStructure, state: State)(f: Streams => T): T = {
val str = std.Streams.closeable(structure.streams(state))
try { f(str) } finally { str.close() }
}
def getTask[T](structure: BuildStructure,
taskKey: ScopedKey[Task[T]],
state: State,
streams: Streams,
ref: ProjectRef): Option[(Task[T], NodeView[Task])] = {
val thisScope = Load.projectScope(ref)
val resolvedScope = Scope.replaceThis(thisScope)(taskKey.scope)
for (t <- structure.data.get(resolvedScope, taskKey.key))
yield (t, nodeView(state, streams, taskKey :: Nil))
}
def nodeView[HL <: HList](state: State,
streams: Streams,
roots: Seq[ScopedKey[_]],
dummies: DummyTaskMap = DummyTaskMap(Nil)): NodeView[Task] =
Transform(
(dummyRoots, roots) :: (Def.dummyStreamsManager, streams) :: (dummyState, state) :: dummies)
def runTask[T](
root: Task[T],
state: State,
streams: Streams,
triggers: Triggers[Task],
config: EvaluateTaskConfig)(implicit taskToNode: NodeView[Task]): (State, Result[T]) = {
import ConcurrentRestrictions.{ completionService, tagged, tagsKey }
val log = state.log
log.debug(
s"Running task... Cancel: ${config.cancelStrategy}, check cycles: ${config.checkCycles}, forcegc: ${config.forceGarbageCollection}")
val tags =
tagged[Task[_]](_.info get tagsKey getOrElse Map.empty, Tags.predicate(config.restrictions))
val (service, shutdownThreads) =
completionService[Task[_], Completed](tags, (s: String) => log.warn(s))
def shutdown(): Unit = {
// First ensure that all threads are stopped for task execution.
shutdownThreads()
// Now we run the gc cleanup to force finalizers to clear out file handles (yay GC!)
if (config.forceGarbageCollection) {
GCUtil.forceGcWithInterval(config.minForcegcInterval, log)
}
}
def getTask[T](structure: BuildStructure, taskKey: ScopedKey[Task[T]], state: State, streams: Streams, ref: ProjectRef): Option[(Task[T], NodeView[Task])] =
{
val thisScope = Load.projectScope(ref)
val resolvedScope = Scope.replaceThis(thisScope)(taskKey.scope)
for (t <- structure.data.get(resolvedScope, taskKey.key)) yield (t, nodeView(state, streams, taskKey :: Nil))
// propagate the defining key for reporting the origin
def overwriteNode(i: Incomplete): Boolean = i.node match {
case Some(t: Task[_]) => transformNode(t).isEmpty
case _ => true
}
def nodeView[HL <: HList](state: State, streams: Streams, roots: Seq[ScopedKey[_]], dummies: DummyTaskMap = DummyTaskMap(Nil)): NodeView[Task] =
Transform((dummyRoots, roots) :: (Def.dummyStreamsManager, streams) :: (dummyState, state) :: dummies)
def runTask[T](root: Task[T], state: State, streams: Streams, triggers: Triggers[Task], config: EvaluateTaskConfig)(implicit taskToNode: NodeView[Task]): (State, Result[T]) =
{
import ConcurrentRestrictions.{ completionService, tagged, tagsKey }
val log = state.log
log.debug(s"Running task... Cancel: ${config.cancelStrategy}, check cycles: ${config.checkCycles}, forcegc: ${config.forceGarbageCollection}")
val tags = tagged[Task[_]](_.info get tagsKey getOrElse Map.empty, Tags.predicate(config.restrictions))
val (service, shutdownThreads) = completionService[Task[_], Completed](tags, (s: String) => log.warn(s))
def shutdown(): Unit = {
// First ensure that all threads are stopped for task execution.
shutdownThreads()
// Now we run the gc cleanup to force finalizers to clear out file handles (yay GC!)
if (config.forceGarbageCollection) {
GCUtil.forceGcWithInterval(config.minForcegcInterval, log)
}
}
// propagate the defining key for reporting the origin
def overwriteNode(i: Incomplete): Boolean = i.node match {
case Some(t: Task[_]) => transformNode(t).isEmpty
case _ => true
}
def run() = {
val x = new Execute[Task](Execute.config(config.checkCycles, overwriteNode), triggers, config.progressReporter)(taskToNode)
val (newState, result) =
try {
val results = x.runKeep(root)(service)
storeValuesForPrevious(results, state, streams)
applyResults(results, state, root)
} catch { case inc: Incomplete => (state, Inc(inc)) }
finally shutdown()
val replaced = transformInc(result)
logIncResult(replaced, state, streams)
(newState, replaced)
}
object runningEngine extends RunningTaskEngine {
def cancelAndShutdown(): Unit = {
println("")
log.warn("Canceling execution...")
shutdown()
}
}
// Register with our cancel handler we're about to start.
val strat = config.cancelStrategy
val cancelState = strat.onTaskEngineStart(runningEngine)
try run()
finally strat.onTaskEngineFinish(cancelState)
def run() = {
val x = new Execute[Task](Execute.config(config.checkCycles, overwriteNode),
triggers,
config.progressReporter)(taskToNode)
val (newState, result) =
try {
val results = x.runKeep(root)(service)
storeValuesForPrevious(results, state, streams)
applyResults(results, state, root)
} catch { case inc: Incomplete => (state, Inc(inc)) } finally shutdown()
val replaced = transformInc(result)
logIncResult(replaced, state, streams)
(newState, replaced)
}
object runningEngine extends RunningTaskEngine {
def cancelAndShutdown(): Unit = {
println("")
log.warn("Canceling execution...")
shutdown()
}
}
// Register with our cancel handler we're about to start.
val strat = config.cancelStrategy
val cancelState = strat.onTaskEngineStart(runningEngine)
try run()
finally strat.onTaskEngineFinish(cancelState)
}
private[this] def storeValuesForPrevious(results: RMap[Task, Result], state: State, streams: Streams): Unit =
private[this] def storeValuesForPrevious(results: RMap[Task, Result],
state: State,
streams: Streams): Unit =
for (referenced <- Previous.references in Global get Project.structure(state).data)
Previous.complete(referenced, results, streams)
def applyResults[T](results: RMap[Task, Result], state: State, root: Task[T]): (State, Result[T]) =
def applyResults[T](results: RMap[Task, Result],
state: State,
root: Task[T]): (State, Result[T]) =
(stateTransform(results)(state), results(root))
def stateTransform(results: RMap[Task, Result]): State => State =
Function.chain(
@ -353,7 +417,9 @@ object EvaluateTask {
def transformInc[T](result: Result[T]): Result[T] =
// taskToKey needs to be before liftAnonymous. liftA only lifts non-keyed (anonymous) Incompletes.
result.toEither.left.map { i => Incomplete.transformBU(i)(convertCyclicInc andThen taskToKey andThen liftAnonymous) }
result.toEither.left.map { i =>
Incomplete.transformBU(i)(convertCyclicInc andThen taskToKey andThen liftAnonymous)
}
def taskToKey: Incomplete => Incomplete = {
case in @ Incomplete(Some(node: Task[_]), _, _, _, _) => in.copy(node = transformNode(node))
case i => i
@ -367,7 +433,8 @@ object EvaluateTask {
def convertCyclic(c: AnyCyclic): String =
(c.caller, c.target) match {
case (caller: Task[_], target: Task[_]) =>
c.toString + (if (caller eq target) "(task: " + name(caller) + ")" else "(caller: " + name(caller) + ", target: " + name(target) + ")")
c.toString + (if (caller eq target) "(task: " + name(caller) + ")"
else "(caller: " + name(caller) + ", target: " + name(target) + ")")
case _ => c.toString
}
@ -381,7 +448,9 @@ object EvaluateTask {
}
def processResult[T](result: Result[T], log: Logger, show: Boolean = false): T =
onResult(result, log) { v => if (show) println("Result: " + v); v }
onResult(result, log) { v =>
if (show) println("Result: " + v); v
}
def onResult[T, S](result: Result[T], log: Logger)(f: T => S): S =
result match {
case Value(v) => f(v)
@ -399,5 +468,5 @@ object EvaluateTask {
}).value
})
else
Nil
Nil
}

View File

@ -9,7 +9,9 @@ import sbt.internal.util.AttributeKey
import sbt.util.Show
import std.Transform.DummyTaskMap
final case class Extracted(structure: BuildStructure, session: SessionSettings, currentRef: ProjectRef)(implicit val showKey: Show[ScopedKey[_]]) {
final case class Extracted(structure: BuildStructure,
session: SessionSettings,
currentRef: ProjectRef)(implicit val showKey: Show[ScopedKey[_]]) {
def rootProject = structure.rootProject
lazy val currentUnit = structure units currentRef.build
lazy val currentProject = currentUnit defined currentRef.project
@ -29,7 +31,8 @@ final case class Extracted(structure: BuildStructure, session: SessionSettings,
* Other axes are resolved to be `Global` if they are not specified.
*/
def getOpt[T](key: SettingKey[T]): Option[T] = structure.data.get(inCurrent(key.scope), key.key)
def getOpt[T](key: TaskKey[T]): Option[Task[T]] = structure.data.get(inCurrent(key.scope), key.key)
def getOpt[T](key: TaskKey[T]): Option[Task[T]] =
structure.data.get(inCurrent(key.scope), key.key)
private[this] def inCurrent[T](scope: Scope): Scope =
if (scope.project == This) scope.copy(project = Select(currentRef)) else scope
@ -41,15 +44,15 @@ final case class Extracted(structure: BuildStructure, session: SessionSettings,
*
* This method requests execution of only the given task and does not aggregate execution. See `runAggregated` for that.
*/
def runTask[T](key: TaskKey[T], state: State): (State, T) =
{
import EvaluateTask._
val rkey = resolve(key.scopedKey)
val config = extractedTaskConfig(this, structure, state)
val value: Option[(State, Result[T])] = apply(structure, key.scopedKey, state, currentRef, config)
val (newS, result) = getOrError(rkey.scope, rkey.key, value)
(newS, processResult(result, newS.log))
}
def runTask[T](key: TaskKey[T], state: State): (State, T) = {
import EvaluateTask._
val rkey = resolve(key.scopedKey)
val config = extractedTaskConfig(this, structure, state)
val value: Option[(State, Result[T])] =
apply(structure, key.scopedKey, state, currentRef, config)
val (newS, result) = getOrError(rkey.scope, rkey.key, value)
(newS, processResult(result, newS.log))
}
/**
* Runs the input task specified by `key`, using the `input` as the input to it, and returns the transformed State
@ -64,7 +67,9 @@ final case class Extracted(structure: BuildStructure, session: SessionSettings,
import EvaluateTask._
val scopedKey = Scoped.scopedSetting(
Scope.resolveScope(Load.projectScope(currentRef), currentRef.build, structure.rootProject)(key.scope), key.key
Scope.resolveScope(Load.projectScope(currentRef), currentRef.build, structure.rootProject)(
key.scope),
key.key
)
val rkey = resolve(scopedKey.scopedKey)
val inputTask = get(Scoped.scopedSetting(rkey.scope, rkey.key))
@ -75,7 +80,8 @@ final case class Extracted(structure: BuildStructure, session: SessionSettings,
val config = extractedTaskConfig(this, structure, state)
withStreams(structure, state) { str =>
val nv = nodeView(state, str, rkey :: Nil)
val (newS, result) = EvaluateTask.runTask(task, state, str, structure.index.triggers, config)(nv)
val (newS, result) =
EvaluateTask.runTask(task, state, str, structure.index.triggers, config)(nv)
(newS, processResult(result, newS.log))
}
}
@ -86,25 +92,33 @@ final case class Extracted(structure: BuildStructure, session: SessionSettings,
* The project axis is what determines where aggregation starts, so ensure this is set to what you want.
* Other axes are resolved to `Global` if unspecified.
*/
def runAggregated[T](key: TaskKey[T], state: State): State =
{
val rkey = resolve(key.scopedKey)
val keys = Aggregation.aggregate(rkey, ScopeMask(), structure.extra)
val tasks = Act.keyValues(structure)(keys)
Aggregation.runTasks(state, structure, tasks, DummyTaskMap(Nil), show = Aggregation.defaultShow(state, false))(showKey)
}
def runAggregated[T](key: TaskKey[T], state: State): State = {
val rkey = resolve(key.scopedKey)
val keys = Aggregation.aggregate(rkey, ScopeMask(), structure.extra)
val tasks = Act.keyValues(structure)(keys)
Aggregation.runTasks(state,
structure,
tasks,
DummyTaskMap(Nil),
show = Aggregation.defaultShow(state, false))(showKey)
}
private[this] def resolve[T](key: ScopedKey[T]): ScopedKey[T] =
Project.mapScope(Scope.resolveScope(GlobalScope, currentRef.build, rootProject))(key.scopedKey)
private def getOrError[T](scope: Scope, key: AttributeKey[_], value: Option[T])(implicit display: Show[ScopedKey[_]]): T =
private def getOrError[T](scope: Scope, key: AttributeKey[_], value: Option[T])(
implicit display: Show[ScopedKey[_]]): T =
value getOrElse sys.error(display.show(ScopedKey(scope, key)) + " is undefined.")
private def getOrError[T](scope: Scope, key: AttributeKey[T])(implicit display: Show[ScopedKey[_]]): T =
structure.data.get(scope, key) getOrElse sys.error(display.show(ScopedKey(scope, key)) + " is undefined.")
private def getOrError[T](scope: Scope, key: AttributeKey[T])(
implicit display: Show[ScopedKey[_]]): T =
structure.data.get(scope, key) getOrElse sys.error(
display.show(ScopedKey(scope, key)) + " is undefined.")
def append(settings: Seq[Setting[_]], state: State): State =
{
val appendSettings = Load.transformSettings(Load.projectScope(currentRef), currentRef.build, rootProject, settings)
val newStructure = Load.reapply(session.original ++ appendSettings, structure)
Project.setProject(session, newStructure, state)
}
def append(settings: Seq[Setting[_]], state: State): State = {
val appendSettings = Load.transformSettings(Load.projectScope(currentRef),
currentRef.build,
rootProject,
settings)
val newStructure = Load.reapply(session.original ++ appendSettings, structure)
Project.setProject(session, newStructure, state)
}
}

View File

@ -28,7 +28,14 @@ import descriptor.ModuleDescriptor, id.ModuleRevisionId
import testing.Framework
import KeyRanks._
import sbt.internal.{ BuildStructure, LoadedBuild, PluginDiscovery, BuildDependencies, SessionSettings, LogManager }
import sbt.internal.{
BuildStructure,
LoadedBuild,
PluginDiscovery,
BuildDependencies,
SessionSettings,
LogManager
}
import sbt.io.FileFilter
import sbt.internal.io.WatchState
import sbt.internal.util.{ AttributeKey, SourcePosition }
@ -77,6 +84,8 @@ import sbt.util.{ Level, Logger }
import org.apache.logging.log4j.core.Appender
import sbt.BuildSyntax._
// format: off
object Keys {
val TraceValues = "-1 to disable, 0 for up to the first sbt frame, or a positive number to set the maximum number of frames shown."
@ -456,3 +465,5 @@ object Keys {
type Streams = std.Streams[ScopedKey[_]]
type TaskStreams = std.TaskStreams[ScopedKey[_]]
}
// format: on

View File

@ -4,13 +4,36 @@
package sbt
import sbt.internal.{
Act, Aggregation, BuildStructure, BuildUnit, CommandExchange, CommandStrings,
EvaluateConfigurations, Inspect, IvyConsole, Load, LoadedBuildUnit, Output,
PluginsDebug, ProjectNavigation, Script, SessionSettings, SetResult,
SettingCompletions, LogManager, DefaultBackgroundJobService
Act,
Aggregation,
BuildStructure,
BuildUnit,
CommandExchange,
CommandStrings,
EvaluateConfigurations,
Inspect,
IvyConsole,
Load,
LoadedBuildUnit,
Output,
PluginsDebug,
ProjectNavigation,
Script,
SessionSettings,
SetResult,
SettingCompletions,
LogManager,
DefaultBackgroundJobService
}
import sbt.internal.util.{
AttributeKey, AttributeMap, ConsoleOut, GlobalLogging, LineRange, MainAppender, SimpleReader, Types
AttributeKey,
AttributeMap,
ConsoleOut,
GlobalLogging,
LineRange,
MainAppender,
SimpleReader,
Types
}
import sbt.util.{ Level, Logger }
@ -36,21 +59,21 @@ import CommandStrings.BootCommand
/** This class is the entry point for sbt. */
final class xMain extends xsbti.AppMain {
def run(configuration: xsbti.AppConfiguration): xsbti.MainResult =
{
import BasicCommands.early
import BasicCommandStrings.runEarly
import BuiltinCommands.defaults
import sbt.internal.CommandStrings.{ BootCommand, DefaultsCommand, InitCommand }
if (!java.lang.Boolean.getBoolean("sbt.skip.version.write")) {
setSbtVersion(configuration.baseDirectory(), configuration.provider().id().version())
}
val state = initialState(configuration,
Seq(defaults, early),
runEarly(DefaultsCommand) :: runEarly(InitCommand) :: BootCommand :: Nil)
notifyUsersAboutShell(state)
runManaged(state)
def run(configuration: xsbti.AppConfiguration): xsbti.MainResult = {
import BasicCommands.early
import BasicCommandStrings.runEarly
import BuiltinCommands.defaults
import sbt.internal.CommandStrings.{ BootCommand, DefaultsCommand, InitCommand }
if (!java.lang.Boolean.getBoolean("sbt.skip.version.write")) {
setSbtVersion(configuration.baseDirectory(), configuration.provider().id().version())
}
val state = initialState(
configuration,
Seq(defaults, early),
runEarly(DefaultsCommand) :: runEarly(InitCommand) :: BootCommand :: Nil)
notifyUsersAboutShell(state)
runManaged(state)
}
private val sbtVersionRegex = """sbt\.version\s*=.*""".r
private def isSbtVersionLine(s: String) = sbtVersionRegex.pattern matcher s matches ()
@ -67,7 +90,8 @@ final class xMain extends xsbti.AppMain {
val sbtVersionAbsent = buildPropsLines forall (!isSbtVersionLine(_))
if (sbtVersionAbsent) {
val errorMessage = s"WARN: No sbt.version set in project/build.properties, base directory: $baseDir"
val errorMessage =
s"WARN: No sbt.version set in project/build.properties, base directory: $baseDir"
try {
if (isSbtProject(baseDir, projectDir)) {
val line = s"sbt.version=$sbtVersion"
@ -83,16 +107,20 @@ final class xMain extends xsbti.AppMain {
private def isInteractive = System.console() != null
private def hasCommand(state: State, cmd: String): Boolean =
(state.remainingCommands find { x => x.commandLine == cmd }).isDefined
(state.remainingCommands find { x =>
x.commandLine == cmd
}).isDefined
/**
* The "boot" command adds "iflast shell" ("if last shell")
* which basically means it falls back to shell if there are no further commands
*/
private def endsWithBoot(state: State) = state.remainingCommands.lastOption exists (_.commandLine == BootCommand)
private def endsWithBoot(state: State) =
state.remainingCommands.lastOption exists (_.commandLine == BootCommand)
private def notifyUsersAboutShell(state: State) =
if (isInteractive && !hasCommand(state, Shell) && !hasCommand(state, OldShell) && !endsWithBoot(state)) {
if (isInteractive && !hasCommand(state, Shell) && !hasCommand(state, OldShell) && !endsWithBoot(
state)) {
state.log warn "Executing in batch mode."
state.log warn " For better performance, hit [ENTER] to switch to interactive mode, or"
state.log warn " consider launching sbt without any commands, or explicitly passing 'shell'"
@ -100,54 +128,69 @@ final class xMain extends xsbti.AppMain {
}
final class ScriptMain extends xsbti.AppMain {
def run(configuration: xsbti.AppConfiguration): xsbti.MainResult =
{
import BasicCommandStrings.runEarly
runManaged(initialState(
def run(configuration: xsbti.AppConfiguration): xsbti.MainResult = {
import BasicCommandStrings.runEarly
runManaged(
initialState(
configuration,
BuiltinCommands.ScriptCommands,
runEarly(Level.Error.toString) :: Script.Name :: Nil
))
}
}
}
final class ConsoleMain extends xsbti.AppMain {
def run(configuration: xsbti.AppConfiguration): xsbti.MainResult =
runManaged(initialState(
configuration,
BuiltinCommands.ConsoleCommands,
IvyConsole.Name :: Nil
))
runManaged(
initialState(
configuration,
BuiltinCommands.ConsoleCommands,
IvyConsole.Name :: Nil
))
}
object StandardMain {
private[sbt] lazy val exchange = new CommandExchange()
def runManaged(s: State): xsbti.MainResult =
{
val previous = TrapExit.installManager()
def runManaged(s: State): xsbti.MainResult = {
val previous = TrapExit.installManager()
try {
try {
try {
MainLoop.runLogged(s)
} finally DefaultBackgroundJobService.backgroundJobService.shutdown()
} finally TrapExit.uninstallManager(previous)
}
MainLoop.runLogged(s)
} finally DefaultBackgroundJobService.backgroundJobService.shutdown()
} finally TrapExit.uninstallManager(previous)
}
/** The common interface to standard output, used for all built-in ConsoleLoggers. */
val console = ConsoleOut.systemOutOverwrite(ConsoleOut.overwriteContaining("Resolving "))
def initialGlobalLogging: GlobalLogging = GlobalLogging.initial(MainAppender.globalDefault(console), File.createTempFile("sbt", ".log"), console)
def initialGlobalLogging: GlobalLogging =
GlobalLogging.initial(MainAppender.globalDefault(console),
File.createTempFile("sbt", ".log"),
console)
def initialState(configuration: xsbti.AppConfiguration, initialDefinitions: Seq[Command], preCommands: Seq[String]): State =
{
import BasicCommandStrings.isEarlyCommand
val userCommands = configuration.arguments.map(_.trim)
val (earlyCommands, normalCommands) = (preCommands ++ userCommands).partition(isEarlyCommand)
val commands = (earlyCommands ++ normalCommands).toList map { x => Exec(x, None) }
val initAttrs = BuiltinCommands.initialAttributes
val s = State(configuration, initialDefinitions, Set.empty, None, commands, State.newHistory, initAttrs, initialGlobalLogging, None, State.Continue)
s.initializeClassLoaderCache
def initialState(configuration: xsbti.AppConfiguration,
initialDefinitions: Seq[Command],
preCommands: Seq[String]): State = {
import BasicCommandStrings.isEarlyCommand
val userCommands = configuration.arguments.map(_.trim)
val (earlyCommands, normalCommands) = (preCommands ++ userCommands).partition(isEarlyCommand)
val commands = (earlyCommands ++ normalCommands).toList map { x =>
Exec(x, None)
}
val initAttrs = BuiltinCommands.initialAttributes
val s = State(configuration,
initialDefinitions,
Set.empty,
None,
commands,
State.newHistory,
initAttrs,
initialGlobalLogging,
None,
State.Continue)
s.initializeClassLoaderCache
}
}
import DefaultParsers._
@ -160,34 +203,79 @@ import TemplateCommandUtil.templateCommand
object BuiltinCommands {
def initialAttributes = AttributeMap.empty
def ConsoleCommands: Seq[Command] = Seq(ignore, exit, IvyConsole.command, setLogLevel, early, act, nop)
def ScriptCommands: Seq[Command] = Seq(ignore, exit, Script.command, setLogLevel, early, act, nop)
def ConsoleCommands: Seq[Command] =
Seq(ignore, exit, IvyConsole.command, setLogLevel, early, act, nop)
def ScriptCommands: Seq[Command] =
Seq(ignore, exit, Script.command, setLogLevel, early, act, nop)
def DefaultCommands: Seq[Command] = Seq(
ignore, help, completionsCommand, about, tasks, settingsCommand, loadProject,
templateCommand, projects, project, reboot, read, history, set, sessionCommand,
inspect, loadProjectImpl, loadFailed, Cross.crossBuild, Cross.switchVersion,
Cross.crossRestoreSession, setOnFailure, clearOnFailure, stashOnFailure, popOnFailure,
setLogLevel, plugin, plugins, ifLast, multi, shell, oldshell, BasicCommands.client,
continuous, eval, alias, append, last, lastGrep, export, boot, nop, call, exit,
early, initialize, act
) ++ compatCommands
def DefaultCommands: Seq[Command] =
Seq(
ignore,
help,
completionsCommand,
about,
tasks,
settingsCommand,
loadProject,
templateCommand,
projects,
project,
reboot,
read,
history,
set,
sessionCommand,
inspect,
loadProjectImpl,
loadFailed,
Cross.crossBuild,
Cross.switchVersion,
Cross.crossRestoreSession,
setOnFailure,
clearOnFailure,
stashOnFailure,
popOnFailure,
setLogLevel,
plugin,
plugins,
ifLast,
multi,
shell,
oldshell,
BasicCommands.client,
continuous,
eval,
alias,
append,
last,
lastGrep,
export,
boot,
nop,
call,
exit,
early,
initialize,
act
) ++ compatCommands
def DefaultBootCommands: Seq[String] = LoadProject :: (IfLast + " " + Shell) :: Nil
def boot = Command.make(BootCommand)(bootParser)
def about = Command.command(AboutCommand, aboutBrief, aboutDetailed) { s => s.log.info(aboutString(s)); s }
def about = Command.command(AboutCommand, aboutBrief, aboutDetailed) { s =>
s.log.info(aboutString(s)); s
}
def setLogLevel = Command.arb(const(logLevelParser), logLevelHelp)(LogManager.setGlobalLogLevel)
private[this] def logLevelParser: Parser[Level.Value] = oneOf(Level.values.toSeq.map(v => v.toString ^^^ v))
private[this] def logLevelParser: Parser[Level.Value] =
oneOf(Level.values.toSeq.map(v => v.toString ^^^ v))
// This parser schedules the default boot commands unless overridden by an alias
def bootParser(s: State) =
{
val orElse = () => DefaultBootCommands.toList ::: s
delegateToAlias(BootCommand, success(orElse))(s)
}
def bootParser(s: State) = {
val orElse = () => DefaultBootCommands.toList ::: s
delegateToAlias(BootCommand, success(orElse))(s)
}
def sbtName(s: State): String = s.configuration.provider.id.name
def sbtVersion(s: State): String = s.configuration.provider.id.version
@ -202,35 +290,35 @@ object BuiltinCommands {
current + built + aboutPlugins(e)
} else "No project is currently loaded"
def aboutPlugins(e: Extracted): String =
{
def list(b: BuildUnit) = b.plugins.detected.autoPlugins.map(_.value.label)
val allPluginNames = e.structure.units.values.flatMap(u => list(u.unit)).toSeq.distinct
if (allPluginNames.isEmpty) "" else allPluginNames.mkString("Available Plugins: ", ", ", "")
}
def aboutPlugins(e: Extracted): String = {
def list(b: BuildUnit) = b.plugins.detected.autoPlugins.map(_.value.label)
val allPluginNames = e.structure.units.values.flatMap(u => list(u.unit)).toSeq.distinct
if (allPluginNames.isEmpty) "" else allPluginNames.mkString("Available Plugins: ", ", ", "")
}
def aboutScala(s: State, e: Extracted): String =
{
val scalaVersion = e.getOpt(Keys.scalaVersion)
val scalaHome = e.getOpt(Keys.scalaHome).flatMap(idFun)
val instance = e.getOpt(Keys.scalaInstance).flatMap(_ => quiet(e.runTask(Keys.scalaInstance, s)._2))
(scalaVersion, scalaHome, instance) match {
case (sv, Some(home), Some(si)) => "local Scala version " + selectScalaVersion(sv, si) + " at " + home.getAbsolutePath
case (_, Some(home), None) => "a local Scala build at " + home.getAbsolutePath
case (sv, None, Some(si)) => "Scala " + selectScalaVersion(sv, si)
case (Some(sv), None, None) => "Scala " + sv
case (None, None, None) => ""
}
def aboutScala(s: State, e: Extracted): String = {
val scalaVersion = e.getOpt(Keys.scalaVersion)
val scalaHome = e.getOpt(Keys.scalaHome).flatMap(idFun)
val instance =
e.getOpt(Keys.scalaInstance).flatMap(_ => quiet(e.runTask(Keys.scalaInstance, s)._2))
(scalaVersion, scalaHome, instance) match {
case (sv, Some(home), Some(si)) =>
"local Scala version " + selectScalaVersion(sv, si) + " at " + home.getAbsolutePath
case (_, Some(home), None) => "a local Scala build at " + home.getAbsolutePath
case (sv, None, Some(si)) => "Scala " + selectScalaVersion(sv, si)
case (Some(sv), None, None) => "Scala " + sv
case (None, None, None) => ""
}
}
def aboutString(s: State): String =
{
val (name, ver, scalaVer, about) = (sbtName(s), sbtVersion(s), scalaVersion(s), aboutProject(s))
"""This is %s %s
def aboutString(s: State): String = {
val (name, ver, scalaVer, about) =
(sbtName(s), sbtVersion(s), scalaVersion(s), aboutProject(s))
"""This is %s %s
|%s
|%s, %s plugins, and build definitions are using Scala %s
|""".stripMargin.format(name, ver, about, name, name, scalaVer)
}
}
private[this] def selectScalaVersion(sv: Option[String], si: ScalaInstance): String =
sv match {
@ -238,15 +326,26 @@ object BuiltinCommands {
case _ => si.actualVersion
}
private[this] def quiet[T](t: => T): Option[T] = try { Some(t) } catch { case e: Exception => None }
private[this] def quiet[T](t: => T): Option[T] = try { Some(t) } catch {
case e: Exception => None
}
def settingsCommand: Command =
showSettingLike(SettingsCommand, settingsPreamble, KeyRanks.MainSettingCutoff, key => !isTask(key.manifest))
showSettingLike(SettingsCommand,
settingsPreamble,
KeyRanks.MainSettingCutoff,
key => !isTask(key.manifest))
def tasks: Command =
showSettingLike(TasksCommand, tasksPreamble, KeyRanks.MainTaskCutoff, key => isTask(key.manifest))
showSettingLike(TasksCommand,
tasksPreamble,
KeyRanks.MainTaskCutoff,
key => isTask(key.manifest))
def showSettingLike(command: String, preamble: String, cutoff: Int, keep: AttributeKey[_] => Boolean): Command =
def showSettingLike(command: String,
preamble: String,
cutoff: Int,
keep: AttributeKey[_] => Boolean): Command =
Command(command, settingsBrief(command), settingsDetailed(command))(showSettingParser(keep)) {
case (s: State, (verbosity: Int, selected: Option[String])) =>
if (selected.isEmpty) System.out.println(preamble)
@ -257,92 +356,107 @@ object BuiltinCommands {
if (prominentOnly) System.out.println(moreAvailableMessage(command, selected.isDefined))
s
}
def showSettingParser(keepKeys: AttributeKey[_] => Boolean)(s: State): Parser[(Int, Option[String])] =
def showSettingParser(keepKeys: AttributeKey[_] => Boolean)(
s: State): Parser[(Int, Option[String])] =
verbosityParser ~ selectedParser(s, keepKeys).?
def selectedParser(s: State, keepKeys: AttributeKey[_] => Boolean): Parser[String] =
singleArgument(allTaskAndSettingKeys(s).filter(keepKeys).map(_.label).toSet)
def verbosityParser: Parser[Int] = success(1) | ((Space ~ "-") ~> (
'v'.id.+.map(_.size + 1) |
("V" ^^^ Int.MaxValue)
))
def verbosityParser: Parser[Int] =
success(1) | ((Space ~ "-") ~> (
'v'.id.+.map(_.size + 1) |
("V" ^^^ Int.MaxValue)
))
def taskDetail(keys: Seq[AttributeKey[_]]): Seq[(String, String)] =
sortByLabel(withDescription(keys)) flatMap taskStrings
def allTaskAndSettingKeys(s: State): Seq[AttributeKey[_]] =
{
val extracted = Project.extract(s)
import extracted._
val index = structure.index
index.keyIndex.keys(Some(currentRef)).toSeq.map { key =>
try
Some(index.keyMap(key))
def allTaskAndSettingKeys(s: State): Seq[AttributeKey[_]] = {
val extracted = Project.extract(s)
import extracted._
val index = structure.index
index.keyIndex
.keys(Some(currentRef))
.toSeq
.map { key =>
try Some(index.keyMap(key))
catch {
case NonFatal(ex) =>
s.log debug ex.getMessage
None
}
}.collect { case Some(s) => s }.distinct
}
}
.collect { case Some(s) => s }
.distinct
}
def sortByLabel(keys: Seq[AttributeKey[_]]): Seq[AttributeKey[_]] = keys.sortBy(_.label)
def sortByRank(keys: Seq[AttributeKey[_]]): Seq[AttributeKey[_]] = keys.sortBy(_.rank)
def withDescription(keys: Seq[AttributeKey[_]]): Seq[AttributeKey[_]] = keys.filter(_.description.isDefined)
def isTask(mf: Manifest[_])(implicit taskMF: Manifest[Task[_]], inputMF: Manifest[InputTask[_]]): Boolean =
def withDescription(keys: Seq[AttributeKey[_]]): Seq[AttributeKey[_]] =
keys.filter(_.description.isDefined)
def isTask(mf: Manifest[_])(implicit taskMF: Manifest[Task[_]],
inputMF: Manifest[InputTask[_]]): Boolean =
mf.runtimeClass == taskMF.runtimeClass || mf.runtimeClass == inputMF.runtimeClass
def topNRanked(n: Int) = (keys: Seq[AttributeKey[_]]) => sortByRank(keys).take(n)
def highPass(rankCutoff: Int) = (keys: Seq[AttributeKey[_]]) => sortByRank(keys).takeWhile(_.rank <= rankCutoff)
def highPass(rankCutoff: Int) =
(keys: Seq[AttributeKey[_]]) => sortByRank(keys).takeWhile(_.rank <= rankCutoff)
def tasksHelp(s: State, filter: Seq[AttributeKey[_]] => Seq[AttributeKey[_]], arg: Option[String]): String =
{
val commandAndDescription = taskDetail(filter(allTaskAndSettingKeys(s)))
arg match {
case Some(selected) => detail(selected, commandAndDescription.toMap)
case None => aligned(" ", " ", commandAndDescription) mkString ("\n", "\n", "")
}
def tasksHelp(s: State,
filter: Seq[AttributeKey[_]] => Seq[AttributeKey[_]],
arg: Option[String]): String = {
val commandAndDescription = taskDetail(filter(allTaskAndSettingKeys(s)))
arg match {
case Some(selected) => detail(selected, commandAndDescription.toMap)
case None => aligned(" ", " ", commandAndDescription) mkString ("\n", "\n", "")
}
}
def taskStrings(key: AttributeKey[_]): Option[(String, String)] = key.description map { d => (key.label, d) }
def taskStrings(key: AttributeKey[_]): Option[(String, String)] = key.description map { d =>
(key.label, d)
}
def defaults = Command.command(DefaultsCommand) { s =>
s.copy(definedCommands = DefaultCommands)
}
def initialize: Command = Command.command(InitCommand) { s =>
/*"load-commands -base ~/.sbt/commands" :: */ readLines(readable(sbtRCs(s))).toList ::: s
/*"load-commands -base ~/.sbt/commands" :: */
readLines(readable(sbtRCs(s))).toList ::: s
}
def eval: Command = Command.single(EvalCommand, Help.more(EvalCommand, evalDetailed)) { (s, arg) =>
if (Project.isProjectLoaded(s)) loadedEval(s, arg) else rawEval(s, arg)
s
def eval: Command = Command.single(EvalCommand, Help.more(EvalCommand, evalDetailed)) {
(s, arg) =>
if (Project.isProjectLoaded(s)) loadedEval(s, arg) else rawEval(s, arg)
s
}
private[this] def loadedEval(s: State, arg: String): Unit = {
val extracted = Project extract s
import extracted._
val result = session.currentEval().eval(arg, srcName = "<eval>", imports = autoImports(extracted))
val result =
session.currentEval().eval(arg, srcName = "<eval>", imports = autoImports(extracted))
s.log.info(s"ans: ${result.tpe} = ${result.getValue(currentLoader)}")
}
private[this] def rawEval(s: State, arg: String): Unit = {
val app = s.configuration.provider
val classpath = app.mainClasspath ++ app.scalaProvider.jars
val result = Load.mkEval(classpath, s.baseDir, Nil).eval(arg, srcName = "<eval>", imports = new EvalImports(Nil, ""))
val result = Load
.mkEval(classpath, s.baseDir, Nil)
.eval(arg, srcName = "<eval>", imports = new EvalImports(Nil, ""))
s.log.info(s"ans: ${result.tpe} = ${result.getValue(app.loader)}")
}
def sessionCommand: Command =
Command.make(SessionCommand, sessionBrief, SessionSettings.Help)(SessionSettings.command)
def reapply(newSession: SessionSettings, structure: BuildStructure, s: State): State =
{
s.log.info("Reapplying settings...")
// For correct behavior, we also need to re-inject a settings logger, as we'll be re-evaluating settings
val loggerInject = LogManager.settingsLogger(s)
val withLogger = newSession.appendRaw(loggerInject :: Nil)
val show = Project.showContextKey(newSession, structure)
val newStructure = Load.reapply(withLogger.mergeSettings, structure)(show)
Project.setProject(newSession, newStructure, s)
}
def reapply(newSession: SessionSettings, structure: BuildStructure, s: State): State = {
s.log.info("Reapplying settings...")
// For correct behavior, we also need to re-inject a settings logger, as we'll be re-evaluating settings
val loggerInject = LogManager.settingsLogger(s)
val withLogger = newSession.appendRaw(loggerInject :: Nil)
val show = Project.showContextKey(newSession, structure)
val newStructure = Load.reapply(withLogger.mergeSettings, structure)(show)
Project.setProject(newSession, newStructure, s)
}
def set: Command = Command(SetCommand, setBrief, setDetailed)(setParser) {
case (s, (all, arg)) =>
@ -369,7 +483,10 @@ object BuiltinCommands {
reapply(setResult.session, structure, s)
}
def setThis(s: State, extracted: Extracted, settings: Seq[Def.Setting[_]], arg: String): SetResult =
def setThis(s: State,
extracted: Extracted,
settings: Seq[Def.Setting[_]],
arg: String): SetResult =
SettingCompletions.setThis(s, extracted, settings, arg)
def inspect: Command = Command(InspectCommand, inspectBrief, inspectDetailed)(Inspect.parser) {
@ -378,15 +495,16 @@ object BuiltinCommands {
s
}
def lastGrep: Command = Command(LastGrepCommand, lastGrepBrief, lastGrepDetailed)(lastGrepParser) {
case (s, (pattern, Some(sks))) =>
val (str, _, display) = extractLast(s)
Output.lastGrep(sks, str.streams(s), pattern, printLast(s))(display)
keepLastLog(s)
case (s, (pattern, None)) =>
for (logFile <- lastLogFile(s)) yield Output.lastGrep(logFile, pattern, printLast(s))
keepLastLog(s)
}
def lastGrep: Command =
Command(LastGrepCommand, lastGrepBrief, lastGrepDetailed)(lastGrepParser) {
case (s, (pattern, Some(sks))) =>
val (str, _, display) = extractLast(s)
Output.lastGrep(sks, str.streams(s), pattern, printLast(s))(display)
keepLastLog(s)
case (s, (pattern, None)) =>
for (logFile <- lastLogFile(s)) yield Output.lastGrep(logFile, pattern, printLast(s))
keepLastLog(s)
}
def extractLast(s: State) = {
val ext = Project.extract(s)
(ext.structure, Select(ext.currentRef), ext.showKey)
@ -399,32 +517,43 @@ object BuiltinCommands {
SettingCompletions.settingParser(structure.data, structure.index.keyMap, currentProject)
}
val spacedAggregatedParser = (s: State) => Act.requireSession(s, token(Space) ~> Act.aggregatedKeyParser(s))
val aggregatedKeyValueParser: State => Parser[Option[AnyKeys]] = (s: State) => spacedAggregatedParser(s).map(x => Act.keyValues(s)(x)).?
val spacedAggregatedParser = (s: State) =>
Act.requireSession(s, token(Space) ~> Act.aggregatedKeyParser(s))
val aggregatedKeyValueParser: State => Parser[Option[AnyKeys]] = (s: State) =>
spacedAggregatedParser(s).map(x => Act.keyValues(s)(x)).?
val exportParser: State => Parser[() => State] = (s: State) => Act.requireSession(s, token(Space) ~> exportParser0(s))
private[sbt] def exportParser0(s: State): Parser[() => State] =
{
val extracted = Project extract s
import extracted.{ showKey, structure }
val keysParser = token(flag("--last" <~ Space)) ~ Act.aggregatedKeyParser(extracted)
val show = Aggregation.ShowConfig(settingValues = true, taskValues = false, print = println(_), success = false)
for {
lastOnly_keys <- keysParser
kvs = Act.keyValues(structure)(lastOnly_keys._2)
f <- if (lastOnly_keys._1) success(() => s) else Aggregation.evaluatingParser(s, structure, show)(kvs)
} yield () => {
val exportParser: State => Parser[() => State] = (s: State) =>
Act.requireSession(s, token(Space) ~> exportParser0(s))
private[sbt] def exportParser0(s: State): Parser[() => State] = {
val extracted = Project extract s
import extracted.{ showKey, structure }
val keysParser = token(flag("--last" <~ Space)) ~ Act.aggregatedKeyParser(extracted)
val show = Aggregation.ShowConfig(settingValues = true,
taskValues = false,
print = println(_),
success = false)
for {
lastOnly_keys <- keysParser
kvs = Act.keyValues(structure)(lastOnly_keys._2)
f <- if (lastOnly_keys._1) success(() => s)
else Aggregation.evaluatingParser(s, structure, show)(kvs)
} yield
() => {
def export0(s: State): State = lastImpl(s, kvs, Some(ExportStream))
val newS = try f() catch {
val newS = try f()
catch {
case NonFatal(e) =>
try export0(s)
finally { throw e }
}
export0(newS)
}
}
}
def lastGrepParser(s: State) = Act.requireSession(s, (token(Space) ~> token(NotSpace, "<pattern>")) ~ aggregatedKeyValueParser(s))
def lastGrepParser(s: State) =
Act.requireSession(
s,
(token(Space) ~> token(NotSpace, "<pattern>")) ~ aggregatedKeyValueParser(s))
def last = Command(LastCommand, lastBrief, lastDetailed)(aggregatedKeyValueParser) {
case (s, Some(sks)) => lastImpl(s, sks, None)
case (s, None) =>
@ -433,19 +562,17 @@ object BuiltinCommands {
}
def export = Command(ExportCommand, exportBrief, exportDetailed)(exportParser)((s, f) => f())
private[this] def lastImpl(s: State, sks: AnyKeys, sid: Option[String]): State =
{
val (str, _, display) = extractLast(s)
Output.last(sks, str.streams(s), printLast(s), sid)(display)
keepLastLog(s)
}
private[this] def lastImpl(s: State, sks: AnyKeys, sid: Option[String]): State = {
val (str, _, display) = extractLast(s)
Output.last(sks, str.streams(s), printLast(s), sid)(display)
keepLastLog(s)
}
/** Determines the log file that last* commands should operate on. See also isLastOnly. */
def lastLogFile(s: State) =
{
val backing = s.globalLogging.backing
if (isLastOnly(s)) backing.last else Some(backing.file)
}
def lastLogFile(s: State) = {
val backing = s.globalLogging.backing
if (isLastOnly(s)) backing.last else Some(backing.file)
}
/**
* If false, shift the current log file to be the log file that 'last' will operate on.
@ -464,22 +591,26 @@ object BuiltinCommands {
def printLast(s: State): Seq[String] => Unit = _ foreach println
def autoImports(extracted: Extracted): EvalImports = new EvalImports(imports(extracted), "<auto-imports>")
def imports(extracted: Extracted): Seq[(String, Int)] =
{
val curi = extracted.currentRef.build
extracted.structure.units(curi).imports.map(s => (s, -1))
}
def autoImports(extracted: Extracted): EvalImports =
new EvalImports(imports(extracted), "<auto-imports>")
def imports(extracted: Extracted): Seq[(String, Int)] = {
val curi = extracted.currentRef.build
extracted.structure.units(curi).imports.map(s => (s, -1))
}
def listBuild(uri: URI, build: LoadedBuildUnit, current: Boolean, currentID: String, log: Logger) =
{
log.info("In " + uri)
def prefix(id: String) = if (currentID != id) " " else if (current) " * " else "(*)"
for (id <- build.defined.keys.toSeq.sorted) log.info("\t" + prefix(id) + id)
}
def listBuild(uri: URI,
build: LoadedBuildUnit,
current: Boolean,
currentID: String,
log: Logger) = {
log.info("In " + uri)
def prefix(id: String) = if (currentID != id) " " else if (current) " * " else "(*)"
for (id <- build.defined.keys.toSeq.sorted) log.info("\t" + prefix(id) + id)
}
def act: Command = Command.customHelp(Act.actParser, actHelp)
def actHelp: State => Help = s => CommandStrings.showHelp ++ CommandStrings.multiTaskHelp ++ keysHelp(s)
def actHelp: State => Help =
s => CommandStrings.showHelp ++ CommandStrings.multiTaskHelp ++ keysHelp(s)
def keysHelp(s: State): Help =
if (Project.isProjectLoaded(s))
@ -505,7 +636,8 @@ object BuiltinCommands {
}
def projects: Command =
Command(ProjectsCommand, (ProjectsCommand, projectsBrief), projectsDetailed)(s => projectsParser(s).?) {
Command(ProjectsCommand, (ProjectsCommand, projectsBrief), projectsDetailed)(s =>
projectsParser(s).?) {
case (s, Some(modifyBuilds)) => transformExtraBuilds(s, modifyBuilds)
case (s, None) => showProjects(s); s
}
@ -518,47 +650,48 @@ object BuiltinCommands {
for ((uri, build) <- structure.units if curi != uri) listBuild(uri, build, false, cid, s.log)
}
def transformExtraBuilds(s: State, f: List[URI] => List[URI]): State =
{
val original = Project.extraBuilds(s)
val extraUpdated = Project.updateExtraBuilds(s, f)
try doLoadProject(extraUpdated, LoadAction.Current)
catch {
case e: Exception =>
s.log.error("Project loading failed: reverting to previous state.")
Project.setExtraBuilds(s, original)
}
def transformExtraBuilds(s: State, f: List[URI] => List[URI]): State = {
val original = Project.extraBuilds(s)
val extraUpdated = Project.updateExtraBuilds(s, f)
try doLoadProject(extraUpdated, LoadAction.Current)
catch {
case e: Exception =>
s.log.error("Project loading failed: reverting to previous state.")
Project.setExtraBuilds(s, original)
}
}
def projectsParser(s: State): Parser[List[URI] => List[URI]] =
{
val addBase = token(Space ~> "add") ~> token(Space ~> basicUri, "<build URI>").+
val removeBase = token(Space ~> "remove") ~> token(Space ~> Uri(Project.extraBuilds(s).toSet)).+
addBase.map(toAdd => (xs: List[URI]) => (toAdd.toList ::: xs).distinct) |
removeBase.map(toRemove => (xs: List[URI]) => xs.filterNot(toRemove.toSet))
}
def projectsParser(s: State): Parser[List[URI] => List[URI]] = {
val addBase = token(Space ~> "add") ~> token(Space ~> basicUri, "<build URI>").+
val removeBase = token(Space ~> "remove") ~> token(Space ~> Uri(Project.extraBuilds(s).toSet)).+
addBase.map(toAdd => (xs: List[URI]) => (toAdd.toList ::: xs).distinct) |
removeBase.map(toRemove => (xs: List[URI]) => xs.filterNot(toRemove.toSet))
}
def project: Command = Command.make(ProjectCommand, projectBrief, projectDetailed)(ProjectNavigation.command)
def project: Command =
Command.make(ProjectCommand, projectBrief, projectDetailed)(ProjectNavigation.command)
def loadFailed: Command = Command(LoadFailed)(loadProjectParser)(doLoadFailed)
@tailrec
private[this] def doLoadFailed(s: State, loadArg: String): State =
{
val result = (SimpleReader.readLine("Project loading failed: (r)etry, (q)uit, (l)ast, or (i)gnore? ") getOrElse Quit).toLowerCase(Locale.ENGLISH)
def matches(s: String) = !result.isEmpty && (s startsWith result)
def retry = loadProjectCommand(LoadProject, loadArg) :: s.clearGlobalLog
def ignoreMsg = if (Project.isProjectLoaded(s)) "using previously loaded project" else "no project loaded"
private[this] def doLoadFailed(s: State, loadArg: String): State = {
val result = (SimpleReader.readLine(
"Project loading failed: (r)etry, (q)uit, (l)ast, or (i)gnore? ") getOrElse Quit)
.toLowerCase(Locale.ENGLISH)
def matches(s: String) = !result.isEmpty && (s startsWith result)
def retry = loadProjectCommand(LoadProject, loadArg) :: s.clearGlobalLog
def ignoreMsg =
if (Project.isProjectLoaded(s)) "using previously loaded project" else "no project loaded"
result match {
case "" => retry
case _ if matches("retry") => retry
case _ if matches(Quit) => s.exit(ok = false)
case _ if matches("ignore") => s.log.warn(s"Ignoring load failure: $ignoreMsg."); s
case _ if matches("last") => LastCommand :: loadProjectCommand(LoadFailed, loadArg) :: s
case _ => println("Invalid response."); doLoadFailed(s, loadArg)
}
result match {
case "" => retry
case _ if matches("retry") => retry
case _ if matches(Quit) => s.exit(ok = false)
case _ if matches("ignore") => s.log.warn(s"Ignoring load failure: $ignoreMsg."); s
case _ if matches("last") => LastCommand :: loadProjectCommand(LoadFailed, loadArg) :: s
case _ => println("Invalid response."); doLoadFailed(s, loadArg)
}
}
def loadProjectCommands(arg: String): List[String] =
StashOnFailure ::
@ -570,49 +703,51 @@ object BuiltinCommands {
def loadProject: Command =
Command(LoadProject, LoadProjectBrief, LoadProjectDetailed)(loadProjectParser)((s, arg) =>
loadProjectCommands(arg) ::: s
)
loadProjectCommands(arg) ::: s)
private[this] def loadProjectParser: State => Parser[String] = _ => matched(Project.loadActionParser)
private[this] def loadProjectCommand(command: String, arg: String): String = s"$command $arg".trim
private[this] def loadProjectParser: State => Parser[String] =
_ => matched(Project.loadActionParser)
private[this] def loadProjectCommand(command: String, arg: String): String =
s"$command $arg".trim
def loadProjectImpl: Command = Command(LoadProjectImpl)(_ => Project.loadActionParser)(doLoadProject)
def loadProjectImpl: Command =
Command(LoadProjectImpl)(_ => Project.loadActionParser)(doLoadProject)
def doLoadProject(s0: State, action: LoadAction.Value): State =
{
val (s1, base) = Project.loadAction(SessionVar.clear(s0), action)
IO.createDirectory(base)
val s = if (s1 has Keys.stateCompilerCache) s1 else registerCompilerCache(s1)
def doLoadProject(s0: State, action: LoadAction.Value): State = {
val (s1, base) = Project.loadAction(SessionVar.clear(s0), action)
IO.createDirectory(base)
val s = if (s1 has Keys.stateCompilerCache) s1 else registerCompilerCache(s1)
val (eval, structure) =
try Load.defaultLoad(s, base, s.log, Project.inPluginProject(s), Project.extraBuilds(s))
val (eval, structure) =
try Load.defaultLoad(s, base, s.log, Project.inPluginProject(s), Project.extraBuilds(s))
catch {
case ex: compiler.EvalException =>
s0.log.debug(ex.getMessage)
ex.getStackTrace map (ste => s"\tat $ste") foreach (s0.log.debug(_))
ex.setStackTrace(Array.empty)
throw ex
}
val session = Load.initialSession(structure, eval, s0)
SessionSettings.checkSession(session, s)
Project.setProject(session, structure, s)
}
def registerCompilerCache(s: State): State = {
val maxCompilers = System.getProperty("sbt.resident.limit")
val cache =
if (maxCompilers == null)
CompilerCache.fresh
else {
val num = try maxCompilers.toInt
catch {
case ex: compiler.EvalException =>
s0.log.debug(ex.getMessage)
ex.getStackTrace map (ste => s"\tat $ste") foreach (s0.log.debug(_))
ex.setStackTrace(Array.empty)
throw ex
case e: NumberFormatException =>
throw new RuntimeException("Resident compiler limit must be an integer.", e)
}
val session = Load.initialSession(structure, eval, s0)
SessionSettings.checkSession(session, s)
Project.setProject(session, structure, s)
}
def registerCompilerCache(s: State): State =
{
val maxCompilers = System.getProperty("sbt.resident.limit")
val cache =
if (maxCompilers == null)
CompilerCache.fresh
else {
val num = try maxCompilers.toInt catch {
case e: NumberFormatException => throw new RuntimeException("Resident compiler limit must be an integer.", e)
}
if (num <= 0) CompilerCache.fresh else CompilerCache(num)
}
s.put(Keys.stateCompilerCache, cache)
}
if (num <= 0) CompilerCache.fresh else CompilerCache(num)
}
s.put(Keys.stateCompilerCache, cache)
}
def shell: Command = Command.command(Shell, Help.more(Shell, ShellDetailed)) { s0 =>
import sbt.internal.{ ConsolePromptEvent, ConsoleUnpromptEvent }
@ -620,7 +755,10 @@ object BuiltinCommands {
val s1 = exchange run s0
exchange publishEventMessage ConsolePromptEvent(s0)
val exec: Exec = exchange.blockUntilNextExec
val newState = s1.copy(onFailure = Some(Exec(Shell, None)), remainingCommands = exec +: Exec(Shell, None) +: s1.remainingCommands).setInteractive(true)
val newState = s1
.copy(onFailure = Some(Exec(Shell, None)),
remainingCommands = exec +: Exec(Shell, None) +: s1.remainingCommands)
.setInteractive(true)
exchange publishEventMessage ConsoleUnpromptEvent(exec.source)
if (exec.commandLine.trim.isEmpty) newState
else newState.clearGlobalLog

View File

@ -14,6 +14,7 @@ import sbt.util.Logger
import sbt.protocol._
object MainLoop {
/** Entry point to run the remaining commands in State with managed global logging.*/
def runLogged(state: State): xsbti.MainResult = {
// We've disabled jline shutdown hooks to prevent classloader leaks, and have been careful to always restore
@ -48,14 +49,14 @@ object MainLoop {
/** Runs the next sequence of commands, cleaning up global logging after any exceptions. */
def runAndClearLast(state: State, logBacking: GlobalLogBacking): RunNext =
try
runWithNewLog(state, logBacking)
try runWithNewLog(state, logBacking)
catch {
case e: xsbti.FullReload =>
deleteLastLog(logBacking)
throw e // pass along a reboot request
case NonFatal(e) =>
System.err.println("sbt appears to be exiting abnormally.\n The log file for this session is at " + logBacking.file)
System.err.println(
"sbt appears to be exiting abnormally.\n The log file for this session is at " + logBacking.file)
deleteLastLog(logBacking)
throw e
}
@ -72,7 +73,8 @@ object MainLoop {
val newLogging = state.globalLogging.newAppender(full, out, logBacking)
// transferLevels(state, newLogging)
val loggedState = state.copy(globalLogging = newLogging)
try run(loggedState) finally out.close()
try run(loggedState)
finally out.close()
}
// /** Transfers logging and trace levels from the old global loggers to the new ones. */
@ -110,7 +112,10 @@ object MainLoop {
def processCommand(exec: Exec, state: State): State = {
import DefaultParsers._
val channelName = exec.source map (_.channelName)
StandardMain.exchange publishEventMessage ExecStatusEvent("Processing", channelName, exec.execId, Vector())
StandardMain.exchange publishEventMessage ExecStatusEvent("Processing",
channelName,
exec.execId,
Vector())
val parser = Command combine state.definedCommands
val newState = parse(exec.commandLine, parser(state)) match {
case Right(s) => s() // apply command. command side effects happen here
@ -118,7 +123,11 @@ object MainLoop {
state.log error errMsg
state.fail
}
StandardMain.exchange publishEventMessage ExecStatusEvent("Done", channelName, exec.execId, newState.remainingCommands.toVector map (_.commandLine))
StandardMain.exchange publishEventMessage ExecStatusEvent(
"Done",
channelName,
exec.execId,
newState.remainingCommands.toVector map (_.commandLine))
newState
}

View File

@ -27,15 +27,22 @@ object Opts {
def sourceUrl(u: String): Seq[String] = Seq("-doc-source-url", u)
def title(t: String): Seq[String] = Seq("-doc-title", t)
def version(v: String): Seq[String] = Seq("-doc-version", v)
def externalAPI(mappings: Iterable[(File, URL)]): Seq[String] = if (mappings.isEmpty) Nil else
mappings.map { case (f, u) => s"${f.getAbsolutePath}#${u.toExternalForm}" }.mkString("-doc-external-doc:", ",", "") :: Nil
def externalAPI(mappings: Iterable[(File, URL)]): Seq[String] =
if (mappings.isEmpty) Nil
else
mappings
.map { case (f, u) => s"${f.getAbsolutePath}#${u.toExternalForm}" }
.mkString("-doc-external-doc:", ",", "") :: Nil
}
object resolver {
import sbt.io.syntax._
val sonatypeReleases = Resolver.sonatypeRepo("releases")
val sonatypeSnapshots = Resolver.sonatypeRepo("snapshots")
val sonatypeStaging = MavenRepository("sonatype-staging", "https://oss.sonatype.org/service/local/staging/deploy/maven2")
val mavenLocalFile = Resolver.file("Local Repository", userHome / ".m2" / "repository" asFile)(Resolver.defaultPatterns)
val sonatypeStaging = MavenRepository(
"sonatype-staging",
"https://oss.sonatype.org/service/local/staging/deploy/maven2")
val mavenLocalFile = Resolver.file("Local Repository", userHome / ".m2" / "repository" asFile)(
Resolver.defaultPatterns)
val sbtSnapshots = Resolver.bintrayRepo("sbt", "maven-snapshots")
val sbtIvySnapshots = Resolver.bintrayIvyRepo("sbt", "ivy-snapshots")
}
@ -50,8 +57,10 @@ object DefaultOptions {
def javac: Seq[String] = compile.encoding("UTF-8")
def scalac: Seq[String] = compile.encoding("UTF-8")
def javadoc(name: String, version: String): Seq[String] = Seq("-doctitle", "%s %s API".format(name, version))
def scaladoc(name: String, version: String): Seq[String] = doc.title(name) ++ doc.version(version)
def javadoc(name: String, version: String): Seq[String] =
Seq("-doctitle", "%s %s API".format(name, version))
def scaladoc(name: String, version: String): Seq[String] =
doc.title(name) ++ doc.version(version)
def resolvers(snapshot: Boolean): Seq[Resolver] = {
if (snapshot) Seq(resolver.sbtSnapshots) else Nil
@ -60,12 +69,15 @@ object DefaultOptions {
if (plugin && snapshot) Seq(resolver.sbtSnapshots, resolver.sbtIvySnapshots) else Nil
}
def addResolvers: Setting[_] = Keys.resolvers ++= { resolvers(Keys.isSnapshot.value) }
def addPluginResolvers: Setting[_] = Keys.resolvers ++= pluginResolvers(Keys.sbtPlugin.value, Keys.isSnapshot.value)
def addPluginResolvers: Setting[_] =
Keys.resolvers ++= pluginResolvers(Keys.sbtPlugin.value, Keys.isSnapshot.value)
def credentials(state: State): Credentials = Credentials(getGlobalSettingsDirectory(state, getGlobalBase(state)) / ".credentials")
def credentials(state: State): Credentials =
Credentials(getGlobalSettingsDirectory(state, getGlobalBase(state)) / ".credentials")
def addCredentials: Setting[_] = Keys.credentials += { credentials(Keys.state.value) }
def shellPrompt(version: String): State => String =
s => "%s:%s:%s> ".format(s.configuration.provider.id.name, extract(s).currentProject.id, version)
s =>
"%s:%s:%s> ".format(s.configuration.provider.id.name, extract(s).currentProject.id, version)
def setupShellPrompt: Setting[_] = Keys.shellPrompt := { shellPrompt(Keys.version.value) }
}

View File

@ -3,7 +3,7 @@ package sbt
TODO:
- index all available AutoPlugins to get the tasks that will be added
- error message when a task doesn't exist that it would be provided by plugin x, enabled by natures y,z, blocked by a, b
*/
*/
import sbt.librarymanagement.Configuration
@ -60,6 +60,7 @@ import sbt.util.Logger
* then the `MyPlugin` settings (and anything that activates only when `MyPlugin` is activated) will not be added.
*/
abstract class AutoPlugin extends Plugins.Basic with PluginsFunctions {
/**
* Determines whether this AutoPlugin will be activated for this project when the `requires` clause is satisfied.
*
@ -122,13 +123,16 @@ abstract class AutoPlugin extends Plugins.Basic with PluginsFunctions {
* An error that occurs when auto-plugins aren't configured properly.
* It translates the error from the underlying logic system to be targeted at end users.
*/
final class AutoPluginException private (val message: String, val origin: Option[LogicException]) extends RuntimeException(message) {
final class AutoPluginException private (val message: String, val origin: Option[LogicException])
extends RuntimeException(message) {
/** Prepends `p` to the error message derived from `origin`. */
def withPrefix(p: String) = new AutoPluginException(p + message, origin)
}
object AutoPluginException {
def apply(msg: String): AutoPluginException = new AutoPluginException(msg, None)
def apply(origin: LogicException): AutoPluginException = new AutoPluginException(Plugins.translateMessage(origin), Some(origin))
def apply(origin: LogicException): AutoPluginException =
new AutoPluginException(Plugins.translateMessage(origin), Some(origin))
}
sealed trait PluginTrigger
@ -141,76 +145,100 @@ sealed trait Plugins {
}
sealed trait PluginsFunctions {
/** [[Plugins]] instance that doesn't require any [[Plugins]]s. */
def empty: Plugins = Plugins.Empty
/** This plugin is activated when all required plugins are present. */
def allRequirements: PluginTrigger = AllRequirements
/** This plugin is activated only when it is manually activated. */
def noTrigger: PluginTrigger = NoTrigger
}
object Plugins extends PluginsFunctions {
/**
* Given the available auto plugins `defined`, returns a function that selects [[AutoPlugin]]s for the provided [[AutoPlugin]]s.
* The [[AutoPlugin]]s are topologically sorted so that a required [[AutoPlugin]] comes before its requiring [[AutoPlugin]].
*/
def deducer(defined0: List[AutoPlugin]): (Plugins, Logger) => Seq[AutoPlugin] =
if (defined0.isEmpty) (_, _) => Nil
if (defined0.isEmpty)(_, _) => Nil
else {
// TODO: defined should return all the plugins
val allReqs = (defined0 flatMap { asRequirements }).toSet
val diff = allReqs diff defined0.toSet
val defined = if (diff.nonEmpty) diff.toList ::: defined0
else defined0
val defined =
if (diff.nonEmpty) diff.toList ::: defined0
else defined0
val byAtom = defined map { x => (Atom(x.label), x) }
val byAtom = defined map { x =>
(Atom(x.label), x)
}
val byAtomMap = byAtom.toMap
if (byAtom.size != byAtomMap.size) duplicateProvidesError(byAtom)
// Ignore clauses for plugins that does not require anything else.
// Avoids the requirement for pure Nature strings *and* possible
// circular dependencies in the logic.
val allRequirementsClause = defined.filterNot(_.isRoot).flatMap(d => asRequirementsClauses(d))
val allRequirementsClause =
defined.filterNot(_.isRoot).flatMap(d => asRequirementsClauses(d))
val allEnabledByClause = defined.filterNot(_.isRoot).flatMap(d => asEnabledByClauses(d))
// Note: Here is where the function begins. We're given a list of plugins now.
(requestedPlugins, log) => {
timed("Plugins.deducer#function", log) {
def explicitlyDisabled(p: AutoPlugin): Boolean = hasExclude(requestedPlugins, p)
val alwaysEnabled: List[AutoPlugin] = defined.filter(_.isAlwaysEnabled).filterNot(explicitlyDisabled)
val knowledge0: Set[Atom] = ((flatten(requestedPlugins) ++ alwaysEnabled) collect {
case x: AutoPlugin => Atom(x.label)
}).toSet
val clauses = Clauses((allRequirementsClause ::: allEnabledByClause) filterNot { _.head subsetOf knowledge0 })
log.debug(s"deducing auto plugins based on known facts ${knowledge0.toString} and clauses ${clauses.toString}")
Logic.reduce(clauses, (flattenConvert(requestedPlugins) ++ convertAll(alwaysEnabled)).toSet) match {
case Left(problem) => throw AutoPluginException(problem)
case Right(results) =>
log.debug(s" :: deduced result: ${results}")
val selectedAtoms: List[Atom] = results.ordered
val selectedPlugins = selectedAtoms map { a =>
byAtomMap.getOrElse(a, throw AutoPluginException(s"${a} was not found in atom map."))
}
val forbidden: Set[AutoPlugin] = (selectedPlugins flatMap { Plugins.asExclusions }).toSet
val c = selectedPlugins.toSet & forbidden
if (c.nonEmpty) {
exclusionConflictError(requestedPlugins, selectedPlugins, c.toSeq sortBy { _.label })
}
val retval = topologicalSort(selectedPlugins, log)
// log.debug(s" :: sorted deduced result: ${retval.toString}")
retval
(requestedPlugins, log) =>
{
timed("Plugins.deducer#function", log) {
def explicitlyDisabled(p: AutoPlugin): Boolean = hasExclude(requestedPlugins, p)
val alwaysEnabled: List[AutoPlugin] =
defined.filter(_.isAlwaysEnabled).filterNot(explicitlyDisabled)
val knowledge0: Set[Atom] = ((flatten(requestedPlugins) ++ alwaysEnabled) collect {
case x: AutoPlugin => Atom(x.label)
}).toSet
val clauses = Clauses((allRequirementsClause ::: allEnabledByClause) filterNot {
_.head subsetOf knowledge0
})
log.debug(
s"deducing auto plugins based on known facts ${knowledge0.toString} and clauses ${clauses.toString}")
Logic.reduce(
clauses,
(flattenConvert(requestedPlugins) ++ convertAll(alwaysEnabled)).toSet) match {
case Left(problem) => throw AutoPluginException(problem)
case Right(results) =>
log.debug(s" :: deduced result: ${results}")
val selectedAtoms: List[Atom] = results.ordered
val selectedPlugins = selectedAtoms map { a =>
byAtomMap.getOrElse(
a,
throw AutoPluginException(s"${a} was not found in atom map."))
}
val forbidden: Set[AutoPlugin] =
(selectedPlugins flatMap { Plugins.asExclusions }).toSet
val c = selectedPlugins.toSet & forbidden
if (c.nonEmpty) {
exclusionConflictError(requestedPlugins, selectedPlugins, c.toSeq sortBy {
_.label
})
}
val retval = topologicalSort(selectedPlugins, log)
// log.debug(s" :: sorted deduced result: ${retval.toString}")
retval
}
}
}
}
}
private[sbt] def topologicalSort(ns: List[AutoPlugin], log: Logger): List[AutoPlugin] = {
// log.debug(s"sorting: ns: ${ns.toString}")
@tailrec def doSort(found0: List[AutoPlugin], notFound0: List[AutoPlugin], limit0: Int): List[AutoPlugin] = {
@tailrec
def doSort(found0: List[AutoPlugin],
notFound0: List[AutoPlugin],
limit0: Int): List[AutoPlugin] = {
// log.debug(s" :: sorting:: found: ${found0.toString} not found ${notFound0.toString}")
if (limit0 < 0) throw AutoPluginException(s"Failed to sort ${ns} topologically")
else if (notFound0.isEmpty) found0
else {
val (found1, notFound1) = notFound0 partition { n => asRequirements(n).toSet subsetOf found0.toSet }
val (found1, notFound1) = notFound0 partition { n =>
asRequirements(n).toSet subsetOf found0.toSet
}
doSort(found0 ::: found1, notFound1, limit0 - 1)
}
}
@ -218,38 +246,50 @@ object Plugins extends PluginsFunctions {
doSort(roots, nonRoots, ns.size * ns.size + 1)
}
private[sbt] def translateMessage(e: LogicException) = e match {
case ic: InitialContradictions => s"Contradiction in selected plugins. These plugins were both included and excluded: ${literalsString(ic.literals.toSeq)}"
case io: InitialOverlap => s"Cannot directly enable plugins. Plugins are enabled when their required plugins are satisfied. The directly selected plugins were: ${literalsString(io.literals.toSeq)}"
case cn: CyclicNegation => s"Cycles in plugin requirements cannot involve excludes. The problematic cycle is: ${literalsString(cn.cycle)}"
case ic: InitialContradictions =>
s"Contradiction in selected plugins. These plugins were both included and excluded: ${literalsString(
ic.literals.toSeq)}"
case io: InitialOverlap =>
s"Cannot directly enable plugins. Plugins are enabled when their required plugins are satisfied. The directly selected plugins were: ${literalsString(
io.literals.toSeq)}"
case cn: CyclicNegation =>
s"Cycles in plugin requirements cannot involve excludes. The problematic cycle is: ${literalsString(cn.cycle)}"
}
private[this] def literalsString(lits: Seq[Literal]): String =
lits map { case Atom(l) => l; case Negated(Atom(l)) => l } mkString (", ")
private[this] def duplicateProvidesError(byAtom: Seq[(Atom, AutoPlugin)]): Unit = {
val dupsByAtom = byAtom.groupBy(_._1).mapValues(_.map(_._2))
val dupStrings = for ((atom, dups) <- dupsByAtom if dups.size > 1) yield s"${atom.label} by ${dups.mkString(", ")}"
val dupStrings = for ((atom, dups) <- dupsByAtom if dups.size > 1)
yield s"${atom.label} by ${dups.mkString(", ")}"
val (ns, nl) = if (dupStrings.size > 1) ("s", "\n\t") else ("", " ")
val message = s"Plugin$ns provided by multiple AutoPlugins:$nl${dupStrings.mkString(nl)}"
throw AutoPluginException(message)
}
private[this] def exclusionConflictError(requested: Plugins, selected: Seq[AutoPlugin], conflicting: Seq[AutoPlugin]): Unit = {
def listConflicts(ns: Seq[AutoPlugin]) = (ns map { c =>
val reasons = (if (flatten(requested) contains c) List("requested")
else Nil) ++
(if (c.requires != empty && c.trigger == allRequirements) List(s"enabled by ${c.requires.toString}")
else Nil) ++
{
val reqs = selected filter { x => asRequirements(x) contains c }
private[this] def exclusionConflictError(requested: Plugins,
selected: Seq[AutoPlugin],
conflicting: Seq[AutoPlugin]): Unit = {
def listConflicts(ns: Seq[AutoPlugin]) =
(ns map { c =>
val reasons = (if (flatten(requested) contains c) List("requested")
else Nil) ++
(if (c.requires != empty && c.trigger == allRequirements)
List(s"enabled by ${c.requires.toString}")
else Nil) ++ {
val reqs = selected filter { x =>
asRequirements(x) contains c
}
if (reqs.nonEmpty) List(s"""required by ${reqs.mkString(", ")}""")
else Nil
} ++
{
val exs = selected filter { x => asExclusions(x) contains c }
} ++ {
val exs = selected filter { x =>
asExclusions(x) contains c
}
if (exs.nonEmpty) List(s"""excluded by ${exs.mkString(", ")}""")
else Nil
}
s""" - conflict: ${c.label} is ${reasons.mkString("; ")}"""
}).mkString("\n")
s""" - conflict: ${c.label} is ${reasons.mkString("; ")}"""
}).mkString("\n")
throw AutoPluginException(s"""Contradiction in enabled plugins:
- requested: ${requested.toString}
- enabled: ${selected.mkString(", ")}
@ -291,16 +331,21 @@ ${listConflicts(conflicting)}""")
// `ap` is the head and the required plugins for `ap` is the body.
if (ap.trigger == AllRequirements) Clause(convert(ap.requires), Set(Atom(ap.label))) :: Nil
else Nil
/** Defines requirements clauses for `ap`. */
private[sbt] def asRequirementsClauses(ap: AutoPlugin): List[Clause] =
// required plugin is the head and `ap` is the body.
asRequirements(ap) map { x => Clause(convert(ap), Set(Atom(x.label))) }
private[sbt] def asRequirements(ap: AutoPlugin): List[AutoPlugin] = flatten(ap.requires).toList collect {
case x: AutoPlugin => x
}
private[sbt] def asExclusions(ap: AutoPlugin): List[AutoPlugin] = flatten(ap.requires).toList collect {
case Exclude(x) => x
}
asRequirements(ap) map { x =>
Clause(convert(ap), Set(Atom(x.label)))
}
private[sbt] def asRequirements(ap: AutoPlugin): List[AutoPlugin] =
flatten(ap.requires).toList collect {
case x: AutoPlugin => x
}
private[sbt] def asExclusions(ap: AutoPlugin): List[AutoPlugin] =
flatten(ap.requires).toList collect {
case Exclude(x) => x
}
// TODO - This doesn't handle nested AND boolean logic...
private[sbt] def hasExclude(n: Plugins, p: AutoPlugin): Boolean = n match {
case `p` => false
@ -308,10 +353,10 @@ ${listConflicts(conflicting)}""")
// TODO - This is stupidly advanced. We do a nested check through possible and-ed
// lists of plugins exclusions to see if the plugin ever winds up in an excluded=true case.
// This would handle things like !!p or !(p && z)
case Exclude(n) => hasInclude(n, p)
case And(ns) => ns.forall(n => hasExclude(n, p))
case b: Basic => false
case Empty => false
case Exclude(n) => hasInclude(n, p)
case And(ns) => ns.forall(n => hasExclude(n, p))
case b: Basic => false
case Empty => false
}
private[sbt] def hasInclude(n: Plugins, p: AutoPlugin): Boolean = n match {
case `p` => true
@ -352,16 +397,16 @@ ${listConflicts(conflicting)}""")
private val autoImport = "autoImport"
/** Determines whether a plugin has a stable autoImport member by:
*
* 1. Checking whether there exists a public field.
* 2. Checking whether there exists a public object.
*
* The above checks work for inherited members too.
*
* @param ap The found plugin.
* @param loader The plugin loader.
* @return True if plugin has a stable member `autoImport`, otherwise false.
*/
*
* 1. Checking whether there exists a public field.
* 2. Checking whether there exists a public object.
*
* The above checks work for inherited members too.
*
* @param ap The found plugin.
* @param loader The plugin loader.
* @return True if plugin has a stable member `autoImport`, otherwise false.
*/
private[sbt] def hasAutoImportGetter(ap: AutoPlugin, loader: ClassLoader): Boolean = {
import java.lang.reflect.Field
import scala.util.control.Exception.catching
@ -374,8 +419,9 @@ ${listConflicts(conflicting)}""")
val pluginClazz = ap.getClass
existsAutoImportVal(pluginClazz)
.orElse(catching(classOf[ClassNotFoundException]).opt(
Class.forName(s"${pluginClazz.getName}$autoImport$$", false, loader)))
.orElse(
catching(classOf[ClassNotFoundException]).opt(
Class.forName(s"${pluginClazz.getName}$autoImport$$", false, loader)))
.isDefined
}

File diff suppressed because it is too large Load Diff

View File

@ -26,15 +26,18 @@ object Resolvers {
val from = new File(uri)
val to = uniqueSubdirectoryFor(uri, in = info.staging)
if (from.isDirectory) Some { () => if (from.canWrite) from else creates(to) { IO.copyDirectory(from, to) } }
else None
if (from.isDirectory) Some { () =>
if (from.canWrite) from else creates(to) { IO.copyDirectory(from, to) }
} else None
}
val remote: Resolver = (info: ResolveInfo) => {
val url = info.uri.toURL
val to = uniqueSubdirectoryFor(info.uri, in = info.staging)
Some { () => creates(to) { IO.unzipURL(url, to) } }
Some { () =>
creates(to) { IO.unzipURL(url, to) }
}
}
val subversion: Resolver = (info: ResolveInfo) => {
@ -47,18 +50,16 @@ object Resolvers {
if (uri.hasFragment) {
val revision = uri.getFragment
Some {
() =>
creates(localCopy) {
run("svn", "checkout", "-q", "-r", revision, from, to)
}
Some { () =>
creates(localCopy) {
run("svn", "checkout", "-q", "-r", revision, from, to)
}
}
} else
Some {
() =>
creates(localCopy) {
run("svn", "checkout", "-q", from, to)
}
Some { () =>
creates(localCopy) {
run("svn", "checkout", "-q", from, to)
}
}
}
@ -87,11 +88,12 @@ object Resolvers {
run(Some(localCopy), "git", "checkout", "-q", branch)
}
}
} else Some { () =>
creates(localCopy) {
run("git", "clone", "--depth", "1", from, localCopy.getAbsolutePath)
} else
Some { () =>
creates(localCopy) {
run("git", "clone", "--depth", "1", from, localCopy.getAbsolutePath)
}
}
}
}
abstract class DistributedVCS {
@ -108,14 +110,16 @@ object Resolvers {
if (uri.hasFragment) {
val branch = uri.getFragment
Some {
() =>
creates(localCopy) {
clone(from, to = localCopy)
checkout(branch, in = localCopy)
}
Some { () =>
creates(localCopy) {
clone(from, to = localCopy)
checkout(branch, in = localCopy)
}
}
} else
Some { () =>
creates(localCopy) { clone(from, to = localCopy) }
}
} else Some { () => creates(localCopy) { clone(from, to = localCopy) } }
}
private def normalized(uri: URI) = uri.copy(scheme = scheme)
@ -124,7 +128,8 @@ object Resolvers {
private lazy val onWindows = {
val os = System.getenv("OSTYPE")
val isCygwin = (os != null) && os.toLowerCase(Locale.ENGLISH).contains("cygwin")
val isWindows = System.getProperty("os.name", "").toLowerCase(Locale.ENGLISH).contains("windows")
val isWindows =
System.getProperty("os.name", "").toLowerCase(Locale.ENGLISH).contains("windows")
isWindows && !isCygwin
}
@ -141,23 +146,24 @@ object Resolvers {
sys.error("Nonzero exit code (" + result + "): " + command.mkString(" "))
}
def creates(file: File)(f: => Unit) =
{
if (!file.exists)
try {
f
} catch {
case NonFatal(e) =>
IO.delete(file)
throw e
}
file
}
def creates(file: File)(f: => Unit) = {
if (!file.exists)
try {
f
} catch {
case NonFatal(e) =>
IO.delete(file)
throw e
}
file
}
def uniqueSubdirectoryFor(uri: URI, in: File) = {
in.mkdirs()
val base = new File(in, Hash.halfHashString(uri.normalize.toASCIIString))
val last = shortName(uri) match { case Some(n) => normalizeDirectoryName(n); case None => "root" }
val last = shortName(uri) match {
case Some(n) => normalizeDirectoryName(n); case None => "root"
}
new File(base, last)
}

View File

@ -8,14 +8,19 @@ import java.net.URI
/** Extends `URI` with additional convenience methods. */
class RichURI(uri: URI) {
/**
* Provides a case-class-like `copy` method for URI.
* Note that this method simply passes the individual components of this URI to the URI constructor
* that accepts each component individually. It is thus limited by the implementation restrictions of the relevant methods.
*/
def copy(scheme: String = uri.getScheme, userInfo: String = uri.getUserInfo,
host: String = uri.getHost, port: Int = uri.getPort, path: String = uri.getPath,
query: String = uri.getQuery, fragment: String = uri.getFragment) =
def copy(scheme: String = uri.getScheme,
userInfo: String = uri.getUserInfo,
host: String = uri.getHost,
port: Int = uri.getPort,
path: String = uri.getPath,
query: String = uri.getQuery,
fragment: String = uri.getFragment) =
new URI(scheme, userInfo, host, port, path, query, fragment)
/** Returns `true` if the fragment of the URI is defined. */
@ -36,19 +41,19 @@ class RichURI(uri: URI) {
* If the URI has a fragment, the fragment is transferred to the wrapped URI.
* If this URI does not have a marker scheme, it is returned unchanged.
*/
def withoutMarkerScheme =
{
if (hasMarkerScheme)
if (hasFragment)
new URI(uri.getRawSchemeSpecificPart + "#" + uri.getRawFragment)
else
new URI(uri.getRawSchemeSpecificPart)
def withoutMarkerScheme = {
if (hasMarkerScheme)
if (hasFragment)
new URI(uri.getRawSchemeSpecificPart + "#" + uri.getRawFragment)
else
uri
}
new URI(uri.getRawSchemeSpecificPart)
else
uri
}
}
object RichURI {
/** Provides additional convenience methods for `uri`. */
implicit def fromURI(uri: URI): RichURI = new RichURI(uri)
}

View File

@ -23,31 +23,34 @@ object ScopeFilter {
* If a task filter is not supplied, global is selected.
* Generally, always specify the project axis.
*/
def apply(projects: ProjectFilter = inProjects(ThisProject), configurations: ConfigurationFilter = globalAxis, tasks: TaskFilter = globalAxis): ScopeFilter =
def apply(projects: ProjectFilter = inProjects(ThisProject),
configurations: ConfigurationFilter = globalAxis,
tasks: TaskFilter = globalAxis): ScopeFilter =
new ScopeFilter {
private[sbt] def apply(data: Data): Scope => Boolean =
{
val pf = projects(data)
val cf = configurations(data)
val tf = tasks(data)
s => pf(s.project) && cf(s.config) && tf(s.task)
}
private[sbt] def apply(data: Data): Scope => Boolean = {
val pf = projects(data)
val cf = configurations(data)
val tf = tasks(data)
s =>
pf(s.project) && cf(s.config) && tf(s.task)
}
}
def debug(delegate: ScopeFilter): ScopeFilter =
new ScopeFilter {
private[sbt] def apply(data: Data): Scope => Boolean =
{
val d = delegate(data)
scope => {
private[sbt] def apply(data: Data): Scope => Boolean = {
val d = delegate(data)
scope =>
{
val accept = d(scope)
println((if (accept) "ACCEPT " else "reject ") + scope)
accept
}
}
}
}
final class SettingKeyAll[T] private[sbt] (i: Initialize[T]) {
/**
* Evaluates the initialization in all scopes selected by the filter. These are dynamic dependencies, so
* static inspections will not show them.
@ -57,6 +60,7 @@ object ScopeFilter {
}
}
final class TaskKeyAll[T] private[sbt] (i: Initialize[Task[T]]) {
/**
* Evaluates the task in all scopes selected by the filter. These are dynamic dependencies, so
* static inspections will not show them.
@ -69,20 +73,26 @@ object ScopeFilter {
private[sbt] val Make = new Make {}
trait Make {
/** Selects the Scopes used in `<key>.all(<ScopeFilter>)`.*/
type ScopeFilter = Base[Scope]
/** Selects Scopes with a global task axis. */
def inGlobalTask: TaskFilter = globalAxis[AttributeKey[_]]
/** Selects Scopes with a global project axis. */
def inGlobalProject: ProjectFilter = globalAxis[Reference]
/** Selects Scopes with a global configuration axis. */
def inGlobalConfiguration: ConfigurationFilter = globalAxis[ConfigKey]
/** Selects all scopes that apply to a single project. Global and build-level scopes are excluded. */
def inAnyProject: ProjectFilter = selectAxis(const { case p: ProjectRef => true; case _ => false })
def inAnyProject: ProjectFilter =
selectAxis(const { case p: ProjectRef => true; case _ => false })
/** Accepts all values for the task axis except Global. */
def inAnyTask: TaskFilter = selectAny[AttributeKey[_]]
/** Accepts all values for the configuration axis except Global. */
def inAnyConfiguration: ConfigurationFilter = selectAny[ConfigKey]
@ -90,18 +100,31 @@ object ScopeFilter {
* Selects Scopes that have a project axis that is aggregated by `ref`, transitively if `transitive` is true.
* If `includeRoot` is true, Scopes with `ref` itself as the project axis value are also selected.
*/
def inAggregates(ref: ProjectReference, transitive: Boolean = true, includeRoot: Boolean = true): ProjectFilter =
byDeps(ref, transitive = transitive, includeRoot = includeRoot, aggregate = true, classpath = false)
def inAggregates(ref: ProjectReference,
transitive: Boolean = true,
includeRoot: Boolean = true): ProjectFilter =
byDeps(ref,
transitive = transitive,
includeRoot = includeRoot,
aggregate = true,
classpath = false)
/**
* Selects Scopes that have a project axis that is a dependency of `ref`, transitively if `transitive` is true.
* If `includeRoot` is true, Scopes with `ref` itself as the project axis value are also selected.
*/
def inDependencies(ref: ProjectReference, transitive: Boolean = true, includeRoot: Boolean = true): ProjectFilter =
byDeps(ref, transitive = transitive, includeRoot = includeRoot, aggregate = false, classpath = true)
def inDependencies(ref: ProjectReference,
transitive: Boolean = true,
includeRoot: Boolean = true): ProjectFilter =
byDeps(ref,
transitive = transitive,
includeRoot = includeRoot,
aggregate = false,
classpath = true)
/** Selects Scopes that have a project axis with one of the provided values.*/
def inProjects(projects: ProjectReference*): ProjectFilter = ScopeFilter.inProjects(projects: _*)
def inProjects(projects: ProjectReference*): ProjectFilter =
ScopeFilter.inProjects(projects: _*)
/** Selects Scopes that have a task axis with one of the provided values.*/
def inTasks(tasks: Scoped*): TaskFilter = {
@ -123,7 +146,9 @@ object ScopeFilter {
* Information provided to Scope filters. These provide project relationships,
* project reference resolution, and the list of all static Scopes.
*/
private final class Data(val units: Map[URI, LoadedBuildUnit], val resolve: ProjectReference => ProjectRef, val allScopes: Set[Scope])
private final class Data(val units: Map[URI, LoadedBuildUnit],
val resolve: ProjectReference => ProjectRef,
val allScopes: Set[Scope])
/** Constructs a Data instance from the list of static scopes and the project relationships.*/
private[this] val getData: Initialize[Data] =
@ -136,20 +161,28 @@ object ScopeFilter {
case None => build.root
}
val rootProject = Load.getRootProject(build.units)
val resolve: ProjectReference => ProjectRef = p => (p, thisRef) match {
case (ThisProject, Some(pref)) => pref
case _ => Scope.resolveProjectRef(current, rootProject, p)
val resolve: ProjectReference => ProjectRef = p =>
(p, thisRef) match {
case (ThisProject, Some(pref)) => pref
case _ => Scope.resolveProjectRef(current, rootProject, p)
}
new Data(build.units, resolve, scopes)
}
private[this] def getDependencies(structure: Map[URI, LoadedBuildUnit], classpath: Boolean, aggregate: Boolean): ProjectRef => Seq[ProjectRef] =
ref => Project.getProject(ref, structure).toList flatMap { p =>
(if (classpath) p.dependencies.map(_.project) else Nil) ++
(if (aggregate) p.aggregate else Nil)
private[this] def getDependencies(structure: Map[URI, LoadedBuildUnit],
classpath: Boolean,
aggregate: Boolean): ProjectRef => Seq[ProjectRef] =
ref =>
Project.getProject(ref, structure).toList flatMap { p =>
(if (classpath) p.dependencies.map(_.project) else Nil) ++
(if (aggregate) p.aggregate else Nil)
}
private[this] def byDeps(ref: ProjectReference, transitive: Boolean, includeRoot: Boolean, aggregate: Boolean, classpath: Boolean): ProjectFilter =
private[this] def byDeps(ref: ProjectReference,
transitive: Boolean,
includeRoot: Boolean,
aggregate: Boolean,
classpath: Boolean): ProjectFilter =
inResolvedProjects { data =>
val resolvedRef = data.resolve(ref)
val direct = getDependencies(data.units, classpath = classpath, aggregate = aggregate)
@ -176,15 +209,17 @@ object ScopeFilter {
private[this] def selectAxis[T](f: Data => T => Boolean): AxisFilter[T] = new AxisFilter[T] {
private[sbt] def apply(data: Data): ScopeAxis[T] => Boolean = {
val g = f(data)
s => s match {
case Select(t) => g(t)
case _ => false
}
s =>
s match {
case Select(t) => g(t)
case _ => false
}
}
}
/** Base functionality for filters on values of type `In` that need access to build data.*/
sealed abstract class Base[In] { self =>
/** Implements this filter. */
private[ScopeFilter] def apply(data: Data): In => Boolean
@ -196,7 +231,8 @@ object ScopeFilter {
private[sbt] def apply(data: Data): In => Boolean = {
val a = self(data)
val b = other(data)
s => a(s) && b(s)
s =>
a(s) && b(s)
}
}
@ -205,14 +241,17 @@ object ScopeFilter {
private[sbt] def apply(data: Data): In => Boolean = {
val a = self(data)
val b = other(data)
s => a(s) || b(s)
s =>
a(s) || b(s)
}
}
/** Constructs a filter that selects values that do not match this filter.*/
def unary_- : Base[In] = new Base[In] {
private[sbt] def apply(data: Data): In => Boolean = {
val a = self(data)
s => !a(s)
s =>
!a(s)
}
}
}

View File

@ -8,8 +8,10 @@ final case class ScopedKeyData[A](scoped: ScopedKey[A], value: Any) {
val scope = scoped.scope
def typeName: String = fold(fmtMf("Task[%s]"), fmtMf("InputTask[%s]"), key.manifest.toString)
def settingValue: Option[Any] = fold(const(None), const(None), Some(value))
def description: String = fold(fmtMf("Task: %s"), fmtMf("Input task: %s"),
"Setting: %s = %s" format (key.manifest.toString, value.toString))
def description: String =
fold(fmtMf("Task: %s"),
fmtMf("Input task: %s"),
"Setting: %s = %s" format (key.manifest.toString, value.toString))
def fold[T](targ: OptManifest[_] => T, itarg: OptManifest[_] => T, s: => T): T =
key.manifest.runtimeClass match {
case TaskClass => targ(key.manifest.typeArguments.head)

View File

@ -20,54 +20,59 @@ object SessionVar {
}
def emptyMap = Map(IMap.empty)
def persistAndSet[T](key: ScopedKey[Task[T]], state: State, value: T)(implicit f: JsonFormat[T]): State =
{
persist(key, state, value)(f)
set(key, state, value)
}
def persistAndSet[T](key: ScopedKey[Task[T]], state: State, value: T)(
implicit f: JsonFormat[T]): State = {
persist(key, state, value)(f)
set(key, state, value)
}
def persist[T](key: ScopedKey[Task[T]], state: State, value: T)(implicit f: JsonFormat[T]): Unit =
Project.structure(state).streams(state).use(key)(s =>
s.getOutput(DefaultDataID).write(value))
def persist[T](key: ScopedKey[Task[T]], state: State, value: T)(
implicit f: JsonFormat[T]): Unit =
Project.structure(state).streams(state).use(key)(s => s.getOutput(DefaultDataID).write(value))
def clear(s: State): State = s.put(sessionVars, SessionVar.emptyMap)
def get[T](key: ScopedKey[Task[T]], state: State): Option[T] = orEmpty(state get sessionVars) get key
def get[T](key: ScopedKey[Task[T]], state: State): Option[T] =
orEmpty(state get sessionVars) get key
def set[T](key: ScopedKey[Task[T]], state: State, value: T): State = state.update(sessionVars)(om => orEmpty(om) put (key, value))
def set[T](key: ScopedKey[Task[T]], state: State, value: T): State =
state.update(sessionVars)(om => orEmpty(om) put (key, value))
def orEmpty(opt: Option[Map]) = opt getOrElse emptyMap
def transform[S](task: Task[S], f: (State, S) => State): Task[S] =
{
val g = (s: S, map: AttributeMap) => map.put(Keys.transformState, (state: State) => f(state, s))
task.copy(info = task.info.postTransform(g))
}
def transform[S](task: Task[S], f: (State, S) => State): Task[S] = {
val g = (s: S, map: AttributeMap) =>
map.put(Keys.transformState, (state: State) => f(state, s))
task.copy(info = task.info.postTransform(g))
}
def resolveContext[T](key: ScopedKey[Task[T]], context: Scope, state: State): ScopedKey[Task[T]] =
{
val subScope = Scope.replaceThis(context)(key.scope)
val scope = Project.structure(state).data.definingScope(subScope, key.key) getOrElse subScope
ScopedKey(scope, key.key)
}
def resolveContext[T](key: ScopedKey[Task[T]],
context: Scope,
state: State): ScopedKey[Task[T]] = {
val subScope = Scope.replaceThis(context)(key.scope)
val scope = Project.structure(state).data.definingScope(subScope, key.key) getOrElse subScope
ScopedKey(scope, key.key)
}
def read[T](key: ScopedKey[Task[T]], state: State)(implicit f: JsonFormat[T]): Option[T] =
Project.structure(state).streams(state).use(key) { s =>
try { Some(s.getInput(key, DefaultDataID).read[T]) }
catch { case NonFatal(e) => None }
try { Some(s.getInput(key, DefaultDataID).read[T]) } catch { case NonFatal(e) => None }
}
def load[T](key: ScopedKey[Task[T]], state: State)(implicit f: JsonFormat[T]): Option[T] =
get(key, state) orElse read(key, state)(f)
def loadAndSet[T](key: ScopedKey[Task[T]], state: State, setIfUnset: Boolean = true)(implicit f: JsonFormat[T]): (State, Option[T]) =
def loadAndSet[T](key: ScopedKey[Task[T]], state: State, setIfUnset: Boolean = true)(
implicit f: JsonFormat[T]): (State, Option[T]) =
get(key, state) match {
case s: Some[T] => (state, s)
case None => read(key, state)(f) match {
case s @ Some(t) =>
val newState = if (setIfUnset && get(key, state).isDefined) state else set(key, state, t)
(newState, s)
case None => (state, None)
}
case None =>
read(key, state)(f) match {
case s @ Some(t) =>
val newState =
if (setIfUnset && get(key, state).isDefined) state else set(key, state, t)
(newState, s)
case None => (state, None)
}
}
}

View File

@ -87,12 +87,13 @@ object Tags {
def limit(tag: Tag, max: Int): Rule = new Single(tag, max)
def limitSum(max: Int, tags: Tag*): Rule = new Sum(tags, max)
/** Ensure that a task with the given tag always executes in isolation.*/
def exclusive(exclusiveTag: Tag): Rule = customLimit { (tags: Map[Tag, Int]) =>
// if there are no exclusive tasks in this group, this rule adds no restrictions
tags.getOrElse(exclusiveTag, 0) == 0 ||
// If there is only one task, allow it to execute.
tags.getOrElse(Tags.All, 0) == 1
// If there is only one task, allow it to execute.
tags.getOrElse(Tags.All, 0) == 1
}
/** Ensure that a task with the given tag only executes with tasks also tagged with the given tag.*/
@ -101,10 +102,10 @@ object Tags {
val allCount = tags.getOrElse(Tags.All, 0)
// If there are no exclusive tasks in this group, this rule adds no restrictions.
exclusiveCount == 0 ||
// If all tasks have this tag, allow them to execute.
exclusiveCount == allCount ||
// Always allow a group containing only one task to execute (fallthrough case).
allCount == 1
// If all tasks have this tag, allow them to execute.
exclusiveCount == allCount ||
// Always allow a group containing only one task to execute (fallthrough case).
allCount == 1
}
/** A task tagged with one of `exclusiveTags` will not execute with another task with any of the other tags in `exclusiveTags`.*/
@ -112,4 +113,4 @@ object Tags {
val groups = exclusiveTags.count(tag => tags.getOrElse(tag, 0) > 0)
groups <= 1
}
}
}

View File

@ -36,8 +36,13 @@ private[sbt] object TemplateCommandUtil {
}
private def run(
infos: List[TemplateResolverInfo], arguments: List[String], config: AppConfiguration,
ivyConf: IvyConfiguration, globalBase: File, ivyScala: Option[IvyScala], log: Logger
infos: List[TemplateResolverInfo],
arguments: List[String],
config: AppConfiguration,
ivyConf: IvyConfiguration,
globalBase: File,
ivyScala: Option[IvyScala],
log: Logger
): Unit =
infos find { info =>
val loader = infoLoader(info, config, ivyConf, globalBase, ivyScala, log)
@ -51,57 +56,67 @@ private[sbt] object TemplateCommandUtil {
case None => System.err.println("Template not found for: " + arguments.mkString(" "))
}
private def tryTemplate(info: TemplateResolverInfo, arguments: List[String], loader: ClassLoader): Boolean =
{
val resultObj = call(info.implementationClass, "isDefined", loader)(
classOf[Array[String]]
)(arguments.toArray)
resultObj.asInstanceOf[Boolean]
}
private def tryTemplate(info: TemplateResolverInfo,
arguments: List[String],
loader: ClassLoader): Boolean = {
val resultObj = call(info.implementationClass, "isDefined", loader)(
classOf[Array[String]]
)(arguments.toArray)
resultObj.asInstanceOf[Boolean]
}
private def runTemplate(info: TemplateResolverInfo, arguments: List[String], loader: ClassLoader): Unit =
private def runTemplate(info: TemplateResolverInfo,
arguments: List[String],
loader: ClassLoader): Unit =
call(info.implementationClass, "run", loader)(classOf[Array[String]])(arguments.toArray)
private def infoLoader(
info: TemplateResolverInfo, config: AppConfiguration, ivyConf: IvyConfiguration, globalBase: File,
ivyScala: Option[IvyScala], log: Logger
info: TemplateResolverInfo,
config: AppConfiguration,
ivyConf: IvyConfiguration,
globalBase: File,
ivyScala: Option[IvyScala],
log: Logger
): ClassLoader = {
val cp = classpathForInfo(info, ivyConf, globalBase, ivyScala, log)
ClasspathUtilities.toLoader(cp, config.provider.loader)
}
private def call(
interfaceClassName: String, methodName: String, loader: ClassLoader
)(argTypes: Class[_]*)(args: AnyRef*): AnyRef =
{
val interfaceClass = getInterfaceClass(interfaceClassName, loader)
val interface = interfaceClass.getDeclaredConstructor().newInstance().asInstanceOf[AnyRef]
val method = interfaceClass.getMethod(methodName, argTypes: _*)
try { method.invoke(interface, args: _*) }
catch {
case e: InvocationTargetException => throw e.getCause
}
interfaceClassName: String,
methodName: String,
loader: ClassLoader
)(argTypes: Class[_]*)(args: AnyRef*): AnyRef = {
val interfaceClass = getInterfaceClass(interfaceClassName, loader)
val interface = interfaceClass.getDeclaredConstructor().newInstance().asInstanceOf[AnyRef]
val method = interfaceClass.getMethod(methodName, argTypes: _*)
try { method.invoke(interface, args: _*) } catch {
case e: InvocationTargetException => throw e.getCause
}
}
private def getInterfaceClass(name: String, loader: ClassLoader) = Class.forName(name, true, loader)
private def getInterfaceClass(name: String, loader: ClassLoader) =
Class.forName(name, true, loader)
// Cache files under ~/.sbt/0.13/templates/org_name_version
private def classpathForInfo(
info: TemplateResolverInfo, ivyConf: IvyConfiguration, globalBase: File, ivyScala: Option[IvyScala],
info: TemplateResolverInfo,
ivyConf: IvyConfiguration,
globalBase: File,
ivyScala: Option[IvyScala],
log: Logger
): List[File] =
{
val lm = new DefaultLibraryManagement(ivyConf, log)
val templatesBaseDirectory = new File(globalBase, "templates")
val templateId = s"${info.module.organization}_${info.module.name}_${info.module.revision}"
val templateDirectory = new File(templatesBaseDirectory, templateId)
def jars = (templateDirectory ** -DirectoryFilter).get
if (!(info.module.revision endsWith "-SNAPSHOT") && jars.nonEmpty) jars.toList
else {
IO.createDirectory(templateDirectory)
val m = lm.getModule(info.module.withConfigurations(Some("component")), ivyScala)
val xs = lm.update(m, templateDirectory)(_ => true).toList.flatten
xs
}
): List[File] = {
val lm = new DefaultLibraryManagement(ivyConf, log)
val templatesBaseDirectory = new File(globalBase, "templates")
val templateId = s"${info.module.organization}_${info.module.name}_${info.module.revision}"
val templateDirectory = new File(templatesBaseDirectory, templateId)
def jars = (templateDirectory ** -DirectoryFilter).get
if (!(info.module.revision endsWith "-SNAPSHOT") && jars.nonEmpty) jars.toList
else {
IO.createDirectory(templateDirectory)
val m = lm.getModule(info.module.withConfigurations(Some("component")), ivyScala)
val xs = lm.update(m, templateDirectory)(_ => true).toList.flatten
xs
}
}
}

View File

@ -17,7 +17,10 @@ private[sbt] object APIMappings {
def extractFromEntry(entry: Attributed[File], log: Logger): Option[(File, URL)] =
entry.get(Keys.entryApiURL) match {
case Some(u) => Some((entry.data, u))
case None => entry.get(Keys.moduleID.key).flatMap { mid => extractFromID(entry.data, mid, log) }
case None =>
entry.get(Keys.moduleID.key).flatMap { mid =>
extractFromID(entry.data, mid, log)
}
}
private[this] def extractFromID(entry: File, mid: ModuleID, log: Logger): Option[(File, URL)] =
@ -27,7 +30,8 @@ private[sbt] object APIMappings {
} yield (entry, u)
private[this] def parseURL(s: String, forEntry: File, log: Logger): Option[URL] =
try Some(new URL(s)) catch {
try Some(new URL(s))
catch {
case e: MalformedURLException =>
log.warn(s"Invalid API base URL '$s' for classpath entry '$forEntry': ${e.toString}")
None

View File

@ -21,54 +21,79 @@ object Act {
val GlobalString = "*"
// this does not take aggregation into account
def scopedKey(index: KeyIndex, current: ProjectRef, defaultConfigs: Option[ResolvedReference] => Seq[String],
keyMap: Map[String, AttributeKey[_]], data: Settings[Scope]): Parser[ScopedKey[_]] =
def scopedKey(index: KeyIndex,
current: ProjectRef,
defaultConfigs: Option[ResolvedReference] => Seq[String],
keyMap: Map[String, AttributeKey[_]],
data: Settings[Scope]): Parser[ScopedKey[_]] =
scopedKeySelected(index, current, defaultConfigs, keyMap, data).map(_.key)
// the index should be an aggregated index for proper tab completion
def scopedKeyAggregated(current: ProjectRef, defaultConfigs: Option[ResolvedReference] => Seq[String], structure: BuildStructure): KeysParser =
for (selected <- scopedKeySelected(structure.index.aggregateKeyIndex, current, defaultConfigs, structure.index.keyMap, structure.data)) yield Aggregation.aggregate(selected.key, selected.mask, structure.extra)
def scopedKeyAggregated(current: ProjectRef,
defaultConfigs: Option[ResolvedReference] => Seq[String],
structure: BuildStructure): KeysParser =
for (selected <- scopedKeySelected(structure.index.aggregateKeyIndex,
current,
defaultConfigs,
structure.index.keyMap,
structure.data))
yield Aggregation.aggregate(selected.key, selected.mask, structure.extra)
def scopedKeySelected(index: KeyIndex, current: ProjectRef, defaultConfigs: Option[ResolvedReference] => Seq[String],
keyMap: Map[String, AttributeKey[_]], data: Settings[Scope]): Parser[ParsedKey] =
def scopedKeySelected(index: KeyIndex,
current: ProjectRef,
defaultConfigs: Option[ResolvedReference] => Seq[String],
keyMap: Map[String, AttributeKey[_]],
data: Settings[Scope]): Parser[ParsedKey] =
scopedKeyFull(index, current, defaultConfigs, keyMap) flatMap { choices =>
select(choices, data)(showRelativeKey(current, index.buildURIs.size > 1))
}
def scopedKeyFull(index: KeyIndex, current: ProjectRef, defaultConfigs: Option[ResolvedReference] => Seq[String], keyMap: Map[String, AttributeKey[_]]): Parser[Seq[Parser[ParsedKey]]] =
{
for {
rawProject <- optProjectRef(index, current)
proj = resolveProject(rawProject, current)
confAmb <- config(index configs proj)
partialMask = ScopeMask(rawProject.isExplicit, confAmb.isExplicit, false, false)
} yield taskKeyExtra(index, defaultConfigs, keyMap, proj, confAmb, partialMask)
}
def scopedKeyFull(index: KeyIndex,
current: ProjectRef,
defaultConfigs: Option[ResolvedReference] => Seq[String],
keyMap: Map[String, AttributeKey[_]]): Parser[Seq[Parser[ParsedKey]]] = {
for {
rawProject <- optProjectRef(index, current)
proj = resolveProject(rawProject, current)
confAmb <- config(index configs proj)
partialMask = ScopeMask(rawProject.isExplicit, confAmb.isExplicit, false, false)
} yield taskKeyExtra(index, defaultConfigs, keyMap, proj, confAmb, partialMask)
}
def taskKeyExtra(
index: KeyIndex,
defaultConfigs: Option[ResolvedReference] => Seq[String],
keyMap: Map[String, AttributeKey[_]],
proj: Option[ResolvedReference],
confAmb: ParsedAxis[String],
baseMask: ScopeMask
index: KeyIndex,
defaultConfigs: Option[ResolvedReference] => Seq[String],
keyMap: Map[String, AttributeKey[_]],
proj: Option[ResolvedReference],
confAmb: ParsedAxis[String],
baseMask: ScopeMask
): Seq[Parser[ParsedKey]] =
for {
conf <- configs(confAmb, defaultConfigs, proj, index)
} yield for {
taskAmb <- taskAxis(conf, index.tasks(proj, conf), keyMap)
task = resolveTask(taskAmb)
key <- key(index, proj, conf, task, keyMap)
extra <- extraAxis(keyMap, IMap.empty)
} yield {
val mask = baseMask.copy(task = taskAmb.isExplicit, extra = true)
new ParsedKey(makeScopedKey(proj, conf, task, extra, key), mask)
}
} yield
for {
taskAmb <- taskAxis(conf, index.tasks(proj, conf), keyMap)
task = resolveTask(taskAmb)
key <- key(index, proj, conf, task, keyMap)
extra <- extraAxis(keyMap, IMap.empty)
} yield {
val mask = baseMask.copy(task = taskAmb.isExplicit, extra = true)
new ParsedKey(makeScopedKey(proj, conf, task, extra, key), mask)
}
def makeScopedKey(proj: Option[ResolvedReference], conf: Option[String], task: Option[AttributeKey[_]], extra: ScopeAxis[AttributeMap], key: AttributeKey[_]): ScopedKey[_] =
ScopedKey(Scope(toAxis(proj, Global), toAxis(conf map ConfigKey.apply, Global), toAxis(task, Global), extra), key)
def makeScopedKey(proj: Option[ResolvedReference],
conf: Option[String],
task: Option[AttributeKey[_]],
extra: ScopeAxis[AttributeMap],
key: AttributeKey[_]): ScopedKey[_] =
ScopedKey(Scope(toAxis(proj, Global),
toAxis(conf map ConfigKey.apply, Global),
toAxis(task, Global),
extra),
key)
def select(allKeys: Seq[Parser[ParsedKey]], data: Settings[Scope])(implicit show: Show[ScopedKey[_]]): Parser[ParsedKey] =
def select(allKeys: Seq[Parser[ParsedKey]], data: Settings[Scope])(
implicit show: Show[ScopedKey[_]]): Parser[ParsedKey] =
seq(allKeys) flatMap { ss =>
val default = ss.headOption match {
case None => noValidKeys
@ -76,7 +101,8 @@ object Act {
}
selectFromValid(ss filter isValid(data), default)
}
def selectFromValid(ss: Seq[ParsedKey], default: Parser[ParsedKey])(implicit show: Show[ScopedKey[_]]): Parser[ParsedKey] =
def selectFromValid(ss: Seq[ParsedKey], default: Parser[ParsedKey])(
implicit show: Show[ScopedKey[_]]): Parser[ParsedKey] =
selectByTask(selectByConfig(ss)) match {
case Seq() => default
case Seq(single) => success(single)
@ -92,22 +118,20 @@ object Act {
case xs => x +: xs
}
}
def selectByTask(ss: Seq[ParsedKey]): Seq[ParsedKey] =
{
val (selects, globals) = ss.partition(_.key.scope.task.isSelect)
if (globals.nonEmpty) globals else selects
}
def selectByTask(ss: Seq[ParsedKey]): Seq[ParsedKey] = {
val (selects, globals) = ss.partition(_.key.scope.task.isSelect)
if (globals.nonEmpty) globals else selects
}
def noValidKeys = failure("No such key.")
def showAmbiguous(keys: Seq[ScopedKey[_]])(implicit show: Show[ScopedKey[_]]): String =
keys.take(3).map(x => show.show(x)).mkString("", ", ", if (keys.size > 3) ", ..." else "")
def isValid(data: Settings[Scope])(parsed: ParsedKey): Boolean =
{
val key = parsed.key
data.definingScope(key.scope, key.key) == Some(key.scope)
}
def isValid(data: Settings[Scope])(parsed: ParsedKey): Boolean = {
val key = parsed.key
data.definingScope(key.scope, key.key) == Some(key.scope)
}
def examples(p: Parser[String], exs: Set[String], label: String): Parser[String] =
p !!! ("Expected " + label) examples exs
@ -115,47 +139,62 @@ object Act {
filterStrings(examples(p, exs, label), exs, label)
def optionalAxis[T](p: Parser[T], ifNone: ScopeAxis[T]): Parser[ScopeAxis[T]] =
p.? map { opt => toAxis(opt, ifNone) }
p.? map { opt =>
toAxis(opt, ifNone)
}
def toAxis[T](opt: Option[T], ifNone: ScopeAxis[T]): ScopeAxis[T] =
opt match { case Some(t) => Select(t); case None => ifNone }
def config(confs: Set[String]): Parser[ParsedAxis[String]] =
{
val sep = ':' !!! "Expected ':' (if selecting a configuration)"
token((GlobalString ^^^ ParsedGlobal | value(examples(ID, confs, "configuration"))) <~ sep) ?? Omitted
}
def config(confs: Set[String]): Parser[ParsedAxis[String]] = {
val sep = ':' !!! "Expected ':' (if selecting a configuration)"
token((GlobalString ^^^ ParsedGlobal | value(examples(ID, confs, "configuration"))) <~ sep) ?? Omitted
}
def configs(explicit: ParsedAxis[String], defaultConfigs: Option[ResolvedReference] => Seq[String], proj: Option[ResolvedReference], index: KeyIndex): Seq[Option[String]] =
def configs(explicit: ParsedAxis[String],
defaultConfigs: Option[ResolvedReference] => Seq[String],
proj: Option[ResolvedReference],
index: KeyIndex): Seq[Option[String]] =
explicit match {
case Omitted => None +: defaultConfigurations(proj, index, defaultConfigs).flatMap(nonEmptyConfig(index, proj))
case Omitted =>
None +: defaultConfigurations(proj, index, defaultConfigs).flatMap(
nonEmptyConfig(index, proj))
case ParsedGlobal => None :: Nil
case pv: ParsedValue[x] => Some(pv.value) :: Nil
}
def defaultConfigurations(proj: Option[ResolvedReference], index: KeyIndex, defaultConfigs: Option[ResolvedReference] => Seq[String]): Seq[String] =
def defaultConfigurations(
proj: Option[ResolvedReference],
index: KeyIndex,
defaultConfigs: Option[ResolvedReference] => Seq[String]): Seq[String] =
if (index exists proj) defaultConfigs(proj) else Nil
def nonEmptyConfig(index: KeyIndex, proj: Option[ResolvedReference]): String => Seq[Option[String]] = config =>
if (index.isEmpty(proj, Some(config))) Nil else Some(config) :: Nil
def nonEmptyConfig(index: KeyIndex,
proj: Option[ResolvedReference]): String => Seq[Option[String]] =
config => if (index.isEmpty(proj, Some(config))) Nil else Some(config) :: Nil
def key(index: KeyIndex, proj: Option[ResolvedReference], conf: Option[String], task: Option[AttributeKey[_]], keyMap: Map[String, AttributeKey[_]]): Parser[AttributeKey[_]] =
{
def dropHyphenated(keys: Set[String]): Set[String] = keys.filterNot(Util.hasHyphen)
def keyParser(keys: Set[String]): Parser[AttributeKey[_]] =
token(ID !!! "Expected key" examples dropHyphenated(keys)) flatMap { keyString =>
getKey(keyMap, keyString, idFun)
}
// Fixes sbt/sbt#2460 and sbt/sbt#2851
// The parser already accepts build-level keys.
// This queries the key index so tab completion will list the build-level keys.
val buildKeys: Set[String] =
proj match {
case Some(ProjectRef(uri, id)) => index.keys(Some(BuildRef(uri)), conf, task)
case _ => Set()
}
val keys: Set[String] = index.keys(proj, conf, task) ++ buildKeys
keyParser(keys)
}
def key(index: KeyIndex,
proj: Option[ResolvedReference],
conf: Option[String],
task: Option[AttributeKey[_]],
keyMap: Map[String, AttributeKey[_]]): Parser[AttributeKey[_]] = {
def dropHyphenated(keys: Set[String]): Set[String] = keys.filterNot(Util.hasHyphen)
def keyParser(keys: Set[String]): Parser[AttributeKey[_]] =
token(ID !!! "Expected key" examples dropHyphenated(keys)) flatMap { keyString =>
getKey(keyMap, keyString, idFun)
}
// Fixes sbt/sbt#2460 and sbt/sbt#2851
// The parser already accepts build-level keys.
// This queries the key index so tab completion will list the build-level keys.
val buildKeys: Set[String] =
proj match {
case Some(ProjectRef(uri, id)) => index.keys(Some(BuildRef(uri)), conf, task)
case _ => Set()
}
val keys: Set[String] = index.keys(proj, conf, task) ++ buildKeys
keyParser(keys)
}
def getKey[T](keyMap: Map[String, AttributeKey[_]], keyString: String, f: AttributeKey[_] => T): Parser[T] =
def getKey[T](keyMap: Map[String, AttributeKey[_]],
keyString: String,
f: AttributeKey[_] => T): Parser[T] =
keyMap.get(keyString) match {
case Some(k) => success(f(k))
case None => failure(Command.invalidValue("key", keyMap.keys)(keyString))
@ -163,23 +202,25 @@ object Act {
val spacedComma = token(OptSpace ~ ',' ~ OptSpace)
def extraAxis(knownKeys: Map[String, AttributeKey[_]], knownValues: IMap[AttributeKey, Set]): Parser[ScopeAxis[AttributeMap]] =
{
val extrasP = extrasParser(knownKeys, knownValues)
val extras = token('(', hide = _ == 1 && knownValues.isEmpty) ~> extrasP <~ token(')')
optionalAxis(extras, Global)
}
def extraAxis(knownKeys: Map[String, AttributeKey[_]],
knownValues: IMap[AttributeKey, Set]): Parser[ScopeAxis[AttributeMap]] = {
val extrasP = extrasParser(knownKeys, knownValues)
val extras = token('(', hide = _ == 1 && knownValues.isEmpty) ~> extrasP <~ token(')')
optionalAxis(extras, Global)
}
def taskAxis(d: Option[String], tasks: Set[AttributeKey[_]], allKnown: Map[String, AttributeKey[_]]): Parser[ParsedAxis[AttributeKey[_]]] =
{
val taskSeq = tasks.toSeq
def taskKeys(f: AttributeKey[_] => String): Seq[(String, AttributeKey[_])] = taskSeq.map(key => (f(key), key))
val normKeys = taskKeys(_.label)
val valid = allKnown ++ normKeys
val suggested = normKeys.map(_._1).toSet
val keyP = filterStrings(examples(ID, suggested, "key"), valid.keySet, "key") map valid
(token(value(keyP) | GlobalString ^^^ ParsedGlobal) <~ token("::".id)) ?? Omitted
}
def taskAxis(d: Option[String],
tasks: Set[AttributeKey[_]],
allKnown: Map[String, AttributeKey[_]]): Parser[ParsedAxis[AttributeKey[_]]] = {
val taskSeq = tasks.toSeq
def taskKeys(f: AttributeKey[_] => String): Seq[(String, AttributeKey[_])] =
taskSeq.map(key => (f(key), key))
val normKeys = taskKeys(_.label)
val valid = allKnown ++ normKeys
val suggested = normKeys.map(_._1).toSet
val keyP = filterStrings(examples(ID, suggested, "key"), valid.keySet, "key") map valid
(token(value(keyP) | GlobalString ^^^ ParsedGlobal) <~ token("::".id)) ?? Omitted
}
def resolveTask(task: ParsedAxis[AttributeKey[_]]): Option[AttributeKey[_]] =
task match {
case ParsedGlobal | Omitted => None
@ -189,24 +230,26 @@ object Act {
def filterStrings(base: Parser[String], valid: Set[String], label: String): Parser[String] =
base.filter(valid, Command.invalidValue(label, valid))
def extrasParser(knownKeys: Map[String, AttributeKey[_]], knownValues: IMap[AttributeKey, Set]): Parser[AttributeMap] =
{
val validKeys = knownKeys.filter { case (_, key) => knownValues get key exists (_.nonEmpty) }
if (validKeys.isEmpty)
failure("No valid extra keys.")
else
rep1sep(extraParser(validKeys, knownValues), spacedComma) map AttributeMap.apply
}
def extrasParser(knownKeys: Map[String, AttributeKey[_]],
knownValues: IMap[AttributeKey, Set]): Parser[AttributeMap] = {
val validKeys = knownKeys.filter { case (_, key) => knownValues get key exists (_.nonEmpty) }
if (validKeys.isEmpty)
failure("No valid extra keys.")
else
rep1sep(extraParser(validKeys, knownValues), spacedComma) map AttributeMap.apply
}
def extraParser(knownKeys: Map[String, AttributeKey[_]], knownValues: IMap[AttributeKey, Set]): Parser[AttributeEntry[_]] =
{
val keyp = knownIDParser(knownKeys, "Not a valid extra key") <~ token(':' ~ OptSpace)
keyp flatMap {
case key: AttributeKey[t] =>
val valueMap: Map[String, t] = knownValues(key).map(v => (v.toString, v)).toMap
knownIDParser(valueMap, "extra value") map { value => AttributeEntry(key, value) }
}
def extraParser(knownKeys: Map[String, AttributeKey[_]],
knownValues: IMap[AttributeKey, Set]): Parser[AttributeEntry[_]] = {
val keyp = knownIDParser(knownKeys, "Not a valid extra key") <~ token(':' ~ OptSpace)
keyp flatMap {
case key: AttributeKey[t] =>
val valueMap: Map[String, t] = knownValues(key).map(v => (v.toString, v)).toMap
knownIDParser(valueMap, "extra value") map { value =>
AttributeEntry(key, value)
}
}
}
def knownIDParser[T](knownKeys: Map[String, T], label: String): Parser[T] =
token(examplesStrict(ID, knownKeys.keys.toSet, label)) map knownKeys
@ -215,29 +258,33 @@ object Act {
token(examplesStrict(pluginLabelParser, knownPlugins.keys.toSet, label)) map knownPlugins
}
def projectRef(index: KeyIndex, currentBuild: URI): Parser[ParsedAxis[ResolvedReference]] =
{
val global = token(GlobalString ~ '/') ^^^ ParsedGlobal
val trailing = '/' !!! "Expected '/' (if selecting a project)"
global | value(resolvedReference(index, currentBuild, trailing))
def projectRef(index: KeyIndex, currentBuild: URI): Parser[ParsedAxis[ResolvedReference]] = {
val global = token(GlobalString ~ '/') ^^^ ParsedGlobal
val trailing = '/' !!! "Expected '/' (if selecting a project)"
global | value(resolvedReference(index, currentBuild, trailing))
}
def resolvedReference(index: KeyIndex,
currentBuild: URI,
trailing: Parser[_]): Parser[ResolvedReference] = {
def projectID(uri: URI) =
token(examplesStrict(ID, index projects uri, "project ID") <~ trailing)
def projectRef(uri: URI) = projectID(uri) map { id =>
ProjectRef(uri, id)
}
def resolvedReference(index: KeyIndex, currentBuild: URI, trailing: Parser[_]): Parser[ResolvedReference] =
{
def projectID(uri: URI) = token(examplesStrict(ID, index projects uri, "project ID") <~ trailing)
def projectRef(uri: URI) = projectID(uri) map { id => ProjectRef(uri, id) }
val uris = index.buildURIs
val resolvedURI = Uri(uris).map(uri => Scope.resolveBuild(currentBuild, uri))
val buildRef = token('{' ~> resolvedURI <~ '}').?
val uris = index.buildURIs
val resolvedURI = Uri(uris).map(uri => Scope.resolveBuild(currentBuild, uri))
val buildRef = token('{' ~> resolvedURI <~ '}').?
buildRef flatMap {
case None => projectRef(currentBuild)
case Some(uri) => projectRef(uri) | token(trailing ^^^ BuildRef(uri))
}
buildRef flatMap {
case None => projectRef(currentBuild)
case Some(uri) => projectRef(uri) | token(trailing ^^^ BuildRef(uri))
}
}
def optProjectRef(index: KeyIndex, current: ProjectRef): Parser[ParsedAxis[ResolvedReference]] =
projectRef(index, current.build) ?? Omitted
def resolveProject(parsed: ParsedAxis[ResolvedReference], current: ProjectRef): Option[ResolvedReference] =
def resolveProject(parsed: ParsedAxis[ResolvedReference],
current: ProjectRef): Option[ResolvedReference] =
parsed match {
case Omitted => Some(current)
case ParsedGlobal => None
@ -246,30 +293,30 @@ object Act {
def actParser(s: State): Parser[() => State] = requireSession(s, actParser0(s))
private[this] def actParser0(state: State): Parser[() => State] =
{
val extracted = Project extract state
import extracted.{ showKey, structure }
import Aggregation.evaluatingParser
actionParser.flatMap { action =>
val akp = aggregatedKeyParser(extracted)
def evaluate(kvs: Seq[ScopedKey[_]]): Parser[() => State] = {
val preparedPairs = anyKeyValues(structure, kvs)
val showConfig = Aggregation.defaultShow(state, showTasks = action == ShowAction)
evaluatingParser(state, structure, showConfig)(preparedPairs) map { evaluate => () => {
private[this] def actParser0(state: State): Parser[() => State] = {
val extracted = Project extract state
import extracted.{ showKey, structure }
import Aggregation.evaluatingParser
actionParser.flatMap { action =>
val akp = aggregatedKeyParser(extracted)
def evaluate(kvs: Seq[ScopedKey[_]]): Parser[() => State] = {
val preparedPairs = anyKeyValues(structure, kvs)
val showConfig = Aggregation.defaultShow(state, showTasks = action == ShowAction)
evaluatingParser(state, structure, showConfig)(preparedPairs) map { evaluate => () =>
{
val keyStrings = preparedPairs.map(pp => showKey.show(pp.key)).mkString(", ")
state.log.debug("Evaluating tasks: " + keyStrings)
evaluate()
}
}
}
action match {
case SingleAction => akp flatMap evaluate
case ShowAction | MultiAction =>
rep1sep(akp, token(Space)).flatMap(kvss => evaluate(kvss.flatten))
}
}
action match {
case SingleAction => akp flatMap evaluate
case ShowAction | MultiAction =>
rep1sep(akp, token(Space)).flatMap(kvss => evaluate(kvss.flatten))
}
}
}
private[this] final class ActAction
private[this] final val ShowAction, MultiAction, SingleAction = new ActAction
@ -281,29 +328,45 @@ object Act {
) ?? SingleAction
def scopedKeyParser(state: State): Parser[ScopedKey[_]] = scopedKeyParser(Project extract state)
def scopedKeyParser(extracted: Extracted): Parser[ScopedKey[_]] = scopedKeyParser(extracted.structure, extracted.currentRef)
def scopedKeyParser(extracted: Extracted): Parser[ScopedKey[_]] =
scopedKeyParser(extracted.structure, extracted.currentRef)
def scopedKeyParser(structure: BuildStructure, currentRef: ProjectRef): Parser[ScopedKey[_]] =
scopedKey(structure.index.keyIndex, currentRef, structure.extra.configurationsForAxis, structure.index.keyMap, structure.data)
scopedKey(structure.index.keyIndex,
currentRef,
structure.extra.configurationsForAxis,
structure.index.keyMap,
structure.data)
type KeysParser = Parser[Seq[ScopedKey[T]] forSome { type T }]
def aggregatedKeyParser(state: State): KeysParser = aggregatedKeyParser(Project extract state)
def aggregatedKeyParser(extracted: Extracted): KeysParser = aggregatedKeyParser(extracted.structure, extracted.currentRef)
def aggregatedKeyParser(extracted: Extracted): KeysParser =
aggregatedKeyParser(extracted.structure, extracted.currentRef)
def aggregatedKeyParser(structure: BuildStructure, currentRef: ProjectRef): KeysParser =
scopedKeyAggregated(currentRef, structure.extra.configurationsForAxis, structure)
def keyValues[T](state: State)(keys: Seq[ScopedKey[T]]): Values[T] = keyValues(Project extract state)(keys)
def keyValues[T](extracted: Extracted)(keys: Seq[ScopedKey[T]]): Values[T] = keyValues(extracted.structure)(keys)
def keyValues[T](state: State)(keys: Seq[ScopedKey[T]]): Values[T] =
keyValues(Project extract state)(keys)
def keyValues[T](extracted: Extracted)(keys: Seq[ScopedKey[T]]): Values[T] =
keyValues(extracted.structure)(keys)
def keyValues[T](structure: BuildStructure)(keys: Seq[ScopedKey[T]]): Values[T] =
keys.flatMap { key =>
getValue(structure.data, key.scope, key.key) map { value => KeyValue(key, value) }
getValue(structure.data, key.scope, key.key) map { value =>
KeyValue(key, value)
}
}
private[this] def anyKeyValues(structure: BuildStructure, keys: Seq[ScopedKey[_]]): Seq[KeyValue[_]] =
private[this] def anyKeyValues(structure: BuildStructure,
keys: Seq[ScopedKey[_]]): Seq[KeyValue[_]] =
keys.flatMap { key =>
getValue(structure.data, key.scope, key.key) map { value => KeyValue(key, value) }
getValue(structure.data, key.scope, key.key) map { value =>
KeyValue(key, value)
}
}
private[this] def getValue[T](data: Settings[Scope], scope: Scope, key: AttributeKey[T]): Option[T] =
if (java.lang.Boolean.getBoolean("sbt.cli.nodelegation")) data.getDirect(scope, key) else data.get(scope, key)
private[this] def getValue[T](data: Settings[Scope],
scope: Scope,
key: AttributeKey[T]): Option[T] =
if (java.lang.Boolean.getBoolean("sbt.cli.nodelegation")) data.getDirect(scope, key)
else data.get(scope, key)
def requireSession[T](s: State, p: => Parser[T]): Parser[T] =
if (s get sessionSettings isEmpty) failure("No project loaded") else p
@ -314,5 +377,7 @@ object Act {
final object ParsedGlobal extends ParsedAxis[Nothing]
final object Omitted extends ParsedAxis[Nothing]
final class ParsedValue[T](val value: T) extends ParsedAxis[T]
def value[T](t: Parser[T]): Parser[ParsedAxis[T]] = t map { v => new ParsedValue(v) }
def value[T](t: Parser[T]): Parser[ParsedAxis[T]] = t map { v =>
new ParsedValue(v)
}
}

View File

@ -19,7 +19,8 @@ object AddSettings {
private[sbt] final object BuildScalaFiles extends AddSettings
/** Adds all settings from autoplugins. */
val autoPlugins: AddSettings = new AutoPlugins(const(true)) // Note: We do not expose fine-grained autoplugins because
val autoPlugins
: AddSettings = new AutoPlugins(const(true)) // Note: We do not expose fine-grained autoplugins because
// it's dangerous to control at that level right now.
// Leaving the hook in place in case we need to expose
// it, but most likely it will remain locked out
@ -55,19 +56,22 @@ object AddSettings {
case _ => seq(a, b)
}
def clearSbtFiles(a: AddSettings): AddSettings = tx(a) {
case _: DefaultSbtFiles | _: SbtFiles => None
case x => Some(x)
} getOrElse seq()
def clearSbtFiles(a: AddSettings): AddSettings =
tx(a) {
case _: DefaultSbtFiles | _: SbtFiles => None
case x => Some(x)
} getOrElse seq()
private[sbt] def tx(a: AddSettings)(f: AddSettings => Option[AddSettings]): Option[AddSettings] = a match {
case s: Sequence =>
s.sequence.flatMap { b => tx(b)(f) } match {
case Seq() => None
case Seq(x) => Some(x)
case ss => Some(new Sequence(ss))
}
case x => f(x)
}
private[sbt] def tx(a: AddSettings)(f: AddSettings => Option[AddSettings]): Option[AddSettings] =
a match {
case s: Sequence =>
s.sequence.flatMap { b =>
tx(b)(f)
} match {
case Seq() => None
case Seq(x) => Some(x)
case ss => Some(new Sequence(ss))
}
case x => f(x)
}
}

View File

@ -14,56 +14,79 @@ import std.Transform.DummyTaskMap
sealed trait Aggregation
object Aggregation {
final case class ShowConfig(settingValues: Boolean, taskValues: Boolean, print: String => Unit, success: Boolean)
final case class Complete[T](start: Long, stop: Long, results: sbt.Result[Seq[KeyValue[T]]], state: State)
final case class ShowConfig(settingValues: Boolean,
taskValues: Boolean,
print: String => Unit,
success: Boolean)
final case class Complete[T](start: Long,
stop: Long,
results: sbt.Result[Seq[KeyValue[T]]],
state: State)
final case class KeyValue[+T](key: ScopedKey[_], value: T)
def defaultShow(state: State, showTasks: Boolean): ShowConfig = ShowConfig(settingValues = true, taskValues = showTasks, s => state.log.info(s), success = true)
def printSettings(xs: Seq[KeyValue[_]], print: String => Unit)(implicit display: Show[ScopedKey[_]]) =
def defaultShow(state: State, showTasks: Boolean): ShowConfig =
ShowConfig(settingValues = true,
taskValues = showTasks,
s => state.log.info(s),
success = true)
def printSettings(xs: Seq[KeyValue[_]], print: String => Unit)(
implicit display: Show[ScopedKey[_]]) =
xs match {
case KeyValue(_, x: Seq[_]) :: Nil => print(x.mkString("* ", "\n* ", ""))
case KeyValue(_, x) :: Nil => print(x.toString)
case _ => xs foreach { case KeyValue(key, value) => print(display.show(key) + "\n\t" + value.toString) }
case _ =>
xs foreach {
case KeyValue(key, value) => print(display.show(key) + "\n\t" + value.toString)
}
}
type Values[T] = Seq[KeyValue[T]]
type AnyKeys = Values[_]
def seqParser[T](ps: Values[Parser[T]]): Parser[Seq[KeyValue[T]]] = seq(ps.map { case KeyValue(k, p) => p.map(v => KeyValue(k, v)) })
def seqParser[T](ps: Values[Parser[T]]): Parser[Seq[KeyValue[T]]] =
seq(ps.map { case KeyValue(k, p) => p.map(v => KeyValue(k, v)) })
def applyTasks[T](s: State, structure: BuildStructure, ps: Values[Parser[Task[T]]], show: ShowConfig)(implicit display: Show[ScopedKey[_]]): Parser[() => State] =
def applyTasks[T](s: State,
structure: BuildStructure,
ps: Values[Parser[Task[T]]],
show: ShowConfig)(implicit display: Show[ScopedKey[_]]): Parser[() => State] =
Command.applyEffect(seqParser(ps)) { ts =>
runTasks(s, structure, ts, DummyTaskMap(Nil), show)
}
private def showRun[T](complete: Complete[T], show: ShowConfig)(implicit display: Show[ScopedKey[_]]): Unit =
{
import complete._
val log = state.log
val extracted = Project.extract(state)
val success = results match { case Value(_) => true; case Inc(_) => false }
results.toEither.right.foreach { r => if (show.taskValues) printSettings(r, show.print) }
if (show.success) printSuccess(start, stop, extracted, success, log)
private def showRun[T](complete: Complete[T], show: ShowConfig)(
implicit display: Show[ScopedKey[_]]): Unit = {
import complete._
val log = state.log
val extracted = Project.extract(state)
val success = results match { case Value(_) => true; case Inc(_) => false }
results.toEither.right.foreach { r =>
if (show.taskValues) printSettings(r, show.print)
}
def timedRun[T](s: State, ts: Values[Task[T]], extra: DummyTaskMap): Complete[T] =
{
import EvaluateTask._
import std.TaskExtra._
if (show.success) printSuccess(start, stop, extracted, success, log)
}
def timedRun[T](s: State, ts: Values[Task[T]], extra: DummyTaskMap): Complete[T] = {
import EvaluateTask._
import std.TaskExtra._
val extracted = Project extract s
import extracted.structure
val toRun = ts map { case KeyValue(k, t) => t.map(v => KeyValue(k, v)) } join;
val roots = ts map { case KeyValue(k, _) => k }
val config = extractedTaskConfig(extracted, structure, s)
val extracted = Project extract s
import extracted.structure
val toRun = ts map { case KeyValue(k, t) => t.map(v => KeyValue(k, v)) } join;
val roots = ts map { case KeyValue(k, _) => k }
val config = extractedTaskConfig(extracted, structure, s)
val start = System.currentTimeMillis
val (newS, result) = withStreams(structure, s) { str =>
val transform = nodeView(s, str, roots, extra)
runTask(toRun, s, str, structure.index.triggers, config)(transform)
}
val stop = System.currentTimeMillis
Complete(start, stop, result, newS)
val start = System.currentTimeMillis
val (newS, result) = withStreams(structure, s) { str =>
val transform = nodeView(s, str, roots, extra)
runTask(toRun, s, str, structure.index.triggers, config)(transform)
}
val stop = System.currentTimeMillis
Complete(start, stop, result, newS)
}
def runTasks[HL <: HList, T](s: State, structure: BuildStructure, ts: Values[Task[T]], extra: DummyTaskMap, show: ShowConfig)(implicit display: Show[ScopedKey[_]]): State = {
def runTasks[HL <: HList, T](s: State,
structure: BuildStructure,
ts: Values[Task[T]],
extra: DummyTaskMap,
show: ShowConfig)(implicit display: Show[ScopedKey[_]]): State = {
val complete = timedRun[T](s, ts, extra)
showRun(complete, show)
complete.results match {
@ -72,9 +95,14 @@ object Aggregation {
}
}
def printSuccess(start: Long, stop: Long, extracted: Extracted, success: Boolean, log: Logger): Unit = {
def printSuccess(start: Long,
stop: Long,
extracted: Extracted,
success: Boolean,
log: Logger): Unit = {
import extracted._
def get(key: SettingKey[Boolean]): Boolean = key in currentRef get structure.data getOrElse true
def get(key: SettingKey[Boolean]): Boolean =
key in currentRef get structure.data getOrElse true
if (get(showSuccess)) {
if (get(showTiming)) {
val msg = timingString(start, stop, "", structure.data, currentRef, log)
@ -83,100 +111,118 @@ object Aggregation {
log.success("")
}
}
private def timingString(startTime: Long, endTime: Long, s: String, data: Settings[Scope], currentRef: ProjectRef, log: Logger): String =
{
val format = timingFormat in currentRef get data getOrElse defaultFormat
timing(format, startTime, endTime, "", log)
}
def timing(format: java.text.DateFormat, startTime: Long, endTime: Long, s: String, log: Logger): String =
{
val ss = if (s.isEmpty) "" else s + " "
val nowString = format.format(new java.util.Date(endTime))
"Total " + ss + "time: " + (endTime - startTime + 500) / 1000 + " s, completed " + nowString
}
def defaultFormat =
{
import java.text.DateFormat
DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM)
}
private def timingString(startTime: Long,
endTime: Long,
s: String,
data: Settings[Scope],
currentRef: ProjectRef,
log: Logger): String = {
val format = timingFormat in currentRef get data getOrElse defaultFormat
timing(format, startTime, endTime, "", log)
}
def timing(format: java.text.DateFormat,
startTime: Long,
endTime: Long,
s: String,
log: Logger): String = {
val ss = if (s.isEmpty) "" else s + " "
val nowString = format.format(new java.util.Date(endTime))
"Total " + ss + "time: " + (endTime - startTime + 500) / 1000 + " s, completed " + nowString
}
def defaultFormat = {
import java.text.DateFormat
DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM)
}
def applyDynamicTasks[I](s: State, structure: BuildStructure, inputs: Values[InputTask[I]], show: ShowConfig)(implicit display: Show[ScopedKey[_]]): Parser[() => State] =
{
val parsers = for (KeyValue(k, it) <- inputs) yield it.parser(s).map(v => KeyValue(k, v))
Command.applyEffect(seq(parsers)) { roots =>
runTasks(s, structure, roots, DummyTaskMap(Nil), show)
def applyDynamicTasks[I](
s: State,
structure: BuildStructure,
inputs: Values[InputTask[I]],
show: ShowConfig)(implicit display: Show[ScopedKey[_]]): Parser[() => State] = {
val parsers = for (KeyValue(k, it) <- inputs) yield it.parser(s).map(v => KeyValue(k, v))
Command.applyEffect(seq(parsers)) { roots =>
runTasks(s, structure, roots, DummyTaskMap(Nil), show)
}
}
def evaluatingParser(s: State, structure: BuildStructure, show: ShowConfig)(
keys: Seq[KeyValue[_]])(implicit display: Show[ScopedKey[_]]): Parser[() => State] = {
// to make the call sites clearer
def separate[L](in: Seq[KeyValue[_]])(
f: KeyValue[_] => Either[KeyValue[L], KeyValue[_]]): (Seq[KeyValue[L]], Seq[KeyValue[_]]) =
Util.separate(in)(f)
val kvs = keys.toList
if (kvs.isEmpty)
failure("No such setting/task")
else {
val (inputTasks, other) = separate[InputTask[_]](kvs) {
case KeyValue(k, v: InputTask[_]) => Left(KeyValue(k, v))
case kv => Right(kv)
}
}
def evaluatingParser(s: State, structure: BuildStructure, show: ShowConfig)(keys: Seq[KeyValue[_]])(implicit display: Show[ScopedKey[_]]): Parser[() => State] =
{
// to make the call sites clearer
def separate[L](in: Seq[KeyValue[_]])(f: KeyValue[_] => Either[KeyValue[L], KeyValue[_]]): (Seq[KeyValue[L]], Seq[KeyValue[_]]) =
Util.separate(in)(f)
val kvs = keys.toList
if (kvs.isEmpty)
failure("No such setting/task")
else {
val (inputTasks, other) = separate[InputTask[_]](kvs) {
case KeyValue(k, v: InputTask[_]) => Left(KeyValue(k, v))
case kv => Right(kv)
}
val (tasks, settings) = separate[Task[_]](other) {
case KeyValue(k, v: Task[_]) => Left(KeyValue(k, v))
case kv => Right(kv)
}
// currently, disallow input tasks to be mixed with normal tasks.
// This occurs in `all` or `show`, which support multiple tasks.
// Previously, multiple tasks could be run in one execution, but they were all for the same key, just in different scopes.
// When `all` was added, it allowed different keys and thus opened the possibility for mixing settings,
// tasks, and input tasks in the same call. The code below allows settings and tasks to be mixed, but not input tasks.
// One problem with input tasks in `all` is that many input tasks consume all input and would need syntactic delimiters.
// Once that is addressed, the tasks constructed by the input tasks would need to be combined with the explicit tasks.
if (inputTasks.nonEmpty) {
if (other.nonEmpty) {
val inputStrings = inputTasks.map(_.key).mkString("Input task(s):\n\t", "\n\t", "\n")
val otherStrings = other.map(_.key).mkString("Task(s)/setting(s):\n\t", "\n\t", "\n")
failure(s"Cannot mix input tasks with plain tasks/settings. $inputStrings $otherStrings")
} else
applyDynamicTasks(s, structure, maps(inputTasks)(castToAny), show)
} else {
val base = if (tasks.isEmpty) success(() => s) else
val (tasks, settings) = separate[Task[_]](other) {
case KeyValue(k, v: Task[_]) => Left(KeyValue(k, v))
case kv => Right(kv)
}
// currently, disallow input tasks to be mixed with normal tasks.
// This occurs in `all` or `show`, which support multiple tasks.
// Previously, multiple tasks could be run in one execution, but they were all for the same key, just in different scopes.
// When `all` was added, it allowed different keys and thus opened the possibility for mixing settings,
// tasks, and input tasks in the same call. The code below allows settings and tasks to be mixed, but not input tasks.
// One problem with input tasks in `all` is that many input tasks consume all input and would need syntactic delimiters.
// Once that is addressed, the tasks constructed by the input tasks would need to be combined with the explicit tasks.
if (inputTasks.nonEmpty) {
if (other.nonEmpty) {
val inputStrings = inputTasks.map(_.key).mkString("Input task(s):\n\t", "\n\t", "\n")
val otherStrings = other.map(_.key).mkString("Task(s)/setting(s):\n\t", "\n\t", "\n")
failure(
s"Cannot mix input tasks with plain tasks/settings. $inputStrings $otherStrings")
} else
applyDynamicTasks(s, structure, maps(inputTasks)(castToAny), show)
} else {
val base =
if (tasks.isEmpty) success(() => s)
else
applyTasks(s, structure, maps(tasks)(x => success(castToAny(x))), show)
base.map { res => () =>
val newState = res()
if (show.settingValues && settings.nonEmpty) printSettings(settings, show.print)
newState
}
base.map { res => () =>
val newState = res()
if (show.settingValues && settings.nonEmpty) printSettings(settings, show.print)
newState
}
}
}
}
// this is a hack to avoid duplicating method implementations
private[this] def castToAny[T[_]](t: T[_]): T[Any] = t.asInstanceOf[T[Any]]
private[this] def maps[T, S](vs: Values[T])(f: T => S): Values[S] =
vs map { case KeyValue(k, v) => KeyValue(k, f(v)) }
def projectAggregates[Proj](proj: Option[Reference], extra: BuildUtil[Proj], reverse: Boolean): Seq[ProjectRef] =
{
val resRef = proj.map(p => extra.projectRefFor(extra.resolveRef(p)))
resRef.toList.flatMap(ref =>
if (reverse) extra.aggregates.reverse(ref) else extra.aggregates.forward(ref))
}
def projectAggregates[Proj](proj: Option[Reference],
extra: BuildUtil[Proj],
reverse: Boolean): Seq[ProjectRef] = {
val resRef = proj.map(p => extra.projectRefFor(extra.resolveRef(p)))
resRef.toList.flatMap(ref =>
if (reverse) extra.aggregates.reverse(ref) else extra.aggregates.forward(ref))
}
def aggregate[T, Proj](key: ScopedKey[T], rawMask: ScopeMask, extra: BuildUtil[Proj], reverse: Boolean = false): Seq[ScopedKey[T]] =
{
val mask = rawMask.copy(project = true)
Dag.topologicalSort(key) { k =>
if (reverse)
reverseAggregatedKeys(k, extra, mask)
else if (aggregationEnabled(k, extra.data))
aggregatedKeys(k, extra, mask)
else
Nil
}
def aggregate[T, Proj](key: ScopedKey[T],
rawMask: ScopeMask,
extra: BuildUtil[Proj],
reverse: Boolean = false): Seq[ScopedKey[T]] = {
val mask = rawMask.copy(project = true)
Dag.topologicalSort(key) { k =>
if (reverse)
reverseAggregatedKeys(k, extra, mask)
else if (aggregationEnabled(k, extra.data))
aggregatedKeys(k, extra, mask)
else
Nil
}
def reverseAggregatedKeys[T](key: ScopedKey[T], extra: BuildUtil[_], mask: ScopeMask): Seq[ScopedKey[T]] =
}
def reverseAggregatedKeys[T](key: ScopedKey[T],
extra: BuildUtil[_],
mask: ScopeMask): Seq[ScopedKey[T]] =
projectAggregates(key.scope.project.toOption, extra, reverse = true) flatMap { ref =>
val toResolve = key.scope.copy(project = Select(ref))
val resolved = Resolve(extra, Global, key.key, mask)(toResolve)
@ -184,7 +230,9 @@ object Aggregation {
if (aggregationEnabled(skey, extra.data)) skey :: Nil else Nil
}
def aggregatedKeys[T](key: ScopedKey[T], extra: BuildUtil[_], mask: ScopeMask): Seq[ScopedKey[T]] =
def aggregatedKeys[T](key: ScopedKey[T],
extra: BuildUtil[_],
mask: ScopeMask): Seq[ScopedKey[T]] =
projectAggregates(key.scope.project.toOption, extra, reverse = false) map { ref =>
val toResolve = key.scope.copy(project = Select(ref))
val resolved = Resolve(extra, Global, key.key, mask)(toResolve)

View File

@ -19,6 +19,7 @@ trait BuildDef {
// TODO: Should we grab the build core settings here or in a plugin?
def settings: Seq[Setting[_]] = Defaults.buildCore
def buildLoaders: Seq[BuildLoader.Components] = Nil
/**
* Explicitly defines the root project.
* If None, the root project is the first project in the build's root directory or just the first project if none are in the root directory.
@ -28,18 +29,25 @@ trait BuildDef {
private[sbt] object BuildDef {
val defaultEmpty: BuildDef = new BuildDef { override def projects = Nil }
val default: BuildDef = new BuildDef { override def projectDefinitions(base: File) = defaultProject(defaultID(base), base) :: Nil }
val default: BuildDef = new BuildDef {
override def projectDefinitions(base: File) = defaultProject(defaultID(base), base) :: Nil
}
def defaultAggregated(id: String, aggregate: Seq[ProjectRef]): BuildDef = new BuildDef {
override def projectDefinitions(base: File) = defaultAggregatedProject(id, base, aggregate) :: Nil
override def projectDefinitions(base: File) =
defaultAggregatedProject(id, base, aggregate) :: Nil
}
def defaultID(base: File, prefix: String = "default"): String = prefix + "-" + Hash.trimHashString(base.getAbsolutePath, 6)
def defaultID(base: File, prefix: String = "default"): String =
prefix + "-" + Hash.trimHashString(base.getAbsolutePath, 6)
def defaultProject(id: String, base: File): Project = Project(id, base).settings(defaultProjectSettings)
def defaultProject(id: String, base: File): Project =
Project(id, base).settings(defaultProjectSettings)
def defaultAggregatedProject(id: String, base: File, agg: Seq[ProjectRef]): Project =
defaultProject(id, base).aggregate(agg: _*)
private[sbt] def generatedRootWithoutIvyPlugin(id: String, base: File, agg: Seq[ProjectRef]): Project =
private[sbt] def generatedRootWithoutIvyPlugin(id: String,
base: File,
agg: Seq[ProjectRef]): Project =
Project.mkGeneratedRoot(id, base, Eval.later(agg)).settings(defaultProjectSettings)
private[sbt] def defaultProjectSettings: Seq[Setting[_]] = Seq(
// TODO - Can we move this somewhere else? ordering of settings is causing this to get borked.
@ -53,5 +61,7 @@ private[sbt] object BuildDef {
},
autoGeneratedProject := true
)
def analyzed(in: Seq[Attributed[_]]): Seq[xsbti.compile.CompileAnalysis] = in.flatMap { _.metadata.get(Keys.analysis) }
def analyzed(in: Seq[Attributed[_]]): Seq[xsbti.compile.CompileAnalysis] = in.flatMap {
_.metadata.get(Keys.analysis)
}
}

View File

@ -5,12 +5,14 @@ import sbt.internal.util.Types.idFun
import sbt.internal.util.Dag
import BuildDependencies._
final class BuildDependencies private (val classpath: DependencyMap[ClasspathDep[ProjectRef]], val aggregate: DependencyMap[ProjectRef]) {
final class BuildDependencies private (val classpath: DependencyMap[ClasspathDep[ProjectRef]],
val aggregate: DependencyMap[ProjectRef]) {
def classpathRefs(ref: ProjectRef): Seq[ProjectRef] = classpath(ref) map getID
def classpathTransitiveRefs(ref: ProjectRef): Seq[ProjectRef] = classpathTransitive(ref)
lazy val classpathTransitive: DependencyMap[ProjectRef] = transitive(classpath, getID)
lazy val aggregateTransitive: DependencyMap[ProjectRef] = transitive(aggregate, idFun[ProjectRef])
lazy val aggregateTransitive: DependencyMap[ProjectRef] =
transitive(aggregate, idFun[ProjectRef])
def addClasspath(ref: ProjectRef, deps: ClasspathDep[ProjectRef]*): BuildDependencies =
new BuildDependencies(classpath.updated(ref, deps ++ classpath.getOrElse(ref, Nil)), aggregate)
@ -18,7 +20,8 @@ final class BuildDependencies private (val classpath: DependencyMap[ClasspathDep
new BuildDependencies(classpath, aggregate.updated(ref, deps ++ aggregate.getOrElse(ref, Nil)))
}
object BuildDependencies {
def apply(classpath: DependencyMap[ClasspathDep[ProjectRef]], aggregate: DependencyMap[ProjectRef]): BuildDependencies =
def apply(classpath: DependencyMap[ClasspathDep[ProjectRef]],
aggregate: DependencyMap[ProjectRef]): BuildDependencies =
new BuildDependencies(classpath, aggregate)
type DependencyMap[D] = Map[ProjectRef, Seq[D]]

View File

@ -12,25 +12,42 @@ import sbt.internal.util.Types.{ const, idFun }
import sbt.util.Logger
import sbt.librarymanagement.ModuleID
final class MultiHandler[S, T](builtIn: S => Option[T], root: Option[S => Option[T]], nonRoots: List[(URI, S => Option[T])], getURI: S => URI, log: S => Logger) {
final class MultiHandler[S, T](builtIn: S => Option[T],
root: Option[S => Option[T]],
nonRoots: List[(URI, S => Option[T])],
getURI: S => URI,
log: S => Logger) {
def applyFun: S => Option[T] = apply
def apply(info: S): Option[T] =
(baseLoader(info), applyNonRoots(info)) match {
case (None, Nil) => None
case (None, xs @ (_, nr) :: ignored) =>
if (ignored.nonEmpty) warn("Using first of multiple matching non-root build resolvers for " + getURI(info), log(info), xs)
if (ignored.nonEmpty)
warn("Using first of multiple matching non-root build resolvers for " + getURI(info),
log(info),
xs)
Some(nr)
case (Some(b), xs) =>
if (xs.nonEmpty) warn("Ignoring shadowed non-root build resolver(s) for " + getURI(info), log(info), xs)
if (xs.nonEmpty)
warn("Ignoring shadowed non-root build resolver(s) for " + getURI(info), log(info), xs)
Some(b)
}
def baseLoader: S => Option[T] = root match { case Some(rl) => rl | builtIn; case None => builtIn }
def baseLoader: S => Option[T] = root match {
case Some(rl) => rl | builtIn; case None => builtIn
}
def addNonRoot(uri: URI, loader: S => Option[T]) = new MultiHandler(builtIn, root, (uri, loader) :: nonRoots, getURI, log)
def setRoot(resolver: S => Option[T]) = new MultiHandler(builtIn, Some(resolver), nonRoots, getURI, log)
def addNonRoot(uri: URI, loader: S => Option[T]) =
new MultiHandler(builtIn, root, (uri, loader) :: nonRoots, getURI, log)
def setRoot(resolver: S => Option[T]) =
new MultiHandler(builtIn, Some(resolver), nonRoots, getURI, log)
def applyNonRoots(info: S): List[(URI, T)] =
nonRoots flatMap { case (definingURI, loader) => loader(info) map { unit => (definingURI, unit) } }
nonRoots flatMap {
case (definingURI, loader) =>
loader(info) map { unit =>
(definingURI, unit)
}
}
private[this] def warn(baseMessage: String, log: Logger, matching: Seq[(URI, T)]): Unit = {
log.warn(baseMessage)
@ -40,6 +57,7 @@ final class MultiHandler[S, T](builtIn: S => Option[T], root: Option[S => Option
}
object BuildLoader {
/**
* in: Build URI and staging directory
* out: None if unhandled or Some containing the retrieve function, which returns the directory retrieved to (can be the same as the staging directory)
@ -50,16 +68,28 @@ object BuildLoader {
type Loader = LoadInfo => Option[() => BuildUnit]
type TransformAll = PartBuild => PartBuild
final class Components(val resolver: Resolver, val builder: Builder, val transformer: Transformer, val full: Loader, val transformAll: TransformAll) {
final class Components(val resolver: Resolver,
val builder: Builder,
val transformer: Transformer,
val full: Loader,
val transformAll: TransformAll) {
def |(cs: Components): Components =
new Components(resolver | cs.resolver, builder | cs.builder, seq(transformer, cs.transformer), full | cs.full, transformAll andThen cs.transformAll)
new Components(resolver | cs.resolver,
builder | cs.builder,
seq(transformer, cs.transformer),
full | cs.full,
transformAll andThen cs.transformAll)
}
def transform(t: Transformer): Components = components(transformer = t)
def resolve(r: Resolver): Components = components(resolver = r)
def build(b: Builder): Components = components(builder = b)
def full(f: Loader): Components = components(full = f)
def transformAll(t: TransformAll) = components(transformAll = t)
def components(resolver: Resolver = const(None), builder: Builder = const(None), transformer: Transformer = _.unit, full: Loader = const(None), transformAll: TransformAll = idFun) =
def components(resolver: Resolver = const(None),
builder: Builder = const(None),
transformer: Transformer = _.unit,
full: Loader = const(None),
transformAll: TransformAll = idFun) =
new Components(resolver, builder, transformer, full, transformAll)
def seq(a: Transformer, b: Transformer): Transformer = info => b(info.setUnit(a(info)))
@ -69,19 +99,48 @@ object BuildLoader {
def config: LoadBuildConfiguration
def state: State
}
final class ResolveInfo(val uri: URI, val staging: File, val config: LoadBuildConfiguration, val state: State) extends Info
final class BuildInfo(val uri: URI, val base: File, val config: LoadBuildConfiguration, val state: State) extends Info
final class TransformInfo(val uri: URI, val base: File, val unit: BuildUnit, val config: LoadBuildConfiguration, val state: State) extends Info {
def setUnit(newUnit: BuildUnit): TransformInfo = new TransformInfo(uri, base, newUnit, config, state)
final class ResolveInfo(val uri: URI,
val staging: File,
val config: LoadBuildConfiguration,
val state: State)
extends Info
final class BuildInfo(val uri: URI,
val base: File,
val config: LoadBuildConfiguration,
val state: State)
extends Info
final class TransformInfo(val uri: URI,
val base: File,
val unit: BuildUnit,
val config: LoadBuildConfiguration,
val state: State)
extends Info {
def setUnit(newUnit: BuildUnit): TransformInfo =
new TransformInfo(uri, base, newUnit, config, state)
}
final class LoadInfo(val uri: URI, val staging: File, val config: LoadBuildConfiguration, val state: State, val components: Components) extends Info
final class LoadInfo(val uri: URI,
val staging: File,
val config: LoadBuildConfiguration,
val state: State,
val components: Components)
extends Info
def apply(base: Components, fail: URI => Nothing, s: State, config: LoadBuildConfiguration): BuildLoader =
{
def makeMulti[S <: Info, T](base: S => Option[T]) = new MultiHandler[S, T](base, None, Nil, _.uri, _.config.log)
new BuildLoader(fail, s, config, makeMulti(base.resolver), makeMulti(base.builder), base.transformer, makeMulti(base.full), base.transformAll)
}
def apply(base: Components,
fail: URI => Nothing,
s: State,
config: LoadBuildConfiguration): BuildLoader = {
def makeMulti[S <: Info, T](base: S => Option[T]) =
new MultiHandler[S, T](base, None, Nil, _.uri, _.config.log)
new BuildLoader(fail,
s,
config,
makeMulti(base.resolver),
makeMulti(base.builder),
base.transformer,
makeMulti(base.full),
base.transformAll)
}
def componentLoader: Loader = (info: LoadInfo) => {
import info.{ config, staging, state, uri }
@ -90,24 +149,25 @@ object BuildLoader {
resolve <- cs.resolver(new ResolveInfo(uri, staging, config, state))
base = resolve()
build <- cs.builder(new BuildInfo(uri, base, config, state))
} yield () => {
val unit = build()
cs.transformer(new TransformInfo(uri, base, unit, config, state))
}
} yield
() => {
val unit = build()
cs.transformer(new TransformInfo(uri, base, unit, config, state))
}
}
}
/** Defines the responsible for loading builds.
*
* @param fail A reporter for failures.
* @param state The state.
* @param config The current configuration for any build.
* @param resolvers The structure responsible of mapping base directories.
* @param builders The structure responsible of mapping to build units.
* @param transformer An instance to modify the created build unit.
* @param full The structure responsible of mapping to loaded build units.
* @param transformAll A function specifying which builds units should be transformed.
*/
*
* @param fail A reporter for failures.
* @param state The state.
* @param config The current configuration for any build.
* @param resolvers The structure responsible of mapping base directories.
* @param builders The structure responsible of mapping to build units.
* @param transformer An instance to modify the created build unit.
* @param full The structure responsible of mapping to loaded build units.
* @param transformAll A function specifying which builds units should be transformed.
*/
final class BuildLoader(
val fail: URI => Nothing,
val state: State,
@ -119,37 +179,43 @@ final class BuildLoader(
val transformAll: TransformAll
) {
def addNonRoot(uri: URI, loaders: Components): BuildLoader =
new BuildLoader(fail, state, config,
new BuildLoader(
fail,
state,
config,
resolvers.addNonRoot(uri, loaders.resolver),
builders.addNonRoot(uri, loaders.builder),
seq(transformer, loaders.transformer),
full.addNonRoot(uri, loaders.full),
transformAll andThen loaders.transformAll)
transformAll andThen loaders.transformAll
)
def setRoot(loaders: Components): BuildLoader =
new BuildLoader(fail, state, config,
new BuildLoader(
fail,
state,
config,
resolvers.setRoot(loaders.resolver),
builders.setRoot(loaders.builder),
seq(loaders.transformer, transformer),
full.setRoot(loaders.full),
loaders.transformAll andThen transformAll)
loaders.transformAll andThen transformAll
)
def resetPluginDepth: BuildLoader = copyWithNewPM(config.pluginManagement.resetDepth)
def updatePluginManagement(overrides: Set[ModuleID]): BuildLoader =
{
val mgmt = config.pluginManagement
copyWithNewPM(mgmt.copy(overrides = mgmt.overrides ++ overrides))
}
private[this] def copyWithNewPM(newpm: PluginManagement): BuildLoader =
{
val newConfig = config.copy(pluginManagement = newpm)
new BuildLoader(fail, state, newConfig, resolvers, builders, transformer, full, transformAll)
}
def updatePluginManagement(overrides: Set[ModuleID]): BuildLoader = {
val mgmt = config.pluginManagement
copyWithNewPM(mgmt.copy(overrides = mgmt.overrides ++ overrides))
}
private[this] def copyWithNewPM(newpm: PluginManagement): BuildLoader = {
val newConfig = config.copy(pluginManagement = newpm)
new BuildLoader(fail, state, newConfig, resolvers, builders, transformer, full, transformAll)
}
def components = new Components(resolvers.applyFun, builders.applyFun, transformer, full.applyFun, transformAll)
def apply(uri: URI): BuildUnit =
{
val info = new LoadInfo(uri, config.stagingDirectory, config, state, components)
val load = full(info) getOrElse fail(uri)
load()
}
def components =
new Components(resolvers.applyFun, builders.applyFun, transformer, full.applyFun, transformAll)
def apply(uri: URI): BuildUnit = {
val info = new LoadInfo(uri, config.stagingDirectory, config, state, components)
val load = full(info) getOrElse fail(uri)
load()
}
}

View File

@ -15,22 +15,35 @@ import sbt.internal.util.{ Attributed, AttributeEntry, AttributeKey, AttributeMa
import sbt.internal.util.Attributed.data
import sbt.util.Logger
final class BuildStructure(val units: Map[URI, LoadedBuildUnit], val root: URI, val settings: Seq[Setting[_]], val data: Settings[Scope], val index: StructureIndex, val streams: State => Streams, val delegates: Scope => Seq[Scope], val scopeLocal: ScopeLocal) {
final class BuildStructure(val units: Map[URI, LoadedBuildUnit],
val root: URI,
val settings: Seq[Setting[_]],
val data: Settings[Scope],
val index: StructureIndex,
val streams: State => Streams,
val delegates: Scope => Seq[Scope],
val scopeLocal: ScopeLocal) {
val rootProject: URI => String = Load getRootProject units
def allProjects: Seq[ResolvedProject] = units.values.flatMap(_.defined.values).toSeq
def allProjects(build: URI): Seq[ResolvedProject] = units.get(build).toList.flatMap(_.defined.values)
def allProjectRefs: Seq[ProjectRef] = units.toSeq flatMap { case (build, unit) => refs(build, unit.defined.values.toSeq) }
def allProjects(build: URI): Seq[ResolvedProject] =
units.get(build).toList.flatMap(_.defined.values)
def allProjectRefs: Seq[ProjectRef] = units.toSeq flatMap {
case (build, unit) => refs(build, unit.defined.values.toSeq)
}
def allProjectRefs(build: URI): Seq[ProjectRef] = refs(build, allProjects(build))
val extra: BuildUtil[ResolvedProject] = BuildUtil(root, units, index.keyIndex, data)
private[this] def refs(build: URI, projects: Seq[ResolvedProject]): Seq[ProjectRef] = projects.map { p => ProjectRef(build, p.id) }
private[this] def refs(build: URI, projects: Seq[ResolvedProject]): Seq[ProjectRef] =
projects.map { p =>
ProjectRef(build, p.id)
}
}
// information that is not original, but can be reconstructed from the rest of BuildStructure
final class StructureIndex(
val keyMap: Map[String, AttributeKey[_]],
val taskToKey: Map[Task[_], ScopedKey[Task[_]]],
val triggers: Triggers[Task],
val keyIndex: KeyIndex,
val aggregateKeyIndex: KeyIndex
val keyMap: Map[String, AttributeKey[_]],
val taskToKey: Map[Task[_], ScopedKey[Task[_]]],
val triggers: Triggers[Task],
val keyIndex: KeyIndex,
val aggregateKeyIndex: KeyIndex
)
/**
@ -41,13 +54,20 @@ final class StructureIndex(
* @param rootProjects The list of project IDs for the projects considered roots of this build.
* The first root project is used as the default in several situations where a project is not otherwise selected.
*/
final class LoadedBuildUnit(val unit: BuildUnit, val defined: Map[String, ResolvedProject], val rootProjects: Seq[String], val buildSettings: Seq[Setting[_]]) extends BuildUnitBase {
final class LoadedBuildUnit(val unit: BuildUnit,
val defined: Map[String, ResolvedProject],
val rootProjects: Seq[String],
val buildSettings: Seq[Setting[_]])
extends BuildUnitBase {
/**
* The project to use as the default when one is not otherwise selected.
* [[LocalRootProject]] resolves to this from within the same build.
*/
val root = rootProjects match {
case Nil => throw new java.lang.AssertionError("assertion failed: No root projects defined for build unit " + unit)
case Nil =>
throw new java.lang.AssertionError(
"assertion failed: No root projects defined for build unit " + unit)
case Seq(root, _*) => root
}
@ -58,7 +78,8 @@ final class LoadedBuildUnit(val unit: BuildUnit, val defined: Map[String, Resolv
* The classpath to use when compiling against this build unit's publicly visible code.
* It includes build definition and plugin classes and classes for .sbt file statements and expressions.
*/
def classpath: Seq[File] = unit.definitions.target ++ unit.plugins.classpath ++ unit.definitions.dslDefinitions.classpath
def classpath: Seq[File] =
unit.definitions.target ++ unit.plugins.classpath ++ unit.definitions.dslDefinitions.classpath
/**
* The class loader to use for this build unit's publicly visible code.
@ -96,17 +117,18 @@ final class LoadedDefinitions(
val dslDefinitions: DefinedSbtValues
) {
def this(
base: File,
target: Seq[File],
loader: ClassLoader,
builds: Seq[BuildDef],
projects: Seq[Project],
buildNames: Seq[String]
base: File,
target: Seq[File],
loader: ClassLoader,
builds: Seq[BuildDef],
projects: Seq[Project],
buildNames: Seq[String]
) = this(base, target, loader, builds, projects, buildNames, DefinedSbtValues.empty)
}
/** Auto-detected top-level modules (as in `object X`) of type `T` paired with their source names. */
final class DetectedModules[T](val modules: Seq[(String, T)]) {
/**
* The source names of the modules. This is "X" in `object X`, as opposed to the implementation class name "X$".
* The names are returned in a stable order such that `names zip values` pairs a name with the actual module.
@ -129,7 +151,9 @@ case class DetectedAutoPlugin(name: String, value: AutoPlugin, hasAutoImport: Bo
*
* @param builds The [[Build]]s detected in the build definition. This does not include the default [[Build]] that sbt creates if none is defined.
*/
final class DetectedPlugins(val autoPlugins: Seq[DetectedAutoPlugin], val builds: DetectedModules[BuildDef]) {
final class DetectedPlugins(val autoPlugins: Seq[DetectedAutoPlugin],
val builds: DetectedModules[BuildDef]) {
/**
* Sequence of import expressions for the build definition.
* This includes the names of the [[BuildDef]], and [[DetectedAutoPlugin]] modules,
@ -141,23 +165,24 @@ final class DetectedPlugins(val autoPlugins: Seq[DetectedAutoPlugin], val builds
BuildUtil.importNamesRoot(autoPlugins.map(_.name).filter(nonTopLevelPlugin))
private[this] lazy val (autoPluginAutoImports, topLevelAutoPluginAutoImports) =
autoPlugins.flatMap {
case DetectedAutoPlugin(name, ap, hasAutoImport) =>
if (hasAutoImport) Some(name)
else None
}.partition(nonTopLevelPlugin)
autoPlugins
.flatMap {
case DetectedAutoPlugin(name, ap, hasAutoImport) =>
if (hasAutoImport) Some(name)
else None
}
.partition(nonTopLevelPlugin)
/** Selects the right [[AutoPlugin]]s from a [[Project]]. */
def deducePluginsFromProject(p: Project, log: Logger): Seq[AutoPlugin] =
{
val ps0 = p.plugins
val allDetected = autoPlugins.toList map { _.value }
val detected = p match {
case _: GeneratedRootProject => allDetected filterNot { _ == sbt.plugins.IvyPlugin }
case _ => allDetected
}
Plugins.deducer(detected)(ps0, log)
def deducePluginsFromProject(p: Project, log: Logger): Seq[AutoPlugin] = {
val ps0 = p.plugins
val allDetected = autoPlugins.toList map { _.value }
val detected = p match {
case _: GeneratedRootProject => allDetected filterNot { _ == sbt.plugins.IvyPlugin }
case _ => allDetected
}
Plugins.deducer(detected)(ps0, log)
}
private[this] def autoImports(pluginNames: Seq[String]) = pluginNames.map(_ + ".autoImport")
@ -172,7 +197,10 @@ final class DetectedPlugins(val autoPlugins: Seq[DetectedAutoPlugin], val builds
* @param loader The class loader for the build definition project, notably excluding classes used for .sbt files.
* @param detected Auto-detected modules in the build definition.
*/
final class LoadedPlugins(val base: File, val pluginData: PluginData, val loader: ClassLoader, val detected: DetectedPlugins) {
final class LoadedPlugins(val base: File,
val pluginData: PluginData,
val loader: ClassLoader,
val detected: DetectedPlugins) {
def fullClasspath: Seq[Attributed[File]] = pluginData.classpath
def classpath = data(fullClasspath)
}
@ -183,21 +211,32 @@ final class LoadedPlugins(val base: File, val pluginData: PluginData, val loader
* @param localBase The working location of the build on the filesystem.
* For local URIs, this is the same as `uri`, but for remote URIs, this is the local copy or workspace allocated for the build.
*/
final class BuildUnit(val uri: URI, val localBase: File, val definitions: LoadedDefinitions, val plugins: LoadedPlugins) {
override def toString = if (uri.getScheme == "file") localBase.toString else (uri + " (locally: " + localBase + ")")
final class BuildUnit(val uri: URI,
val localBase: File,
val definitions: LoadedDefinitions,
val plugins: LoadedPlugins) {
override def toString =
if (uri.getScheme == "file") localBase.toString else (uri + " (locally: " + localBase + ")")
}
final class LoadedBuild(val root: URI, val units: Map[URI, LoadedBuildUnit]) {
BuildUtil.checkCycles(units)
def allProjectRefs: Seq[(ProjectRef, ResolvedProject)] = for ((uri, unit) <- units.toSeq; (id, proj) <- unit.defined) yield ProjectRef(uri, id) -> proj
def extra(data: Settings[Scope])(keyIndex: KeyIndex): BuildUtil[ResolvedProject] = BuildUtil(root, units, keyIndex, data)
def allProjectRefs: Seq[(ProjectRef, ResolvedProject)] =
for ((uri, unit) <- units.toSeq; (id, proj) <- unit.defined) yield ProjectRef(uri, id) -> proj
def extra(data: Settings[Scope])(keyIndex: KeyIndex): BuildUtil[ResolvedProject] =
BuildUtil(root, units, keyIndex, data)
private[sbt] def autos = GroupedAutoPlugins(units)
}
final class PartBuild(val root: URI, val units: Map[URI, PartBuildUnit])
sealed trait BuildUnitBase { def rootProjects: Seq[String]; def buildSettings: Seq[Setting[_]] }
final class PartBuildUnit(val unit: BuildUnit, val defined: Map[String, Project], val rootProjects: Seq[String], val buildSettings: Seq[Setting[_]]) extends BuildUnitBase {
def resolve(f: Project => ResolvedProject): LoadedBuildUnit = new LoadedBuildUnit(unit, defined mapValues f toMap, rootProjects, buildSettings)
final class PartBuildUnit(val unit: BuildUnit,
val defined: Map[String, Project],
val rootProjects: Seq[String],
val buildSettings: Seq[Setting[_]])
extends BuildUnitBase {
def resolve(f: Project => ResolvedProject): LoadedBuildUnit =
new LoadedBuildUnit(unit, defined mapValues f toMap, rootProjects, buildSettings)
def resolveRefs(f: ProjectReference => ProjectRef): LoadedBuildUnit = resolve(_ resolve f)
}
@ -208,43 +247,58 @@ object BuildStreams {
final val BuildUnitPath = "$build"
final val StreamsDirectory = "streams"
def mkStreams(units: Map[URI, LoadedBuildUnit], root: URI, data: Settings[Scope]): State => Streams = s => {
implicit val isoString: sjsonnew.IsoString[scala.json.ast.unsafe.JValue] = sjsonnew.IsoString.iso(sjsonnew.support.scalajson.unsafe.CompactPrinter.apply, sjsonnew.support.scalajson.unsafe.Parser.parseUnsafe)
def mkStreams(units: Map[URI, LoadedBuildUnit],
root: URI,
data: Settings[Scope]): State => Streams = s => {
implicit val isoString: sjsonnew.IsoString[scala.json.ast.unsafe.JValue] =
sjsonnew.IsoString.iso(sjsonnew.support.scalajson.unsafe.CompactPrinter.apply,
sjsonnew.support.scalajson.unsafe.Parser.parseUnsafe)
(s get Keys.stateStreams) getOrElse {
std.Streams(path(units, root, data), displayFull, LogManager.construct(data, s), sjsonnew.support.scalajson.unsafe.Converter)
std.Streams(path(units, root, data),
displayFull,
LogManager.construct(data, s),
sjsonnew.support.scalajson.unsafe.Converter)
}
}
def path(units: Map[URI, LoadedBuildUnit], root: URI, data: Settings[Scope])(scoped: ScopedKey[_]): File =
def path(units: Map[URI, LoadedBuildUnit], root: URI, data: Settings[Scope])(
scoped: ScopedKey[_]): File =
resolvePath(projectPath(units, root, scoped, data), nonProjectPath(scoped))
def resolvePath(base: File, components: Seq[String]): File =
(base /: components)((b, p) => new File(b, p))
def pathComponent[T](axis: ScopeAxis[T], scoped: ScopedKey[_], label: String)(show: T => String): String =
def pathComponent[T](axis: ScopeAxis[T], scoped: ScopedKey[_], label: String)(
show: T => String): String =
axis match {
case Global => GlobalPath
case This => sys.error("Unresolved This reference for " + label + " in " + displayFull(scoped))
case Global => GlobalPath
case This =>
sys.error("Unresolved This reference for " + label + " in " + displayFull(scoped))
case Select(t) => show(t)
}
def nonProjectPath[T](scoped: ScopedKey[T]): Seq[String] =
{
val scope = scoped.scope
pathComponent(scope.config, scoped, "config")(_.name) ::
pathComponent(scope.task, scoped, "task")(_.label) ::
pathComponent(scope.extra, scoped, "extra")(showAMap) ::
scoped.key.label ::
Nil
}
def nonProjectPath[T](scoped: ScopedKey[T]): Seq[String] = {
val scope = scoped.scope
pathComponent(scope.config, scoped, "config")(_.name) ::
pathComponent(scope.task, scoped, "task")(_.label) ::
pathComponent(scope.extra, scoped, "extra")(showAMap) ::
scoped.key.label ::
Nil
}
def showAMap(a: AttributeMap): String =
a.entries.toSeq.sortBy(_.key.label).map { case AttributeEntry(key, value) => key.label + "=" + value.toString } mkString (" ")
def projectPath(units: Map[URI, LoadedBuildUnit], root: URI, scoped: ScopedKey[_], data: Settings[Scope]): File =
a.entries.toSeq.sortBy(_.key.label).map {
case AttributeEntry(key, value) => key.label + "=" + value.toString
} mkString (" ")
def projectPath(units: Map[URI, LoadedBuildUnit],
root: URI,
scoped: ScopedKey[_],
data: Settings[Scope]): File =
scoped.scope.project match {
case Global => refTarget(GlobalScope, units(root).localBase, data) / GlobalPath
case Select(br @ BuildRef(uri)) => refTarget(br, units(uri).localBase, data) / BuildUnitPath
case Select(pr @ ProjectRef(uri, id)) => refTarget(pr, units(uri).defined(id).base, data)
case Select(pr) => sys.error("Unresolved project reference (" + pr + ") in " + displayFull(scoped))
case This => sys.error("Unresolved project reference (This) in " + displayFull(scoped))
case Select(pr) =>
sys.error("Unresolved project reference (" + pr + ") in " + displayFull(scoped))
case This => sys.error("Unresolved project reference (This) in " + displayFull(scoped))
}
def refTarget(ref: ResolvedReference, fallbackBase: File, data: Settings[Scope]): File =

View File

@ -41,30 +41,33 @@ final class BuildUtil[Proj](
refOpt => configurations(projectForAxis(refOpt)).map(_.name)
}
object BuildUtil {
def apply(root: URI, units: Map[URI, LoadedBuildUnit], keyIndex: KeyIndex, data: Settings[Scope]): BuildUtil[ResolvedProject] =
{
val getp = (build: URI, project: String) => Load.getProject(units, build, project)
val configs = (_: ResolvedProject).configurations.map(c => ConfigKey(c.name))
val aggregates = aggregationRelation(units)
new BuildUtil(keyIndex, data, root, Load getRootProject units, getp, configs, aggregates)
}
def apply(root: URI,
units: Map[URI, LoadedBuildUnit],
keyIndex: KeyIndex,
data: Settings[Scope]): BuildUtil[ResolvedProject] = {
val getp = (build: URI, project: String) => Load.getProject(units, build, project)
val configs = (_: ResolvedProject).configurations.map(c => ConfigKey(c.name))
val aggregates = aggregationRelation(units)
new BuildUtil(keyIndex, data, root, Load getRootProject units, getp, configs, aggregates)
}
def dependencies(units: Map[URI, LoadedBuildUnit]): BuildDependencies =
{
import scala.collection.mutable
val agg = new mutable.HashMap[ProjectRef, Seq[ProjectRef]]
val cp = new mutable.HashMap[ProjectRef, Seq[ClasspathDep[ProjectRef]]]
for (lbu <- units.values; rp <- lbu.defined.values) {
val ref = ProjectRef(lbu.unit.uri, rp.id)
cp(ref) = rp.dependencies
agg(ref) = rp.aggregate
}
BuildDependencies(cp.toMap, agg.toMap)
def dependencies(units: Map[URI, LoadedBuildUnit]): BuildDependencies = {
import scala.collection.mutable
val agg = new mutable.HashMap[ProjectRef, Seq[ProjectRef]]
val cp = new mutable.HashMap[ProjectRef, Seq[ClasspathDep[ProjectRef]]]
for (lbu <- units.values; rp <- lbu.defined.values) {
val ref = ProjectRef(lbu.unit.uri, rp.id)
cp(ref) = rp.dependencies
agg(ref) = rp.aggregate
}
BuildDependencies(cp.toMap, agg.toMap)
}
def checkCycles(units: Map[URI, LoadedBuildUnit]): Unit = {
def getRef(pref: ProjectRef) = units(pref.build).defined(pref.project)
def deps(proj: ResolvedProject)(base: ResolvedProject => Seq[ProjectRef]): Seq[ResolvedProject] = Dag.topologicalSort(proj)(p => base(p) map getRef)
def deps(proj: ResolvedProject)(
base: ResolvedProject => Seq[ProjectRef]): Seq[ResolvedProject] =
Dag.topologicalSort(proj)(p => base(p) map getRef)
// check for cycles
for ((_, lbu) <- units; proj <- lbu.defined.values) {
deps(proj)(_.dependencies.map(_.project))
@ -72,15 +75,18 @@ object BuildUtil {
}
}
def baseImports: Seq[String] = "import _root_.scala.xml.{TopScope=>$scope}" :: "import _root_.sbt._" :: "import _root_.sbt.Keys._" :: Nil
def baseImports: Seq[String] =
"import _root_.scala.xml.{TopScope=>$scope}" :: "import _root_.sbt._" :: "import _root_.sbt.Keys._" :: Nil
def getImports(unit: BuildUnit): Seq[String] = unit.plugins.detected.imports ++ unit.definitions.dslDefinitions.imports
def getImports(unit: BuildUnit): Seq[String] =
unit.plugins.detected.imports ++ unit.definitions.dslDefinitions.imports
/** `import sbt._, Keys._`, and wildcard import `._` for all names. */
def getImports(names: Seq[String]): Seq[String] = baseImports ++ importAllRoot(names)
/** Import just the names. */
def importNames(names: Seq[String]): Seq[String] = if (names.isEmpty) Nil else names.mkString("import ", ", ", "") :: Nil
def importNames(names: Seq[String]): Seq[String] =
if (names.isEmpty) Nil else names.mkString("import ", ", ", "") :: Nil
/** Prepend `_root_` and import just the names. */
def importNamesRoot(names: Seq[String]): Seq[String] = importNames(names map rootedName)
@ -90,15 +96,14 @@ object BuildUtil {
def importAllRoot(values: Seq[String]): Seq[String] = importAll(values map rootedName)
def rootedName(s: String): String = if (s contains '.') "_root_." + s else s
def aggregationRelation(units: Map[URI, LoadedBuildUnit]): Relation[ProjectRef, ProjectRef] =
{
val depPairs =
for {
(uri, unit) <- units.toIterable // don't lose this toIterable, doing so breaks actions/cross-multiproject & actions/update-state-fail
project <- unit.defined.values
ref = ProjectRef(uri, project.id)
agg <- project.aggregate
} yield (ref, agg)
Relation.empty ++ depPairs
}
def aggregationRelation(units: Map[URI, LoadedBuildUnit]): Relation[ProjectRef, ProjectRef] = {
val depPairs =
for {
(uri, unit) <- units.toIterable // don't lose this toIterable, doing so breaks actions/cross-multiproject & actions/update-state-fail
project <- unit.defined.values
ref = ProjectRef(uri, project.id)
agg <- project.aggregate
} yield (ref, agg)
Relation.empty ++ depPairs
}
}

View File

@ -36,155 +36,148 @@ private[sbt] final class CommandExchange {
}
// periodically move all messages from all the channels
@tailrec def blockUntilNextExec: Exec =
{
@tailrec def slurpMessages(): Unit =
(((None: Option[Exec]) /: channels) { _ orElse _.poll }) match {
case Some(x) =>
commandQueue.add(x)
slurpMessages
case _ => ()
}
slurpMessages()
Option(commandQueue.poll) match {
case Some(x) => x
case _ =>
Thread.sleep(50)
blockUntilNextExec
@tailrec def blockUntilNextExec: Exec = {
@tailrec def slurpMessages(): Unit =
(((None: Option[Exec]) /: channels) { _ orElse _.poll }) match {
case Some(x) =>
commandQueue.add(x)
slurpMessages
case _ => ()
}
slurpMessages()
Option(commandQueue.poll) match {
case Some(x) => x
case _ =>
Thread.sleep(50)
blockUntilNextExec
}
}
def run(s: State): State =
{
consoleChannel match {
case Some(x) => // do nothing
case _ =>
val x = new ConsoleChannel("console0")
consoleChannel = Some(x)
subscribe(x)
}
runServer(s)
def run(s: State): State = {
consoleChannel match {
case Some(x) => // do nothing
case _ =>
val x = new ConsoleChannel("console0")
consoleChannel = Some(x)
subscribe(x)
}
runServer(s)
}
private def newChannelName: String = s"channel-${nextChannelId.incrementAndGet()}"
private def runServer(s: State): State =
{
val port = (s get serverPort) match {
case Some(x) => x
case None => 5001
}
def onIncomingSocket(socket: Socket): Unit =
{
s.log.info(s"new client connected from: ${socket.getPort}")
val channel = new NetworkChannel(newChannelName, socket, Project structure s)
subscribe(channel)
channel.publishEventMessage(ChannelAcceptedEvent(channel.name))
private def runServer(s: State): State = {
val port = (s get serverPort) match {
case Some(x) => x
case None => 5001
}
def onIncomingSocket(socket: Socket): Unit = {
s.log.info(s"new client connected from: ${socket.getPort}")
val channel = new NetworkChannel(newChannelName, socket, Project structure s)
subscribe(channel)
channel.publishEventMessage(ChannelAcceptedEvent(channel.name))
}
server match {
case Some(x) => // do nothing
case _ =>
val x = Server.start("127.0.0.1", port, onIncomingSocket, s.log)
Await.ready(x.ready, Duration("10s"))
x.ready.value match {
case Some(Success(_)) =>
case Some(Failure(e)) =>
s.log.error(e.toString)
case None => // this won't happen because we awaited
}
server match {
case Some(x) => // do nothing
case _ =>
val x = Server.start("127.0.0.1", port, onIncomingSocket, s.log)
Await.ready(x.ready, Duration("10s"))
x.ready.value match {
case Some(Success(_)) =>
case Some(Failure(e)) =>
s.log.error(e.toString)
case None => // this won't happen because we awaited
}
server = Some(x)
}
s
server = Some(x)
}
s
}
def shutdown(): Unit =
{
channels foreach { c =>
c.shutdown()
}
// interrupt and kill the thread
server.foreach(_.shutdown())
server = None
def shutdown(): Unit = {
channels foreach { c =>
c.shutdown()
}
// interrupt and kill the thread
server.foreach(_.shutdown())
server = None
}
def publishEvent[A: JsonFormat](event: A): Unit =
{
val toDel: ListBuffer[CommandChannel] = ListBuffer.empty
val bytes = Serialization.serializeEvent(event)
event match {
case entry: StringEvent =>
channels.foreach {
case c: ConsoleChannel =>
if (entry.channelName.isEmpty || entry.channelName == Some(c.name)) {
c.publishEvent(event)
}
case c: NetworkChannel =>
try {
if (entry.channelName == Some(c.name)) {
c.publishBytes(bytes)
}
} catch {
case e: SocketException =>
toDel += c
}
}
case _ =>
channels.foreach {
case c: ConsoleChannel =>
def publishEvent[A: JsonFormat](event: A): Unit = {
val toDel: ListBuffer[CommandChannel] = ListBuffer.empty
val bytes = Serialization.serializeEvent(event)
event match {
case entry: StringEvent =>
channels.foreach {
case c: ConsoleChannel =>
if (entry.channelName.isEmpty || entry.channelName == Some(c.name)) {
c.publishEvent(event)
case c: NetworkChannel =>
try {
}
case c: NetworkChannel =>
try {
if (entry.channelName == Some(c.name)) {
c.publishBytes(bytes)
} catch {
case e: SocketException =>
toDel += c
}
}
}
toDel.toList match {
case Nil => // do nothing
case xs =>
lock.synchronized {
channelBuffer --= xs
}
}
} catch {
case e: SocketException =>
toDel += c
}
}
case _ =>
channels.foreach {
case c: ConsoleChannel =>
c.publishEvent(event)
case c: NetworkChannel =>
try {
c.publishBytes(bytes)
} catch {
case e: SocketException =>
toDel += c
}
}
}
toDel.toList match {
case Nil => // do nothing
case xs =>
lock.synchronized {
channelBuffer --= xs
}
}
}
// fanout publishEvent
def publishEventMessage(event: EventMessage): Unit =
{
val toDel: ListBuffer[CommandChannel] = ListBuffer.empty
event match {
// Special treatment for ConsolePromptEvent since it's hand coded without codec.
case e: ConsolePromptEvent =>
channels collect {
case c: ConsoleChannel => c.publishEventMessage(e)
}
case e: ConsoleUnpromptEvent =>
channels collect {
case c: ConsoleChannel => c.publishEventMessage(e)
}
case _ =>
// TODO do not do this on the calling thread
val bytes = Serialization.serializeEventMessage(event)
channels.foreach {
case c: ConsoleChannel =>
c.publishEventMessage(event)
case c: NetworkChannel =>
try {
c.publishBytes(bytes)
} catch {
case e: SocketException =>
toDel += c
}
}
}
toDel.toList match {
case Nil => // do nothing
case xs =>
lock.synchronized {
channelBuffer --= xs
}
}
def publishEventMessage(event: EventMessage): Unit = {
val toDel: ListBuffer[CommandChannel] = ListBuffer.empty
event match {
// Special treatment for ConsolePromptEvent since it's hand coded without codec.
case e: ConsolePromptEvent =>
channels collect {
case c: ConsoleChannel => c.publishEventMessage(e)
}
case e: ConsoleUnpromptEvent =>
channels collect {
case c: ConsoleChannel => c.publishEventMessage(e)
}
case _ =>
// TODO do not do this on the calling thread
val bytes = Serialization.serializeEventMessage(event)
channels.foreach {
case c: ConsoleChannel =>
c.publishEventMessage(event)
case c: NetworkChannel =>
try {
c.publishBytes(bytes)
} catch {
case e: SocketException =>
toDel += c
}
}
}
toDel.toList match {
case Nil => // do nothing
case xs =>
lock.synchronized {
channelBuffer --= xs
}
}
}
}

View File

@ -7,6 +7,7 @@ package internal
import sbt.io.Path
object CommandStrings {
/** The prefix used to identify a request to execute the remaining input on source changes.*/
val AboutCommand = "about"
val TasksCommand = "tasks"
@ -18,7 +19,8 @@ object CommandStrings {
val BootCommand = "boot"
val EvalCommand = "eval"
val evalBrief = (s"$EvalCommand <expression>", "Evaluates a Scala expression and prints the result and type.")
val evalBrief =
(s"$EvalCommand <expression>", "Evaluates a Scala expression and prints the result and type.")
val evalDetailed =
s"""$EvalCommand <expression>
@ -55,7 +57,8 @@ $ShowCommand <task>
val ExportCommand = "export"
val ExportStream = "export"
val lastGrepBrief = (LastGrepCommand, "Shows lines from the last output for 'key' that match 'pattern'.")
val lastGrepBrief =
(LastGrepCommand, "Shows lines from the last output for 'key' that match 'pattern'.")
val lastGrepDetailed =
s"""$LastGrepCommand <pattern>
Displays lines from the logging of previous commands that match `pattern`.
@ -66,7 +69,8 @@ $LastGrepCommand <pattern> [key]
<pattern> is a regular expression interpreted by java.util.Pattern. Matching text is highlighted (when highlighting is supported and enabled).
See also '$LastCommand'."""
val lastBrief = (LastCommand, "Displays output from a previous command or the output from a specific task.")
val lastBrief =
(LastCommand, "Displays output from a previous command or the output from a specific task.")
val lastDetailed =
s"""$LastCommand
Prints the logging for the previous command, typically at a more verbose level.
@ -76,7 +80,8 @@ $LastCommand <key>
See also '$LastGrepCommand'."""
val exportBrief = (s"$ExportCommand <tasks>+", "Executes tasks and displays the equivalent command lines.")
val exportBrief =
(s"$ExportCommand <tasks>+", "Executes tasks and displays the equivalent command lines.")
val exportDetailed =
s"""$ExportCommand [--last] <task>+
Runs the specified tasks and prints the equivalent command lines or other exportable information for those runs.
@ -92,7 +97,9 @@ $LastCommand <key>
"""
val InspectCommand = "inspect"
val inspectBrief = (s"$InspectCommand [uses|tree|definitions] <key>", "Prints the value for 'key', the defining scope, delegates, related definitions, and dependencies.")
val inspectBrief =
(s"$InspectCommand [uses|tree|definitions] <key>",
"Prints the value for 'key', the defining scope, delegates, related definitions, and dependencies.")
val inspectDetailed = s"""
|$InspectCommand <key>
|
@ -131,7 +138,8 @@ $LastCommand <key>
""".stripMargin.trim
val SetCommand = "set"
val setBrief = (s"$SetCommand [every] <setting>", "Evaluates a Setting and applies it to the current project.")
val setBrief = (s"$SetCommand [every] <setting>",
"Evaluates a Setting and applies it to the current project.")
val setDetailed =
s"""$SetCommand [every] <setting-expression>
@ -156,7 +164,8 @@ $LastCommand <key>
def settingsPreamble = commonPreamble("settings")
def tasksPreamble = commonPreamble("tasks") + """
def tasksPreamble =
commonPreamble("tasks") + """
Tasks produce values. Use the 'show' command to run the task and print the resulting value."""
def commonPreamble(label: String) = s"""
@ -200,7 +209,8 @@ $label
def aboutBrief = "Displays basic information about sbt and the build."
def aboutDetailed = aboutBrief
def projectBrief = (ProjectCommand, "Displays the current project or changes to the provided `project`.")
def projectBrief =
(ProjectCommand, "Displays the current project or changes to the provided `project`.")
def projectDetailed =
s"""$ProjectCommand
@ -233,7 +243,8 @@ $ProjectCommand ..
Use n+1 dots to change to the nth parent.
For example, 'project ....' is equivalent to three consecutive 'project ..' commands."""
def projectsBrief = "Lists the names of available projects or temporarily adds/removes extra builds to the session."
def projectsBrief =
"Lists the names of available projects or temporarily adds/removes extra builds to the session."
def projectsDetailed =
s"""$ProjectsCommand
List the names of available builds and the projects defined in those builds.
@ -263,7 +274,9 @@ $ProjectsCommand remove <URI>+
def LoadProjectImpl = "loadp"
def LoadProject = "reload"
def LoadProjectBrief = (LoadProject, "(Re)loads the current project or changes to plugins project or returns from it.")
def LoadProjectBrief =
(LoadProject,
"(Re)loads the current project or changes to plugins project or returns from it.")
def LoadProjectDetailed =
s"""$LoadProject

View File

@ -8,7 +8,8 @@ import sbt.util.Logger
import sbt.internal.inc.{ ClasspathOptionsUtil, ScalaInstance }
object ConsoleProject {
def apply(state: State, extra: String, cleanupCommands: String = "", options: Seq[String] = Nil)(implicit log: Logger): Unit = {
def apply(state: State, extra: String, cleanupCommands: String = "", options: Seq[String] = Nil)(
implicit log: Logger): Unit = {
val extracted = Project extract state
val cpImports = new Imports(extracted, state)
val bindings = ("currentState" -> state) :: ("extracted" -> extracted) :: ("cpHelpers" -> cpImports) :: Nil
@ -19,19 +20,28 @@ object ConsoleProject {
ScalaInstance(scalaProvider.version, scalaProvider.launcher)
}
val sourcesModule = extracted.get(Keys.scalaCompilerBridgeSource)
val compiler = Compiler.scalaCompiler(scalaInstance, ClasspathOptionsUtil.repl, None, ivyConf, sourcesModule)(state.configuration, log)
val compiler = Compiler.scalaCompiler(scalaInstance,
ClasspathOptionsUtil.repl,
None,
ivyConf,
sourcesModule)(state.configuration, log)
val imports = BuildUtil.getImports(unit.unit) ++ BuildUtil.importAll(bindings.map(_._1))
val importString = imports.mkString("", ";\n", ";\n\n")
val initCommands = importString + extra
// TODO - Hook up dsl classpath correctly...
(new Console(compiler))(
unit.classpath, options, initCommands, cleanupCommands
unit.classpath,
options,
initCommands,
cleanupCommands
)(Some(unit.loader), bindings)
}
/** Conveniences for consoleProject that shouldn't normally be used for builds. */
final class Imports private[sbt] (extracted: Extracted, state: State) {
import extracted._
implicit def taskKeyEvaluate[T](t: TaskKey[T]): Evaluate[T] = new Evaluate(runTask(t, state)._2)
implicit def taskKeyEvaluate[T](t: TaskKey[T]): Evaluate[T] =
new Evaluate(runTask(t, state)._2)
implicit def settingKeyEvaluate[T](s: SettingKey[T]): Evaluate[T] = new Evaluate(get(s))
}
final class Evaluate[T] private[sbt] (val eval: T)

View File

@ -30,7 +30,8 @@ private[sbt] abstract class BackgroundJob {
}
private[sbt] abstract class AbstractJobHandle extends JobHandle {
override def toString = s"JobHandle(${id}, ${humanReadableName}, ${Def.showFullKey.show(spawningTask)})"
override def toString =
s"JobHandle(${id}, ${humanReadableName}, ${Def.showFullKey.show(spawningTask)})"
}
private[sbt] abstract class AbstractBackgroundJobService extends BackgroundJobService {
@ -59,8 +60,11 @@ private[sbt] abstract class AbstractBackgroundJobService extends BackgroundJobSe
override def jobs: Vector[ThreadJobHandle] = jobSet.toVector
final class ThreadJobHandle(
override val id: Long, override val spawningTask: ScopedKey[_],
val logger: ManagedLogger, val workingDirectory: File, val job: BackgroundJob
override val id: Long,
override val spawningTask: ScopedKey[_],
val logger: ManagedLogger,
val workingDirectory: File,
val job: BackgroundJob
) extends AbstractJobHandle {
def humanReadableName: String = job.humanReadableName
// EC for onStop handler below
@ -89,7 +93,9 @@ private[sbt] abstract class AbstractBackgroundJobService extends BackgroundJobSe
protected def makeContext(id: Long, spawningTask: ScopedKey[_], state: State): ManagedLogger
def doRunInBackground(spawningTask: ScopedKey[_], state: State, start: (Logger, File) => BackgroundJob): JobHandle = {
def doRunInBackground(spawningTask: ScopedKey[_],
state: State,
start: (Logger, File) => BackgroundJob): JobHandle = {
val id = nextId.getAndIncrement()
val logger = makeContext(id, spawningTask, state)
val workingDir = serviceTempDir / s"job-$id"
@ -105,7 +111,8 @@ private[sbt] abstract class AbstractBackgroundJobService extends BackgroundJobSe
job
}
override def runInBackground(spawningTask: ScopedKey[_], state: State)(start: (Logger, File) => Unit): JobHandle = {
override def runInBackground(spawningTask: ScopedKey[_], state: State)(
start: (Logger, File) => Unit): JobHandle = {
pool.run(this, spawningTask, state)(start)
}
@ -126,7 +133,9 @@ private[sbt] abstract class AbstractBackgroundJobService extends BackgroundJobSe
private def withHandle(job: JobHandle)(f: ThreadJobHandle => Unit): Unit = job match {
case handle: ThreadJobHandle @unchecked => f(handle)
case dead: DeadHandle @unchecked => () // nothing to stop or wait for
case other => sys.error(s"BackgroundJobHandle does not originate with the current BackgroundJobService: $other")
case other =>
sys.error(
s"BackgroundJobHandle does not originate with the current BackgroundJobService: $other")
}
override def stop(job: JobHandle): Unit =
@ -144,21 +153,21 @@ private[sbt] abstract class AbstractBackgroundJobService extends BackgroundJobSe
* Since working directory is wiped out when the background job ends, the product JAR is deleted too.
* Meanwhile, the rest of the dependencies are cached for the duration of this service.
*/
override def copyClasspath(products: Classpath, full: Classpath, workingDirectory: File): Classpath =
{
def syncTo(dir: File)(source0: Attributed[File]): Attributed[File] =
{
val source = source0.data
val hash8 = Hash.toHex(Hash(source)).take(8)
val dest = dir / hash8 / source.getName
if (!dest.exists) { IO.copyFile(source, dest) }
Attributed.blank(dest)
}
val xs = (products.toVector map { syncTo(workingDirectory / "target") }) ++
((full diff products) map { syncTo(serviceTempDir / "target") })
Thread.sleep(100)
xs
override def copyClasspath(products: Classpath,
full: Classpath,
workingDirectory: File): Classpath = {
def syncTo(dir: File)(source0: Attributed[File]): Attributed[File] = {
val source = source0.data
val hash8 = Hash.toHex(Hash(source)).take(8)
val dest = dir / hash8 / source.getName
if (!dest.exists) { IO.copyFile(source, dest) }
Attributed.blank(dest)
}
val xs = (products.toVector map { syncTo(workingDirectory / "target") }) ++
((full diff products) map { syncTo(serviceTempDir / "target") })
Thread.sleep(100)
xs
}
}
private[sbt] object BackgroundThreadPool {
@ -176,7 +185,8 @@ private[sbt] class BackgroundThreadPool extends java.io.Closeable {
private val threadFactory = new java.util.concurrent.ThreadFactory() {
override def newThread(runnable: Runnable): Thread = {
val thread = new Thread(threadGroup, runnable, s"sbt-bg-threads-${nextThreadId.getAndIncrement}")
val thread =
new Thread(threadGroup, runnable, s"sbt-bg-threads-${nextThreadId.getAndIncrement}")
// Do NOT setDaemon because then the code in TaskExit.scala in sbt will insta-kill
// the backgrounded process, at least for the case of the run task.
thread
@ -186,13 +196,16 @@ private[sbt] class BackgroundThreadPool extends java.io.Closeable {
private val executor = new java.util.concurrent.ThreadPoolExecutor(
0, /* corePoolSize */
32, /* maxPoolSize, max # of bg tasks */
2, java.util.concurrent.TimeUnit.SECONDS, /* keep alive unused threads this long (if corePoolSize < maxPoolSize) */
2,
java.util.concurrent.TimeUnit.SECONDS,
/* keep alive unused threads this long (if corePoolSize < maxPoolSize) */
new java.util.concurrent.SynchronousQueue[Runnable](),
threadFactory
)
private class BackgroundRunnable(val taskName: String, body: () => Unit)
extends BackgroundJob with Runnable {
extends BackgroundJob
with Runnable {
import BackgroundThreadPool._
private val finishedLatch = new java.util.concurrent.CountDownLatch(1)
// synchronize to read/write this, no sync to just read
@ -200,23 +213,24 @@ private[sbt] class BackgroundThreadPool extends java.io.Closeable {
private var status: Status = Waiting
// double-finally for extra paranoia that we will finishedLatch.countDown
override def run() = try {
val go = synchronized {
status match {
case Waiting =>
status = Running(Thread.currentThread())
true
case Stopped(_) =>
false
case Running(_) =>
throw new RuntimeException("Impossible status of bg thread")
override def run() =
try {
val go = synchronized {
status match {
case Waiting =>
status = Running(Thread.currentThread())
true
case Stopped(_) =>
false
case Running(_) =>
throw new RuntimeException("Impossible status of bg thread")
}
}
}
try { if (go) body() }
finally cleanup()
} finally finishedLatch.countDown()
try { if (go) body() } finally cleanup()
} finally finishedLatch.countDown()
private class StopListener(val callback: () => Unit, val executionContext: ExecutionContext) extends Closeable {
private class StopListener(val callback: () => Unit, val executionContext: ExecutionContext)
extends Closeable {
override def close(): Unit = removeListener(this)
override def hashCode: Int = System.identityHashCode(this)
override def equals(other: Any): Boolean = other match {
@ -277,7 +291,8 @@ private[sbt] class BackgroundThreadPool extends java.io.Closeable {
}
}
def run(manager: AbstractBackgroundJobService, spawningTask: ScopedKey[_], state: State)(work: (Logger, File) => Unit): JobHandle = {
def run(manager: AbstractBackgroundJobService, spawningTask: ScopedKey[_], state: State)(
work: (Logger, File) => Unit): JobHandle = {
def start(logger: Logger, workingDir: File): BackgroundJob = {
val runnable = new BackgroundRunnable(spawningTask.key.label, { () =>
work(logger, workingDir)

View File

@ -10,6 +10,7 @@ import Def._
/** This represents a `Setting` expression configured by the sbt DSL. */
sealed trait DslEntry {
/** Called by the parser. Sets the position where this entry was defined in the build.sbt file. */
def withPos(pos: RangePosition): DslEntry
}
@ -48,24 +49,31 @@ object DslEntry {
/** this represents an actually Setting[_] or Seq[Setting[_]] configured by the sbt DSL. */
case class DslSetting(settings: SettingsDefinition) extends ProjectSettings {
def toSettings = settings.settings
final def withPos(pos: RangePosition): DslEntry = DslSetting(settings.settings.map(_.withPos(pos)))
final def withPos(pos: RangePosition): DslEntry =
DslSetting(settings.settings.map(_.withPos(pos)))
}
/** this represents an `enablePlugins()` in the sbt DSL */
case class DslEnablePlugins(plugins: Seq[AutoPlugin]) extends ProjectManipulation {
override val toFunction: Project => Project = _.enablePlugins(plugins: _*)
}
/** this represents an `disablePlugins()` in the sbt DSL */
case class DslDisablePlugins(plugins: Seq[AutoPlugin]) extends ProjectManipulation {
override val toFunction: Project => Project = _.disablePlugins(plugins: _*)
}
/** Represents registering an internal dependency for the current project */
case class DslDependsOn(cs: Seq[Eval[ClasspathDep[ProjectReference]]]) extends ProjectManipulation {
case class DslDependsOn(cs: Seq[Eval[ClasspathDep[ProjectReference]]])
extends ProjectManipulation {
override val toFunction: Project => Project = _.dependsOn(cs: _*)
}
/** Represents registering a set of configurations with the current project. */
case class DslConfigs(cs: Seq[Configuration]) extends ProjectManipulation {
override val toFunction: Project => Project = _.configs(cs: _*)
}
/** this represents an `aggregate()` in the sbt DSL */
case class DslAggregate(refs: Seq[Eval[ProjectReference]]) extends ProjectManipulation {
override val toFunction: Project => Project = _.aggregate(refs: _*)

View File

@ -13,16 +13,15 @@ private[sbt] object GCUtil {
val defaultMinForcegcInterval: Duration = 60.seconds
val lastGcCheck: AtomicLong = new AtomicLong(0L)
def forceGcWithInterval(minForcegcInterval: Duration, log: Logger): Unit =
{
val now = System.currentTimeMillis
val last = lastGcCheck.get
// This throttles System.gc calls to interval
if (now - last > minForcegcInterval.toMillis) {
lastGcCheck.lazySet(now)
forceGc(log)
}
def forceGcWithInterval(minForcegcInterval: Duration, log: Logger): Unit = {
val now = System.currentTimeMillis
val last = lastGcCheck.get
// This throttles System.gc calls to interval
if (now - last > minForcegcInterval.toMillis) {
lastGcCheck.lazySet(now)
forceGc(log)
}
}
def forceGc(log: Logger): Unit =
try {

View File

@ -1,7 +1,14 @@
package sbt
package internal
import sbt.librarymanagement.{ Configuration, Configurations, ModuleID, Resolver, SbtArtifacts, UpdateReport }
import sbt.librarymanagement.{
Configuration,
Configurations,
ModuleID,
Resolver,
SbtArtifacts,
UpdateReport
}
import sbt.internal.util.Attributed
import Def.{ ScopedKey, Setting }
import Keys._
@ -27,61 +34,78 @@ object GlobalPlugin {
injectInternalClasspath(Runtime, gp.internalClasspath),
injectInternalClasspath(Compile, gp.internalClasspath)
)
private[this] def injectInternalClasspath(config: Configuration, cp: Seq[Attributed[File]]): Setting[_] =
internalDependencyClasspath in config ~= { prev => (prev ++ cp).distinct }
private[this] def injectInternalClasspath(config: Configuration,
cp: Seq[Attributed[File]]): Setting[_] =
internalDependencyClasspath in config ~= { prev =>
(prev ++ cp).distinct
}
def build(base: File, s: State, config: LoadBuildConfiguration): (BuildStructure, State) =
{
val newInject = config.injectSettings.copy(global = config.injectSettings.global ++ globalPluginSettings)
val globalConfig = config.copy(injectSettings = newInject, pluginManagement = config.pluginManagement.forGlobalPlugin)
val (eval, structure) = Load(base, s, globalConfig)
val session = Load.initialSession(structure, eval)
(structure, Project.setProject(session, structure, s))
}
def load(base: File, s: State, config: LoadBuildConfiguration): GlobalPlugin =
{
val (structure, state) = build(base, s, config)
val (newS, data) = extract(state, structure)
Project.runUnloadHooks(newS) // discard state
GlobalPlugin(data, structure, inject(data), base)
}
def extract(state: State, structure: BuildStructure): (State, GlobalPluginData) =
{
import structure.{ data, root, rootProject }
val p: Scope = Scope.GlobalScope in ProjectRef(root, rootProject(root))
def build(base: File, s: State, config: LoadBuildConfiguration): (BuildStructure, State) = {
val newInject =
config.injectSettings.copy(global = config.injectSettings.global ++ globalPluginSettings)
val globalConfig = config.copy(injectSettings = newInject,
pluginManagement = config.pluginManagement.forGlobalPlugin)
val (eval, structure) = Load(base, s, globalConfig)
val session = Load.initialSession(structure, eval)
(structure, Project.setProject(session, structure, s))
}
def load(base: File, s: State, config: LoadBuildConfiguration): GlobalPlugin = {
val (structure, state) = build(base, s, config)
val (newS, data) = extract(state, structure)
Project.runUnloadHooks(newS) // discard state
GlobalPlugin(data, structure, inject(data), base)
}
def extract(state: State, structure: BuildStructure): (State, GlobalPluginData) = {
import structure.{ data, root, rootProject }
val p: Scope = Scope.GlobalScope in ProjectRef(root, rootProject(root))
val taskInit = Def.task {
val intcp = (internalDependencyClasspath in Runtime).value
val prods = (exportedProducts in Runtime).value
val depMap = projectDescriptors.value + ivyModule.value.dependencyMapping(state.log)
// If we reference it directly (if it's an executionRoot) then it forces an update, which is not what we want.
val updateReport = Def.taskDyn { Def.task { update.value } }.value
val taskInit = Def.task {
val intcp = (internalDependencyClasspath in Runtime).value
val prods = (exportedProducts in Runtime).value
val depMap = projectDescriptors.value + ivyModule.value.dependencyMapping(state.log)
// If we reference it directly (if it's an executionRoot) then it forces an update, which is not what we want.
val updateReport = Def.taskDyn { Def.task { update.value } }.value
GlobalPluginData(projectID.value, projectDependencies.value, depMap, resolvers.value, (fullClasspath in Runtime).value,
(prods ++ intcp).distinct)(updateReport)
}
val resolvedTaskInit = taskInit mapReferenced Project.mapScope(Scope replaceThis p)
val task = resolvedTaskInit evaluate data
val roots = resolvedTaskInit.dependencies
evaluate(state, structure, task, roots)
GlobalPluginData(projectID.value,
projectDependencies.value,
depMap,
resolvers.value,
(fullClasspath in Runtime).value,
(prods ++ intcp).distinct)(updateReport)
}
def evaluate[T](state: State, structure: BuildStructure, t: Task[T], roots: Seq[ScopedKey[_]]): (State, T) =
{
import EvaluateTask._
withStreams(structure, state) { str =>
val nv = nodeView(state, str, roots)
val config = EvaluateTask.extractedTaskConfig(Project.extract(state), structure, state)
val (newS, result) = runTask(t, state, str, structure.index.triggers, config)(nv)
(newS, processResult(result, newS.log))
}
val resolvedTaskInit = taskInit mapReferenced Project.mapScope(Scope replaceThis p)
val task = resolvedTaskInit evaluate data
val roots = resolvedTaskInit.dependencies
evaluate(state, structure, task, roots)
}
def evaluate[T](state: State,
structure: BuildStructure,
t: Task[T],
roots: Seq[ScopedKey[_]]): (State, T) = {
import EvaluateTask._
withStreams(structure, state) { str =>
val nv = nodeView(state, str, roots)
val config = EvaluateTask.extractedTaskConfig(Project.extract(state), structure, state)
val (newS, result) = runTask(t, state, str, structure.index.triggers, config)(nv)
(newS, processResult(result, newS.log))
}
val globalPluginSettings = Project.inScope(Scope.GlobalScope in LocalRootProject)(Seq(
organization := SbtArtifacts.Organization,
onLoadMessage := Keys.baseDirectory("Loading global plugins from " + _).value,
name := "global-plugin",
sbtPlugin := true,
version := "0.0"
))
}
val globalPluginSettings = Project.inScope(Scope.GlobalScope in LocalRootProject)(
Seq(
organization := SbtArtifacts.Organization,
onLoadMessage := Keys.baseDirectory("Loading global plugins from " + _).value,
name := "global-plugin",
sbtPlugin := true,
version := "0.0"
))
}
final case class GlobalPluginData(projectID: ModuleID, dependencies: Seq[ModuleID], descriptors: Map[ModuleRevisionId, ModuleDescriptor], resolvers: Seq[Resolver], fullClasspath: Classpath, internalClasspath: Classpath)(val updateReport: UpdateReport)
final case class GlobalPlugin(data: GlobalPluginData, structure: BuildStructure, inject: Seq[Setting[_]], base: File)
final case class GlobalPluginData(projectID: ModuleID,
dependencies: Seq[ModuleID],
descriptors: Map[ModuleRevisionId, ModuleDescriptor],
resolvers: Seq[Resolver],
fullClasspath: Classpath,
internalClasspath: Classpath)(val updateReport: UpdateReport)
final case class GlobalPlugin(data: GlobalPluginData,
structure: BuildStructure,
inject: Seq[Setting[_]],
base: File)

View File

@ -4,16 +4,18 @@ package internal
import Def.Setting
import java.net.URI
private[sbt] final class GroupedAutoPlugins(val all: Seq[AutoPlugin], val byBuild: Map[URI, Seq[AutoPlugin]]) {
private[sbt] final class GroupedAutoPlugins(val all: Seq[AutoPlugin],
val byBuild: Map[URI, Seq[AutoPlugin]]) {
def globalSettings: Seq[Setting[_]] = all.flatMap(_.globalSettings)
def buildSettings(uri: URI): Seq[Setting[_]] = byBuild.getOrElse(uri, Nil).flatMap(_.buildSettings)
def buildSettings(uri: URI): Seq[Setting[_]] =
byBuild.getOrElse(uri, Nil).flatMap(_.buildSettings)
}
private[sbt] object GroupedAutoPlugins {
private[sbt] def apply(units: Map[URI, LoadedBuildUnit]): GroupedAutoPlugins =
{
val byBuild: Map[URI, Seq[AutoPlugin]] = units.mapValues(unit => unit.defined.values.flatMap(_.autoPlugins).toSeq.distinct).toMap
val all: Seq[AutoPlugin] = byBuild.values.toSeq.flatten.distinct
new GroupedAutoPlugins(all, byBuild)
}
private[sbt] def apply(units: Map[URI, LoadedBuildUnit]): GroupedAutoPlugins = {
val byBuild: Map[URI, Seq[AutoPlugin]] =
units.mapValues(unit => unit.defined.values.flatMap(_.autoPlugins).toSeq.distinct).toMap
val all: Seq[AutoPlugin] = byBuild.values.toSeq.flatten.distinct
new GroupedAutoPlugins(all, byBuild)
}
}

View File

@ -19,10 +19,13 @@ object Inspect {
val Uses: Mode = UsesMode
val Definitions: Mode = DefinitionsMode
def parser: State => Parser[(Inspect.Mode, ScopedKey[_])] = (s: State) => spacedModeParser(s) flatMap {
case opt @ (UsesMode | DefinitionsMode) => allKeyParser(s).map(key => (opt, Def.ScopedKey(Global, key)))
case opt @ (DependencyTreeMode | Details(_)) => spacedKeyParser(s).map(key => (opt, key))
}
def parser: State => Parser[(Inspect.Mode, ScopedKey[_])] =
(s: State) =>
spacedModeParser(s) flatMap {
case opt @ (UsesMode | DefinitionsMode) =>
allKeyParser(s).map(key => (opt, Def.ScopedKey(Global, key)))
case opt @ (DependencyTreeMode | Details(_)) => spacedKeyParser(s).map(key => (opt, key))
}
val spacedModeParser: (State => Parser[Mode]) = (s: State) => {
val actual = "actual" ^^^ Details(true)
val tree = "tree" ^^^ DependencyTree
@ -31,28 +34,29 @@ object Inspect {
token(Space ~> (tree | actual | uses | definitions)) ?? Details(false)
}
def allKeyParser(s: State): Parser[AttributeKey[_]] =
{
val keyMap = Project.structure(s).index.keyMap
token(Space ~> (ID !!! "Expected key" examples keyMap.keySet)) flatMap { key => Act.getKey(keyMap, key, idFun) }
def allKeyParser(s: State): Parser[AttributeKey[_]] = {
val keyMap = Project.structure(s).index.keyMap
token(Space ~> (ID !!! "Expected key" examples keyMap.keySet)) flatMap { key =>
Act.getKey(keyMap, key, idFun)
}
val spacedKeyParser: State => Parser[ScopedKey[_]] = (s: State) => Act.requireSession(s, token(Space) ~> Act.scopedKeyParser(s))
}
val spacedKeyParser: State => Parser[ScopedKey[_]] = (s: State) =>
Act.requireSession(s, token(Space) ~> Act.scopedKeyParser(s))
def output(s: State, option: Mode, sk: Def.ScopedKey[_]): String =
{
val extracted = Project.extract(s)
import extracted._
option match {
case Details(actual) =>
Project.details(structure, actual, sk.scope, sk.key)
case DependencyTreeMode =>
val basedir = new File(Project.session(s).current.build)
Project.settingGraph(structure, basedir, sk).dependsAscii(get(sbt.Keys.asciiGraphWidth))
case UsesMode =>
Project.showUses(Project.usedBy(structure, true, sk.key))
case DefinitionsMode =>
Project.showDefinitions(sk.key, Project.definitions(structure, true, sk.key))
}
def output(s: State, option: Mode, sk: Def.ScopedKey[_]): String = {
val extracted = Project.extract(s)
import extracted._
option match {
case Details(actual) =>
Project.details(structure, actual, sk.scope, sk.key)
case DependencyTreeMode =>
val basedir = new File(Project.session(s).current.build)
Project.settingGraph(structure, basedir, sk).dependsAscii(get(sbt.Keys.asciiGraphWidth))
case UsesMode =>
Project.showUses(Project.usedBy(structure, true, sk.key))
case DefinitionsMode =>
Project.showDefinitions(sk.key, Project.definitions(structure, true, sk.key))
}
}
}

View File

@ -7,7 +7,14 @@ package internal
import sbt.internal.util.Attributed
import sbt.util.{ Level, Logger }
import sbt.librarymanagement.{ Configurations, CrossVersion, Disabled, MavenRepository, ModuleID, Resolver }
import sbt.librarymanagement.{
Configurations,
CrossVersion,
Disabled,
MavenRepository,
ModuleID,
Resolver
}
import java.io.File
import Configurations.Compile
@ -20,7 +27,9 @@ object IvyConsole {
final val Name = "ivy-console"
lazy val command =
Command.command(Name) { state =>
val Dependencies(managed, repos, unmanaged) = parseDependencies(state.remainingCommands map { _.commandLine }, state.log)
val Dependencies(managed, repos, unmanaged) = parseDependencies(state.remainingCommands map {
_.commandLine
}, state.log)
val base = new File(CommandUtil.bootDirectory(state), Name)
IO.createDirectory(base)
@ -36,15 +45,21 @@ object IvyConsole {
logLevel in Global := Level.Warn,
showSuccess in Global := false
)
val append = Load.transformSettings(Load.projectScope(currentRef), currentRef.build, rootProject, depSettings)
val append = Load.transformSettings(Load.projectScope(currentRef),
currentRef.build,
rootProject,
depSettings)
val newStructure = Load.reapply(session.original ++ append, structure)
val newState = state.copy(remainingCommands = Exec("console-quick", None) :: Nil)
Project.setProject(session, newStructure, newState)
}
final case class Dependencies(managed: Seq[ModuleID], resolvers: Seq[Resolver], unmanaged: Seq[File])
def parseDependencies(args: Seq[String], log: Logger): Dependencies = (Dependencies(Nil, Nil, Nil) /: args)(parseArgument(log))
final case class Dependencies(managed: Seq[ModuleID],
resolvers: Seq[Resolver],
unmanaged: Seq[File])
def parseDependencies(args: Seq[String], log: Logger): Dependencies =
(Dependencies(Nil, Nil, Nil) /: args)(parseArgument(log))
def parseArgument(log: Logger)(acc: Dependencies, arg: String): Dependencies =
arg match {
case _ if arg contains " at " => acc.copy(resolvers = parseResolver(arg) +: acc.resolvers)
@ -52,11 +67,10 @@ object IvyConsole {
case _ => acc.copy(managed = parseManaged(arg, log) ++ acc.managed)
}
private[this] def parseResolver(arg: String): MavenRepository =
{
val Array(name, url) = arg.split(" at ")
MavenRepository(name.trim, url.trim)
}
private[this] def parseResolver(arg: String): MavenRepository = {
val Array(name, url) = arg.split(" at ")
MavenRepository(name.trim, url.trim)
}
val DepPattern = """([^%]+)%(%?)([^%]+)%([^%]+)""".r
def parseManaged(arg: String, log: Logger): Seq[ModuleID] =

View File

@ -14,16 +14,19 @@ object KeyIndex {
def empty: ExtendableKeyIndex = new KeyIndex0(emptyBuildIndex)
def apply(known: Iterable[ScopedKey[_]], projects: Map[URI, Set[String]]): ExtendableKeyIndex =
(base(projects) /: known) { _ add _ }
def aggregate(known: Iterable[ScopedKey[_]], extra: BuildUtil[_], projects: Map[URI, Set[String]]): ExtendableKeyIndex =
(base(projects) /: known) { (index, key) => index.addAggregated(key, extra) }
private[this] def base(projects: Map[URI, Set[String]]): ExtendableKeyIndex =
{
val data = for ((uri, ids) <- projects) yield {
val data = ids.map(id => Option(id) -> new ConfigIndex(Map.empty))
Option(uri) -> new ProjectIndex(data.toMap)
}
new KeyIndex0(new BuildIndex(data.toMap))
def aggregate(known: Iterable[ScopedKey[_]],
extra: BuildUtil[_],
projects: Map[URI, Set[String]]): ExtendableKeyIndex =
(base(projects) /: known) { (index, key) =>
index.addAggregated(key, extra)
}
private[this] def base(projects: Map[URI, Set[String]]): ExtendableKeyIndex = {
val data = for ((uri, ids) <- projects) yield {
val data = ids.map(id => Option(id) -> new ConfigIndex(Map.empty))
Option(uri) -> new ProjectIndex(data.toMap)
}
new KeyIndex0(new BuildIndex(data.toMap))
}
def combine(indices: Seq[KeyIndex]): KeyIndex = new KeyIndex {
def buildURIs = concat(_.buildURIs)
@ -31,10 +34,13 @@ object KeyIndex {
def exists(project: Option[ResolvedReference]): Boolean = indices.exists(_ exists project)
def configs(proj: Option[ResolvedReference]) = concat(_.configs(proj))
def tasks(proj: Option[ResolvedReference], conf: Option[String]) = concat(_.tasks(proj, conf))
def tasks(proj: Option[ResolvedReference], conf: Option[String], key: String) = concat(_.tasks(proj, conf, key))
def tasks(proj: Option[ResolvedReference], conf: Option[String], key: String) =
concat(_.tasks(proj, conf, key))
def keys(proj: Option[ResolvedReference]) = concat(_.keys(proj))
def keys(proj: Option[ResolvedReference], conf: Option[String]) = concat(_.keys(proj, conf))
def keys(proj: Option[ResolvedReference], conf: Option[String], task: Option[AttributeKey[_]]) = concat(_.keys(proj, conf, task))
def keys(proj: Option[ResolvedReference],
conf: Option[String],
task: Option[AttributeKey[_]]) = concat(_.keys(proj, conf, task))
def concat[T](f: KeyIndex => Set[T]): Set[T] =
(Set.empty[T] /: indices)((s, k) => s ++ f(k))
}
@ -49,18 +55,25 @@ import KeyIndex._
trait KeyIndex {
// TODO, optimize
def isEmpty(proj: Option[ResolvedReference], conf: Option[String]): Boolean = keys(proj, conf).isEmpty
def isEmpty(proj: Option[ResolvedReference], conf: Option[String], task: Option[AttributeKey[_]]): Boolean = keys(proj, conf, task).isEmpty
def isEmpty(proj: Option[ResolvedReference], conf: Option[String]): Boolean =
keys(proj, conf).isEmpty
def isEmpty(proj: Option[ResolvedReference],
conf: Option[String],
task: Option[AttributeKey[_]]): Boolean = keys(proj, conf, task).isEmpty
def buildURIs: Set[URI]
def projects(uri: URI): Set[String]
def exists(project: Option[ResolvedReference]): Boolean
def configs(proj: Option[ResolvedReference]): Set[String]
def tasks(proj: Option[ResolvedReference], conf: Option[String]): Set[AttributeKey[_]]
def tasks(proj: Option[ResolvedReference], conf: Option[String], key: String): Set[AttributeKey[_]]
def tasks(proj: Option[ResolvedReference],
conf: Option[String],
key: String): Set[AttributeKey[_]]
def keys(proj: Option[ResolvedReference]): Set[String]
def keys(proj: Option[ResolvedReference], conf: Option[String]): Set[String]
def keys(proj: Option[ResolvedReference], conf: Option[String], task: Option[AttributeKey[_]]): Set[String]
def keys(proj: Option[ResolvedReference],
conf: Option[String],
task: Option[AttributeKey[_]]): Set[String]
}
trait ExtendableKeyIndex extends KeyIndex {
def add(scoped: ScopedKey[_]): ExtendableKeyIndex
@ -68,26 +81,36 @@ trait ExtendableKeyIndex extends KeyIndex {
}
// task axis <-> key
private[sbt] final class AKeyIndex(val data: Relation[Option[AttributeKey[_]], String]) {
def add(task: Option[AttributeKey[_]], key: AttributeKey[_]): AKeyIndex = new AKeyIndex(data + (task, key.label))
def add(task: Option[AttributeKey[_]], key: AttributeKey[_]): AKeyIndex =
new AKeyIndex(data + (task, key.label))
def keys(task: Option[AttributeKey[_]]): Set[String] = data.forward(task)
def allKeys: Set[String] = data._2s.toSet
def tasks: Set[AttributeKey[_]] = data._1s.flatten.toSet
def tasks(key: String): Set[AttributeKey[_]] = data.reverse(key).flatten.toSet
}
private[sbt] final class ConfigIndex(val data: Map[Option[String], AKeyIndex]) {
def add(config: Option[String], task: Option[AttributeKey[_]], key: AttributeKey[_]): ConfigIndex =
def add(config: Option[String],
task: Option[AttributeKey[_]],
key: AttributeKey[_]): ConfigIndex =
new ConfigIndex(data updated (config, keyIndex(config).add(task, key)))
def keyIndex(conf: Option[String]): AKeyIndex = getOr(data, conf, emptyAKeyIndex)
def configs: Set[String] = keySet(data)
}
private[sbt] final class ProjectIndex(val data: Map[Option[String], ConfigIndex]) {
def add(id: Option[String], config: Option[String], task: Option[AttributeKey[_]], key: AttributeKey[_]): ProjectIndex =
def add(id: Option[String],
config: Option[String],
task: Option[AttributeKey[_]],
key: AttributeKey[_]): ProjectIndex =
new ProjectIndex(data updated (id, confIndex(id).add(config, task, key)))
def confIndex(id: Option[String]): ConfigIndex = getOr(data, id, emptyConfigIndex)
def projects: Set[String] = keySet(data)
}
private[sbt] final class BuildIndex(val data: Map[Option[URI], ProjectIndex]) {
def add(build: Option[URI], project: Option[String], config: Option[String], task: Option[AttributeKey[_]], key: AttributeKey[_]): BuildIndex =
def add(build: Option[URI],
project: Option[String],
config: Option[String],
task: Option[AttributeKey[_]],
key: AttributeKey[_]): BuildIndex =
new BuildIndex(data updated (build, projectIndex(build).add(project, config, task, key)))
def projectIndex(build: Option[URI]): ProjectIndex = getOr(data, build, emptyProjectIndex)
def builds: Set[URI] = keySet(data)
@ -95,32 +118,40 @@ private[sbt] final class BuildIndex(val data: Map[Option[URI], ProjectIndex]) {
private[sbt] final class KeyIndex0(val data: BuildIndex) extends ExtendableKeyIndex {
def buildURIs: Set[URI] = data.builds
def projects(uri: URI): Set[String] = data.projectIndex(Some(uri)).projects
def exists(proj: Option[ResolvedReference]): Boolean =
{
val (build, project) = parts(proj)
data.data.get(build).flatMap(_.data.get(project)).isDefined
}
def exists(proj: Option[ResolvedReference]): Boolean = {
val (build, project) = parts(proj)
data.data.get(build).flatMap(_.data.get(project)).isDefined
}
def configs(project: Option[ResolvedReference]): Set[String] = confIndex(project).configs
def tasks(proj: Option[ResolvedReference], conf: Option[String]): Set[AttributeKey[_]] = keyIndex(proj, conf).tasks
def tasks(proj: Option[ResolvedReference], conf: Option[String], key: String): Set[AttributeKey[_]] = keyIndex(proj, conf).tasks(key)
def keys(proj: Option[ResolvedReference]): Set[String] = (Set.empty[String] /: optConfigs(proj)) { (s, c) => s ++ keys(proj, c) }
def keys(proj: Option[ResolvedReference], conf: Option[String]): Set[String] = keyIndex(proj, conf).allKeys
def keys(proj: Option[ResolvedReference], conf: Option[String], task: Option[AttributeKey[_]]): Set[String] = keyIndex(proj, conf).keys(task)
def tasks(proj: Option[ResolvedReference], conf: Option[String]): Set[AttributeKey[_]] =
keyIndex(proj, conf).tasks
def tasks(proj: Option[ResolvedReference],
conf: Option[String],
key: String): Set[AttributeKey[_]] = keyIndex(proj, conf).tasks(key)
def keys(proj: Option[ResolvedReference]): Set[String] =
(Set.empty[String] /: optConfigs(proj)) { (s, c) =>
s ++ keys(proj, c)
}
def keys(proj: Option[ResolvedReference], conf: Option[String]): Set[String] =
keyIndex(proj, conf).allKeys
def keys(proj: Option[ResolvedReference],
conf: Option[String],
task: Option[AttributeKey[_]]): Set[String] = keyIndex(proj, conf).keys(task)
def keyIndex(proj: Option[ResolvedReference], conf: Option[String]): AKeyIndex =
confIndex(proj).keyIndex(conf)
def confIndex(proj: Option[ResolvedReference]): ConfigIndex =
{
val (build, project) = parts(proj)
data.projectIndex(build).confIndex(project)
}
def confIndex(proj: Option[ResolvedReference]): ConfigIndex = {
val (build, project) = parts(proj)
data.projectIndex(build).confIndex(project)
}
def parts(proj: Option[Reference]): (Option[URI], Option[String]) =
proj match {
case Some(ProjectRef(uri, id)) => (Some(uri), Some(id))
case Some(BuildRef(uri)) => (Some(uri), None)
case _ => (None, None)
}
private[this] def optConfigs(project: Option[ResolvedReference]): Seq[Option[String]] = None +: (configs(project).toSeq map some.fn)
private[this] def optConfigs(project: Option[ResolvedReference]): Seq[Option[String]] =
None +: (configs(project).toSeq map some.fn)
def addAggregated(scoped: ScopedKey[_], extra: BuildUtil[_]): ExtendableKeyIndex =
if (validID(scoped.key.label)) {
@ -131,11 +162,14 @@ private[sbt] final class KeyIndex0(val data: BuildIndex) extends ExtendableKeyIn
def add(scoped: ScopedKey[_]): ExtendableKeyIndex =
if (validID(scoped.key.label)) add0(scoped) else this
private[this] def add0(scoped: ScopedKey[_]): ExtendableKeyIndex =
{
val (build, project) = parts(scoped.scope.project.toOption)
add1(build, project, scoped.scope.config, scoped.scope.task, scoped.key)
}
private[this] def add1(uri: Option[URI], id: Option[String], config: ScopeAxis[ConfigKey], task: ScopeAxis[AttributeKey[_]], key: AttributeKey[_]): ExtendableKeyIndex =
private[this] def add0(scoped: ScopedKey[_]): ExtendableKeyIndex = {
val (build, project) = parts(scoped.scope.project.toOption)
add1(build, project, scoped.scope.config, scoped.scope.task, scoped.key)
}
private[this] def add1(uri: Option[URI],
id: Option[String],
config: ScopeAxis[ConfigKey],
task: ScopeAxis[AttributeKey[_]],
key: AttributeKey[_]): ExtendableKeyIndex =
new KeyIndex0(data.add(uri, id, config.toOption.map(_.name), task.toOption, key))
}

File diff suppressed because it is too large Load Diff

View File

@ -29,7 +29,8 @@ private[sbt] final class LoadedSbtFile(
generatedFiles ++ o.generatedFiles
)
def clearProjects = new LoadedSbtFile(settings, Nil, importedDefs, manipulations, definitions, generatedFiles)
def clearProjects =
new LoadedSbtFile(settings, Nil, importedDefs, manipulations, definitions, generatedFiles)
}
/**
@ -42,7 +43,9 @@ private[sbt] final class DefinedSbtValues(val sbtFiles: Seq[compiler.EvalDefinit
sbtFiles flatMap (_ values parent)
def classloader(parent: ClassLoader): ClassLoader =
sbtFiles.foldLeft(parent) { (cl, e) => e.loader(cl) }
sbtFiles.foldLeft(parent) { (cl, e) =>
e.loader(cl)
}
def imports: Seq[String] = {
// TODO - Sanity check duplicates and such, so users get a nice warning rather
@ -69,16 +72,18 @@ private[sbt] final class DefinedSbtValues(val sbtFiles: Seq[compiler.EvalDefinit
new DefinedSbtValues(sbtFiles ++ other.sbtFiles)
}
private[sbt] object DefinedSbtValues {
/** Construct a DefinedSbtValues object directly from the underlying representation. */
def apply(eval: compiler.EvalDefinitions): DefinedSbtValues =
new DefinedSbtValues(Seq(eval))
/** Construct an empty value object. */
def empty = new DefinedSbtValues(Nil)
}
private[sbt] object LoadedSbtFile {
/** Represents an empty .sbt file: no Projects, imports, or settings.*/
def empty = new LoadedSbtFile(Nil, Nil, Nil, Nil, DefinedSbtValues.empty, Nil)
}

View File

@ -9,14 +9,23 @@ import Def.ScopedKey
import Scope.GlobalScope
import Keys.{ logLevel, logManager, persistLogLevel, persistTraceLevel, sLog, traceLevel }
import scala.Console.{ BLUE, RESET }
import sbt.internal.util.{ AttributeKey, ConsoleOut, Settings, SuppressedTraceContext, MainAppender }
import sbt.internal.util.{
AttributeKey,
ConsoleOut,
Settings,
SuppressedTraceContext,
MainAppender
}
import MainAppender._
import sbt.util.{ Level, Logger, LogExchange }
import sbt.internal.util.ManagedLogger
import org.apache.logging.log4j.core.Appender
sealed abstract class LogManager {
def apply(data: Settings[Scope], state: State, task: ScopedKey[_], writer: PrintWriter): ManagedLogger
def apply(data: Settings[Scope],
state: State,
task: ScopedKey[_],
writer: PrintWriter): ManagedLogger
def backgroundLog(data: Settings[Scope], state: State, task: ScopedKey[_]): ManagedLogger
}
@ -25,31 +34,40 @@ object LogManager {
private val generateId: AtomicInteger = new AtomicInteger
// This is called by mkStreams
def construct(data: Settings[Scope], state: State): (ScopedKey[_], PrintWriter) => ManagedLogger = (task: ScopedKey[_], to: PrintWriter) =>
{
val manager: LogManager = (logManager in task.scope).get(data) getOrElse { defaultManager(state.globalLogging.console) }
def construct(data: Settings[Scope],
state: State): (ScopedKey[_], PrintWriter) => ManagedLogger =
(task: ScopedKey[_], to: PrintWriter) => {
val manager: LogManager = (logManager in task.scope).get(data) getOrElse {
defaultManager(state.globalLogging.console)
}
manager(data, state, task, to)
}
def constructBackgroundLog(data: Settings[Scope], state: State): (ScopedKey[_]) => ManagedLogger = (task: ScopedKey[_]) =>
{
val manager: LogManager = (logManager in task.scope).get(data) getOrElse { defaultManager(state.globalLogging.console) }
def constructBackgroundLog(data: Settings[Scope],
state: State): (ScopedKey[_]) => ManagedLogger =
(task: ScopedKey[_]) => {
val manager: LogManager = (logManager in task.scope).get(data) getOrElse {
defaultManager(state.globalLogging.console)
}
manager.backgroundLog(data, state, task)
}
def defaultManager(console: ConsoleOut): LogManager = withLoggers((sk, s) => defaultScreen(console))
def defaultManager(console: ConsoleOut): LogManager =
withLoggers((sk, s) => defaultScreen(console))
// This is called by Defaults.
def defaults(extra: ScopedKey[_] => Seq[Appender], console: ConsoleOut): LogManager =
withLoggers((task, state) => defaultScreen(console, suppressedMessage(task, state)), extra = extra)
withLoggers((task, state) => defaultScreen(console, suppressedMessage(task, state)),
extra = extra)
def withScreenLogger(mk: (ScopedKey[_], State) => Appender): LogManager = withLoggers(screen = mk)
def withScreenLogger(mk: (ScopedKey[_], State) => Appender): LogManager =
withLoggers(screen = mk)
def withLoggers(
screen: (ScopedKey[_], State) => Appender = (sk, s) => defaultScreen(s.globalLogging.console),
backed: PrintWriter => Appender = defaultBacked,
relay: Unit => Appender = defaultRelay,
extra: ScopedKey[_] => Seq[Appender] = _ => Nil
screen: (ScopedKey[_], State) => Appender = (sk, s) => defaultScreen(s.globalLogging.console),
backed: PrintWriter => Appender = defaultBacked,
relay: Unit => Appender = defaultRelay,
extra: ScopedKey[_] => Seq[Appender] = _ => Nil
): LogManager = new DefaultLogManager(screen, backed, relay, extra)
private class DefaultLogManager(
@ -58,34 +76,61 @@ object LogManager {
relay: Unit => Appender,
extra: ScopedKey[_] => Seq[Appender]
) extends LogManager {
def apply(data: Settings[Scope], state: State, task: ScopedKey[_], to: PrintWriter): ManagedLogger =
defaultLogger(data, state, task, screen(task, state), backed(to), relay(()), extra(task).toList)
def apply(data: Settings[Scope],
state: State,
task: ScopedKey[_],
to: PrintWriter): ManagedLogger =
defaultLogger(data,
state,
task,
screen(task, state),
backed(to),
relay(()),
extra(task).toList)
def backgroundLog(data: Settings[Scope], state: State, task: ScopedKey[_]): ManagedLogger =
LogManager.backgroundLog(data, state, task, screen(task, state), relay(()), extra(task).toList)
LogManager.backgroundLog(data,
state,
task,
screen(task, state),
relay(()),
extra(task).toList)
}
// This is the main function that is used to generate the logger for tasks.
def defaultLogger(data: Settings[Scope], state: State, task: ScopedKey[_],
console: Appender, backed: Appender, relay: Appender, extra: List[Appender]): ManagedLogger =
{
val execOpt = state.currentCommand
val loggerName: String = s"${task.key.label}-${generateId.incrementAndGet}"
val channelName: Option[String] = execOpt flatMap { e => e.source map { _.channelName } }
val execId: Option[String] = execOpt flatMap { _.execId }
val log = LogExchange.logger(loggerName, channelName, execId)
val scope = task.scope
// to change from global being the default to overriding, switch the order of state.get and data.get
def getOr[T](key: AttributeKey[T], default: T): T = data.get(scope, key) orElse state.get(key) getOrElse default
val screenLevel = getOr(logLevel.key, Level.Info)
val backingLevel = getOr(persistLogLevel.key, Level.Debug)
val screenTrace = getOr(traceLevel.key, defaultTraceLevel(state))
val backingTrace = getOr(persistTraceLevel.key, Int.MaxValue)
val extraBacked = state.globalLogging.backed :: relay :: Nil
val consoleOpt = consoleLocally(state, console)
multiLogger(log, MainAppender.MainAppenderConfig(consoleOpt, backed,
extraBacked ::: extra, screenLevel, backingLevel, screenTrace, backingTrace))
def defaultLogger(data: Settings[Scope],
state: State,
task: ScopedKey[_],
console: Appender,
backed: Appender,
relay: Appender,
extra: List[Appender]): ManagedLogger = {
val execOpt = state.currentCommand
val loggerName: String = s"${task.key.label}-${generateId.incrementAndGet}"
val channelName: Option[String] = execOpt flatMap { e =>
e.source map { _.channelName }
}
val execId: Option[String] = execOpt flatMap { _.execId }
val log = LogExchange.logger(loggerName, channelName, execId)
val scope = task.scope
// to change from global being the default to overriding, switch the order of state.get and data.get
def getOr[T](key: AttributeKey[T], default: T): T =
data.get(scope, key) orElse state.get(key) getOrElse default
val screenLevel = getOr(logLevel.key, Level.Info)
val backingLevel = getOr(persistLogLevel.key, Level.Debug)
val screenTrace = getOr(traceLevel.key, defaultTraceLevel(state))
val backingTrace = getOr(persistTraceLevel.key, Int.MaxValue)
val extraBacked = state.globalLogging.backed :: relay :: Nil
val consoleOpt = consoleLocally(state, console)
multiLogger(log,
MainAppender.MainAppenderConfig(consoleOpt,
backed,
extraBacked ::: extra,
screenLevel,
backingLevel,
screenTrace,
backingTrace))
}
// Return None if the exec is not from console origin.
def consoleLocally(state: State, console: Appender): Option[Appender] =
state.currentCommand match {
@ -100,31 +145,39 @@ object LogManager {
}
def defaultTraceLevel(state: State): Int =
if (state.interactive) -1 else Int.MaxValue
def suppressedMessage(key: ScopedKey[_], state: State): SuppressedTraceContext => Option[String] =
{
lazy val display = Project.showContextKey(state)
def commandBase = "last " + display.show(unwrapStreamsKey(key))
def command(useColor: Boolean) = if (useColor) BLUE + commandBase + RESET else "'" + commandBase + "'"
context => Some("Stack trace suppressed: run %s for the full output.".format(command(context.useColor)))
}
def suppressedMessage(key: ScopedKey[_],
state: State): SuppressedTraceContext => Option[String] = {
lazy val display = Project.showContextKey(state)
def commandBase = "last " + display.show(unwrapStreamsKey(key))
def command(useColor: Boolean) =
if (useColor) BLUE + commandBase + RESET else "'" + commandBase + "'"
context =>
Some("Stack trace suppressed: run %s for the full output.".format(command(context.useColor)))
}
def unwrapStreamsKey(key: ScopedKey[_]): ScopedKey[_] = key.scope.task match {
case Select(task) => ScopedKey(key.scope.copy(task = Global), task)
case _ => key // should never get here
}
def backgroundLog(data: Settings[Scope], state: State, task: ScopedKey[_],
console: Appender, /* TODO: backed: Appender,*/ relay: Appender, extra: List[Appender]): ManagedLogger =
{
val execOpt = state.currentCommand
val loggerName: String = s"bg-${task.key.label}-${generateId.incrementAndGet}"
val channelName: Option[String] = execOpt flatMap { e => e.source map { _.channelName } }
// val execId: Option[String] = execOpt flatMap { _.execId }
val log = LogExchange.logger(loggerName, channelName, None)
LogExchange.unbindLoggerAppenders(loggerName)
val consoleOpt = consoleLocally(state, console)
LogExchange.bindLoggerAppenders(loggerName, (consoleOpt.toList map { _ -> Level.Debug }) ::: (relay -> Level.Debug) :: Nil)
log
def backgroundLog(data: Settings[Scope],
state: State,
task: ScopedKey[_],
console: Appender, /* TODO: backed: Appender,*/ relay: Appender,
extra: List[Appender]): ManagedLogger = {
val execOpt = state.currentCommand
val loggerName: String = s"bg-${task.key.label}-${generateId.incrementAndGet}"
val channelName: Option[String] = execOpt flatMap { e =>
e.source map { _.channelName }
}
// val execId: Option[String] = execOpt flatMap { _.execId }
val log = LogExchange.logger(loggerName, channelName, None)
LogExchange.unbindLoggerAppenders(loggerName)
val consoleOpt = consoleLocally(state, console)
LogExchange.bindLoggerAppenders(
loggerName,
(consoleOpt.toList map { _ -> Level.Debug }) ::: (relay -> Level.Debug) :: Nil)
log
}
// TODO: Fix this
// if global logging levels are not explicitly set, set them from project settings
@ -167,7 +220,8 @@ object LogManager {
private[this] def globalWrapper(s: State): Logger = {
new Logger {
private[this] val ref = new java.lang.ref.WeakReference(s.globalLogging.full)
private[this] def slog: Logger = Option(ref.get) getOrElse sys.error("Settings logger used after project was loaded.")
private[this] def slog: Logger =
Option(ref.get) getOrElse sys.error("Settings logger used after project was loaded.")
override val ansiCodesSupported = slog.ansiCodesSupported
override def trace(t: => Throwable) = slog.trace(t)

View File

@ -20,58 +20,73 @@ import sbt.io.IO
object Output {
final val DefaultTail = "> "
def last(keys: Values[_], streams: Streams, printLines: Seq[String] => Unit, sid: Option[String])(implicit display: Show[ScopedKey[_]]): Unit =
def last(keys: Values[_],
streams: Streams,
printLines: Seq[String] => Unit,
sid: Option[String])(implicit display: Show[ScopedKey[_]]): Unit =
printLines(flatLines(lastLines(keys, streams, sid))(idFun))
def last(file: File, printLines: Seq[String] => Unit, tailDelim: String = DefaultTail): Unit =
printLines(tailLines(file, tailDelim))
def lastGrep(keys: Values[_], streams: Streams, patternString: String, printLines: Seq[String] => Unit)(implicit display: Show[ScopedKey[_]]): Unit =
{
val pattern = Pattern compile patternString
val lines = flatLines(lastLines(keys, streams))(_ flatMap showMatches(pattern))
printLines(lines)
}
def lastGrep(file: File, patternString: String, printLines: Seq[String] => Unit, tailDelim: String = DefaultTail): Unit =
def lastGrep(keys: Values[_],
streams: Streams,
patternString: String,
printLines: Seq[String] => Unit)(implicit display: Show[ScopedKey[_]]): Unit = {
val pattern = Pattern compile patternString
val lines = flatLines(lastLines(keys, streams))(_ flatMap showMatches(pattern))
printLines(lines)
}
def lastGrep(file: File,
patternString: String,
printLines: Seq[String] => Unit,
tailDelim: String = DefaultTail): Unit =
printLines(grep(tailLines(file, tailDelim), patternString))
def grep(lines: Seq[String], patternString: String): Seq[String] =
lines flatMap showMatches(Pattern compile patternString)
def flatLines(outputs: Values[Seq[String]])(f: Seq[String] => Seq[String])(implicit display: Show[ScopedKey[_]]): Seq[String] =
{
val single = outputs.size == 1
outputs flatMap {
case KeyValue(key, lines) =>
val flines = f(lines)
if (!single) bold(display.show(key)) +: flines else flines
}
def flatLines(outputs: Values[Seq[String]])(f: Seq[String] => Seq[String])(
implicit display: Show[ScopedKey[_]]): Seq[String] = {
val single = outputs.size == 1
outputs flatMap {
case KeyValue(key, lines) =>
val flines = f(lines)
if (!single) bold(display.show(key)) +: flines else flines
}
}
def lastLines(keys: Values[_], streams: Streams, sid: Option[String] = None): Values[Seq[String]] =
{
val outputs = keys map { (kv: KeyValue[_]) => KeyValue(kv.key, lastLines(kv.key, streams, sid)) }
outputs.filterNot(_.value.isEmpty)
def lastLines(keys: Values[_],
streams: Streams,
sid: Option[String] = None): Values[Seq[String]] = {
val outputs = keys map { (kv: KeyValue[_]) =>
KeyValue(kv.key, lastLines(kv.key, streams, sid))
}
outputs.filterNot(_.value.isEmpty)
}
def lastLines(key: ScopedKey[_], mgr: Streams, sid: Option[String]): Seq[String] =
mgr.use(key) { s =>
// Workaround for #1155 - Keys.streams are always scoped by the task they're included in
// but are keyed by the Keys.streams key. I think this isn't actually a workaround, but
// is how things are expected to work now.
// You can see where streams are injected using their own key scope in
// You can see where streams are injected using their own key scope in
// EvaluateTask.injectStreams.
val streamScopedKey: ScopedKey[_] = ScopedKey(Project.fillTaskAxis(key).scope, Keys.streams.key)
val streamScopedKey: ScopedKey[_] =
ScopedKey(Project.fillTaskAxis(key).scope, Keys.streams.key)
val tmp = s.readText(streamScopedKey, sid)
IO.readLines(tmp)
}
def tailLines(file: File, tailDelim: String): Seq[String] = headLines(IO.readLines(file).reverse, tailDelim).reverse
def tailLines(file: File, tailDelim: String): Seq[String] =
headLines(IO.readLines(file).reverse, tailDelim).reverse
@tailrec def headLines(lines: Seq[String], tailDelim: String): Seq[String] =
if (lines.isEmpty)
lines
else {
val (first, tail) = lines.span { line => !(line startsWith tailDelim) }
val (first, tail) = lines.span { line =>
!(line startsWith tailDelim)
}
if (first.isEmpty) headLines(tail drop 1, tailDelim) else first
}
}

View File

@ -13,6 +13,7 @@ import sbt.internal.inc.ModuleUtilities
import sbt.io.IO
object PluginDiscovery {
/**
* Relative paths of resources that list top-level modules that are available.
* Normally, the classes for those modules will be in the same classpath entry as the resource.
@ -21,6 +22,7 @@ object PluginDiscovery {
final val AutoPlugins = "sbt/sbt.autoplugins"
final val Builds = "sbt/sbt.builds"
}
/** Names of top-level modules that subclass sbt plugin-related classes: [[AutoPlugin]], and [[BuildDef]]. */
final class DiscoveredNames(val autoPlugins: Seq[String], val builds: Seq[String]) {
override def toString: String = s"""DiscoveredNames($autoPlugins, $builds)"""
@ -29,111 +31,117 @@ object PluginDiscovery {
def emptyDiscoveredNames: DiscoveredNames = new DiscoveredNames(Nil, Nil)
/** Discovers and loads the sbt-plugin-related top-level modules from the classpath and source analysis in `data` and using the provided class `loader`. */
def discoverAll(data: PluginData, loader: ClassLoader): DetectedPlugins =
{
def discover[T](resource: String)(implicit classTag: reflect.ClassTag[T]) =
binarySourceModules[T](data, loader, resource)
import Paths._
// TODO - Fix this once we can autodetect AutoPlugins defined by sbt itself.
val defaultAutoPlugins = Seq(
"sbt.plugins.IvyPlugin" -> sbt.plugins.IvyPlugin,
"sbt.plugins.JvmPlugin" -> sbt.plugins.JvmPlugin,
"sbt.plugins.CorePlugin" -> sbt.plugins.CorePlugin,
"sbt.plugins.JUnitXmlReportPlugin" -> sbt.plugins.JUnitXmlReportPlugin,
"sbt.plugins.Giter8TemplatePlugin" -> sbt.plugins.Giter8TemplatePlugin
)
val detectedAutoPlugins = discover[AutoPlugin](AutoPlugins)
val allAutoPlugins = (defaultAutoPlugins ++ detectedAutoPlugins.modules) map {
case (name, value) =>
DetectedAutoPlugin(name, value, sbt.Plugins.hasAutoImportGetter(value, loader))
}
new DetectedPlugins(allAutoPlugins, discover[BuildDef](Builds))
def discoverAll(data: PluginData, loader: ClassLoader): DetectedPlugins = {
def discover[T](resource: String)(implicit classTag: reflect.ClassTag[T]) =
binarySourceModules[T](data, loader, resource)
import Paths._
// TODO - Fix this once we can autodetect AutoPlugins defined by sbt itself.
val defaultAutoPlugins = Seq(
"sbt.plugins.IvyPlugin" -> sbt.plugins.IvyPlugin,
"sbt.plugins.JvmPlugin" -> sbt.plugins.JvmPlugin,
"sbt.plugins.CorePlugin" -> sbt.plugins.CorePlugin,
"sbt.plugins.JUnitXmlReportPlugin" -> sbt.plugins.JUnitXmlReportPlugin,
"sbt.plugins.Giter8TemplatePlugin" -> sbt.plugins.Giter8TemplatePlugin
)
val detectedAutoPlugins = discover[AutoPlugin](AutoPlugins)
val allAutoPlugins = (defaultAutoPlugins ++ detectedAutoPlugins.modules) map {
case (name, value) =>
DetectedAutoPlugin(name, value, sbt.Plugins.hasAutoImportGetter(value, loader))
}
new DetectedPlugins(allAutoPlugins, discover[BuildDef](Builds))
}
/** Discovers the sbt-plugin-related top-level modules from the provided source `analysis`. */
def discoverSourceAll(analysis: CompileAnalysis): DiscoveredNames =
{
def discover[T](implicit classTag: reflect.ClassTag[T]): Seq[String] =
sourceModuleNames(analysis, classTag.runtimeClass.getName)
new DiscoveredNames(discover[AutoPlugin], discover[BuildDef])
}
def discoverSourceAll(analysis: CompileAnalysis): DiscoveredNames = {
def discover[T](implicit classTag: reflect.ClassTag[T]): Seq[String] =
sourceModuleNames(analysis, classTag.runtimeClass.getName)
new DiscoveredNames(discover[AutoPlugin], discover[BuildDef])
}
// TODO: for 0.14.0, consider consolidating into a single file, which would make the classpath search 4x faster
/** Writes discovered module `names` to zero or more files in `dir` as per [[writeDescriptor]] and returns the list of files written. */
def writeDescriptors(names: DiscoveredNames, dir: File): Seq[File] =
{
import Paths._
val files =
writeDescriptor(names.autoPlugins, dir, AutoPlugins) ::
writeDescriptor(names.builds, dir, Builds) ::
Nil
files.flatMap(_.toList)
}
def writeDescriptors(names: DiscoveredNames, dir: File): Seq[File] = {
import Paths._
val files =
writeDescriptor(names.autoPlugins, dir, AutoPlugins) ::
writeDescriptor(names.builds, dir, Builds) ::
Nil
files.flatMap(_.toList)
}
/** Stores the module `names` in `dir / path`, one per line, unless `names` is empty and then the file is deleted and `None` returned. */
def writeDescriptor(names: Seq[String], dir: File, path: String): Option[File] =
{
val descriptor: File = new File(dir, path)
if (names.isEmpty) {
IO.delete(descriptor)
None
} else {
IO.writeLines(descriptor, names.distinct.sorted)
Some(descriptor)
}
def writeDescriptor(names: Seq[String], dir: File, path: String): Option[File] = {
val descriptor: File = new File(dir, path)
if (names.isEmpty) {
IO.delete(descriptor)
None
} else {
IO.writeLines(descriptor, names.distinct.sorted)
Some(descriptor)
}
}
/**
* Discovers the names of top-level modules listed in resources named `resourceName` as per [[binaryModuleNames]] or
* available as analyzed source and extending from any of `subclasses` as per [[sourceModuleNames]].
*/
def binarySourceModuleNames(classpath: Seq[Attributed[File]], loader: ClassLoader, resourceName: String, subclasses: String*): Seq[String] =
def binarySourceModuleNames(classpath: Seq[Attributed[File]],
loader: ClassLoader,
resourceName: String,
subclasses: String*): Seq[String] =
(
binaryModuleNames(data(classpath), loader, resourceName) ++
(analyzed(classpath) flatMap (a => sourceModuleNames(a, subclasses: _*)))
(analyzed(classpath) flatMap (a => sourceModuleNames(a, subclasses: _*)))
).distinct
/** Discovers top-level modules in `analysis` that inherit from any of `subclasses`. */
def sourceModuleNames(analysis: CompileAnalysis, subclasses: String*): Seq[String] =
{
val subclassSet = subclasses.toSet
val defs = Tests.allDefs(analysis)
val ds = Discovery(subclassSet, Set.empty)(defs)
ds.flatMap {
case (definition, Discovered(subs, _, _, true)) =>
if ((subs & subclassSet).isEmpty) Nil else definition.name :: Nil
case _ => Nil
}
def sourceModuleNames(analysis: CompileAnalysis, subclasses: String*): Seq[String] = {
val subclassSet = subclasses.toSet
val defs = Tests.allDefs(analysis)
val ds = Discovery(subclassSet, Set.empty)(defs)
ds.flatMap {
case (definition, Discovered(subs, _, _, true)) =>
if ((subs & subclassSet).isEmpty) Nil else definition.name :: Nil
case _ => Nil
}
}
/**
* Obtains the list of modules identified in all resource files `resourceName` from `loader` that are on `classpath`.
* `classpath` and `loader` are both required to ensure that `loader`
* doesn't bring in any resources outside of the intended `classpath`, such as from parent loaders.
*/
def binaryModuleNames(classpath: Seq[File], loader: ClassLoader, resourceName: String): Seq[String] =
{
import collection.JavaConverters._
loader.getResources(resourceName).asScala.toSeq.filter(onClasspath(classpath)) flatMap { u =>
IO.readLinesURL(u).map(_.trim).filter(!_.isEmpty)
}
def binaryModuleNames(classpath: Seq[File],
loader: ClassLoader,
resourceName: String): Seq[String] = {
import collection.JavaConverters._
loader.getResources(resourceName).asScala.toSeq.filter(onClasspath(classpath)) flatMap { u =>
IO.readLinesURL(u).map(_.trim).filter(!_.isEmpty)
}
}
/** Returns `true` if `url` is an entry in `classpath`.*/
def onClasspath(classpath: Seq[File])(url: URL): Boolean =
IO.urlAsFile(url) exists (classpath.contains _)
private[sbt] def binarySourceModules[T](data: PluginData, loader: ClassLoader, resourceName: String)(implicit classTag: reflect.ClassTag[T]): DetectedModules[T] =
{
val classpath = data.classpath
val namesAndValues = if (classpath.isEmpty) Nil else {
val names = binarySourceModuleNames(classpath, loader, resourceName, classTag.runtimeClass.getName)
private[sbt] def binarySourceModules[T](
data: PluginData,
loader: ClassLoader,
resourceName: String)(implicit classTag: reflect.ClassTag[T]): DetectedModules[T] = {
val classpath = data.classpath
val namesAndValues =
if (classpath.isEmpty) Nil
else {
val names =
binarySourceModuleNames(classpath, loader, resourceName, classTag.runtimeClass.getName)
loadModules[T](data, names, loader)
}
new DetectedModules(namesAndValues)
}
new DetectedModules(namesAndValues)
}
private[this] def loadModules[T: reflect.ClassTag](data: PluginData, names: Seq[String], loader: ClassLoader): Seq[(String, T)] =
private[this] def loadModules[T: reflect.ClassTag](data: PluginData,
names: Seq[String],
loader: ClassLoader): Seq[(String, T)] =
try ModuleUtilities.getCheckedObjects[T](names, loader)
catch {
case e: ExceptionInInitializerError =>
@ -142,13 +150,18 @@ object PluginDiscovery {
case e: LinkageError => incompatiblePlugins(data, e)
}
private[this] def incompatiblePlugins(data: PluginData, t: LinkageError): Nothing =
{
val evicted = data.report.toList.flatMap(_.configurations.flatMap(_.evicted))
val evictedModules = evicted map { id => (id.organization, id.name) } distinct;
val evictedStrings = evictedModules map { case (o, n) => o + ":" + n }
val msgBase = "Binary incompatibility in plugins detected."
val msgExtra = if (evictedStrings.isEmpty) "" else "\nNote that conflicts were resolved for some dependencies:\n\t" + evictedStrings.mkString("\n\t")
throw new IncompatiblePluginsException(msgBase + msgExtra, t)
}
private[this] def incompatiblePlugins(data: PluginData, t: LinkageError): Nothing = {
val evicted = data.report.toList.flatMap(_.configurations.flatMap(_.evicted))
val evictedModules = evicted map { id =>
(id.organization, id.name)
} distinct;
val evictedStrings = evictedModules map { case (o, n) => o + ":" + n }
val msgBase = "Binary incompatibility in plugins detected."
val msgExtra =
if (evictedStrings.isEmpty) ""
else
"\nNote that conflicts were resolved for some dependencies:\n\t" + evictedStrings.mkString(
"\n\t")
throw new IncompatiblePluginsException(msgBase + msgExtra, t)
}
}

View File

@ -8,9 +8,17 @@ import sbt.librarymanagement.ModuleID
import java.net.{ URI, URL, URLClassLoader }
final case class PluginManagement(overrides: Set[ModuleID], applyOverrides: Set[ModuleID], loader: PluginClassLoader, initialLoader: ClassLoader, context: Context) {
final case class PluginManagement(overrides: Set[ModuleID],
applyOverrides: Set[ModuleID],
loader: PluginClassLoader,
initialLoader: ClassLoader,
context: Context) {
def shift: PluginManagement =
PluginManagement(Set.empty, overrides, new PluginClassLoader(initialLoader), initialLoader, context)
PluginManagement(Set.empty,
overrides,
new PluginClassLoader(initialLoader),
initialLoader,
context)
def addOverrides(os: Set[ModuleID]): PluginManagement =
copy(overrides = overrides ++ os)
@ -22,16 +30,23 @@ final case class PluginManagement(overrides: Set[ModuleID], applyOverrides: Set[
Keys.dependencyOverrides ++= overrides
)
def resetDepth: PluginManagement = copy(context = Context(globalPluginProject = false, pluginProjectDepth = 0))
def forGlobalPlugin: PluginManagement = copy(context = Context(globalPluginProject = true, pluginProjectDepth = 0))
def forPlugin: PluginManagement = copy(context = context.copy(pluginProjectDepth = context.pluginProjectDepth + 1))
def resetDepth: PluginManagement =
copy(context = Context(globalPluginProject = false, pluginProjectDepth = 0))
def forGlobalPlugin: PluginManagement =
copy(context = Context(globalPluginProject = true, pluginProjectDepth = 0))
def forPlugin: PluginManagement =
copy(context = context.copy(pluginProjectDepth = context.pluginProjectDepth + 1))
}
object PluginManagement {
final case class Context private[sbt] (globalPluginProject: Boolean, pluginProjectDepth: Int)
val emptyContext: Context = Context(false, 0)
def apply(initialLoader: ClassLoader): PluginManagement =
PluginManagement(Set.empty, Set.empty, new PluginClassLoader(initialLoader), initialLoader, emptyContext)
PluginManagement(Set.empty,
Set.empty,
new PluginClassLoader(initialLoader),
initialLoader,
emptyContext)
def extractOverrides(classpath: Classpath): Set[ModuleID] =
classpath flatMap { _.metadata get Keys.moduleID.key map keepOverrideInfo } toSet;

View File

@ -14,6 +14,7 @@ private[sbt] class PluginsDebug(
val nameToKey: Map[String, AttributeKey[_]],
val provided: Relation[AutoPlugin, AttributeKey[_]]
) {
/**
* The set of [[AutoPlugin]]s that might define a key named `keyName`.
* Because plugins can define keys in different scopes, this should only be used as a guideline.
@ -28,69 +29,82 @@ private[sbt] class PluginsDebug(
providers(keyName).toList.map(plugin => pluginEnable(context, plugin))
/** Provides text to suggest how `notFoundKey` can be defined in [[Context]]. */
def debug(notFoundKey: String, context: Context): String =
{
val (activated, deactivated) = Util.separate(toEnable(notFoundKey, context)) {
case pa: PluginActivated => Left(pa)
case pd: EnableDeactivated => Right(pd)
}
val activePrefix = if (activated.isEmpty) "" else
def debug(notFoundKey: String, context: Context): String = {
val (activated, deactivated) = Util.separate(toEnable(notFoundKey, context)) {
case pa: PluginActivated => Left(pa)
case pd: EnableDeactivated => Right(pd)
}
val activePrefix =
if (activated.isEmpty) ""
else
s"Some already activated plugins define $notFoundKey: ${activated.mkString(", ")}\n"
activePrefix + debugDeactivated(notFoundKey, deactivated)
}
activePrefix + debugDeactivated(notFoundKey, deactivated)
}
private[this] def debugDeactivated(notFoundKey: String, deactivated: Seq[EnableDeactivated]): String =
{
val (impossible, possible) = Util.separate(deactivated) {
case pi: PluginImpossible => Left(pi)
case pr: PluginRequirements => Right(pr)
}
if (possible.nonEmpty) {
val explained = possible.map(explainPluginEnable)
val possibleString =
if (explained.size > 1) explained.zipWithIndex.map { case (s, i) => s"$i. $s" }
.mkString(s"Multiple plugins are available that can provide $notFoundKey:\n", "\n", "")
else s"$notFoundKey is provided by an available (but not activated) plugin:\n${explained.mkString}"
def impossiblePlugins = impossible.map(_.plugin.label).mkString(", ")
val imPostfix = if (impossible
.isEmpty) "" else s"\n\nThere are other available plugins that provide $notFoundKey, but they are " +
s"impossible to add: $impossiblePlugins"
possibleString + imPostfix
} else if (impossible.isEmpty)
s"No available plugin provides key $notFoundKey."
else {
val explanations = impossible.map(explainPluginEnable)
val preamble = s"Plugins are available that could provide $notFoundKey"
explanations.mkString(s"$preamble, but they are impossible to add:\n\t", "\n\t", "")
}
private[this] def debugDeactivated(notFoundKey: String,
deactivated: Seq[EnableDeactivated]): String = {
val (impossible, possible) = Util.separate(deactivated) {
case pi: PluginImpossible => Left(pi)
case pr: PluginRequirements => Right(pr)
}
if (possible.nonEmpty) {
val explained = possible.map(explainPluginEnable)
val possibleString =
if (explained.size > 1)
explained.zipWithIndex
.map { case (s, i) => s"$i. $s" }
.mkString(s"Multiple plugins are available that can provide $notFoundKey:\n", "\n", "")
else
s"$notFoundKey is provided by an available (but not activated) plugin:\n${explained.mkString}"
def impossiblePlugins = impossible.map(_.plugin.label).mkString(", ")
val imPostfix =
if (impossible.isEmpty) ""
else
s"\n\nThere are other available plugins that provide $notFoundKey, but they are " +
s"impossible to add: $impossiblePlugins"
possibleString + imPostfix
} else if (impossible.isEmpty)
s"No available plugin provides key $notFoundKey."
else {
val explanations = impossible.map(explainPluginEnable)
val preamble = s"Plugins are available that could provide $notFoundKey"
explanations.mkString(s"$preamble, but they are impossible to add:\n\t", "\n\t", "")
}
}
/** Text that suggests how to activate [[AutoPlugin]] in [[Context]] if possible and if it is not already activated. */
def help(plugin: AutoPlugin, context: Context): String =
if (context.enabled.contains(plugin)) activatedHelp(plugin) else deactivatedHelp(plugin, context)
if (context.enabled.contains(plugin)) activatedHelp(plugin)
else deactivatedHelp(plugin, context)
private def activatedHelp(plugin: AutoPlugin): String =
{
val prefix = s"${plugin.label} is activated."
val keys = provided.forward(plugin)
val keysString = if (keys.isEmpty) "" else s"\nIt may affect these keys: ${multi(keys.toList.map(_.label))}"
val configs = plugin.projectConfigurations
val confsString = if (configs.isEmpty) "" else s"\nIt defines these configurations: ${multi(configs.map(_.name))}"
prefix + keysString + confsString
}
private def activatedHelp(plugin: AutoPlugin): String = {
val prefix = s"${plugin.label} is activated."
val keys = provided.forward(plugin)
val keysString =
if (keys.isEmpty) "" else s"\nIt may affect these keys: ${multi(keys.toList.map(_.label))}"
val configs = plugin.projectConfigurations
val confsString =
if (configs.isEmpty) ""
else s"\nIt defines these configurations: ${multi(configs.map(_.name))}"
prefix + keysString + confsString
}
private def deactivatedHelp(plugin: AutoPlugin, context: Context): String =
{
val prefix = s"${plugin.label} is NOT activated."
val keys = provided.forward(plugin)
val keysString = if (keys.isEmpty) "" else s"\nActivating it may affect these keys: ${multi(keys.toList.map(_.label))}"
val configs = plugin.projectConfigurations
val confsString = if (configs.isEmpty) "" else s"\nActivating it will define these configurations: ${multi(configs.map(_.name))}"
val toActivate = explainPluginEnable(pluginEnable(context, plugin))
s"$prefix$keysString$confsString\n$toActivate"
}
private def deactivatedHelp(plugin: AutoPlugin, context: Context): String = {
val prefix = s"${plugin.label} is NOT activated."
val keys = provided.forward(plugin)
val keysString =
if (keys.isEmpty) ""
else s"\nActivating it may affect these keys: ${multi(keys.toList.map(_.label))}"
val configs = plugin.projectConfigurations
val confsString =
if (configs.isEmpty) ""
else s"\nActivating it will define these configurations: ${multi(configs.map(_.name))}"
val toActivate = explainPluginEnable(pluginEnable(context, plugin))
s"$prefix$keysString$confsString\n$toActivate"
}
private[this] def multi(strs: Seq[String]): String = strs.mkString(if (strs.size > 4) "\n\t" else ", ")
private[this] def multi(strs: Seq[String]): String =
strs.mkString(if (strs.size > 4) "\n\t" else ", ")
}
private[sbt] object PluginsDebug {
@ -98,71 +112,84 @@ private[sbt] object PluginsDebug {
if (Project.isProjectLoaded(s)) {
val extracted = Project.extract(s)
import extracted._
def helpBuild(uri: URI, build: LoadedBuildUnit): String =
{
val pluginStrings = for (plugin <- availableAutoPlugins(build)) yield {
val activatedIn = build.defined.values.toList.filter(_.autoPlugins.contains(plugin)).map(_.id)
val actString = if (activatedIn.nonEmpty) activatedIn.mkString(": enabled in ", ", ", "") else "" // TODO: deal with large builds
s"\n\t${plugin.label}$actString"
}
s"In $uri${pluginStrings.mkString}"
def helpBuild(uri: URI, build: LoadedBuildUnit): String = {
val pluginStrings = for (plugin <- availableAutoPlugins(build)) yield {
val activatedIn =
build.defined.values.toList.filter(_.autoPlugins.contains(plugin)).map(_.id)
val actString =
if (activatedIn.nonEmpty) activatedIn.mkString(": enabled in ", ", ", "")
else "" // TODO: deal with large builds
s"\n\t${plugin.label}$actString"
}
s"In $uri${pluginStrings.mkString}"
}
val buildStrings = for ((uri, build) <- structure.units) yield helpBuild(uri, build)
buildStrings.mkString("\n")
} else "No project is currently loaded."
def autoPluginMap(s: State): Map[String, AutoPlugin] =
{
val extracted = Project.extract(s)
import extracted._
structure.units.values.toList.flatMap(availableAutoPlugins).map(plugin => (plugin.label, plugin)).toMap
}
def autoPluginMap(s: State): Map[String, AutoPlugin] = {
val extracted = Project.extract(s)
import extracted._
structure.units.values.toList
.flatMap(availableAutoPlugins)
.map(plugin => (plugin.label, plugin))
.toMap
}
private[this] def availableAutoPlugins(build: LoadedBuildUnit): Seq[AutoPlugin] =
build.unit.plugins.detected.autoPlugins map { _.value }
def help(plugin: AutoPlugin, s: State): String =
{
val extracted = Project.extract(s)
import extracted._
def definesPlugin(p: ResolvedProject): Boolean = p.autoPlugins.contains(plugin)
def projectForRef(ref: ProjectRef): ResolvedProject = get(Keys.thisProject in ref)
val perBuild: Map[URI, Set[AutoPlugin]] = structure.units.mapValues(unit => availableAutoPlugins(unit).toSet)
val pluginsThisBuild = perBuild.getOrElse(currentRef.build, Set.empty).toList
lazy val context = Context(currentProject.plugins, currentProject.autoPlugins, Plugins.deducer(pluginsThisBuild), pluginsThisBuild, s.log)
lazy val debug = PluginsDebug(context.available)
if (!pluginsThisBuild.contains(plugin)) {
val availableInBuilds: List[URI] = perBuild.toList.filter(_._2(plugin)).map(_._1)
val s1 = s"Plugin ${plugin.label} is only available in builds:"
val s2 = availableInBuilds.mkString("\n\t")
val s3 = s"Switch to a project in one of those builds using `project` and rerun this command for more information."
s"$s1\n\t$s2\n$s3"
} else if (definesPlugin(currentProject))
debug.activatedHelp(plugin)
else {
val thisAggregated = BuildUtil.dependencies(structure.units).aggregateTransitive.getOrElse(currentRef, Nil)
val definedInAggregated = thisAggregated.filter(ref => definesPlugin(projectForRef(ref)))
if (definedInAggregated.nonEmpty) {
val projectNames = definedInAggregated.map(_.project) // TODO: usually in this build, but could technically require the build to be qualified
val s2 = projectNames.mkString("\n\t")
s"Plugin ${plugin.label} is not activated on this project, but this project aggregates projects where it is activated:\n\t$s2"
} else {
val base = debug.deactivatedHelp(plugin, context)
val aggNote = if (thisAggregated.nonEmpty) "Note: This project aggregates other projects and this" else "Note: This"
val common = " information is for this project only."
val helpOther = "To see how to activate this plugin for another project, change to the project using `project <name>` and rerun this command."
s"$base\n$aggNote$common\n$helpOther"
}
def help(plugin: AutoPlugin, s: State): String = {
val extracted = Project.extract(s)
import extracted._
def definesPlugin(p: ResolvedProject): Boolean = p.autoPlugins.contains(plugin)
def projectForRef(ref: ProjectRef): ResolvedProject = get(Keys.thisProject in ref)
val perBuild: Map[URI, Set[AutoPlugin]] =
structure.units.mapValues(unit => availableAutoPlugins(unit).toSet)
val pluginsThisBuild = perBuild.getOrElse(currentRef.build, Set.empty).toList
lazy val context = Context(currentProject.plugins,
currentProject.autoPlugins,
Plugins.deducer(pluginsThisBuild),
pluginsThisBuild,
s.log)
lazy val debug = PluginsDebug(context.available)
if (!pluginsThisBuild.contains(plugin)) {
val availableInBuilds: List[URI] = perBuild.toList.filter(_._2(plugin)).map(_._1)
val s1 = s"Plugin ${plugin.label} is only available in builds:"
val s2 = availableInBuilds.mkString("\n\t")
val s3 =
s"Switch to a project in one of those builds using `project` and rerun this command for more information."
s"$s1\n\t$s2\n$s3"
} else if (definesPlugin(currentProject))
debug.activatedHelp(plugin)
else {
val thisAggregated =
BuildUtil.dependencies(structure.units).aggregateTransitive.getOrElse(currentRef, Nil)
val definedInAggregated = thisAggregated.filter(ref => definesPlugin(projectForRef(ref)))
if (definedInAggregated.nonEmpty) {
val projectNames = definedInAggregated.map(_.project) // TODO: usually in this build, but could technically require the build to be qualified
val s2 = projectNames.mkString("\n\t")
s"Plugin ${plugin.label} is not activated on this project, but this project aggregates projects where it is activated:\n\t$s2"
} else {
val base = debug.deactivatedHelp(plugin, context)
val aggNote =
if (thisAggregated.nonEmpty) "Note: This project aggregates other projects and this"
else "Note: This"
val common = " information is for this project only."
val helpOther =
"To see how to activate this plugin for another project, change to the project using `project <name>` and rerun this command."
s"$base\n$aggNote$common\n$helpOther"
}
}
}
/** Pre-computes information for debugging plugins. */
def apply(available: List[AutoPlugin]): PluginsDebug =
{
val keyR = definedKeys(available)
val nameToKey: Map[String, AttributeKey[_]] = keyR._2s.toList.map(key => (key.label, key)).toMap
new PluginsDebug(available, nameToKey, keyR)
}
def apply(available: List[AutoPlugin]): PluginsDebug = {
val keyR = definedKeys(available)
val nameToKey: Map[String, AttributeKey[_]] =
keyR._2s.toList.map(key => (key.label, key)).toMap
new PluginsDebug(available, nameToKey, keyR)
}
/**
* The context for debugging a plugin (de)activation.
@ -188,7 +215,9 @@ private[sbt] object PluginsDebug {
sealed abstract class EnableDeactivated extends PluginEnable
/** Describes a [[plugin]] that cannot be activated in a [[context]] due to [[contradictions]] in requirements. */
final case class PluginImpossible(plugin: AutoPlugin, context: Context, contradictions: Set[AutoPlugin])
final case class PluginImpossible(plugin: AutoPlugin,
context: Context,
contradictions: Set[AutoPlugin])
extends EnableDeactivated
/**
@ -220,7 +249,9 @@ private[sbt] object PluginsDebug {
* affecting the other plugin. If empty, a direct exclusion is required.
* @param newlySelected If false, this plugin was selected in the original context.
*/
final case class DeactivatePlugin(plugin: AutoPlugin, removeOneOf: Set[AutoPlugin], newlySelected: Boolean)
final case class DeactivatePlugin(plugin: AutoPlugin,
removeOneOf: Set[AutoPlugin],
newlySelected: Boolean)
/** Determines how to enable [[AutoPlugin]] in [[Context]]. */
def pluginEnable(context: Context, plugin: AutoPlugin): PluginEnable =
@ -252,7 +283,7 @@ private[sbt] object PluginsDebug {
C :- B
initial: <empty>
propose: B, exclude C
*/
*/
// `plugin` will only be activated when all of these plugins are activated
// Deactivating any one of these would deactivate `plugin`.
@ -293,7 +324,8 @@ private[sbt] object PluginsDebug {
// If both A and B must be deactivated, but A transitively depends on B, deactivating B will deactivate A.
// If A must be deactivated, but one if its (transitively) required plugins isn't present, it won't be activated.
// So, in either of these cases, A doesn't need to be considered further and won't be included in this set.
val minDeactivate = minAbsentPlugins.filter(p => Plugins.satisfied(p.requires, incrementalModel))
val minDeactivate =
minAbsentPlugins.filter(p => Plugins.satisfied(p.requires, incrementalModel))
val deactivate = for (d <- minDeactivate.toList) yield {
// removing any one of these plugins will deactivate `d`. TODO: This is not an especially efficient implementation.
@ -305,55 +337,72 @@ private[sbt] object PluginsDebug {
DeactivatePlugin(d, removeToDeactivate, newlySelected)
}
PluginRequirements(plugin, context, blockingExcludes, addToExistingPlugins, extraPlugins, willRemove, deactivate)
PluginRequirements(plugin,
context,
blockingExcludes,
addToExistingPlugins,
extraPlugins,
willRemove,
deactivate)
}
}
private[this] def includeAll[T <: Basic](basic: Set[T]): Plugins = And(basic.toList)
private[this] def excludeAll(plugins: Set[AutoPlugin]): Plugins = And(plugins map (p => Exclude(p)) toList)
private[this] def excludeAll(plugins: Set[AutoPlugin]): Plugins =
And(plugins map (p => Exclude(p)) toList)
private[this] def excludes(bs: Seq[Basic]): Set[AutoPlugin] = bs.collect { case Exclude(b) => b }.toSet
private[this] def plugins(bs: Seq[Basic]): Set[AutoPlugin] = bs.collect { case n: AutoPlugin => n }.toSet
private[this] def excludes(bs: Seq[Basic]): Set[AutoPlugin] =
bs.collect { case Exclude(b) => b }.toSet
private[this] def plugins(bs: Seq[Basic]): Set[AutoPlugin] =
bs.collect { case n: AutoPlugin => n }.toSet
// If there is a model that includes `plugin`, it includes at least what is returned by this method.
// This is the list of plugins that must be included as well as list of plugins that must not be present.
// It might not be valid, such as if there are contradictions or if there are cycles that are unsatisfiable.
// The actual model might be larger, since other plugins might be enabled by the selected plugins.
private[this] def minimalModel(plugin: AutoPlugin): Seq[Basic] = Dag.topologicalSortUnchecked(plugin: Basic) {
case _: Exclude => Nil
case ap: AutoPlugin => Plugins.flatten(ap.requires) :+ plugin
}
private[this] def minimalModel(plugin: AutoPlugin): Seq[Basic] =
Dag.topologicalSortUnchecked(plugin: Basic) {
case _: Exclude => Nil
case ap: AutoPlugin => Plugins.flatten(ap.requires) :+ plugin
}
/** String representation of [[PluginEnable]], intended for end users. */
def explainPluginEnable(ps: PluginEnable): String =
ps match {
case PluginRequirements(plugin, context, blockingExcludes, enablingPlugins, extraEnabledPlugins, toBeRemoved, deactivate) =>
case PluginRequirements(plugin,
context,
blockingExcludes,
enablingPlugins,
extraEnabledPlugins,
toBeRemoved,
deactivate) =>
def indent(str: String) = if (str.isEmpty) "" else s"\t$str"
def note(str: String) = if (str.isEmpty) "" else s"Note: $str"
val parts =
indent(excludedError(false /* TODO */ , blockingExcludes.toList)) ::
indent(excludedError(false /* TODO */, blockingExcludes.toList)) ::
indent(required(enablingPlugins.toList)) ::
indent(needToDeactivate(deactivate)) ::
note(willAdd(plugin, extraEnabledPlugins.toList)) ::
note(willRemove(plugin, toBeRemoved.toList)) ::
Nil
parts.filterNot(_.isEmpty).mkString("\n")
case PluginImpossible(plugin, context, contradictions) => pluginImpossible(plugin, contradictions)
case PluginActivated(plugin, context) => s"Plugin ${plugin.label} already activated."
case PluginImpossible(plugin, context, contradictions) =>
pluginImpossible(plugin, contradictions)
case PluginActivated(plugin, context) => s"Plugin ${plugin.label} already activated."
}
/**
* Provides a [[Relation]] between plugins and the keys they potentially define.
* Because plugins can define keys in different scopes and keys can be overridden, this is not definitive.
*/
def definedKeys(available: List[AutoPlugin]): Relation[AutoPlugin, AttributeKey[_]] =
{
def extractDefinedKeys(ss: Seq[Setting[_]]): Seq[AttributeKey[_]] =
ss.map(_.key.key)
def allSettings(p: AutoPlugin): Seq[Setting[_]] = p.projectSettings ++ p.buildSettings ++ p.globalSettings
val empty = Relation.empty[AutoPlugin, AttributeKey[_]]
(empty /: available)((r, p) => r + (p, extractDefinedKeys(allSettings(p))))
}
def definedKeys(available: List[AutoPlugin]): Relation[AutoPlugin, AttributeKey[_]] = {
def extractDefinedKeys(ss: Seq[Setting[_]]): Seq[AttributeKey[_]] =
ss.map(_.key.key)
def allSettings(p: AutoPlugin): Seq[Setting[_]] =
p.projectSettings ++ p.buildSettings ++ p.globalSettings
val empty = Relation.empty[AutoPlugin, AttributeKey[_]]
(empty /: available)((r, p) => r + (p, extractDefinedKeys(allSettings(p))))
}
private[this] def excludedError(transitive: Boolean, dependencies: List[AutoPlugin]): String =
str(dependencies)(excludedPluginError(transitive), excludedPluginsError(transitive))
@ -362,7 +411,8 @@ private[sbt] object PluginsDebug {
s"Required ${transitiveString(transitive)}dependency ${dependency.label} was excluded."
private[this] def excludedPluginsError(transitive: Boolean)(dependencies: List[AutoPlugin]) =
s"Required ${transitiveString(transitive)}dependencies were excluded:\n\t${labels(dependencies).mkString("\n\t")}"
s"Required ${transitiveString(transitive)}dependencies were excluded:\n\t${labels(dependencies)
.mkString("\n\t")}"
private[this] def transitiveString(transitive: Boolean) =
if (transitive) "(transitive) " else ""
@ -413,28 +463,29 @@ private[sbt] object PluginsDebug {
private[this] def deactivate1(deactivate: DeactivatePlugin): String =
s"Need to deactivate ${deactivateString(deactivate)}"
private[this] def deactivateString(d: DeactivatePlugin): String =
{
val removePluginsString: String =
d.removeOneOf.toList match {
case Nil => ""
case x :: Nil => s" or no longer include $x"
case xs => s" or remove one of ${xs.mkString(", ")}"
}
s"${d.plugin.label}: directly exclude it${removePluginsString}"
}
private[this] def deactivateString(d: DeactivatePlugin): String = {
val removePluginsString: String =
d.removeOneOf.toList match {
case Nil => ""
case x :: Nil => s" or no longer include $x"
case xs => s" or remove one of ${xs.mkString(", ")}"
}
s"${d.plugin.label}: directly exclude it${removePluginsString}"
}
private[this] def pluginImpossible(plugin: AutoPlugin, contradictions: Set[AutoPlugin]): String =
str(contradictions.toList)(pluginImpossible1(plugin), pluginImpossibleN(plugin))
private[this] def pluginImpossible1(plugin: AutoPlugin)(contradiction: AutoPlugin): String = {
val s1 = s"There is no way to enable plugin ${plugin.label}."
val s2 = s"It (or its dependencies) requires plugin ${contradiction.label} to both be present and absent."
val s2 =
s"It (or its dependencies) requires plugin ${contradiction.label} to both be present and absent."
val s3 = s"Please report the problem to the plugin's author."
s"$s1 $s2 $s3"
}
private[this] def pluginImpossibleN(plugin: AutoPlugin)(contradictions: List[AutoPlugin]): String = {
private[this] def pluginImpossibleN(plugin: AutoPlugin)(
contradictions: List[AutoPlugin]): String = {
val s1 = s"There is no way to enable plugin ${plugin.label}."
val s2 = s"It (or its dependencies) requires these plugins to be both present and absent:"
val s3 = s"Please report the problem to the plugin's author."

View File

@ -12,18 +12,18 @@ import Project.updateCurrent
object ProjectNavigation {
def command(s: State): Parser[() => State] =
if (s get sessionSettings isEmpty) failure("No project loaded") else (new ProjectNavigation(s)).command
if (s get sessionSettings isEmpty) failure("No project loaded")
else (new ProjectNavigation(s)).command
}
final class ProjectNavigation(s: State) {
val extracted: Extracted = Project extract s
import extracted.{ currentRef, structure, session }
def setProject(nuri: URI, nid: String): State =
{
val neval = if (currentRef.build == nuri) session.currentEval else mkEval(nuri)
updateCurrent(s.put(sessionSettings, session.setCurrent(nuri, nid, neval)))
}
def setProject(nuri: URI, nid: String): State = {
val neval = if (currentRef.build == nuri) session.currentEval else mkEval(nuri)
updateCurrent(s.put(sessionSettings, session.setCurrent(nuri, nid, neval)))
}
def mkEval(nuri: URI): () => Eval = Load.lazyEval(structure.units(nuri).unit)
def getRoot(uri: URI): String = Load.getRootProject(structure.units)(uri)
@ -44,7 +44,8 @@ final class ProjectNavigation(s: State) {
if (structure.units(uri).defined.contains(to))
setProject(uri, to)
else
fail(s"Invalid project name '$to' in build $uri (type 'projects' to list available projects).")
fail(
s"Invalid project name '$to' in build $uri (type 'projects' to list available projects).")
def changeBuild(newBuild: URI): State =
if (structure.units contains newBuild)
@ -56,12 +57,11 @@ final class ProjectNavigation(s: State) {
import Parser._, complete.Parsers._
val parser: Parser[Option[ResolvedReference]] =
{
val reference = Act.resolvedReference(structure.index.keyIndex, currentRef.build, success(()))
val root = token('/' ^^^ rootRef)
success(None) | some(token(Space) ~> (root | reference))
}
val parser: Parser[Option[ResolvedReference]] = {
val reference = Act.resolvedReference(structure.index.keyIndex, currentRef.build, success(()))
val root = token('/' ^^^ rootRef)
success(None) | some(token(Space) ~> (root | reference))
}
def rootRef = ProjectRef(currentRef.build, getRoot(currentRef.build))

View File

@ -12,43 +12,45 @@ import sbt.protocol.LogEvent
import sbt.internal.util.codec._
import scala.json.ast.unsafe._
class RelayAppender(name: String) extends AbstractAppender(name, null, PatternLayout.createDefaultLayout(), true) {
class RelayAppender(name: String)
extends AbstractAppender(name, null, PatternLayout.createDefaultLayout(), true) {
lazy val exchange = StandardMain.exchange
lazy val jsonFormat = new sjsonnew.BasicJsonProtocol
with JValueFormats {}
lazy val jsonFormat = new sjsonnew.BasicJsonProtocol with JValueFormats {}
def append(event: XLogEvent): Unit =
{
val level = ConsoleAppender.toLevel(event.getLevel)
val message = event.getMessage
message match {
case o: ObjectMessage => appendEvent(level, o.getParameter)
case p: ParameterizedMessage => appendLog(level, p.getFormattedMessage)
case r: RingBufferLogEvent => appendLog(level, r.getFormattedMessage)
case _ => appendLog(level, message.toString)
}
def append(event: XLogEvent): Unit = {
val level = ConsoleAppender.toLevel(event.getLevel)
val message = event.getMessage
message match {
case o: ObjectMessage => appendEvent(level, o.getParameter)
case p: ParameterizedMessage => appendLog(level, p.getFormattedMessage)
case r: RingBufferLogEvent => appendLog(level, r.getFormattedMessage)
case _ => appendLog(level, message.toString)
}
}
def appendLog(level: Level.Value, message: => String): Unit = {
exchange.publishEventMessage(LogEvent(level.toString, message))
}
def appendEvent(level: Level.Value, event: AnyRef): Unit =
event match {
case x: StringEvent =>
{
import JsonProtocol._
exchange.publishEvent(x: AbstractEntry)
}
case x: ObjectEvent[_] =>
{
import jsonFormat._
val json = JObject(JField("type", JString(x.contentType)), (
Vector(JField("message", x.json),
JField("level", JString(x.level.toString))) ++
(x.channelName.toVector map { channelName => JField("channelName", JString(channelName)) }) ++
(x.execId.toVector map { execId => JField("execId", JString(execId)) })): _*)
exchange.publishEvent(json: JValue)
}
case _ =>
case x: StringEvent => {
import JsonProtocol._
exchange.publishEvent(x: AbstractEntry)
}
case x: ObjectEvent[_] => {
import jsonFormat._
val json = JObject(
JField("type", JString(x.contentType)),
(Vector(JField("message", x.json), JField("level", JString(x.level.toString))) ++
(x.channelName.toVector map { channelName =>
JField("channelName", JString(channelName))
}) ++
(x.execId.toVector map { execId =>
JField("execId", JString(execId))
})): _*
)
exchange.publishEvent(json: JValue)
}
case _ =>
println(s"appendEvent: ${event.getClass}")
()
}

View File

@ -4,16 +4,21 @@ package internal
import sbt.internal.util.AttributeKey
object Resolve {
def apply(index: BuildUtil[_], current: ScopeAxis[Reference], key: AttributeKey[_], mask: ScopeMask): Scope => Scope =
{
val rs =
resolveProject(current, mask) _ ::
resolveExtra(mask) _ ::
resolveTask(mask) _ ::
resolveConfig(index, key, mask) _ ::
Nil
scope => (scope /: rs) { (s, f) => f(s) }
}
def apply(index: BuildUtil[_],
current: ScopeAxis[Reference],
key: AttributeKey[_],
mask: ScopeMask): Scope => Scope = {
val rs =
resolveProject(current, mask) _ ::
resolveExtra(mask) _ ::
resolveTask(mask) _ ::
resolveConfig(index, key, mask) _ ::
Nil
scope =>
(scope /: rs) { (s, f) =>
f(s)
}
}
def resolveTask(mask: ScopeMask)(scope: Scope): Scope =
if (mask.task) scope else scope.copy(task = Global)
@ -23,7 +28,8 @@ object Resolve {
def resolveExtra(mask: ScopeMask)(scope: Scope): Scope =
if (mask.extra) scope else scope.copy(extra = Global)
def resolveConfig[P](index: BuildUtil[P], key: AttributeKey[_], mask: ScopeMask)(scope: Scope): Scope =
def resolveConfig[P](index: BuildUtil[P], key: AttributeKey[_], mask: ScopeMask)(
scope: Scope): Scope =
if (mask.config)
scope
else {
@ -36,9 +42,11 @@ object Resolve {
}
val task = scope.task.toOption
val keyIndex = index.keyIndex
val definesKey = (c: ScopeAxis[ConfigKey]) => keyIndex.keys(resolvedRef, c.toOption.map(_.name), task) contains key.label
val definesKey = (c: ScopeAxis[ConfigKey]) =>
keyIndex.keys(resolvedRef, c.toOption.map(_.name), task) contains key.label
val projectConfigs = index.configurations(proj).map(ck => Select(ck))
val config: ScopeAxis[ConfigKey] = (Global +: projectConfigs) find definesKey getOrElse Global
val config
: ScopeAxis[ConfigKey] = (Global +: projectConfigs) find definesKey getOrElse Global
scope.copy(config = config)
}
}

View File

@ -9,18 +9,17 @@ import java.net.URI
import sbt.internal.BuildLoader.ResolveInfo
object RetrieveUnit {
def apply(info: ResolveInfo): Option[() => File] =
{
info.uri match {
case Scheme("svn") | Scheme("svn+ssh") => Resolvers.subversion(info)
case Scheme("hg") => Resolvers.mercurial(info)
case Scheme("git") => Resolvers.git(info)
case Path(path) if path.endsWith(".git") => Resolvers.git(info)
case Scheme("http") | Scheme("https") | Scheme("ftp") => Resolvers.remote(info)
case Scheme("file") => Resolvers.local(info)
case _ => None
}
def apply(info: ResolveInfo): Option[() => File] = {
info.uri match {
case Scheme("svn") | Scheme("svn+ssh") => Resolvers.subversion(info)
case Scheme("hg") => Resolvers.mercurial(info)
case Scheme("git") => Resolvers.git(info)
case Path(path) if path.endsWith(".git") => Resolvers.git(info)
case Scheme("http") | Scheme("https") | Scheme("ftp") => Resolvers.remote(info)
case Scheme("file") => Resolvers.local(info)
case _ => None
}
}
object Scheme {
def unapply(uri: URI) = Option(uri.getScheme)

View File

@ -19,7 +19,8 @@ object Script {
final val Name = "script"
lazy val command =
Command.command(Name) { state =>
val scriptArg = state.remainingCommands.headOption map { _.commandLine } getOrElse sys.error("No script file specified")
val scriptArg = state.remainingCommands.headOption map { _.commandLine } getOrElse sys.error(
"No script file specified")
val scriptFile = new File(scriptArg).getAbsoluteFile
val hash = Hash.halve(Hash.toHex(Hash(scriptFile.getAbsolutePath)))
val base = new File(CommandUtil.bootDirectory(state), hash)
@ -45,8 +46,14 @@ object Script {
}
val scriptAsSource = sources in Compile := script :: Nil
val asScript = scalacOptions ++= Seq("-Xscript", script.getName.stripSuffix(".scala"))
val scriptSettings = Seq(asScript, scriptAsSource, logLevel in Global := Level.Warn, showSuccess in Global := false)
val append = Load.transformSettings(Load.projectScope(currentRef), currentRef.build, rootProject, scriptSettings ++ embeddedSettings)
val scriptSettings = Seq(asScript,
scriptAsSource,
logLevel in Global := Level.Warn,
showSuccess in Global := false)
val append = Load.transformSettings(Load.projectScope(currentRef),
currentRef.build,
rootProject,
scriptSettings ++ embeddedSettings)
val newStructure = Load.reapply(session.original ++ append, structure)
val arguments = state.remainingCommands.drop(1).map(e => s""""${e.commandLine}"""")
@ -55,25 +62,27 @@ object Script {
}
final case class Block(offset: Int, lines: Seq[String])
def blocks(file: File): Seq[Block] =
{
val lines = IO.readLines(file).toIndexedSeq
def blocks(b: Block, acc: List[Block]): List[Block] =
if (b.lines.isEmpty)
acc.reverse
else {
val (dropped, blockToEnd) = b.lines.span { line => !line.startsWith(BlockStart) }
val (block, remaining) = blockToEnd.span { line => !line.startsWith(BlockEnd) }
val offset = b.offset + dropped.length
blocks(Block(offset + block.length, remaining), Block(offset, block.drop(1)) :: acc)
def blocks(file: File): Seq[Block] = {
val lines = IO.readLines(file).toIndexedSeq
def blocks(b: Block, acc: List[Block]): List[Block] =
if (b.lines.isEmpty)
acc.reverse
else {
val (dropped, blockToEnd) = b.lines.span { line =>
!line.startsWith(BlockStart)
}
blocks(Block(0, lines), Nil)
}
val (block, remaining) = blockToEnd.span { line =>
!line.startsWith(BlockEnd)
}
val offset = b.offset + dropped.length
blocks(Block(offset + block.length, remaining), Block(offset, block.drop(1)) :: acc)
}
blocks(Block(0, lines), Nil)
}
val BlockStart = "/***"
val BlockEnd = "*/"
def fail(s: State, msg: String): State =
{
System.err.println(msg)
s.fail
}
def fail(s: State, msg: String): State = {
System.err.println(msg)
s.fail
}
}

View File

@ -37,7 +37,7 @@ final case class SessionSettings(
) {
assert(currentProject contains currentBuild,
s"Current build ($currentBuild) not associated with a current project.")
s"Current build ($currentBuild) not associated with a current project.")
/**
* Modifiy the current state.
@ -48,7 +48,9 @@ final case class SessionSettings(
* @return A new SessionSettings object
*/
def setCurrent(build: URI, project: String, eval: () => Eval): SessionSettings =
copy(currentBuild = build, currentProject = currentProject.updated(build, project), currentEval = eval)
copy(currentBuild = build,
currentProject = currentProject.updated(build, project),
currentEval = eval)
/**
* @return The current ProjectRef with which we scope settings.
@ -60,7 +62,8 @@ final case class SessionSettings(
* @param s A sequence of SessionSetting objects, which contain a Setting[_] and a string.
* @return A new SessionSettings which contains this new sequence.
*/
def appendSettings(s: Seq[SessionSetting]): SessionSettings = copy(append = modify(append, _ ++ s))
def appendSettings(s: Seq[SessionSetting]): SessionSettings =
copy(append = modify(append, _ ++ s))
/**
* Appends a set of raw Setting[_] objects to the current session.
@ -79,7 +82,8 @@ final case class SessionSettings(
*/
def clearExtraSettings: SessionSettings = copy(append = Map.empty, rawAppend = Nil)
private[this] def merge(map: SessionMap): Seq[Setting[_]] = map.values.toSeq.flatten[SessionSetting].map(_._1)
private[this] def merge(map: SessionMap): Seq[Setting[_]] =
map.values.toSeq.flatten[SessionSetting].map(_._1)
private[this] def modify(map: SessionMap, onSeq: Endo[Seq[SessionSetting]]): SessionMap = {
val cur = current
@ -88,6 +92,7 @@ final case class SessionSettings(
}
object SessionSettings {
/** A session setting is simply a tuple of a Setting[_] and the strings which define it. */
type SessionSetting = (Setting[_], Seq[String])
@ -137,7 +142,8 @@ object SessionSettings {
def checkSession(newSession: SessionSettings, oldState: State): Unit = {
val oldSettings = (oldState get Keys.sessionSettings).toList.flatMap(_.append).flatMap(_._2)
if (newSession.append.isEmpty && oldSettings.nonEmpty)
oldState.log.warn("Discarding " + pluralize(oldSettings.size, " session setting") + ". Use 'session save' to persist session settings.")
oldState.log.warn(
"Discarding " + pluralize(oldSettings.size, " session setting") + ". Use 'session save' to persist session settings.")
}
def removeRanges[T](in: Seq[T], ranges: Seq[(Int, Int)]): Seq[T] = {
@ -154,7 +160,8 @@ object SessionSettings {
def removeSettings(s: State, ranges: Seq[(Int, Int)]): State =
withSettings(s) { session =>
val current = session.current
val newAppend = session.append.updated(current, removeRanges(session.append.getOrElse(current, Nil), ranges))
val newAppend = session.append
.updated(current, removeRanges(session.append.getOrElse(current, Nil), ranges))
reapply(session.copy(append = newAppend), s)
}
@ -177,7 +184,8 @@ object SessionSettings {
withSettings(s) { session =>
val newSettings =
for ((ref, settings) <- session.append if settings.nonEmpty && include(ref)) yield {
val (news, olds) = writeSettings(ref, settings.toList, session.original, Project.structure(s))
val (news, olds) =
writeSettings(ref, settings.toList, session.original, Project.structure(s))
(ref -> news, olds)
}
val (newAppend, newOriginal) = newSettings.unzip
@ -185,26 +193,36 @@ object SessionSettings {
reapply(newSession.copy(original = newSession.mergeSettings, append = Map.empty), s)
}
def writeSettings(pref: ProjectRef, settings: List[SessionSetting], original: Seq[Setting[_]], structure: BuildStructure): (Seq[SessionSetting], Seq[Setting[_]]) = {
val project = Project.getProject(pref, structure).getOrElse(sys.error("Invalid project reference " + pref))
val writeTo: File = BuildPaths.configurationSources(project.base).headOption.getOrElse(new File(project.base, "build.sbt"))
def writeSettings(pref: ProjectRef,
settings: List[SessionSetting],
original: Seq[Setting[_]],
structure: BuildStructure): (Seq[SessionSetting], Seq[Setting[_]]) = {
val project =
Project.getProject(pref, structure).getOrElse(sys.error("Invalid project reference " + pref))
val writeTo: File = BuildPaths
.configurationSources(project.base)
.headOption
.getOrElse(new File(project.base, "build.sbt"))
writeTo.createNewFile()
val path = writeTo.getAbsolutePath
val (inFile, other, _) = ((List[Setting[_]](), List[Setting[_]](), Set.empty[ScopedKey[_]]) /: original.reverse) {
case ((in, oth, keys), s) =>
s.pos match {
case RangePosition(`path`, _) if !keys.contains(s.key) => (s :: in, oth, keys + s.key)
case _ => (in, s :: oth, keys)
}
}
val (inFile, other, _) =
((List[Setting[_]](), List[Setting[_]](), Set.empty[ScopedKey[_]]) /: original.reverse) {
case ((in, oth, keys), s) =>
s.pos match {
case RangePosition(`path`, _) if !keys.contains(s.key) => (s :: in, oth, keys + s.key)
case _ => (in, s :: oth, keys)
}
}
val (_, oldShifted, replace) = ((0, List[Setting[_]](), Seq[SessionSetting]()) /: inFile) {
case ((offs, olds, repl), s) =>
val RangePosition(_, r @ LineRange(start, end)) = s.pos
settings find (_._1.key == s.key) match {
case Some(ss @ (ns, newLines)) if !ns.init.dependencies.contains(ns.key) =>
val shifted = ns withPos RangePosition(path, LineRange(start - offs, start - offs + newLines.size))
val shifted = ns withPos RangePosition(path,
LineRange(start - offs,
start - offs + newLines.size))
(offs + end - start - newLines.size, shifted :: olds, ss +: repl)
case _ =>
val shifted = s withPos RangePosition(path, r shift -offs)
@ -225,7 +243,8 @@ object SessionSettings {
(newWithPos.reverse, other ++ oldShifted)
}
def needsTrailingBlank(lines: Seq[String]) = lines.nonEmpty && !lines.takeRight(1).exists(_.trim.isEmpty)
def needsTrailingBlank(lines: Seq[String]) =
lines.nonEmpty && !lines.takeRight(1).exists(_.trim.isEmpty)
/** Prints all the user-defined SessionSettings (not raw) to System.out. */
def printAllSettings(s: State): State =
@ -249,7 +268,8 @@ object SessionSettings {
for (((_, stringRep), index) <- settings.zipWithIndex)
println(" " + (index + 1) + ". " + stringRep.mkString("\n"))
def Help = """session <command>
def Help =
"""session <command>
Manipulates session settings, which are temporary settings that do not persist past the current sbt execution (that is, the current session).
Valid commands are:
@ -299,15 +319,19 @@ save, save-all
/** Parser for the session command. */
lazy val parser =
token(Space) ~>
(token("list-all" ^^^ new Print(true)) | token("list" ^^^ new Print(false)) | token("clear" ^^^ new Clear(false)) |
token("save-all" ^^^ new Save(true)) | token("save" ^^^ new Save(false)) | token("clear-all" ^^^ new Clear(true)) |
(token("list-all" ^^^ new Print(true)) | token("list" ^^^ new Print(false)) | token(
"clear" ^^^ new Clear(false)) |
token("save-all" ^^^ new Save(true)) | token("save" ^^^ new Save(false)) | token(
"clear-all" ^^^ new Clear(true)) |
remove)
lazy val remove = token("remove") ~> token(Space) ~> natSelect.map(ranges => new Remove(ranges))
def natSelect = rep1sep(token(range, "<range>"), ',')
def range: Parser[(Int, Int)] = (NatBasic ~ ('-' ~> NatBasic).?).map { case lo ~ hi => (lo, hi getOrElse lo) }
def range: Parser[(Int, Int)] = (NatBasic ~ ('-' ~> NatBasic).?).map {
case lo ~ hi => (lo, hi getOrElse lo)
}
/** The raw implementation of the session command. */
def command(s: State): Parser[() => State] = Command.applyEffect(parser) {

View File

@ -17,88 +17,104 @@ import DefaultParsers._
* The verbose summary will typically use more vertical space and show full details,
* while the quiet summary will be a couple of lines and truncate information.
*/
private[sbt] class SetResult(val session: SessionSettings, val verboseSummary: String, val quietSummary: String)
private[sbt] class SetResult(val session: SessionSettings,
val verboseSummary: String,
val quietSummary: String)
/** Defines methods for implementing the `set` command.*/
private[sbt] object SettingCompletions {
/**
* Implementation of the `set every` command. Each setting in the provided `settings` sequence will be applied in all scopes,
* overriding all previous definitions of the underlying AttributeKey.
* The settings injected by this method cannot be later persisted by the `session save` command.
*/
def setAll(extracted: Extracted, settings: Seq[Setting[_]]): SetResult =
{
import extracted._
val r = relation(extracted.structure, true)
val allDefs = Def.flattenLocals(Def.compiled(extracted.structure.settings, true)(structure.delegates, structure.scopeLocal, implicitly[Show[ScopedKey[_]]])).keys
val projectScope = Load.projectScope(currentRef)
def resolve(s: Setting[_]): Seq[Setting[_]] = Load.transformSettings(projectScope, currentRef.build, rootProject, s :: Nil)
def rescope[T](setting: Setting[T]): Seq[Setting[_]] =
{
val akey = setting.key.key
val global = ScopedKey(Global, akey)
val globalSetting = resolve(Def.setting(global, setting.init, setting.pos))
globalSetting ++ allDefs.flatMap { d =>
if (d.key == akey)
Seq(SettingKey(akey) in d.scope := { global.value })
else
Nil
}
}
val redefined = settings.flatMap(x => rescope(x))
val session = extracted.session.appendRaw(redefined)
setResult(session, r, redefined)
}
/** Implementation of the `set` command that will reload the current project with `settings` appended to the current settings. */
def setThis(s: State, extracted: Extracted, settings: Seq[Def.Setting[_]], arg: String): SetResult =
{
import extracted._
val append = Load.transformSettings(Load.projectScope(currentRef), currentRef.build, rootProject, settings)
val newSession = session.appendSettings(append map (a => (a, arg.split('\n').toList)))
val r = relation(newSession.mergeSettings, true)(structure.delegates, structure.scopeLocal, implicitly)
setResult(newSession, r, append)
}
private[this] def setResult(session: SessionSettings, r: Relation[ScopedKey[_], ScopedKey[_]], redefined: Seq[Setting[_]])(implicit show: Show[ScopedKey[_]]): SetResult =
{
val redefinedKeys = redefined.map(_.key).toSet
val affectedKeys = redefinedKeys.flatMap(r.reverse)
def summary(verbose: Boolean): String = setSummary(redefinedKeys, affectedKeys, verbose)
new SetResult(session, summary(true), summary(false))
}
private[this] def setSummary(redefined: Set[ScopedKey[_]], affected: Set[ScopedKey[_]], verbose: Boolean)(implicit display: Show[ScopedKey[_]]): String =
{
val QuietLimit = 3
def strings(in: Set[ScopedKey[_]]): Seq[String] = in.toSeq.map(sk => display.show(sk)).sorted
def lines(in: Seq[String]): (String, Boolean) =
if (in.isEmpty)
("no settings or tasks.", false)
else if (verbose)
(in.mkString("\n\t", "\n\t", "\n"), false)
def setAll(extracted: Extracted, settings: Seq[Setting[_]]): SetResult = {
import extracted._
val r = relation(extracted.structure, true)
val allDefs = Def
.flattenLocals(
Def.compiled(extracted.structure.settings, true)(structure.delegates,
structure.scopeLocal,
implicitly[Show[ScopedKey[_]]]))
.keys
val projectScope = Load.projectScope(currentRef)
def resolve(s: Setting[_]): Seq[Setting[_]] =
Load.transformSettings(projectScope, currentRef.build, rootProject, s :: Nil)
def rescope[T](setting: Setting[T]): Seq[Setting[_]] = {
val akey = setting.key.key
val global = ScopedKey(Global, akey)
val globalSetting = resolve(Def.setting(global, setting.init, setting.pos))
globalSetting ++ allDefs.flatMap { d =>
if (d.key == akey)
Seq(SettingKey(akey) in d.scope := { global.value })
else
quietList(in)
def quietList(in: Seq[String]): (String, Boolean) =
{
val (first, last) = in.splitAt(QuietLimit)
if (last.isEmpty)
(first.mkString(", "), false)
else {
val s = first.take(QuietLimit - 1).mkString("", ", ", " and " + last.size + " others.")
(s, true)
}
}
if (redefined.isEmpty)
"No settings or tasks were redefined."
else {
val (redef, trimR) = lines(strings(redefined))
val (used, trimU) = lines(strings(affected))
val details = if (trimR || trimU) "\n\tRun `last` for details." else ""
val valuesString = if (redefined.size == 1) "value" else "values"
"Defining %s\nThe new %s will be used by %s%s".format(redef, valuesString, used, details)
Nil
}
}
val redefined = settings.flatMap(x => rescope(x))
val session = extracted.session.appendRaw(redefined)
setResult(session, r, redefined)
}
/** Implementation of the `set` command that will reload the current project with `settings` appended to the current settings. */
def setThis(s: State,
extracted: Extracted,
settings: Seq[Def.Setting[_]],
arg: String): SetResult = {
import extracted._
val append = Load.transformSettings(Load.projectScope(currentRef),
currentRef.build,
rootProject,
settings)
val newSession = session.appendSettings(append map (a => (a, arg.split('\n').toList)))
val r = relation(newSession.mergeSettings, true)(structure.delegates,
structure.scopeLocal,
implicitly)
setResult(newSession, r, append)
}
private[this] def setResult(
session: SessionSettings,
r: Relation[ScopedKey[_], ScopedKey[_]],
redefined: Seq[Setting[_]])(implicit show: Show[ScopedKey[_]]): SetResult = {
val redefinedKeys = redefined.map(_.key).toSet
val affectedKeys = redefinedKeys.flatMap(r.reverse)
def summary(verbose: Boolean): String = setSummary(redefinedKeys, affectedKeys, verbose)
new SetResult(session, summary(true), summary(false))
}
private[this] def setSummary(redefined: Set[ScopedKey[_]],
affected: Set[ScopedKey[_]],
verbose: Boolean)(implicit display: Show[ScopedKey[_]]): String = {
val QuietLimit = 3
def strings(in: Set[ScopedKey[_]]): Seq[String] = in.toSeq.map(sk => display.show(sk)).sorted
def lines(in: Seq[String]): (String, Boolean) =
if (in.isEmpty)
("no settings or tasks.", false)
else if (verbose)
(in.mkString("\n\t", "\n\t", "\n"), false)
else
quietList(in)
def quietList(in: Seq[String]): (String, Boolean) = {
val (first, last) = in.splitAt(QuietLimit)
if (last.isEmpty)
(first.mkString(", "), false)
else {
val s = first.take(QuietLimit - 1).mkString("", ", ", " and " + last.size + " others.")
(s, true)
}
}
if (redefined.isEmpty)
"No settings or tasks were redefined."
else {
val (redef, trimR) = lines(strings(redefined))
val (used, trimU) = lines(strings(affected))
val details = if (trimR || trimU) "\n\tRun `last` for details." else ""
val valuesString = if (redefined.size == 1) "value" else "values"
"Defining %s\nThe new %s will be used by %s%s".format(redef, valuesString, used, details)
}
}
/**
* Parser that provides tab completion for the main argument to the `set` command.
@ -108,29 +124,36 @@ private[sbt] object SettingCompletions {
* when there are fewer choices or tab is pressed multiple times.
* The last part of the completion will generate a template for the value or function literal that will initialize the setting or task.
*/
def settingParser(settings: Settings[Scope], rawKeyMap: Map[String, AttributeKey[_]], context: ResolvedProject): Parser[String] =
{
val keyMap: Map[String, AttributeKey[_]] = rawKeyMap.map { case (k, v) => (keyScalaID(k), v) }.toMap
def inputScopedKey(pred: AttributeKey[_] => Boolean): Parser[ScopedKey[_]] =
scopedKeyParser(keyMap.filter { case (_, k) => pred(k) }, settings, context)
val full = for {
defineKey <- scopedKeyParser(keyMap, settings, context)
a <- assign(defineKey)
_ <- valueParser(defineKey, a, inputScopedKey(keyFilter(defineKey.key)))
} yield () // parser is currently only for completion and the parsed data structures are not used
def settingParser(settings: Settings[Scope],
rawKeyMap: Map[String, AttributeKey[_]],
context: ResolvedProject): Parser[String] = {
val keyMap
: Map[String, AttributeKey[_]] = rawKeyMap.map { case (k, v) => (keyScalaID(k), v) }.toMap
def inputScopedKey(pred: AttributeKey[_] => Boolean): Parser[ScopedKey[_]] =
scopedKeyParser(keyMap.filter { case (_, k) => pred(k) }, settings, context)
val full = for {
defineKey <- scopedKeyParser(keyMap, settings, context)
a <- assign(defineKey)
_ <- valueParser(defineKey, a, inputScopedKey(keyFilter(defineKey.key)))
} yield
() // parser is currently only for completion and the parsed data structures are not used
matched(full) | any.+.string
}
matched(full) | any.+.string
}
/** Parser for a Scope+AttributeKey (ScopedKey). */
def scopedKeyParser(keyMap: Map[String, AttributeKey[_]], settings: Settings[Scope], context: ResolvedProject): Parser[ScopedKey[_]] =
{
val cutoff = KeyRanks.MainCutoff
val keyCompletions = fixedCompletions { (seen, level) => completeKey(seen, keyMap, level, cutoff, 10).toSet }
val keyID: Parser[AttributeKey[_]] = scalaID(keyMap, "key")
val keyParser = token(keyID, keyCompletions)
for (key <- keyParser; scope <- scopeParser(key, settings, context)) yield ScopedKey(scope, key)
def scopedKeyParser(keyMap: Map[String, AttributeKey[_]],
settings: Settings[Scope],
context: ResolvedProject): Parser[ScopedKey[_]] = {
val cutoff = KeyRanks.MainCutoff
val keyCompletions = fixedCompletions { (seen, level) =>
completeKey(seen, keyMap, level, cutoff, 10).toSet
}
val keyID: Parser[AttributeKey[_]] = scalaID(keyMap, "key")
val keyParser = token(keyID, keyCompletions)
for (key <- keyParser; scope <- scopeParser(key, settings, context))
yield ScopedKey(scope, key)
}
/** Parser for the `in` method name that slightly augments the naive completion to give a hint of the purpose of `in`.*/
val inParser = tokenDisplay(Space ~> InMethod, "%s <scope>".format(InMethod))
@ -139,18 +162,19 @@ private[sbt] object SettingCompletions {
* Parser for the initialization expression for the assignment method `assign` on the key `sk`.
* `scopedKeyP` is used to parse and complete the input keys for an initialization that depends on other keys.
*/
def valueParser(sk: ScopedKey[_], assign: Assign.Value, scopedKeyP: Parser[ScopedKey[_]]): Parser[Seq[ScopedKey[_]]] =
{
val fullTypeString = keyTypeString(sk.key)
val typeString = if (assignNoAppend(assign)) fullTypeString else "..."
if (assign == Assign.Update) {
val function = "{(prev: " + typeString + ") => /*" + typeString + "*/ }"
token(OptSpace ~ function) ^^^ Nil
} else {
val value = "/* value of type " + typeString + " */"
token(Space ~ value) ^^^ Nil
}
def valueParser(sk: ScopedKey[_],
assign: Assign.Value,
scopedKeyP: Parser[ScopedKey[_]]): Parser[Seq[ScopedKey[_]]] = {
val fullTypeString = keyTypeString(sk.key)
val typeString = if (assignNoAppend(assign)) fullTypeString else "..."
if (assign == Assign.Update) {
val function = "{(prev: " + typeString + ") => /*" + typeString + "*/ }"
token(OptSpace ~ function) ^^^ Nil
} else {
val value = "/* value of type " + typeString + " */"
token(Space ~ value) ^^^ Nil
}
}
/**
* For a setting definition `definingKey <<= (..., in, ...) { ... }`,
@ -165,106 +189,136 @@ private[sbt] object SettingCompletions {
* The completions are restricted to be more useful. Currently, this parser will suggest
* only known axis values for configurations and tasks and only in that order.
*/
def scopeParser(key: AttributeKey[_], settings: Settings[Scope], context: ResolvedProject): Parser[Scope] = {
def scopeParser(key: AttributeKey[_],
settings: Settings[Scope],
context: ResolvedProject): Parser[Scope] = {
val data = settings.data
val allScopes = data.keys.toSeq
val definedScopes = data.toSeq flatMap { case (scope, attrs) => if (attrs contains key) scope :: Nil else Nil }
val definedScopes = data.toSeq flatMap {
case (scope, attrs) => if (attrs contains key) scope :: Nil else Nil
}
scope(key, allScopes, definedScopes, context)
}
private[this] def scope(key: AttributeKey[_], allScopes: Seq[Scope], definedScopes: Seq[Scope], context: ResolvedProject): Parser[Scope] =
{
def axisParser[T](axis: Scope => ScopeAxis[T], name: T => String, description: T => Option[String], label: String): Parser[ScopeAxis[T]] =
{
def getChoice(s: Scope): Seq[(String, T)] = axis(s) match {
case Select(t) => (name(t), t) :: Nil
case _ => Nil
}
def getChoices(scopes: Seq[Scope]): Map[String, T] = scopes.flatMap(getChoice).toMap
val definedChoices: Set[String] = definedScopes.flatMap(s => axis(s).toOption.map(name)).toSet
val fullChoices: Map[String, T] = getChoices(allScopes.toSeq)
val completions = fixedCompletions { (seen, level) => completeScope(seen, level, definedChoices, fullChoices)(description).toSet }
Act.optionalAxis(inParser ~> token(Space) ~> token(scalaID(fullChoices, label), completions), This)
}
val configurations: Map[String, Configuration] = context.configurations.map(c => (configScalaID(c.name), c)).toMap
val configParser = axisParser[ConfigKey](_.config, c => configScalaID(c.name), ck => configurations.get(ck.name).map(_.description), "configuration")
val taskParser = axisParser[AttributeKey[_]](_.task, k => keyScalaID(k.label), _.description, "task")
val nonGlobal = (configParser ~ taskParser) map { case (c, t) => Scope(This, c, t, Global) }
val global = inParser ~> token((Space ~ GlobalID) ^^^ GlobalScope)
global | nonGlobal
private[this] def scope(key: AttributeKey[_],
allScopes: Seq[Scope],
definedScopes: Seq[Scope],
context: ResolvedProject): Parser[Scope] = {
def axisParser[T](axis: Scope => ScopeAxis[T],
name: T => String,
description: T => Option[String],
label: String): Parser[ScopeAxis[T]] = {
def getChoice(s: Scope): Seq[(String, T)] = axis(s) match {
case Select(t) => (name(t), t) :: Nil
case _ => Nil
}
def getChoices(scopes: Seq[Scope]): Map[String, T] = scopes.flatMap(getChoice).toMap
val definedChoices: Set[String] =
definedScopes.flatMap(s => axis(s).toOption.map(name)).toSet
val fullChoices: Map[String, T] = getChoices(allScopes.toSeq)
val completions = fixedCompletions { (seen, level) =>
completeScope(seen, level, definedChoices, fullChoices)(description).toSet
}
Act.optionalAxis(inParser ~> token(Space) ~> token(scalaID(fullChoices, label), completions),
This)
}
val configurations: Map[String, Configuration] =
context.configurations.map(c => (configScalaID(c.name), c)).toMap
val configParser = axisParser[ConfigKey](_.config,
c => configScalaID(c.name),
ck => configurations.get(ck.name).map(_.description),
"configuration")
val taskParser =
axisParser[AttributeKey[_]](_.task, k => keyScalaID(k.label), _.description, "task")
val nonGlobal = (configParser ~ taskParser) map { case (c, t) => Scope(This, c, t, Global) }
val global = inParser ~> token((Space ~ GlobalID) ^^^ GlobalScope)
global | nonGlobal
}
/** Parser for the assignment method (such as `:=`) for defining `key`. */
def assign(key: ScopedKey[_]): Parser[Assign.Value] =
{
val completions = fixedCompletions { (seen, level) => completeAssign(seen, level, key).toSet }
val identifier = Act.filterStrings(Op, Assign.values.map(_.toString), "assignment method") map Assign.withName
token(Space) ~> token(optionallyQuoted(identifier), completions)
def assign(key: ScopedKey[_]): Parser[Assign.Value] = {
val completions = fixedCompletions { (seen, level) =>
completeAssign(seen, level, key).toSet
}
val identifier = Act.filterStrings(Op, Assign.values.map(_.toString), "assignment method") map Assign.withName
token(Space) ~> token(optionallyQuoted(identifier), completions)
}
private[this] def fixedCompletions(f: (String, Int) => Set[Completion]): TokenCompletions =
TokenCompletions.fixed((s, l) => Completions(f(s, l)))
private[this] def scalaID[T](keyMap: Map[String, T], label: String): Parser[T] =
{
val identifier = Act.filterStrings(ScalaID, keyMap.keySet, label) map keyMap
optionallyQuoted(identifier)
}
private[this] def scalaID[T](keyMap: Map[String, T], label: String): Parser[T] = {
val identifier = Act.filterStrings(ScalaID, keyMap.keySet, label) map keyMap
optionallyQuoted(identifier)
}
/** Produce a new parser that allows the input accepted by `p` to be quoted in backticks. */
def optionallyQuoted[T](p: Parser[T]): Parser[T] =
(Backtick.? ~ p) flatMap { case (quote, id) => if (quote.isDefined) Backtick.? ^^^ id else success(id) }
(Backtick.? ~ p) flatMap {
case (quote, id) => if (quote.isDefined) Backtick.? ^^^ id else success(id)
}
/**
* Completions for an assignment method for `key` given the tab completion `level` and existing partial string `seen`.
* This will filter possible assignment methods based on the underlying type of `key`, so that only `<<=` is shown for input tasks, for example.
*/
def completeAssign(seen: String, level: Int, key: ScopedKey[_]): Seq[Completion] =
{
val allowed: Iterable[Assign.Value] =
if (appendable(key.key)) Assign.values
else assignNoAppend
val applicable = allowed.toSeq.flatMap { a =>
val s = a.toString
if (s startsWith seen) (s, a) :: Nil else Nil
def completeAssign(seen: String, level: Int, key: ScopedKey[_]): Seq[Completion] = {
val allowed: Iterable[Assign.Value] =
if (appendable(key.key)) Assign.values
else assignNoAppend
val applicable = allowed.toSeq.flatMap { a =>
val s = a.toString
if (s startsWith seen) (s, a) :: Nil else Nil
}
completeDescribed(seen, true, applicable)(assignDescription)
}
def completeKey(seen: String,
keys: Map[String, AttributeKey[_]],
level: Int,
prominentCutoff: Int,
detailLimit: Int): Seq[Completion] =
completeSelectDescribed(seen, level, keys, detailLimit)(_.description) {
case (k, v) => v.rank <= prominentCutoff
}
def completeScope[T](
seen: String,
level: Int,
definedChoices: Set[String],
allChoices: Map[String, T])(description: T => Option[String]): Seq[Completion] =
completeSelectDescribed(seen, level, allChoices, 10)(description) {
case (k, v) => definedChoices(k)
}
def completeSelectDescribed[T](seen: String, level: Int, all: Map[String, T], detailLimit: Int)(
description: T => Option[String])(prominent: (String, T) => Boolean): Seq[Completion] = {
val applicable = all.toSeq.filter { case (k, v) => k startsWith seen }
val prominentOnly = applicable filter { case (k, v) => prominent(k, v) }
val showAll = (level >= 3) || (level == 2 && prominentOnly.size <= detailLimit) || prominentOnly.isEmpty
val showKeys = if (showAll) applicable else prominentOnly
val showDescriptions = (level >= 2) || (showKeys.size <= detailLimit)
completeDescribed(seen, showDescriptions, showKeys)(s => description(s).toList.mkString)
}
def completeDescribed[T](seen: String, showDescriptions: Boolean, in: Seq[(String, T)])(
description: T => String): Seq[Completion] = {
def appendString(id: String): String = id.stripPrefix(seen) + " "
if (in.isEmpty)
Nil
else if (showDescriptions) {
val withDescriptions = in map { case (id, key) => (id, description(key)) }
val padded = CommandUtil.aligned("", " ", withDescriptions)
(padded, in).zipped.map {
case (line, (id, key)) =>
Completion.tokenDisplay(append = appendString(id), display = line + "\n")
}
completeDescribed(seen, true, applicable)(assignDescription)
}
def completeKey(seen: String, keys: Map[String, AttributeKey[_]], level: Int, prominentCutoff: Int, detailLimit: Int): Seq[Completion] =
completeSelectDescribed(seen, level, keys, detailLimit)(_.description) { case (k, v) => v.rank <= prominentCutoff }
def completeScope[T](seen: String, level: Int, definedChoices: Set[String], allChoices: Map[String, T])(description: T => Option[String]): Seq[Completion] =
completeSelectDescribed(seen, level, allChoices, 10)(description) { case (k, v) => definedChoices(k) }
def completeSelectDescribed[T](seen: String, level: Int, all: Map[String, T], detailLimit: Int)(description: T => Option[String])(prominent: (String, T) => Boolean): Seq[Completion] =
{
val applicable = all.toSeq.filter { case (k, v) => k startsWith seen }
val prominentOnly = applicable filter { case (k, v) => prominent(k, v) }
val showAll = (level >= 3) || (level == 2 && prominentOnly.size <= detailLimit) || prominentOnly.isEmpty
val showKeys = if (showAll) applicable else prominentOnly
val showDescriptions = (level >= 2) || (showKeys.size <= detailLimit)
completeDescribed(seen, showDescriptions, showKeys)(s => description(s).toList.mkString)
}
def completeDescribed[T](seen: String, showDescriptions: Boolean, in: Seq[(String, T)])(description: T => String): Seq[Completion] =
{
def appendString(id: String): String = id.stripPrefix(seen) + " "
if (in.isEmpty)
Nil
else if (showDescriptions) {
val withDescriptions = in map { case (id, key) => (id, description(key)) }
val padded = CommandUtil.aligned("", " ", withDescriptions)
(padded, in).zipped.map {
case (line, (id, key)) =>
Completion.tokenDisplay(append = appendString(id), display = line + "\n")
}
} else
in map {
case (id, key) =>
Completion.tokenDisplay(display = id, append = appendString(id))
}
}
} else
in map {
case (id, key) =>
Completion.tokenDisplay(display = id, append = appendString(id))
}
}
/**
* Transforms the hyphenated key label `k` into camel-case and quotes it with backticks if it is a Scala keyword.
@ -279,17 +333,19 @@ private[sbt] object SettingCompletions {
def configScalaID(c: String): String = Util.quoteIfKeyword(c.capitalize)
/** Applies a function on the underlying manifest for T for `key` depending if it is for a `Setting[T]`, `Task[T]`, or `InputTask[T]`.*/
def keyType[S](key: AttributeKey[_])(onSetting: Manifest[_] => S, onTask: Manifest[_] => S, onInput: Manifest[_] => S)(implicit tm: Manifest[Task[_]], im: Manifest[InputTask[_]]): S =
{
def argTpe = key.manifest.typeArguments.head
val TaskClass = tm.runtimeClass
val InputTaskClass = im.runtimeClass
key.manifest.runtimeClass match {
case TaskClass => onTask(argTpe)
case InputTaskClass => onInput(argTpe)
case _ => onSetting(key.manifest)
}
def keyType[S](key: AttributeKey[_])(
onSetting: Manifest[_] => S,
onTask: Manifest[_] => S,
onInput: Manifest[_] => S)(implicit tm: Manifest[Task[_]], im: Manifest[InputTask[_]]): S = {
def argTpe = key.manifest.typeArguments.head
val TaskClass = tm.runtimeClass
val InputTaskClass = im.runtimeClass
key.manifest.runtimeClass match {
case TaskClass => onTask(argTpe)
case InputTaskClass => onInput(argTpe)
case _ => onSetting(key.manifest)
}
}
/** For a Task[T], InputTask[T], or Setting[T], this returns the manifest for T. */
def keyUnderlyingType(key: AttributeKey[_]): Manifest[_] = keyType(key)(idFun, idFun, idFun)
@ -298,27 +354,28 @@ private[sbt] object SettingCompletions {
* Returns a string representation of the underlying type T for a `key` representing a `Setting[T]`, `Task[T]`, or `InputTask[T]`.
* This string representation is currently a cleaned up toString of the underlying Manifest.
*/
def keyTypeString[T](key: AttributeKey[_]): String =
{
val mfToString = (mf: Manifest[_]) => complete.TypeString.cleanup(mf.toString)
keyType(key)(mfToString, mfToString, mfToString)
}
def keyTypeString[T](key: AttributeKey[_]): String = {
val mfToString = (mf: Manifest[_]) => complete.TypeString.cleanup(mf.toString)
keyType(key)(mfToString, mfToString, mfToString)
}
/** True if the `key` represents an input task, false if it represents a task or setting. */
def isInputTask(key: AttributeKey[_]): Boolean = keyType(key)(const(false), const(false), const(true))
def isInputTask(key: AttributeKey[_]): Boolean =
keyType(key)(const(false), const(false), const(true))
/** True if the `key` represents a setting, false if it represents a task or an input task.*/
def isSetting(key: AttributeKey[_]): Boolean = keyType(key)(const(true), const(false), const(false))
def isSetting(key: AttributeKey[_]): Boolean =
keyType(key)(const(true), const(false), const(false))
/** True if the `key` represents a setting or task, false if it is for an input task. */
def isTaskOrSetting(key: AttributeKey[_]): Boolean = keyType(key)(const(true), const(true), const(false))
def isTaskOrSetting(key: AttributeKey[_]): Boolean =
keyType(key)(const(true), const(true), const(false))
/** True if the `key` represents a setting or task that may be appended using an assignment method such as `+=`. */
def appendable(key: AttributeKey[_]): Boolean =
{
val underlying = keyUnderlyingType(key).runtimeClass
appendableClasses.exists(_ isAssignableFrom underlying)
}
def appendable(key: AttributeKey[_]): Boolean = {
val underlying = keyUnderlyingType(key).runtimeClass
appendableClasses.exists(_ isAssignableFrom underlying)
}
/** The simple name of the global scope axis, which can be used to reference it in the default setting context. */
final val GlobalID = Global.getClass.getSimpleName.stripSuffix("$")
@ -337,6 +394,7 @@ private[sbt] object SettingCompletions {
val Update = Value("~=")
}
import Assign._
/** Returns the description associated with the provided assignment method. */
def assignDescription(a: Assign.Value): String = a match {
case AppendValue => "append value"
@ -344,6 +402,7 @@ private[sbt] object SettingCompletions {
case Define => "define value, overwriting any existing value"
case Update => "transform existing value"
}
/** The assignment methods except for the ones that append. */
val assignNoAppend: Set[Assign.Value] = Set(Define, Update)

Some files were not shown because too many files have changed in this diff Show More