diff --git a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala index 20047277e..9cf2698ed 100644 --- a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala +++ b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala @@ -7,8 +7,9 @@ package server import java.net.{ Socket, SocketTimeoutException } import java.util.concurrent.atomic.AtomicBoolean -import sbt.protocol.{ Serialization, CommandMessage, ExecCommand, EventMessage } -import sjsonnew.JsonFormat +import scala.util.{ Left, Right } +import sbt.protocol._ +import sjsonnew._, LList.:*: final class NetworkChannel(val name: String, connection: Socket, state: State) extends CommandChannel { private val running = new AtomicBoolean(true) @@ -71,15 +72,136 @@ final class NetworkChannel(val name: String, connection: Socket, state: State) e } def onCommand(command: CommandMessage): Unit = command match { - case x: ExecCommand => onExecCommand(x) + case x: ExecCommand => onExecCommand(x) + case x: SettingQuery => onSettingQuery(x) } 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 extracted = Project extract state + val keys = Parser.parse(req.setting, Act aggregatedKeyParser extracted) + + def getSettingValue[A](key: Def.ScopedKey[A]) = + extracted.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 zeroValues: Either[Vector[String], Vector[Any]] = Right(Vector.empty) + def anyLeftsOrAllRights[A, B](acc: Either[Vector[A], Vector[B]], elem: Either[A, B]): Either[Vector[A], Vector[B]] = + (acc, elem) match { + case (Right(a), Right(x)) => Right(a :+ x) + case (Right(_), Left(x)) => Left(Vector(x)) + case (Left(a), Right(_)) => Left(a) + case (Left(a), Left(x)) => Left(a :+ x) + } + + val values = keys match { + case Left(msg) => Left(s"Invalid programmatic input:" +: (msg.lines.toVector map (" " + _))) + case Right(keys) => keys.map(getSettingValue(_)).foldLeft(zeroValues)(anyLeftsOrAllRights) + } + + val jsonValues = values match { + case Left(errors) => errors + case Right(values) => values map (_.toString) + } + + StandardMain.exchange publishEventMessage SettingQueryResponse(jsonValues) + } + def shutdown(): Unit = { println("Shutting down client connection") running.set(false) out.close() } } + +trait SettingQueryInstances { + import BasicJsonProtocol._ + + type SettingQueryRepr = String :*: LNil + implicit def settingQueryIso: IsoLList.Aux[SettingQuery, SettingQueryRepr] = LList.iso( + (x => "settingKey" -> x.setting :*: LNil), + (x => SettingQuery(x.head)) + ) + + import sbt.internal.util._ + + type AttrKeyRepr[A] = String :*: Manifest[A] :*: Option[String] :*: Vector[AttributeKey[_]] :*: Boolean :*: Int :*: LNil + + // FIXME: Can't go this IsoLList way because AttributeKey depends on AttributeKey (extend) + implicit def attrKeyIso[A]: IsoLList.Aux[AttributeKey[A], AttrKeyRepr[A]] = ??? + // LList.iso[AttributeKey[A], AttrKeyRepr[A]](attrKeyToRepr, attrKeyFromRepr) + + // def attrKeyToRepr[A](x: AttributeKey[A]): AttrKeyRepr[A] = ( + // /* */ "label" -> x.label /* */ :*: + // /**/ "manifest" -> x.manifest /* */ :*: + // /* */ "desc" -> x.description /* */ :*: + // /* */ "extend" -> x.extend.toVector /**/ :*: + // /* */ "isLocal" -> x.isLocal /* */ :*: + // /* */ "rank" -> x.rank /* */ :*: + // LNil + // ) + + def attrKeyFromRepr[A](x: AttrKeyRepr[A]): AttributeKey[A] = { + val LCons("label", label, + LCons("manifest", manifest, + LCons("desc", desc, + LCons("extend", extend, + LCons("isLabel", isLocal, + LCons("rank", rank, LNil) + ))))) = x + if (isLocal) AttributeKey.local[A](manifest) + else desc match { + case Some(desc) => AttributeKey(label, desc, extend, rank)(manifest) + case None => extend match { + case Seq() => AttributeKey(label, rank)(manifest) + case _ => + // With the given API it's not possible to create an AttributeKey + // which extends other attribute keys without having a description + // But that's not enforced in the data types. So default description to "" + AttributeKey(label, "", extend, rank)(manifest) + } + } + } + + // TODO: or use AttributeKey label? (String) + // implicit def attrMapFormat: JsonFormat[AttributeMap] = project[AttributeMap, Map[AttributeKey[_], Any]]( + // attrMap => attrMap.entries.iterator.map(x => x.key -> x.value).toMap, + // map => AttributeMap(map.iterator.map { case (k: AttributeKey[kt], v) => AttributeEntry(k, v.asInstanceOf[kt]) }.toSeq) + // ) + + implicit def scopeAxisIso[A](implicit z: JsonFormat[A]): JsonFormat[ScopeAxis[A]] = + new JsonFormat[ScopeAxis[A]] { + def write[J](obj: ScopeAxis[A], builder: Builder[J]): Unit = obj match { + case This => builder writeString "This" + case Global => builder writeString "Global" + case Select(s) => z.write(s, builder) + } + def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): ScopeAxis[A] = jsOpt match { + case None => deserializationError("Expected some JSON but found None") + case Some("This") => This + case Some("Global") => Global + case Some(_) => Select(z.read(jsOpt, unbuilder)) + } + } + + type ScopeRepr = ScopeAxis[Reference] :*: ScopeAxis[ConfigKey] :*: ScopeAxis[AttributeKey[_]] :*: ScopeAxis[AttributeMap] :*: LNil + // implicit def scopeIso: IsoLList.Aux[Scope, ScopeRepr] = LList.iso[Scope, ScopeRepr]( + // { x: Scope => "project" -> x.project :*: "config" -> x.config :*: "task" -> x.task :*: "extra" -> x.extra :*: LNil }, + // { x: ScopeRepr => Scope(x.head, x.tail.head, x.tail.tail.head, x.tail.tail.tail.head) } + // ) + + type SettingKeyRepr[A] = Scope :*: AttributeKey[A] :*: LNil + // implicit def settingKeyIso[A]: IsoLList.Aux[SettingKey[A], SettingKeyRepr[A]] = LList.iso( + // { x: SettingKey[A] => "scope" -> x.scope :*: "attrKey" -> x.key :*: LNil }, + // { x: SettingKeyRepr[A] => Scoped.scopedSetting(x.head, x.tail.head) } + // ) +} diff --git a/protocol/src/main/contraband-scala/sbt/protocol/SettingQuery.scala b/protocol/src/main/contraband-scala/sbt/protocol/SettingQuery.scala new file mode 100644 index 000000000..a0408cffb --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/protocol/SettingQuery.scala @@ -0,0 +1,32 @@ +/** + * This code is generated using sbt-datatype. + */ + +// DO NOT EDIT MANUALLY +package sbt.protocol +final class SettingQuery private ( + val setting: String) extends sbt.protocol.CommandMessage() with Serializable { + + + + override def equals(o: Any): Boolean = o match { + case x: SettingQuery => (this.setting == x.setting) + case _ => false + } + override def hashCode: Int = { + 37 * (17 + setting.##) + } + override def toString: String = { + "SettingQuery(" + setting + ")" + } + protected[this] def copy(setting: String = setting): SettingQuery = { + new SettingQuery(setting) + } + def withSetting(setting: String): SettingQuery = { + copy(setting = setting) + } +} +object SettingQuery { + + def apply(setting: String): SettingQuery = new SettingQuery(setting) +} diff --git a/protocol/src/main/contraband-scala/sbt/protocol/SettingQueryResponse.scala b/protocol/src/main/contraband-scala/sbt/protocol/SettingQueryResponse.scala new file mode 100644 index 000000000..f89b2b6a3 --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/protocol/SettingQueryResponse.scala @@ -0,0 +1,32 @@ +/** + * This code is generated using sbt-datatype. + */ + +// DO NOT EDIT MANUALLY +package sbt.protocol +final class SettingQueryResponse private ( + val values: Vector[String]) extends sbt.protocol.EventMessage() with Serializable { + + + + override def equals(o: Any): Boolean = o match { + case x: SettingQueryResponse => (this.values == x.values) + case _ => false + } + override def hashCode: Int = { + 37 * (17 + values.##) + } + override def toString: String = { + "SettingQueryResponse(" + values + ")" + } + protected[this] def copy(values: Vector[String] = values): SettingQueryResponse = { + new SettingQueryResponse(values) + } + def withValues(values: Vector[String]): SettingQueryResponse = { + copy(values = values) + } +} +object SettingQueryResponse { + + def apply(values: Vector[String]): SettingQueryResponse = new SettingQueryResponse(values) +} diff --git a/protocol/src/main/contraband-scala/sbt/protocol/codec/CommandMessageFormats.scala b/protocol/src/main/contraband-scala/sbt/protocol/codec/CommandMessageFormats.scala index 3a747c4e3..5dc704932 100644 --- a/protocol/src/main/contraband-scala/sbt/protocol/codec/CommandMessageFormats.scala +++ b/protocol/src/main/contraband-scala/sbt/protocol/codec/CommandMessageFormats.scala @@ -5,6 +5,6 @@ // DO NOT EDIT MANUALLY package sbt.protocol.codec import _root_.sjsonnew.{ deserializationError, serializationError, Builder, JsonFormat, Unbuilder } -trait CommandMessageFormats { self: sjsonnew.BasicJsonProtocol with sbt.protocol.codec.ExecCommandFormats => -implicit lazy val CommandMessageFormat: JsonFormat[sbt.protocol.CommandMessage] = flatUnionFormat1[sbt.protocol.CommandMessage, sbt.protocol.ExecCommand]("type") +trait CommandMessageFormats { self: sjsonnew.BasicJsonProtocol with sbt.protocol.codec.ExecCommandFormats with sbt.protocol.codec.SettingQueryFormats => +implicit lazy val CommandMessageFormat: JsonFormat[sbt.protocol.CommandMessage] = flatUnionFormat2[sbt.protocol.CommandMessage, sbt.protocol.ExecCommand, sbt.protocol.SettingQuery]("type") } 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 547cd7031..345b60771 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 => -implicit lazy val EventMessageFormat: JsonFormat[sbt.protocol.EventMessage] = flatUnionFormat3[sbt.protocol.EventMessage, sbt.protocol.ChannelAcceptedEvent, sbt.protocol.LogEvent, sbt.protocol.ExecStatusEvent]("type") +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") } 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 fa9fb28ac..7741b72e7 100644 --- a/protocol/src/main/contraband-scala/sbt/protocol/codec/JsonProtocol.scala +++ b/protocol/src/main/contraband-scala/sbt/protocol/codec/JsonProtocol.scala @@ -6,10 +6,12 @@ package sbt.protocol.codec trait JsonProtocol extends sjsonnew.BasicJsonProtocol with sbt.protocol.codec.ExecCommandFormats + with sbt.protocol.codec.SettingQueryFormats with sbt.protocol.codec.CommandMessageFormats with sbt.protocol.codec.ChannelAcceptedEventFormats with sbt.protocol.codec.LogEventFormats with sbt.protocol.codec.ExecStatusEventFormats + with sbt.protocol.codec.SettingQueryResponseFormats with sbt.protocol.codec.EventMessageFormats 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/SettingQueryFormats.scala b/protocol/src/main/contraband-scala/sbt/protocol/codec/SettingQueryFormats.scala new file mode 100644 index 000000000..774db9104 --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/protocol/codec/SettingQueryFormats.scala @@ -0,0 +1,27 @@ +/** + * This code is generated using sbt-datatype. + */ + +// DO NOT EDIT MANUALLY +package sbt.protocol.codec +import _root_.sjsonnew.{ deserializationError, serializationError, Builder, JsonFormat, Unbuilder } +trait SettingQueryFormats { self: sjsonnew.BasicJsonProtocol => +implicit lazy val SettingQueryFormat: JsonFormat[sbt.protocol.SettingQuery] = new JsonFormat[sbt.protocol.SettingQuery] { + override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.protocol.SettingQuery = { + jsOpt match { + case Some(js) => + unbuilder.beginObject(js) + val setting = unbuilder.readField[String]("setting") + unbuilder.endObject() + sbt.protocol.SettingQuery(setting) + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.protocol.SettingQuery, builder: Builder[J]): Unit = { + builder.beginObject() + builder.addField("setting", obj.setting) + 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 new file mode 100644 index 000000000..dfe1477fa --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/protocol/codec/SettingQueryResponseFormats.scala @@ -0,0 +1,27 @@ +/** + * This code is generated using sbt-datatype. + */ + +// 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 values = unbuilder.readField[Vector[String]]("values") + unbuilder.endObject() + sbt.protocol.SettingQueryResponse(values) + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.protocol.SettingQueryResponse, builder: Builder[J]): Unit = { + builder.beginObject() + builder.addField("values", obj.values) + builder.endObject() + } +} +} diff --git a/protocol/src/main/contraband/server.contra b/protocol/src/main/contraband/server.contra index 717b74d61..129f1bb91 100644 --- a/protocol/src/main/contraband/server.contra +++ b/protocol/src/main/contraband/server.contra @@ -13,6 +13,11 @@ type ExecCommand implements CommandMessage { execId: String @since("0.0.1") } +type SettingQuery implements CommandMessage { + setting: String! +} + + ## Message for events. interface EventMessage { } @@ -35,6 +40,10 @@ type ExecStatusEvent implements EventMessage { commandQueue: [String] } +type SettingQueryResponse implements EventMessage { + values: [String] +} + # enum Status { # Ready # Processing