diff --git a/build.sbt b/build.sbt index b8ff283b6..d4fc0cbab 100644 --- a/build.sbt +++ b/build.sbt @@ -181,7 +181,8 @@ lazy val protocolProj = (project in file("protocol")). testedBaseSettings, name := "Protocol", libraryDependencies ++= Seq(sjsonNewScalaJson), - sourceManaged in (Compile, generateContrabands) := 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) @@ -211,6 +212,7 @@ lazy val mainSettingsProj = (project in file("main-settings")). // 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( testedBaseSettings, name := "Main", diff --git a/main-settings/src/main/scala/sbt/Structure.scala b/main-settings/src/main/scala/sbt/Structure.scala index 5752c7e96..c06c34bf0 100644 --- a/main-settings/src/main/scala/sbt/Structure.scala +++ b/main-settings/src/main/scala/sbt/Structure.scala @@ -3,19 +3,20 @@ */ package sbt -/** An abstraction on top of Settings for build configuration and task definition. */ +import scala.language.experimental.macros import java.io.File -import ConcurrentRestrictions.Tag -import Def.{ Initialize, KeyedInitialize, ScopedKey, Setting, setting } import sbt.io.{ FileFilter, PathFinder } import sbt.io.syntax._ -import std.TaskExtra.{ task => mktask, _ } import sbt.internal.util.Types._ import sbt.internal.util.{ ~>, AList, AttributeKey, Settings, SourcePosition } +import sbt.util.OptJsonWriter +import sbt.ConcurrentRestrictions.Tag +import sbt.Def.{ Initialize, KeyedInitialize, ScopedKey, Setting, setting } +import std.TaskExtra.{ task => mktask, _ } -import language.experimental.macros +/** An abstraction on top of Settings for build configuration and task definition. */ sealed trait Scoped { def scope: Scope; val key: AttributeKey[_] } @@ -471,17 +472,17 @@ object TaskKey { /** Constructs SettingKeys, which are associated with a value to define a basic setting.*/ object SettingKey { - def apply[T: Manifest](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](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](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] = new SettingKey[T] { val key = akey; def scope = Scope.ThisScope } - def local[T: Manifest]: SettingKey[T] = apply[T](AttributeKey.local[T]) + def local[T: Manifest: OptJsonWriter]: SettingKey[T] = apply[T](AttributeKey.local[T]) } diff --git a/main-settings/src/main/scala/sbt/std/KeyMacro.scala b/main-settings/src/main/scala/sbt/std/KeyMacro.scala index e770d0b43..bf0af5ee1 100644 --- a/main-settings/src/main/scala/sbt/std/KeyMacro.scala +++ b/main-settings/src/main/scala/sbt/std/KeyMacro.scala @@ -1,12 +1,15 @@ package sbt package std -import reflect.macros._ +import scala.annotation.tailrec +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]] = - keyImpl[T, SettingKey[T]](c) { (name, mf) => - c.universe.reify { SettingKey[T](name.splice, description.splice)(mf.splice) } + 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]] = keyImpl[T, TaskKey[T]](c) { (name, mf) => @@ -17,20 +20,33 @@ private[sbt] object KeyMacro { 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]): c.Expr[S] = - { - 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 name = c.Expr[String](Literal(Constant(enclosingValName))) - val mf = c.Expr[Manifest[T]](c.inferImplicitValue(weakTypeOf[Manifest[T]])) - f(name, mf) - } + def keyImpl[T: c.WeakTypeTag, S: c.WeakTypeTag](c: blackbox.Context)( + 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] + ): 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")`.""") + c.Expr[String](Literal(Constant(enclosingValName))) + } + + private def getImplicit[T: c.WeakTypeTag](c: blackbox.Context): c.Expr[T] = { + import c.universe._ + 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 - def enclosingVal(trees: List[c.Tree]): String = + @tailrec def enclosingVal(trees: List[c.Tree]): String = { trees match { case vd @ ValDef(_, name, _, _) :: ts => processName(name) @@ -44,6 +60,7 @@ private[sbt] object KeyMacro { } 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]) } diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index bb42f5105..0559fc834 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -34,6 +34,7 @@ import sbt.internal.io.WatchState import sbt.internal.util.{ AttributeKey, CacheStore, SourcePosition } import sbt.librarymanagement.Configurations.CompilerPlugin +import sbt.librarymanagement.LibraryManagementCodec._ import sbt.librarymanagement.{ Artifact, Configuration, diff --git a/main/src/main/scala/sbt/internal/CommandExchange.scala b/main/src/main/scala/sbt/internal/CommandExchange.scala index 39697fdc3..d9ed0f40c 100644 --- a/main/src/main/scala/sbt/internal/CommandExchange.scala +++ b/main/src/main/scala/sbt/internal/CommandExchange.scala @@ -74,7 +74,7 @@ private[sbt] final class CommandExchange { def onIncomingSocket(socket: Socket): Unit = { s.log.info(s"new client connected from: ${socket.getPort}") - val channel = new NetworkChannel(newChannelName, socket, Project structure s, Project.session(s).currentBuild) + val channel = new NetworkChannel(newChannelName, socket, Project structure s) subscribe(channel) channel.publishEventMessage(ChannelAcceptedEvent(channel.name)) } diff --git a/main/src/main/scala/sbt/internal/Load.scala b/main/src/main/scala/sbt/internal/Load.scala index 76e0f907c..8a3eae709 100755 --- a/main/src/main/scala/sbt/internal/Load.scala +++ b/main/src/main/scala/sbt/internal/Load.scala @@ -699,7 +699,7 @@ private[sbt] object Load { * sbt file to resolve a project. * @param log A logger to report auto-plugin issues to. */ - private[this] def resolveProject( + private[sbt] def resolveProject( p: Project, projectPlugins: Seq[AutoPlugin], loadedPlugins: LoadedPlugins, diff --git a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala index 953a2d57a..5113b8d2b 100644 --- a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala +++ b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala @@ -5,13 +5,12 @@ package sbt package internal package server -import java.net.{ Socket, SocketTimeoutException, URI } +import java.net.{ Socket, SocketTimeoutException } import java.util.concurrent.atomic.AtomicBoolean -import scala.util.{ Left, Right } import sbt.protocol._ import sjsonnew._ -final class NetworkChannel(val name: String, connection: Socket, structure: BuildStructure, currentBuild: URI) extends CommandChannel { +final class NetworkChannel(val name: String, connection: Socket, structure: BuildStructure) extends CommandChannel { private val running = new AtomicBoolean(true) private val delimiter: Byte = '\n'.toByte private val out = connection.getOutputStream @@ -79,32 +78,8 @@ final class NetworkChannel(val name: String, connection: Socket, structure: Buil private def onExecCommand(cmd: ExecCommand) = append(Exec(cmd.commandLine, cmd.execId orElse Some(Exec.newExecId), Some(CommandSource(name)))) - private def onSettingQuery(req: SettingQuery) = { - import sbt.internal.util.complete.Parser - - val key = Parser.parse(req.setting, SettingQuery.scopedKeyParser(structure, currentBuild)) - - def getSettingValue[A](key: Def.ScopedKey[A]) = - structure.data.get(key.scope, key.key) - .toRight(s"Key ${Def displayFull key} not found") - .flatMap { - case _: Task[_] => Left(s"Key ${Def displayFull key} is a task, can only query settings") - case _: InputTask[_] => Left(s"Key ${Def displayFull key} is an input task, can only query settings") - case x => Right(x) - } - - val values = key match { - case Left(msg) => Left(s"Invalid programmatic input: $msg") - case Right(key) => Right(getSettingValue(key)) - } - - val jsonValues = values match { - case Left(errors) => errors - case Right(value) => value.toString - } - - StandardMain.exchange publishEventMessage SettingQueryResponse(jsonValues) - } + private def onSettingQuery(req: SettingQuery) = + StandardMain.exchange publishEventMessage SettingQuery.handleSettingQuery(req, structure) def shutdown(): Unit = { println("Shutting down client connection") @@ -112,70 +87,3 @@ final class NetworkChannel(val name: String, connection: Socket, structure: Buil out.close() } } - -object SettingQuery { - import sbt.internal.util.{ AttributeKey, Settings } - import sbt.internal.util.complete.{ DefaultParsers, Parser }, DefaultParsers._ - import sbt.Def.{ showBuildRelativeKey, ScopedKey } - - // Similar to Act.ParsedAxis / Act.projectRef / Act.resolveProject except you can't omit the project reference - - sealed trait ParsedExplicitAxis[+T] - final object ParsedExplicitGlobal extends ParsedExplicitAxis[Nothing] - final class ParsedExplicitValue[T](val value: T) extends ParsedExplicitAxis[T] - def explicitValue[T](t: Parser[T]): Parser[ParsedExplicitAxis[T]] = t map { v => new ParsedExplicitValue(v) } - - def projectRef(index: KeyIndex, currentBuild: URI): Parser[ParsedExplicitAxis[ResolvedReference]] = { - val global = token(Act.GlobalString ~ '/') ^^^ ParsedExplicitGlobal - val trailing = '/' !!! "Expected '/' (if selecting a project)" - global | explicitValue(Act.resolvedReference(index, currentBuild, trailing)) - } - - def resolveProject(parsed: ParsedExplicitAxis[ResolvedReference]): Option[ResolvedReference] = parsed match { - case ParsedExplicitGlobal => None - case pv: ParsedExplicitValue[_] => Some(pv.value) - } - - def scopedKeyFull( - index: KeyIndex, - currentBuild: URI, - defaultConfigs: Option[ResolvedReference] => Seq[String], - keyMap: Map[String, AttributeKey[_]] - ): Parser[Seq[Parser[ParsedKey]]] = { - for { - rawProject <- projectRef(index, currentBuild) - proj = resolveProject(rawProject) - confAmb <- Act.config(index configs proj) - partialMask = ScopeMask(true, confAmb.isExplicit, false, false) - } yield Act.taskKeyExtra(index, defaultConfigs, keyMap, proj, confAmb, partialMask) - } - - def scopedKeyParser(structure: BuildStructure, currentBuild: URI): Parser[ScopedKey[_]] = - scopedKey( - structure.index.keyIndex, - currentBuild, - structure.extra.configurationsForAxis, - structure.index.keyMap, - structure.data - ) - - def scopedKeySelected( - index: KeyIndex, - currentBuild: URI, - defaultConfigs: Option[ResolvedReference] => Seq[String], - keyMap: Map[String, AttributeKey[_]], - data: Settings[Scope] - ): Parser[ParsedKey] = - scopedKeyFull(index, currentBuild, defaultConfigs, keyMap) flatMap { choices => - Act.select(choices, data)(showBuildRelativeKey(currentBuild, index.buildURIs.size > 1)) - } - - def scopedKey( - index: KeyIndex, - currentBuild: URI, - defaultConfigs: Option[ResolvedReference] => Seq[String], - keyMap: Map[String, AttributeKey[_]], - data: Settings[Scope] - ): Parser[ScopedKey[_]] = - scopedKeySelected(index, currentBuild, defaultConfigs, keyMap, data).map(_.key) -} diff --git a/main/src/main/scala/sbt/internal/server/SettingQuery.scala b/main/src/main/scala/sbt/internal/server/SettingQuery.scala new file mode 100644 index 000000000..54fbc7b42 --- /dev/null +++ b/main/src/main/scala/sbt/internal/server/SettingQuery.scala @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2016-2017 Lightbend Inc. + */ +package sbt +package internal +package server + +import java.net.URI +import scala.json.ast.unsafe.JValue +import scala.util.{ Left, Right } +import sbt.util.{ SomeJsonWriter, NoJsonWriter } +import sbt.librarymanagement.LibraryManagementCodec._ +import sbt.protocol._ +import sjsonnew._ +import sjsonnew.support.scalajson.unsafe._ + +object SettingQuery { + import sbt.internal.util.{ AttributeKey, Settings } + import sbt.internal.util.complete.{ DefaultParsers, Parser }, DefaultParsers._ + import sbt.Def.{ showBuildRelativeKey, ScopedKey } + + // Similar to Act.ParsedAxis / Act.projectRef / Act.resolveProject except you can't omit the project reference + + sealed trait ParsedExplicitAxis[+T] + final object ParsedExplicitGlobal extends ParsedExplicitAxis[Nothing] + final class ParsedExplicitValue[T](val value: T) extends ParsedExplicitAxis[T] + def explicitValue[T](t: Parser[T]): Parser[ParsedExplicitAxis[T]] = t map { v => new ParsedExplicitValue(v) } + + def projectRef(index: KeyIndex, currentBuild: URI): Parser[ParsedExplicitAxis[ResolvedReference]] = { + val global = token(Act.GlobalString ~ '/') ^^^ ParsedExplicitGlobal + val trailing = '/' !!! "Expected '/' (if selecting a project)" + global | explicitValue(Act.resolvedReference(index, currentBuild, trailing)) + } + + def resolveProject(parsed: ParsedExplicitAxis[ResolvedReference]): Option[ResolvedReference] = parsed match { + case ParsedExplicitGlobal => None + case pv: ParsedExplicitValue[_] => Some(pv.value) + } + + def scopedKeyFull( + index: KeyIndex, + currentBuild: URI, + defaultConfigs: Option[ResolvedReference] => Seq[String], + keyMap: Map[String, AttributeKey[_]] + ): Parser[Seq[Parser[ParsedKey]]] = { + for { + rawProject <- projectRef(index, currentBuild) + proj = resolveProject(rawProject) + confAmb <- Act.config(index configs proj) + partialMask = ScopeMask(true, confAmb.isExplicit, false, false) + } yield Act.taskKeyExtra(index, defaultConfigs, keyMap, proj, confAmb, partialMask) + } + + def scopedKeySelected( + index: KeyIndex, + currentBuild: URI, + defaultConfigs: Option[ResolvedReference] => Seq[String], + keyMap: Map[String, AttributeKey[_]], + data: Settings[Scope] + ): Parser[ParsedKey] = + scopedKeyFull(index, currentBuild, defaultConfigs, keyMap) flatMap { choices => + Act.select(choices, data)(showBuildRelativeKey(currentBuild, index.buildURIs.size > 1)) + } + + def scopedKey( + index: KeyIndex, + currentBuild: URI, + defaultConfigs: Option[ResolvedReference] => Seq[String], + keyMap: Map[String, AttributeKey[_]], + data: Settings[Scope] + ): Parser[ScopedKey[_]] = + scopedKeySelected(index, currentBuild, defaultConfigs, keyMap, data).map(_.key) + + def scopedKeyParser(structure: BuildStructure): Parser[ScopedKey[_]] = + scopedKey( + structure.index.keyIndex, + structure.root, + structure.extra.configurationsForAxis, + structure.index.keyMap, + structure.data + ) + + def getSettingValue[A](structure: BuildStructure, key: Def.ScopedKey[A]): Either[String, A] = + structure.data.get(key.scope, key.key) + .toRight(s"Key ${Def displayFull key} not found") + .flatMap { + case _: Task[_] => Left(s"Key ${Def displayFull key} is a task, can only query settings") + case _: InputTask[_] => Left(s"Key ${Def displayFull key} is an input task, can only query settings") + case x => Right(x) + } + + def getJsonWriter[A](key: AttributeKey[A]): Either[String, JsonWriter[A]] = key.optJsonWriter match { + case SomeJsonWriter(jw) => Right(jw) + case NoJsonWriter() => Left(s"JsonWriter for ${key.manifest} not found") + } + + def toJson[A: JsonWriter](x: A): JValue = Converter toJsonUnsafe x + + def getSettingJsonValue[A](structure: BuildStructure, key: Def.ScopedKey[A]): Either[String, JValue] = + getSettingValue(structure, key) flatMap (value => + getJsonWriter(key.key) map { implicit jw: JsonWriter[A] => toJson(value) } + ) + + def handleSettingQuery(req: SettingQuery, structure: BuildStructure): SettingQueryResponse = { + val key = Parser.parse(req.setting, scopedKeyParser(structure)) + + val result = + for { + key <- key + json <- getSettingJsonValue(structure, key) + } yield SettingQuerySuccess(json, key.key.manifest.toString) + + result match { + case Right(x) => x + case Left(s) => SettingQueryFailure(s) + } + } +} diff --git a/main/src/test/scala/sbt/internal/server/SettingQueryTest.scala b/main/src/test/scala/sbt/internal/server/SettingQueryTest.scala new file mode 100644 index 000000000..7be5082fc --- /dev/null +++ b/main/src/test/scala/sbt/internal/server/SettingQueryTest.scala @@ -0,0 +1,184 @@ +package sbt +package internal +package server + +import java.io._ +import java.net._ +import java.nio.file._ +import java.util.concurrent._ + +import scala.collection.mutable + +import xsbti._ +import sbt.io.IO +import sbt.internal.util._ +import sbt.internal.BuildStreams.{ Streams => _, _ } +import sbt.internal.Load._ +import sbt.util._ +import sbt.BuildPaths._ +import sbt.Def.{ ScopeLocal, ScopedKey, Setting } +import sbt.Keys._ + +object SettingQueryTest extends org.specs2.mutable.Specification { + implicit class PathOps(val path: Path) extends AnyVal { + def /(other: String): Path = if (other == ".") path else path resolve other + } + + val baseDir: Path = Files createTempDirectory "sbt-setting-query-test" + val globalDir: Path = Files createTempDirectory "sbt-setting-query-test-global-dir" + val bootDir: Path = Files createTempDirectory "sbt-setting-query-test-boot-dir" + val ivyHome: Path = Files createTempDirectory "sbt-setting-query-test-ivy-home" + val logFile: File = File.createTempFile("sbt", ".log") + + val baseFile: File = baseDir.toFile + val baseUri: URI = IO directoryURI baseFile + IO assertAbsolute baseUri + + val globalDirFile: File = globalDir.toFile + + def ??? : Nothing = { Thread.dumpStack(); throw new NotImplementedError } + + val noopLoader: ClassLoader = new URLClassLoader(Array(), null) + + object NoGlobalLock extends GlobalLock { def apply[T](lockFile: File, run: Callable[T]) = run.call() } + + lazy val structure: BuildStructure = { + val projectSettings: Seq[Setting[_]] = Seq(scalaVersion := "2.12.1") + + val appConfig: AppConfiguration = new AppConfiguration { + def baseDirectory(): File = baseFile + def arguments(): Array[String] = Array() + def provider(): AppProvider = new AppProvider { + def scalaProvider(): ScalaProvider = new ScalaProvider { scalaProvider => + def launcher(): Launcher = new Launcher { + def getScala(version: String): ScalaProvider = getScala(version, "") + def getScala(version: String, reason: String): ScalaProvider = getScala(version, reason, "org.scala-lang") + def getScala(version: String, reason: String, scalaOrg: String): ScalaProvider = scalaProvider + + def app(id: ApplicationID, version: String): AppProvider = ??? + + def topLoader(): ClassLoader = noopLoader + def globalLock(): GlobalLock = NoGlobalLock + def bootDirectory(): File = bootDir.toFile + def ivyRepositories(): Array[Repository] = Array() + def appRepositories(): Array[Repository] = Array() + def isOverrideRepositories: Boolean = false + def ivyHome(): File = SettingQueryTest.this.ivyHome.toFile + def checksums(): Array[String] = Array() + } + def version(): String = "2.12.1" + + def loader(): ClassLoader = noopLoader + def jars(): Array[File] = Array(libraryJar, compilerJar) + + def libraryJar(): File = new File("scala-library.jar") + def compilerJar(): File = new File("scala-compiler.jar") + + def app(id: ApplicationID): AppProvider = ??? + } + + def id(): ApplicationID = sbt.ApplicationID( + "org.scala-sbt", "sbt", "0.13.13", "sbt.xMain", + components = Seq(), crossVersionedValue = CrossValue.Disabled, extra = Seq() + ) + + def loader(): ClassLoader = noopLoader + + def entryPoint(): Class[_] = ??? + def mainClass(): Class[_ <: AppMain] = ??? + def newMain(): AppMain = ??? + + def mainClasspath(): Array[File] = Array() + + def components(): ComponentProvider = new ComponentProvider { + def componentLocation(id: String): File = ??? + def component(componentID: String): Array[File] = ??? + def defineComponent(componentID: String, components: Array[File]): Unit = ??? + def addToComponent(componentID: String, components: Array[File]): Boolean = ??? + def lockFile(): File = ??? + } + } + } + + val state: State = + StandardMain.initialState(appConfig, initialDefinitions = Seq(), preCommands = Seq()) + .put(globalBaseDirectory, globalDirFile) + + val config0 = defaultPreGlobal(state, baseFile, globalDirFile, state.log) + val config = defaultWithGlobal(state, baseFile, config0, globalDirFile, state.log) + + val buildUnit: BuildUnit = { + val loadedPlugins: LoadedPlugins = + noPlugins(projectStandard(baseFile), config.copy(pluginManagement = config.pluginManagement.forPlugin)) + + val project: Project = { + val project0 = Project("t", baseFile) settings projectSettings + val fileToLoadedSbtFileMap = new mutable.HashMap[File, LoadedSbtFile] + val autoPlugins = loadedPlugins.detected.deducePluginsFromProject(project0, state.log) + val injectSettings = config.injectSettings + resolveProject(project0, autoPlugins, loadedPlugins, injectSettings, fileToLoadedSbtFileMap, state.log) + } + + val projects: Seq[Project] = Seq(project) + val builds: Seq[BuildDef] = BuildDef.defaultAggregated(project.id, Nil) :: Nil + val defs: LoadedDefinitions = new LoadedDefinitions(baseFile, Nil, noopLoader, builds, projects, Nil) + new BuildUnit(baseUri, baseFile, defs, loadedPlugins) + } + + val (partBuildUnit: PartBuildUnit, projectRefs: List[ProjectReference]) = loaded(buildUnit) + val partBuildUnits: Map[URI, PartBuildUnit] = Map(buildUnit.uri -> partBuildUnit) + val allProjectRefs: Map[URI, List[ProjectReference]] = Map(buildUnit.uri -> projectRefs) + checkAll(allProjectRefs, partBuildUnits) + + val partBuild: PartBuild = new PartBuild(baseUri, partBuildUnits) + val loadedBuild: LoadedBuild = resolveProjects(partBuild) + + val units: Map[URI, LoadedBuildUnit] = loadedBuild.units + + val settings: Seq[Setting[_]] = finalTransforms(buildConfigurations(loadedBuild, getRootProject(units), config.injectSettings)) + val delegates: Scope => Seq[Scope] = defaultDelegates(loadedBuild) + val scopeLocal: ScopeLocal = EvaluateTask.injectStreams + val display: Show[ScopedKey[_]] = Project showLoadingKey loadedBuild + + val data: Settings[Scope] = Def.make(settings)(delegates, scopeLocal, display) + val extra: KeyIndex => BuildUtil[_] = index => BuildUtil(baseUri, units, index, data) + + val index: StructureIndex = structureIndex(data, settings, extra, units) + val streams: State => Streams = mkStreams(units, baseUri, data) + + val structure: BuildStructure = + new BuildStructure(units, baseUri, settings, data, index, streams, delegates, scopeLocal) + + structure + } + + def query(setting: String): String = { + import sbt.protocol._ + val req: SettingQuery = protocol.SettingQuery(setting) + val rsp: SettingQueryResponse = server.SettingQuery.handleSettingQuery(req, structure) + val bytes: Array[Byte] = Serialization serializeEventMessage rsp + val payload: String = new String(bytes, java.nio.charset.StandardCharsets.UTF_8) + payload + } + + // -.- avoid specs2's ko/ok + import org.specs2.matcher.MatchResult + def qok(x: String, t: String): String => MatchResult[Any] = query(_) must_== """{"type":"SettingQuerySuccess","value":""" + x + ""","contentType":"""" + t + """"}""" + def qko(msg: String): String => MatchResult[Any] = query(_) must_== """{"type":"SettingQueryFailure","message":"""" + msg + """"}""" + + "setting query" should { + "t/scalaVersion" in qok("\"2.12.1\"", "java.lang.String") + "t/pollInterval" in qok("500", "Int") + "t/sourcesInBase" in qok("true", "Boolean") + "t/startYear" in qok("null", "scala.Option[Int]") + "t/scalaArtifacts" in qok("""["scala-library","scala-compiler","scala-reflect","scala-actors","scalap"]""", "scala.collection.Seq[java.lang.String]") + + "t/libraryDependencies" in qok( + """[{"organization":"org.scala-lang","name":"scala-library","revision":"2.12.1","isChanging":false,"isTransitive":true,"isForce":false,"crossVersion":{"type":"Disabled"}}]""", + "scala.collection.Seq[sbt.librarymanagement.ModuleID]") + + "scalaVersion" in qko("Not a valid project ID: scalaVersion\\nscalaVersion\\n ^") + "t/scalacOptions" in qko(s"Key {$baseUri}t/compile:scalacOptions is a task, can only query settings") + "t/fooo" in qko("Expected ':' (if selecting a configuration)\\nNot a valid key: fooo (similar: fork)\\nt/fooo\\n ^") + } +} diff --git a/project/ContrabandConfig.scala b/project/ContrabandConfig.scala index d57d87afa..7fb8a2b8e 100644 --- a/project/ContrabandConfig.scala +++ b/project/ContrabandConfig.scala @@ -22,6 +22,7 @@ object ContrabandConfig { case "Option" | "Set" | "scala.Vector" => { tpe => getFormats(oneArg(tpe)) } case "Map" | "Tuple2" | "scala.Tuple2" => { tpe => twoArgs(tpe).flatMap(getFormats) } case "Int" | "Long" => { _ => Nil } + case "scala.json.ast.unsafe.JValue" => { _ => "sbt.internal.JValueFormat" :: Nil } } /** Returns the list of formats required to encode the given `TpeRef`. */ diff --git a/project/Formatting.scala b/project/Formatting.scala index a760af123..30259e009 100644 --- a/project/Formatting.scala +++ b/project/Formatting.scala @@ -1,15 +1,13 @@ -import sbt._ -import sbt.Keys._ -import com.typesafe.sbt.SbtScalariform._ -import ScalariformKeys.{ format => scalariformFormat, preferences => scalariformPreferences } +import sbt._, Keys._ +import com.typesafe.sbt.SbtScalariform._, autoImport._ object Formatting { - lazy val BuildConfig = config("build") extend Compile - lazy val BuildSbtConfig = config("buildsbt") extend Compile + val BuildConfig = config("build") extend Compile + val BuildSbtConfig = config("buildsbt") extend Compile val scalariformCheck = taskKey[Unit]("Checks that the existing code is formatted, via git diff") - lazy val prefs: Seq[Setting[_]] = { + private val prefs: Seq[Setting[_]] = { import scalariform.formatter.preferences._ Seq( scalariformPreferences ~= (_ @@ -20,8 +18,8 @@ object Formatting { ) } - lazy val settings: Seq[Setting[_]] = Seq() ++ scalariformSettings ++ prefs - lazy val sbtFilesSettings: Seq[Setting[_]] = Seq() ++ scalariformSettings ++ prefs ++ + val settings: Seq[Setting[_]] = Seq() ++ prefs + val sbtFilesSettings: Seq[Setting[_]] = Seq() ++ prefs ++ inConfig(BuildConfig)(configScalariformSettings) ++ inConfig(BuildSbtConfig)(configScalariformSettings) ++ Seq( diff --git a/protocol/src/main/contraband-scala/sbt/protocol/SettingQueryFailure.scala b/protocol/src/main/contraband-scala/sbt/protocol/SettingQueryFailure.scala new file mode 100644 index 000000000..5369ddd9f --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/protocol/SettingQueryFailure.scala @@ -0,0 +1,32 @@ +/** + * This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.protocol +final class SettingQueryFailure private ( + val message: String) extends sbt.protocol.SettingQueryResponse() with Serializable { + + + + override def equals(o: Any): Boolean = o match { + case x: SettingQueryFailure => (this.message == x.message) + case _ => false + } + override def hashCode: Int = { + 37 * (17 + message.##) + } + override def toString: String = { + "SettingQueryFailure(" + message + ")" + } + protected[this] def copy(message: String = message): SettingQueryFailure = { + new SettingQueryFailure(message) + } + def withMessage(message: String): SettingQueryFailure = { + copy(message = message) + } +} +object SettingQueryFailure { + + def apply(message: String): SettingQueryFailure = new SettingQueryFailure(message) +} diff --git a/protocol/src/main/contraband-scala/sbt/protocol/SettingQueryResponse.scala b/protocol/src/main/contraband-scala/sbt/protocol/SettingQueryResponse.scala index 0c7878968..68ad82fee 100644 --- a/protocol/src/main/contraband-scala/sbt/protocol/SettingQueryResponse.scala +++ b/protocol/src/main/contraband-scala/sbt/protocol/SettingQueryResponse.scala @@ -4,29 +4,22 @@ // DO NOT EDIT MANUALLY package sbt.protocol -final class SettingQueryResponse private ( - val value: String) extends sbt.protocol.EventMessage() with Serializable { - - - - override def equals(o: Any): Boolean = o match { - case x: SettingQueryResponse => (this.value == x.value) - case _ => false - } - override def hashCode: Int = { - 37 * (17 + value.##) - } - override def toString: String = { - "SettingQueryResponse(" + value + ")" - } - protected[this] def copy(value: String = value): SettingQueryResponse = { - new SettingQueryResponse(value) - } - def withValue(value: String): SettingQueryResponse = { - copy(value = value) - } +abstract class SettingQueryResponse() extends sbt.protocol.EventMessage() with Serializable { + + + + +override def equals(o: Any): Boolean = o match { + case x: SettingQueryResponse => true + case _ => false +} +override def hashCode: Int = { + 17 +} +override def toString: String = { + "SettingQueryResponse()" +} } object SettingQueryResponse { - def apply(value: String): SettingQueryResponse = new SettingQueryResponse(value) } diff --git a/protocol/src/main/contraband-scala/sbt/protocol/SettingQuerySuccess.scala b/protocol/src/main/contraband-scala/sbt/protocol/SettingQuerySuccess.scala new file mode 100644 index 000000000..a53aeb41f --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/protocol/SettingQuerySuccess.scala @@ -0,0 +1,36 @@ +/** + * This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.protocol +final class SettingQuerySuccess private ( + val value: scala.json.ast.unsafe.JValue, + val contentType: String) extends sbt.protocol.SettingQueryResponse() with Serializable { + + + + override def equals(o: Any): Boolean = o match { + case x: SettingQuerySuccess => (this.value == x.value) && (this.contentType == x.contentType) + case _ => false + } + override def hashCode: Int = { + 37 * (37 * (17 + value.##) + contentType.##) + } + override def toString: String = { + "SettingQuerySuccess(" + value + ", " + contentType + ")" + } + protected[this] def copy(value: scala.json.ast.unsafe.JValue = value, contentType: String = contentType): SettingQuerySuccess = { + new SettingQuerySuccess(value, contentType) + } + def withValue(value: scala.json.ast.unsafe.JValue): SettingQuerySuccess = { + copy(value = value) + } + def withContentType(contentType: String): SettingQuerySuccess = { + copy(contentType = contentType) + } +} +object SettingQuerySuccess { + + def apply(value: scala.json.ast.unsafe.JValue, contentType: String): SettingQuerySuccess = new SettingQuerySuccess(value, contentType) +} diff --git a/protocol/src/main/contraband-scala/sbt/protocol/codec/EventMessageFormats.scala b/protocol/src/main/contraband-scala/sbt/protocol/codec/EventMessageFormats.scala index 41ffd6dc9..0727e822c 100644 --- a/protocol/src/main/contraband-scala/sbt/protocol/codec/EventMessageFormats.scala +++ b/protocol/src/main/contraband-scala/sbt/protocol/codec/EventMessageFormats.scala @@ -5,6 +5,6 @@ // DO NOT EDIT MANUALLY package sbt.protocol.codec import _root_.sjsonnew.{ deserializationError, serializationError, Builder, JsonFormat, Unbuilder } -trait EventMessageFormats { self: sjsonnew.BasicJsonProtocol with sbt.protocol.codec.ChannelAcceptedEventFormats with sbt.protocol.codec.LogEventFormats with sbt.protocol.codec.ExecStatusEventFormats with sbt.protocol.codec.SettingQueryResponseFormats => -implicit lazy val EventMessageFormat: JsonFormat[sbt.protocol.EventMessage] = flatUnionFormat4[sbt.protocol.EventMessage, sbt.protocol.ChannelAcceptedEvent, sbt.protocol.LogEvent, sbt.protocol.ExecStatusEvent, sbt.protocol.SettingQueryResponse]("type") +trait EventMessageFormats { self: sjsonnew.BasicJsonProtocol with sbt.protocol.codec.ChannelAcceptedEventFormats with sbt.protocol.codec.LogEventFormats with sbt.protocol.codec.ExecStatusEventFormats with sbt.internal.JValueFormat with sbt.protocol.codec.SettingQuerySuccessFormats with sbt.protocol.codec.SettingQueryFailureFormats => +implicit lazy val EventMessageFormat: JsonFormat[sbt.protocol.EventMessage] = flatUnionFormat5[sbt.protocol.EventMessage, sbt.protocol.ChannelAcceptedEvent, sbt.protocol.LogEvent, sbt.protocol.ExecStatusEvent, sbt.protocol.SettingQuerySuccess, sbt.protocol.SettingQueryFailure]("type") } diff --git a/protocol/src/main/contraband-scala/sbt/protocol/codec/JsonProtocol.scala b/protocol/src/main/contraband-scala/sbt/protocol/codec/JsonProtocol.scala index e2a840a52..f755ceb8c 100644 --- a/protocol/src/main/contraband-scala/sbt/protocol/codec/JsonProtocol.scala +++ b/protocol/src/main/contraband-scala/sbt/protocol/codec/JsonProtocol.scala @@ -11,7 +11,10 @@ trait JsonProtocol extends sjsonnew.BasicJsonProtocol with sbt.protocol.codec.ChannelAcceptedEventFormats with sbt.protocol.codec.LogEventFormats with sbt.protocol.codec.ExecStatusEventFormats - with sbt.protocol.codec.SettingQueryResponseFormats + with sbt.internal.JValueFormat + with sbt.protocol.codec.SettingQuerySuccessFormats + with sbt.protocol.codec.SettingQueryFailureFormats with sbt.protocol.codec.EventMessageFormats + with sbt.protocol.codec.SettingQueryResponseFormats with sbt.protocol.codec.ExecutionEventFormats object JsonProtocol extends JsonProtocol \ No newline at end of file diff --git a/protocol/src/main/contraband-scala/sbt/protocol/codec/SettingQueryFailureFormats.scala b/protocol/src/main/contraband-scala/sbt/protocol/codec/SettingQueryFailureFormats.scala new file mode 100644 index 000000000..1ad39d383 --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/protocol/codec/SettingQueryFailureFormats.scala @@ -0,0 +1,27 @@ +/** + * This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.protocol.codec +import _root_.sjsonnew.{ deserializationError, serializationError, Builder, JsonFormat, Unbuilder } +trait SettingQueryFailureFormats { self: sjsonnew.BasicJsonProtocol => +implicit lazy val SettingQueryFailureFormat: JsonFormat[sbt.protocol.SettingQueryFailure] = new JsonFormat[sbt.protocol.SettingQueryFailure] { + override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.protocol.SettingQueryFailure = { + jsOpt match { + case Some(js) => + unbuilder.beginObject(js) + val message = unbuilder.readField[String]("message") + unbuilder.endObject() + sbt.protocol.SettingQueryFailure(message) + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.protocol.SettingQueryFailure, builder: Builder[J]): Unit = { + builder.beginObject() + builder.addField("message", obj.message) + builder.endObject() + } +} +} diff --git a/protocol/src/main/contraband-scala/sbt/protocol/codec/SettingQueryResponseFormats.scala b/protocol/src/main/contraband-scala/sbt/protocol/codec/SettingQueryResponseFormats.scala index ce7f7a217..9e0aebed9 100644 --- a/protocol/src/main/contraband-scala/sbt/protocol/codec/SettingQueryResponseFormats.scala +++ b/protocol/src/main/contraband-scala/sbt/protocol/codec/SettingQueryResponseFormats.scala @@ -5,23 +5,6 @@ // DO NOT EDIT MANUALLY package sbt.protocol.codec import _root_.sjsonnew.{ deserializationError, serializationError, Builder, JsonFormat, Unbuilder } -trait SettingQueryResponseFormats { self: sjsonnew.BasicJsonProtocol => -implicit lazy val SettingQueryResponseFormat: JsonFormat[sbt.protocol.SettingQueryResponse] = new JsonFormat[sbt.protocol.SettingQueryResponse] { - override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.protocol.SettingQueryResponse = { - jsOpt match { - case Some(js) => - unbuilder.beginObject(js) - val value = unbuilder.readField[String]("value") - unbuilder.endObject() - sbt.protocol.SettingQueryResponse(value) - case None => - deserializationError("Expected JsObject but found None") - } - } - override def write[J](obj: sbt.protocol.SettingQueryResponse, builder: Builder[J]): Unit = { - builder.beginObject() - builder.addField("value", obj.value) - builder.endObject() - } -} +trait SettingQueryResponseFormats { self: sbt.internal.JValueFormat with sjsonnew.BasicJsonProtocol with sbt.protocol.codec.SettingQuerySuccessFormats with sbt.protocol.codec.SettingQueryFailureFormats => +implicit lazy val SettingQueryResponseFormat: JsonFormat[sbt.protocol.SettingQueryResponse] = flatUnionFormat2[sbt.protocol.SettingQueryResponse, sbt.protocol.SettingQuerySuccess, sbt.protocol.SettingQueryFailure]("type") } diff --git a/protocol/src/main/contraband-scala/sbt/protocol/codec/SettingQuerySuccessFormats.scala b/protocol/src/main/contraband-scala/sbt/protocol/codec/SettingQuerySuccessFormats.scala new file mode 100644 index 000000000..431a23d71 --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/protocol/codec/SettingQuerySuccessFormats.scala @@ -0,0 +1,29 @@ +/** + * This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.protocol.codec +import _root_.sjsonnew.{ deserializationError, serializationError, Builder, JsonFormat, Unbuilder } +trait SettingQuerySuccessFormats { self: sbt.internal.JValueFormat with sjsonnew.BasicJsonProtocol => +implicit lazy val SettingQuerySuccessFormat: JsonFormat[sbt.protocol.SettingQuerySuccess] = new JsonFormat[sbt.protocol.SettingQuerySuccess] { + override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.protocol.SettingQuerySuccess = { + jsOpt match { + case Some(js) => + unbuilder.beginObject(js) + val value = unbuilder.readField[scala.json.ast.unsafe.JValue]("value") + val contentType = unbuilder.readField[String]("contentType") + unbuilder.endObject() + sbt.protocol.SettingQuerySuccess(value, contentType) + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.protocol.SettingQuerySuccess, builder: Builder[J]): Unit = { + builder.beginObject() + builder.addField("value", obj.value) + builder.addField("contentType", obj.contentType) + builder.endObject() + } +} +} diff --git a/protocol/src/main/contraband/server.contra b/protocol/src/main/contraband/server.contra index 44103c0c1..bf92437c4 100644 --- a/protocol/src/main/contraband/server.contra +++ b/protocol/src/main/contraband/server.contra @@ -40,8 +40,15 @@ type ExecStatusEvent implements EventMessage { commandQueue: [String] } -type SettingQueryResponse implements EventMessage { - value: String! +interface SettingQueryResponse implements EventMessage {} + +type SettingQuerySuccess implements SettingQueryResponse { + value: scala.json.ast.unsafe.JValue! + contentType: String! +} + +type SettingQueryFailure implements SettingQueryResponse { + message: String! } # enum Status { diff --git a/protocol/src/main/scala/sbt/internal/JValueFormat.scala b/protocol/src/main/scala/sbt/internal/JValueFormat.scala new file mode 100644 index 000000000..d6f2a6dc9 --- /dev/null +++ b/protocol/src/main/scala/sbt/internal/JValueFormat.scala @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2017 Lightbend Inc. + */ +package sbt +package internal + +import sjsonnew.{ JsonWriter => JW, JsonReader => JR, JsonFormat => JF, _ } +import scala.json.ast.unsafe._ + +trait JValueFormat { self: sjsonnew.BasicJsonProtocol => + implicit val JNullJF: JF[JNull.type] = new JF[JNull.type] { + def write[J](x: JNull.type, b: Builder[J]) = b.writeNull() + def read[J](j: Option[J], u: Unbuilder[J]) = JNull + } + + implicit val JBooleanJF: JF[JBoolean] = project(_.get, JBoolean(_)) + implicit val JStringJF: JF[JString] = project(_.value, JString(_)) + implicit val JNumberJF: JF[JNumber] = project(x => BigDecimal(x.value), (x: BigDecimal) => JNumber(x.toString)) + implicit val JArrayJF: JF[JArray] = project[JArray, Array[JValue]](_.value, JArray(_)) + + implicit lazy val JObjectJW: JW[JObject] = new JW[JObject] { + def write[J](x: JObject, b: Builder[J]) = { + b.beginObject() + x.value foreach (jsonField => JValueJW.addField(jsonField.field, jsonField.value, b)) + b.endObject() + } + } + + implicit lazy val JValueJW: JW[JValue] = new JW[JValue] { + def write[J](x: JValue, b: Builder[J]) = x match { + case x: JNull.type => JNullJF.write(x, b) + case x: JBoolean => JBooleanJF.write(x, b) + case x: JString => JStringJF.write(x, b) + case x: JNumber => JNumberJF.write(x, b) + case x: JArray => JArrayJF.write(x, b) + case x: JObject => JObjectJW.write(x, b) + } + } + + implicit lazy val JValueJR: JR[JValue] = new JR[JValue] { + def read[J](j: Option[J], u: Unbuilder[J]) = ??? // Is this even possible? with no Manifest[J]? + } + + implicit lazy val JValueJF: JF[JValue] = jsonFormat[JValue](JValueJR, JValueJW) +}