Merge pull request #4131 from eed3si9n/wip/merge-1.1.x

Merge 1.1.x
This commit is contained in:
eugene yokota 2018-04-30 16:51:34 -04:00 committed by GitHub
commit 36c3215320
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 1078 additions and 800 deletions

View File

@ -239,6 +239,8 @@ lazy val testingProj = (project in file("testing"))
exclude[DirectMissingMethodProblem]("sbt.protocol.testing.TestItemEvent.copy$default$*"),
exclude[DirectMissingMethodProblem]("sbt.protocol.testing.TestStringEvent.copy"),
exclude[DirectMissingMethodProblem]("sbt.protocol.testing.TestStringEvent.copy$default$1"),
//no reason to use
exclude[DirectMissingMethodProblem]("sbt.JUnitXmlTestsListener.testSuite"),
)
)
.configure(addSbtIO, addSbtCompilerClasspath, addSbtUtilLogging)
@ -357,6 +359,9 @@ lazy val actionsProj = (project in file("main-actions"))
exclude[MissingClassProblem]("sbt.Doc$Scaladoc"),
// Removed no longer used private[sbt] method
exclude[DirectMissingMethodProblem]("sbt.Doc.generate"),
exclude[DirectMissingMethodProblem]("sbt.compiler.Eval.filesModifiedBytes"),
exclude[DirectMissingMethodProblem]("sbt.compiler.Eval.fileModifiedBytes"),
),
)
.configure(
@ -564,13 +569,15 @@ lazy val sbtProj = (project in file("sbt"))
BuildInfoPlugin.buildInfoDefaultSettings,
buildInfoObject in Test := "TestBuildInfo",
buildInfoKeys in Test := Seq[BuildInfoKey](
version,
// WORKAROUND https://github.com/sbt/sbt-buildinfo/issues/117
BuildInfoKey.map((fullClasspath in Compile).taskValue) { case (ident, cp) => ident -> cp.files },
classDirectory in Compile,
classDirectory in Test,
),
connectInput in run in Test := true,
outputStrategy in run in Test := Some(StdoutOutput),
fork in Test := true,
parallelExecution in Test := false,
Test / run / connectInput := true,
Test / run / outputStrategy := Some(StdoutOutput),
Test / run / fork := true,
)
.configure(addSbtCompilerBridge)

View File

@ -9,8 +9,8 @@ package sbt
import java.io.File
import sbt.internal.inc.AnalyzingCompiler
import sbt.internal.util.JLine
import sbt.util.Logger
import xsbti.compile.{ Inputs, Compilers }
import scala.util.Try
@ -46,11 +46,13 @@ final class Console(compiler: AnalyzingCompiler) {
)(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)
JLine.usingTerminal { t =>
t.init
Run.executeTrapExit(console0, log)
}
}
}
object Console {
def apply(conf: Inputs): Console =
conf.compilers match {

View File

@ -15,9 +15,10 @@ import ast.parser.Tokens
import reporters.{ ConsoleReporter, Reporter }
import scala.reflect.internal.util.{ AbstractFileClassLoader, BatchSourceFile }
import Tokens.{ EOF, NEWLINE, NEWLINES, SEMI }
import java.io.File
import java.io.{ File, FileNotFoundException }
import java.nio.ByteBuffer
import java.net.URLClassLoader
import java.security.MessageDigest
import Eval.{ getModule, getValue, WrapValName }
import sbt.io.{ DirectoryFilter, FileFilter, GlobFilter, Hash, IO, Path }
@ -171,17 +172,31 @@ final class 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
)
)
)
// This is a hot path.
val digester = MessageDigest.getInstance("SHA")
content foreach { c =>
digester.update(bytes(c))
}
backing foreach { x =>
digester.update(fileExistsBytes(x))
}
options foreach { o =>
digester.update(bytes(o))
}
classpath foreach { f =>
fileModifiedHash(f, digester)
}
imports.strings.map(_._1) foreach { x =>
digester.update(bytes(x))
}
tpeName foreach { x =>
digester.update(bytes(x))
}
digester.update(bytes(ev.extraHash))
val d = digester.digest()
val hash = Hash.toHex(d)
val moduleName = makeModuleName(hash)
lazy val unit = {
@ -514,13 +529,26 @@ 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 fileModifiedBytes(f: File): Array[Byte] =
(if (f.isDirectory)
filesModifiedBytes(f listFiles classDirFilter)
else
bytes(IO.getModifiedTimeOrZero(f))) ++ bytes(f.getAbsolutePath)
// fileModifiedBytes is a hot method, taking up 0.85% of reload time
// This is a procedural version
def fileModifiedHash(f: File, digester: MessageDigest): Unit = {
if (f.isDirectory)
(f listFiles classDirFilter) foreach { x =>
fileModifiedHash(x, digester)
} else digester.update(bytes(getModifiedTimeOrZero(f)))
digester.update(bytes(f.getAbsolutePath))
}
// This uses NIO instead of the JNA-based IO.getModifiedTimeOrZero for speed
def getModifiedTimeOrZero(f: File): Long =
try {
sbt.io.JavaMilli.getModifiedTime(f.getPath)
} catch {
case _: FileNotFoundException => 0L
}
def fileExistsBytes(f: File): Array[Byte] =
bytes(f.exists) ++
bytes(f.getAbsolutePath)

View File

@ -142,14 +142,14 @@ object Watched {
AttributeKey[Watched]("watched-configuration", "Configures continuous execution.")
def createWatchService(): WatchService = {
def closeWatch = new MacOSXWatchService()
sys.props.get("sbt.watch.mode") match {
case Some("polling") =>
new PollingWatchService(PollDelay)
case Some("nio") =>
FileSystems.getDefault.newWatchService()
case _ if Properties.isMac =>
// WatchService is slow on macOS - use old polling mode
new PollingWatchService(PollDelay)
case Some("closewatch") => closeWatch
case _ if Properties.isMac => closeWatch
case _ =>
FileSystems.getDefault.newWatchService()
}

View File

@ -91,12 +91,12 @@ object TaskMacro {
final val InputTaskCreateDynName = "createDyn"
final val InputTaskCreateFreeName = "createFree"
final val append1Migration =
"`<+=` operator is removed. Try `lhs += { x.value }`\n or see http://www.scala-sbt.org/1.x/docs/Migrating-from-sbt-013x.html#Migrating+from+sbt+0.12+style."
"`<+=` operator is removed. Try `lhs += { x.value }`\n or see http://www.scala-sbt.org/1.x/docs/Migrating-from-sbt-013x.html."
final val appendNMigration =
"`<++=` operator is removed. Try `lhs ++= { x.value }`\n or see http://www.scala-sbt.org/1.x/docs/Migrating-from-sbt-013x.html#Migrating+from+sbt+0.12+style."
"`<++=` operator is removed. Try `lhs ++= { x.value }`\n or see http://www.scala-sbt.org/1.x/docs/Migrating-from-sbt-013x.html."
final val assignMigration =
"""`<<=` operator is removed. Use `key := { x.value }` or `key ~= (old => { newValue })`.
|See http://www.scala-sbt.org/1.x/docs/Migrating-from-sbt-013x.html#Migrating+from+sbt+0.12+style""".stripMargin
|See http://www.scala-sbt.org/1.x/docs/Migrating-from-sbt-013x.html""".stripMargin
import LinterDSL.{ Empty => EmptyLinter }

View File

@ -25,6 +25,6 @@ object TestUtil {
val mainClassesDir = buildinfo.TestBuildInfo.classDirectory
val testClassesDir = buildinfo.TestBuildInfo.test_classDirectory
val depsClasspath = buildinfo.TestBuildInfo.dependencyClasspath
mainClassesDir +: testClassesDir +: depsClasspath mkString ":"
mainClassesDir +: testClassesDir +: depsClasspath mkString java.io.File.pathSeparator
}
}

View File

@ -167,9 +167,7 @@ object Defaults extends BuildCommon {
artifactClassifier in packageSrc :== Some(SourceClassifier),
artifactClassifier in packageDoc :== Some(DocClassifier),
includeFilter :== NothingFilter,
includeFilter in unmanagedSources :== ("*.java" | "*.scala") && new SimpleFileFilter(
_.isFile
),
includeFilter in unmanagedSources :== ("*.java" | "*.scala") -- DirectoryFilter,
includeFilter in unmanagedJars :== "*.jar" | "*.so" | "*.dll" | "*.jnilib" | "*.zip",
includeFilter in unmanagedResources :== AllPassFilter,
bgList := { bgJobService.value.jobs },
@ -1440,10 +1438,8 @@ object Defaults extends BuildCommon {
val sc = (scalacOptions in task).value
val ic = (initialCommands in task).value
val cc = (cleanupCommands in task).value
JLine.usingTerminal { _ =>
(new Console(compiler))(cpFiles, sc, loader, ic, cc)()(s.log).get
println()
}
(new Console(compiler))(cpFiles, sc, loader, ic, cc)()(s.log).get
println()
}
private[this] def exported(w: PrintWriter, command: String): Seq[String] => Unit =
@ -2269,6 +2265,7 @@ object Classpaths {
).withScalaOrganization(scalaOrganization.value)
)
},
dependencyResolution := IvyDependencyResolution(ivyConfiguration.value),
updateSbtClassifiers in TaskGlobal := (Def.task {
val lm = dependencyResolution.value
val s = streams.value
@ -2281,6 +2278,9 @@ object Classpaths {
val log = s.log
val out = is.withIvy(log)(_.getSettings.getDefaultIvyUserDir)
val uwConfig = (unresolvedWarningConfiguration in update).value
val depDir = dependencyCacheDirectory.value
val ivy = scalaModuleInfo.value
val st = state.value
withExcludes(out, mod.classifiers, lock(app)) {
excludes =>
// val noExplicitCheck = ivy.map(_.withCheckExplicit(false))

View File

@ -65,7 +65,7 @@ private[sbt] object LanguageServerProtocol {
else throw LangServerError(ErrorCodes.InvalidRequest, "invalid token")
} else ()
setInitialized(true)
appendExec(Exec(s"collectAnalyses", Some(r.id), Some(CommandSource(name))))
appendExec(Exec(s"collectAnalyses", None, Some(CommandSource(name))))
jsonRpcRespond(InitializeResult(serverCapabilities), Option(r.id))
case r: JsonRpcRequestMessage if r.method == "textDocument/definition" =>

View File

@ -16,10 +16,10 @@ import Prop._
import Gen._
object Delegates extends Properties("delegates") {
property("generate non-empty configs") = forAll { (c: Seq[Configuration]) =>
property("generate non-empty configs") = forAll { (c: Vector[Configuration]) =>
c.nonEmpty
}
property("generate non-empty tasks") = forAll { (t: Seq[Taskk]) =>
property("generate non-empty tasks") = forAll { (t: Vector[Taskk]) =>
t.nonEmpty
}
@ -46,6 +46,7 @@ object Delegates extends Properties("delegates") {
global forall { _ == Zero }
}
}
property("Initial scope present with all combinations of Global axes") = allAxes(
(s, ds, _) => globalCombinations(s, ds)
)
@ -55,12 +56,58 @@ object Delegates extends Properties("delegates") {
ds.head == scope
}
}
property("global scope last") = forAll { (keys: Keys) =>
allDelegates(keys) { (_, ds) =>
ds.last == Scope.GlobalScope
}
}
property("Project axis delegates to BuildRef then Zero") = forAll { (keys: Keys) =>
allDelegates(keys) { (key, ds) =>
key.project match {
case Zero => true // filtering out of testing
case Select(ProjectRef(uri, _)) =>
val buildScoped = key.copy(project = Select(BuildRef(uri)))
val idxKey = ds.indexOf(key)
val idxB = ds.indexOf(buildScoped)
val z = key.copy(project = Zero)
val idxZ = ds.indexOf(z)
if (z == Scope.GlobalScope) true
else {
(s"idxKey = $idxKey; idxB = $idxB; idxZ = $idxZ") |:
(idxKey < idxB) && (idxB < idxZ)
}
case Select(BuildRef(_)) =>
ds.indexOf(key) < ds.indexOf(key.copy(project = Zero))
}
}
}
property("Config axis delegates to parent configuration") = forAll { (keys: Keys) =>
allDelegates(keys) { (key, ds) =>
key.config match {
case Zero => true
case Select(config) if key.project.isSelect =>
val p = key.project.toOption.get
val r = keys.env.resolve(p)
val proj = keys.env.projectFor(r)
val inh: Vector[ConfigKey] = keys.env.inheritConfig(r, config)
val conf = proj.confMap(config.name)
if (inh.isEmpty) true
else {
val idxKey = ds.indexOf(key)
val parent = inh.head
val a = key.copy(config = Select(parent))
val idxA = ds.indexOf(a)
(s"idxKey = $idxKey; a = $a; idxA = $idxA") |:
idxKey < idxA
}
case _ => true
}
}
}
def allAxes(f: (Scope, Seq[Scope], Scope => ScopeAxis[_]) => Prop): Prop = forAll {
(keys: Keys) =>
allDelegates(keys) { (s, ds) =>

View File

@ -119,7 +119,7 @@ abstract class TestBuild {
(taskAxes, zero.toSet, single.toSet, multi.toSet)
}
}
final class Env(val builds: Seq[Build], val tasks: Seq[Taskk]) {
final class Env(val builds: Vector[Build], val tasks: Vector[Taskk]) {
override def toString =
"Env:\n " + " Tasks:\n " + tasks.mkString("\n ") + "\n" + builds.mkString("\n ")
val root = builds.head
@ -135,14 +135,15 @@ abstract class TestBuild {
def inheritConfig(ref: ResolvedReference, config: ConfigKey) =
projectFor(ref).confMap(config.name).extendsConfigs map toConfigKey
def inheritTask(task: AttributeKey[_]) = taskMap.get(task) match {
case None => Nil; case Some(t) => t.delegates map getKey
case None => Vector()
case Some(t) => t.delegates.toVector map getKey
}
def inheritProject(ref: ProjectRef) = project(ref).delegates
def inheritProject(ref: ProjectRef) = project(ref).delegates.toVector
def resolve(ref: Reference) = Scope.resolveReference(root.uri, rootProject, ref)
lazy val delegates: Scope => Seq[Scope] =
Scope.delegates(
allProjects,
(_: Proj).configurations.map(toConfigKey),
(_: Proj).configurations.toVector.map(toConfigKey),
resolve,
uri => buildMap(uri).root.id,
inheritProject,
@ -267,13 +268,14 @@ abstract class TestBuild {
implicit lazy val uriGen: Gen[URI] = for (sch <- idGen; ssp <- idGen; frag <- optIDGen)
yield new URI(sch, ssp, frag.orNull)
implicit def envGen(implicit bGen: Gen[Build], tasks: Gen[Seq[Taskk]]): Gen[Env] =
for (i <- MaxBuildsGen; bs <- listOfN(i, bGen); ts <- tasks) yield new Env(bs, ts)
implicit def envGen(implicit bGen: Gen[Build], tasks: Gen[Vector[Taskk]]): Gen[Env] =
for (i <- MaxBuildsGen; bs <- containerOfN[Vector, Build](i, bGen); ts <- tasks)
yield new Env(bs, ts)
implicit def buildGen(implicit uGen: Gen[URI], pGen: URI => Gen[Seq[Proj]]): Gen[Build] =
for (u <- uGen; ps <- pGen(u)) yield new Build(u, ps)
def nGen[T](igen: Gen[Int])(implicit g: Gen[T]): Gen[List[T]] = igen flatMap { ig =>
listOfN(ig, g)
def nGen[T](igen: Gen[Int])(implicit g: Gen[T]): Gen[Vector[T]] = igen flatMap { ig =>
containerOfN[Vector, T](ig, g)
}
implicit def genProjects(build: URI)(
@ -294,7 +296,7 @@ abstract class TestBuild {
implicit genName: Gen[String],
maxDeps: Gen[Int],
count: Gen[Int]
): Gen[Seq[Configuration]] =
): Gen[Vector[Configuration]] =
genAcyclicDirect[Configuration, String](maxDeps, genName, count)(
(key, deps) =>
Configuration
@ -302,30 +304,36 @@ abstract class TestBuild {
.withExtendsConfigs(deps.toVector)
)
def genTasks(implicit genName: Gen[String], maxDeps: Gen[Int], count: Gen[Int]): Gen[Seq[Taskk]] =
def genTasks(
implicit genName: Gen[String],
maxDeps: Gen[Int],
count: Gen[Int]
): Gen[Vector[Taskk]] =
genAcyclicDirect[Taskk, String](maxDeps, genName, count)(
(key, deps) => new Taskk(AttributeKey[String](key), deps)
)
def genAcyclicDirect[A, T](maxDeps: Gen[Int], keyGen: Gen[T], max: Gen[Int])(
make: (T, Seq[A]) => A
): Gen[Seq[A]] =
make: (T, Vector[A]) => A
): Gen[Vector[A]] =
genAcyclic[A, T](maxDeps, keyGen, max) { t =>
Gen.const { deps =>
make(t, deps)
make(t, deps.toVector)
}
}
def genAcyclic[A, T](maxDeps: Gen[Int], keyGen: Gen[T], max: Gen[Int])(
make: T => Gen[Seq[A] => A]
): Gen[Seq[A]] =
make: T => Gen[Vector[A] => A]
): Gen[Vector[A]] =
max flatMap { count =>
listOfN(count, keyGen) flatMap { keys =>
containerOfN[Vector, T](count, keyGen) flatMap { keys =>
genAcyclic(maxDeps, keys.distinct)(make)
}
}
def genAcyclic[A, T](maxDeps: Gen[Int], keys: List[T])(make: T => Gen[Seq[A] => A]): Gen[Seq[A]] =
genAcyclic(maxDeps, keys, Nil) flatMap { pairs =>
def genAcyclic[A, T](maxDeps: Gen[Int], keys: Vector[T])(
make: T => Gen[Vector[A] => A]
): Gen[Vector[A]] =
genAcyclic(maxDeps, keys, Vector()) flatMap { pairs =>
sequence(pairs.map { case (key, deps) => mapMake(key, deps, make) }) flatMap { inputs =>
val made = new collection.mutable.HashMap[T, A]
for ((key, deps, mk) <- inputs)
@ -334,27 +342,27 @@ abstract class TestBuild {
}
}
def mapMake[A, T](key: T, deps: Seq[T], make: T => Gen[Seq[A] => A]): Gen[Inputs[A, T]] =
make(key) map { (mk: Seq[A] => A) =>
def mapMake[A, T](key: T, deps: Vector[T], make: T => Gen[Vector[A] => A]): Gen[Inputs[A, T]] =
make(key) map { (mk: Vector[A] => A) =>
(key, deps, mk)
}
def genAcyclic[T](
maxDeps: Gen[Int],
names: List[T],
acc: List[Gen[(T, Seq[T])]]
): Gen[Seq[(T, Seq[T])]] =
names: Vector[T],
acc: Vector[Gen[(T, Vector[T])]]
): Gen[Vector[(T, Vector[T])]] =
names match {
case Nil => sequence(acc)
case x :: xs =>
case Vector() => sequence(acc)
case Vector(x, xs @ _*) =>
val next = for (depCount <- maxDeps; d <- pick(depCount min xs.size, xs))
yield (x, d.toList)
genAcyclic(maxDeps, xs, next :: acc)
yield (x, d.toVector)
genAcyclic(maxDeps, xs.toVector, next +: acc)
}
def sequence[T](gs: Seq[Gen[T]]): Gen[Seq[T]] = Gen.parameterized { prms =>
def sequence[T](gs: Vector[Gen[T]]): Gen[Vector[T]] = Gen.parameterized { prms =>
delay(gs map { g =>
g(prms, seed) getOrElse sys.error("failed generator")
})
}
type Inputs[A, T] = (T, Seq[T], Seq[A] => A)
type Inputs[A, T] = (T, Vector[T], Vector[A] => A)
}

View File

@ -4,14 +4,14 @@ import sbt.contraband.ContrabandPlugin.autoImport._
object Dependencies {
// WARNING: Please Scala update versions in PluginCross.scala too
val scala212 = "2.12.4"
val scala212 = "2.12.5"
val baseScalaVersion = scala212
// sbt modules
private val ioVersion = "1.1.4"
private val ioVersion = "1.1.6"
private val utilVersion = "1.1.3"
private val lmVersion = "1.1.4"
private val zincVersion = "1.1.3"
private val zincVersion = "1.1.5"
private val sbtIO = "org.scala-sbt" %% "io" % ioVersion
@ -26,8 +26,9 @@ object Dependencies {
private val libraryManagementCore = "org.scala-sbt" %% "librarymanagement-core" % lmVersion
private val libraryManagementIvy = "org.scala-sbt" %% "librarymanagement-ivy" % lmVersion
val launcherInterface = "org.scala-sbt" % "launcher-interface" % "1.0.3"
val rawLauncher = "org.scala-sbt" % "launcher" % "1.0.3"
val launcherVersion = "1.0.4"
val launcherInterface = "org.scala-sbt" % "launcher-interface" % launcherVersion
val rawLauncher = "org.scala-sbt" % "launcher" % launcherVersion
val testInterface = "org.scala-sbt" % "test-interface" % "1.0"
val ipcSocket = "org.scala-sbt.ipcsocket" % "ipcsocket" % "1.0.0"

View File

@ -48,8 +48,7 @@ object Util {
"-Yno-adapted-args",
"-Ywarn-dead-code",
"-Ywarn-numeric-widen",
//"-Ywarn-value-discard",
"-Ywarn-unused",
"-Ywarn-unused:-patvars,-implicits,_",
"-Ywarn-unused-import"
)
}),

View File

@ -2,7 +2,7 @@ scalaVersion := "2.12.4"
scalacOptions ++= Seq("-feature", "-language:postfixOps")
addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.2.0")
addSbtPlugin("org.scala-sbt" % "sbt-houserules" % "0.3.5")
addSbtPlugin("org.scala-sbt" % "sbt-houserules" % "0.3.6")
addSbtPlugin("org.scala-sbt" % "sbt-contraband" % "0.4.0")
addSbtPlugin("de.heikoseeberger" % "sbt-header" % "3.0.2")
addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.8.0")

View File

@ -1 +1,9 @@
scalaVersion := "2.11.11"
ivyConfiguration := {
throw new RuntimeException("updateSbtClassifiers should use updateSbtClassifiers / ivyConfiguration")
}
dependencyResolution := {
throw new RuntimeException("updateSbtClassifiers should use updateSbtClassifiers / dependencyResolution")
}

View File

@ -1,23 +1,24 @@
import sbt.internal.ServerHandler
// import sbt.internal.ServerHandler
lazy val root = (project in file("."))
.settings(
Global / serverLog / logLevel := Level.Debug,
// custom handler
Global / serverHandlers += ServerHandler({ callback =>
import callback._
import sjsonnew.BasicJsonProtocol._
import sbt.internal.protocol.JsonRpcRequestMessage
ServerIntent(
{
case r: JsonRpcRequestMessage if r.method == "lunar/helo" =>
jsonRpcNotify("lunar/oleh", "")
()
},
PartialFunction.empty
)
}),
// Global / serverHandlers += ServerHandler({ callback =>
// import callback._
// import sjsonnew.BasicJsonProtocol._
// import sbt.internal.protocol.JsonRpcRequestMessage
// ServerIntent(
// {
// case r: JsonRpcRequestMessage if r.method == "lunar/helo" =>
// jsonRpcNotify("lunar/oleh", "")
// ()
// },
// PartialFunction.empty
// )
// }),
name := "handshake",
scalaVersion := "2.12.3",
)

View File

@ -7,14 +7,34 @@
package sbt
import scala.util.Try
import sbt.util.LogExchange
import scala.annotation.tailrec
import buildinfo.TestBuildInfo
import xsbti._
object RunFromSourceMain {
private val sbtVersion = "1.1.0" // "dev"
private val sbtVersion = "1.1.4" // TestBuildInfo.version
private val scalaVersion = "2.12.4"
def fork(workingDirectory: File): Try[Unit] = {
val fo = ForkOptions()
.withOutputStrategy(OutputStrategy.StdoutOutput)
fork(fo, workingDirectory)
}
def fork(fo0: ForkOptions, workingDirectory: File): Try[Unit] = {
val fo = fo0
.withWorkingDirectory(workingDirectory)
implicit val runner = new ForkRun(fo)
val cp = {
TestBuildInfo.test_classDirectory +: TestBuildInfo.fullClasspath
}
val options = Vector(workingDirectory.toString)
val log = LogExchange.logger("RunFromSourceMain.fork", None, None)
Run.run("sbt.RunFromSourceMain", cp, options, log)
}
def main(args: Array[String]): Unit = args match {
case Array() => sys.error(s"Must specify working directory as the first argument")
case Array(wd, args @ _*) => run(file(wd), args)

View File

@ -1,206 +0,0 @@
/*
* sbt
* Copyright 2011 - 2017, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under BSD-3-Clause license (see LICENSE)
*/
package sbt
import org.scalatest._
import scala.concurrent._
import scala.annotation.tailrec
import java.io.{ InputStream, OutputStream }
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.{ ThreadFactory, ThreadPoolExecutor }
import sbt.protocol.ClientSocket
class ServerSpec extends AsyncFlatSpec with Matchers {
import ServerSpec._
"server" should "start" in {
withBuildSocket("handshake") { (out, in, tkn) =>
writeLine(
"""{ "jsonrpc": "2.0", "id": 3, "method": "sbt/setting", "params": { "setting": "handshake/name" } }""",
out
)
Thread.sleep(100)
assert(waitFor(in, 10) { s =>
s contains """"id":3"""
})
}
}
@tailrec
private[this] def waitFor(in: InputStream, num: Int)(f: String => Boolean): Boolean = {
if (num < 0) false
else
readFrame(in) match {
case Some(x) if f(x) => true
case _ =>
waitFor(in, num - 1)(f)
}
}
}
object ServerSpec {
private val serverTestBase: File = new File(".").getAbsoluteFile / "sbt" / "src" / "server-test"
private val nextThreadId = new AtomicInteger(1)
private val threadGroup = Thread.currentThread.getThreadGroup()
val readBuffer = new Array[Byte](4096)
var buffer: Vector[Byte] = Vector.empty
var bytesRead = 0
private val delimiter: Byte = '\n'.toByte
private val RetByte = '\r'.toByte
private val threadFactory = new ThreadFactory() {
override def newThread(runnable: Runnable): Thread = {
val thread =
new Thread(
threadGroup,
runnable,
s"sbt-test-server-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
}
}
private val executor = new ThreadPoolExecutor(
0, /* corePoolSize */
1, /* maxPoolSize, max # of servers */
2,
java.util.concurrent.TimeUnit.SECONDS,
/* keep alive unused threads this long (if corePoolSize < maxPoolSize) */
new java.util.concurrent.SynchronousQueue[Runnable](),
threadFactory
)
def backgroundRun(baseDir: File, args: Seq[String]): Unit = {
executor.execute(new Runnable {
def run(): Unit = {
RunFromSourceMain.run(baseDir, args)
}
})
}
def shutdown(): Unit = executor.shutdown()
def withBuildSocket(
testBuild: String
)(f: (OutputStream, InputStream, Option[String]) => Future[Assertion]): Future[Assertion] = {
IO.withTemporaryDirectory { temp =>
IO.copyDirectory(serverTestBase / testBuild, temp / testBuild)
withBuildSocket(temp / testBuild)(f)
}
}
def sendJsonRpc(message: String, out: OutputStream): Unit = {
writeLine(s"""Content-Length: ${message.size + 2}""", out)
writeLine("", out)
writeLine(message, out)
}
def readFrame(in: InputStream): Option[String] = {
val l = contentLength(in)
println(l)
readLine(in)
readLine(in)
readContentLength(in, l)
}
def contentLength(in: InputStream): Int = {
readLine(in) map { line =>
line.drop(16).toInt
} getOrElse (0)
}
def readLine(in: InputStream): Option[String] = {
if (buffer.isEmpty) {
val bytesRead = try {
in.read(readBuffer)
} catch {
case _: java.io.IOException => 0
}
if (bytesRead > 0) {
buffer = buffer ++ readBuffer.toVector.take(bytesRead)
}
}
val delimPos = buffer.indexOf(delimiter)
if (delimPos > 0) {
val chunk0 = buffer.take(delimPos)
buffer = buffer.drop(delimPos + 1)
// remove \r at the end of line.
val chunk1 = if (chunk0.lastOption contains RetByte) chunk0.dropRight(1) else chunk0
Some(new String(chunk1.toArray, "utf-8"))
} else None // no EOL yet, so skip this turn.
}
def readContentLength(in: InputStream, length: Int): Option[String] = {
if (buffer.isEmpty) {
val bytesRead = in.read(readBuffer)
if (bytesRead > 0) {
buffer = buffer ++ readBuffer.toVector.take(bytesRead)
}
}
if (length <= buffer.size) {
val chunk = buffer.take(length)
buffer = buffer.drop(length)
Some(new String(chunk.toArray, "utf-8"))
} else None // have not read enough yet, so skip this turn.
}
def writeLine(s: String, out: OutputStream): Unit = {
def writeEndLine(): Unit = {
val retByte: Byte = '\r'.toByte
val delimiter: Byte = '\n'.toByte
out.write(retByte.toInt)
out.write(delimiter.toInt)
out.flush
}
if (s != "") {
out.write(s.getBytes("UTF-8"))
}
writeEndLine
}
def withBuildSocket(
baseDirectory: File
)(f: (OutputStream, InputStream, Option[String]) => Future[Assertion]): Future[Assertion] = {
backgroundRun(baseDirectory, Nil)
val portfile = baseDirectory / "project" / "target" / "active.json"
def waitForPortfile(n: Int): Unit =
if (portfile.exists) ()
else {
if (n <= 0) sys.error(s"Timeout. $portfile is not found.")
else {
println(s" waiting for $portfile...")
Thread.sleep(1000)
waitForPortfile(n - 1)
}
}
waitForPortfile(20)
val (sk, tkn) = ClientSocket.socket(portfile)
val out = sk.getOutputStream
val in = sk.getInputStream
sendJsonRpc(
"""{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "initializationOptions": { } } }""",
out
)
try {
f(out, in, tkn)
} finally {
sendJsonRpc(
"""{ "jsonrpc": "2.0", "id": 9, "method": "sbt/exec", "params": { "commandLine": "exit" } }""",
out
)
// shutdown()
}
}
}

View File

@ -0,0 +1,192 @@
/*
* sbt
* Copyright 2011 - 2017, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under BSD-3-Clause license (see LICENSE)
*/
package testpkg
import org.scalatest._
import scala.concurrent._
import scala.annotation.tailrec
import sbt.protocol.ClientSocket
import TestServer.withTestServer
import java.io.File
import sbt.io.syntax._
import sbt.io.IO
import sbt.RunFromSourceMain
class ServerSpec extends AsyncFreeSpec with Matchers {
"server" - {
"should start" in withTestServer("handshake") { p =>
p.writeLine(
"""{ "jsonrpc": "2.0", "id": "3", "method": "sbt/setting", "params": { "setting": "root/name" } }"""
)
assert(p.waitForString(10) { s =>
s contains """"id":"3""""
})
}
"return number id when number id is sent" in withTestServer("handshake") { p =>
p.writeLine(
"""{ "jsonrpc": "2.0", "id": 3, "method": "sbt/setting", "params": { "setting": "root/name" } }"""
)
assert(p.waitForString(10) { s =>
s contains """"id":3"""
})
}
}
}
object TestServer {
private val serverTestBase: File = new File(".").getAbsoluteFile / "sbt" / "src" / "server-test"
def withTestServer(testBuild: String)(f: TestServer => Future[Assertion]): Future[Assertion] = {
IO.withTemporaryDirectory { temp =>
IO.copyDirectory(serverTestBase / testBuild, temp / testBuild)
withTestServer(temp / testBuild)(f)
}
}
def withTestServer(baseDirectory: File)(f: TestServer => Future[Assertion]): Future[Assertion] = {
val testServer = TestServer(baseDirectory)
try {
f(testServer)
} finally {
testServer.bye()
}
}
def hostLog(s: String): Unit = {
println(s"""[${scala.Console.MAGENTA}build-1${scala.Console.RESET}] $s""")
}
}
case class TestServer(baseDirectory: File) {
import TestServer.hostLog
val readBuffer = new Array[Byte](4096)
var buffer: Vector[Byte] = Vector.empty
var bytesRead = 0
private val delimiter: Byte = '\n'.toByte
private val RetByte = '\r'.toByte
hostLog("fork to a new sbt instance")
import scala.concurrent.ExecutionContext.Implicits.global
Future {
RunFromSourceMain.fork(baseDirectory)
()
}
lazy val portfile = baseDirectory / "project" / "target" / "active.json"
hostLog("wait 30s until the server is ready to respond")
def waitForPortfile(n: Int): Unit =
if (portfile.exists) ()
else {
if (n <= 0) sys.error(s"Timeout. $portfile is not found.")
else {
Thread.sleep(1000)
waitForPortfile(n - 1)
}
}
waitForPortfile(30)
// make connection to the socket described in the portfile
val (sk, tkn) = ClientSocket.socket(portfile)
val out = sk.getOutputStream
val in = sk.getInputStream
// initiate handshake
sendJsonRpc(
"""{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "initializationOptions": { } } }"""
)
def test(f: TestServer => Future[Assertion]): Future[Assertion] = {
f(this)
}
def bye(): Unit = {
hostLog("sending exit")
sendJsonRpc(
"""{ "jsonrpc": "2.0", "id": 9, "method": "sbt/exec", "params": { "commandLine": "exit" } }"""
)
}
def sendJsonRpc(message: String): Unit = {
writeLine(s"""Content-Length: ${message.size + 2}""")
writeLine("")
writeLine(message)
}
def writeLine(s: String): Unit = {
def writeEndLine(): Unit = {
val retByte: Byte = '\r'.toByte
val delimiter: Byte = '\n'.toByte
out.write(retByte.toInt)
out.write(delimiter.toInt)
out.flush
}
if (s != "") {
out.write(s.getBytes("UTF-8"))
}
writeEndLine
}
def readFrame: Option[String] = {
def getContentLength: Int = {
readLine map { line =>
line.drop(16).toInt
} getOrElse (0)
}
val l = getContentLength
readLine
readLine
readContentLength(l)
}
@tailrec
final def waitForString(num: Int)(f: String => Boolean): Boolean = {
if (num < 0) false
else
readFrame match {
case Some(x) if f(x) => true
case _ =>
waitForString(num - 1)(f)
}
}
def readLine: Option[String] = {
if (buffer.isEmpty) {
val bytesRead = in.read(readBuffer)
if (bytesRead > 0) {
buffer = buffer ++ readBuffer.toVector.take(bytesRead)
}
}
val delimPos = buffer.indexOf(delimiter)
if (delimPos > 0) {
val chunk0 = buffer.take(delimPos)
buffer = buffer.drop(delimPos + 1)
// remove \r at the end of line.
val chunk1 = if (chunk0.lastOption contains RetByte) chunk0.dropRight(1) else chunk0
Some(new String(chunk1.toArray, "utf-8"))
} else None // no EOL yet, so skip this turn.
}
def readContentLength(length: Int): Option[String] = {
if (buffer.isEmpty) {
val bytesRead = in.read(readBuffer)
if (bytesRead > 0) {
buffer = buffer ++ readBuffer.toVector.take(bytesRead)
}
}
if (length <= buffer.size) {
val chunk = buffer.take(length)
buffer = buffer.drop(length)
Some(new String(chunk.toArray, "utf-8"))
} else None // have not read enough yet, so skip this turn.
}
}

View File

@ -17,7 +17,8 @@ import java.util.concurrent.{
CompletionService => JCompletionService,
Executor,
Executors,
ExecutorCompletionService
ExecutorCompletionService,
RejectedExecutionException,
}
object CompletionService {
@ -33,7 +34,9 @@ object CompletionService {
def take() = completion.take().get()
}
def submit[T](work: () => T, completion: JCompletionService[T]): () => T = {
val future = completion.submit { new Callable[T] { def call = work() } }
val future = try completion.submit { new Callable[T] { def call = work() } } catch {
case _: RejectedExecutionException => throw Incomplete(None, message = Some("cancelled"))
}
() =>
future.get()
}

View File

@ -8,7 +8,7 @@
package sbt
import sbt.internal.util.ErrorHandling.wideConvert
import sbt.internal.util.{ DelegatingPMap, PMap, RMap, IDSet, ~> }
import sbt.internal.util.{ DelegatingPMap, IDSet, PMap, RMap, ~> }
import sbt.internal.util.Types._
import Execute._
@ -82,6 +82,7 @@ private[sbt] final class Execute[A[_] <: AnyRef](
def run[T](root: A[T])(implicit strategy: Strategy): Result[T] =
try { runKeep(root)(strategy)(root) } catch { case i: Incomplete => Inc(i) }
def runKeep[T](root: A[T])(implicit strategy: Strategy): RMap[A, Result] = {
assert(state.isEmpty, "Execute already running/ran.")

View File

@ -12,7 +12,6 @@ import java.net.InetAddress
import java.util.Hashtable
import scala.collection.mutable.ListBuffer
import scala.util.DynamicVariable
import scala.xml.{ Elem, Node => XNode, XML }
import testing.{
Event => TEvent,
@ -125,21 +124,28 @@ class JUnitXmlTestsListener(val outputDir: String) extends TestsListener {
}
/**The currently running test suite*/
val testSuite = new DynamicVariable(null: TestSuite)
private val testSuite = new InheritableThreadLocal[Option[TestSuite]] {
override def initialValue(): Option[TestSuite] = None
}
private def withTestSuite[T](f: TestSuite => T) =
testSuite.get().map(f).getOrElse(sys.error("no test suite"))
/**Creates the output Dir*/
override def doInit() = { targetDir.mkdirs(); () }
override def doInit() = {
val _ = targetDir.mkdirs()
}
/**
* Starts a new, initially empty Suite with the given name.
*/
override def startGroup(name: String): Unit = testSuite.value_=(new TestSuite(name))
override def startGroup(name: String): Unit = testSuite.set(Some(new TestSuite(name)))
/**
* Adds all details for the given even to the current suite.
*/
override def testEvent(event: TestEvent): Unit = for (e <- event.detail) {
testSuite.value.addEvent(e)
withTestSuite(_.addEvent(e))
}
/**
@ -175,7 +181,7 @@ class JUnitXmlTestsListener(val outputDir: String) extends TestsListener {
def selector = null
def throwable = new OptionalThrowable(t)
}
testSuite.value.addEvent(event)
withTestSuite(_.addEvent(event))
writeSuite()
}
@ -192,10 +198,11 @@ class JUnitXmlTestsListener(val outputDir: String) extends TestsListener {
private[this] def normalizeName(s: String) = s.replaceAll("""\s+""", "-")
private def writeSuite() = {
val file = new File(targetDir, s"${normalizeName(testSuite.value.name)}.xml").getAbsolutePath
val file = new File(targetDir, s"${normalizeName(withTestSuite(_.name))}.xml").getAbsolutePath
// TODO would be nice to have a logger and log this with level debug
// System.err.println("Writing JUnit XML test report: " + file)
XML.save(file, testSuite.value.stop(), "UTF-8", true, null)
XML.save(file, withTestSuite(_.stop()), "UTF-8", true, null)
testSuite.remove()
}
/**Does nothing, as we write each file after a suite is done.*/

File diff suppressed because it is too large Load Diff

View File

@ -52,7 +52,8 @@
"postinstall": "node ./node_modules/vscode/bin/install"
},
"dependencies": {
"vscode": "^1.1.5",
"vscode-languageclient": "^3.4.2"
"vscode": "^1.1.16",
"vscode-languageclient": "^3.4.2",
"hoek": "^4.2.1"
}
}

View File

@ -3,10 +3,30 @@
import * as path from 'path';
let fs = require('fs');
import * as vscode from 'vscode';
import { ExtensionContext, workspace } from 'vscode'; // workspace,
import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient';
export function activate(context: ExtensionContext) {
export async function activate(context: ExtensionContext) {
// Start sbt
const terminal = vscode.window.createTerminal(`sbt`);
terminal.show();
terminal.sendText("sbt");
function delay(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Wait for SBT server to start
let retries = 30;
while (retries > 0) {
retries--;
await delay(1000);
if (fs.existsSync(path.join(workspace.rootPath, 'project', 'target', 'active.json'))) {
break;
}
}
// The server is implemented in node
let serverModule = context.asAbsolutePath(path.join('server', 'server.js'));
// The debug options for the server

View File

@ -11,15 +11,15 @@
"dev": true
},
"@types/node": {
"version": "6.0.88",
"resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.88.tgz",
"integrity": "sha512-bYDPZTX0/s1aihdjLuAgogUAT5M+TpoWChEMea2p0yOcfn5bu3k6cJb9cp6nw268XeSNIGGr+4+/8V5K6BGzLQ==",
"version": "7.0.61",
"resolved": "https://registry.npmjs.org/@types/node/-/node-7.0.61.tgz",
"integrity": "sha512-X4MNN+Z36OmVPv7n08wxq46/t61/rauW4+xeyxGPueDQ9t7SetHnuEPS0p9n6wU/15HvJLGjzfLTc/RwN7id3A==",
"dev": true
},
"typescript": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.5.2.tgz",
"integrity": "sha1-A4qV99m7tCCxvzW6MdTFwd0//jQ=",
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.8.3.tgz",
"integrity": "sha512-K7g15Bb6Ra4lKf7Iq2l/I5/En+hLIHmxWZGq3D4DIRNFxMNV6j2SHSvDOqs2tGd4UvD/fJvrwopzQXjLrT7Itw==",
"dev": true
}
}

View File

@ -20,7 +20,7 @@
},
"devDependencies": {
"@types/mocha": "^2.2.42",
"@types/node": "^6.0.88",
"typescript": "^2.5.2"
"@types/node": "^7.0.10",
"typescript": "^2.7.2"
}
}

View File

@ -4,38 +4,40 @@
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"vscode-jsonrpc": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-3.4.0.tgz",
"integrity": "sha1-qpWsWDvzHYD3JdV8J8CfTCz+n6k="
},
"vscode-languageserver": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-3.4.2.tgz",
"integrity": "sha1-CMvlDuJpAdN91LXcUsJbkJNjwfE=",
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-3.5.1.tgz",
"integrity": "sha512-RYUKn0DgHTFcS8kS4VaNCjNMaQXYqiXdN9bKrFjXzu5RPKfjIYcoh47oVWwZj4L3R/DPB0Se7HPaDatvYY2XgQ==",
"requires": {
"vscode-languageserver-protocol": "3.4.1",
"vscode-uri": "1.0.1"
"vscode-languageserver-protocol": "3.5.1",
"vscode-uri": "1.0.3"
},
"dependencies": {
"vscode-jsonrpc": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-3.5.0.tgz",
"integrity": "sha1-hyOdnhZrLXNSJFuKgTWXgEwdY6o="
},
"vscode-languageserver-protocol": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.1.tgz",
"integrity": "sha512-1fPDIwsAv1difCV+8daOrJEGunClNJWqnUHq/ncWrjhitKWXgGmRCjlwZ3gDUTt54yRcvXz1PXJDaRNvNH6pYA==",
"requires": {
"vscode-jsonrpc": "3.5.0",
"vscode-languageserver-types": "3.5.0"
}
},
"vscode-languageserver-types": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0.tgz",
"integrity": "sha1-5I15li8LjgLelV4/UkkI4rGcA3Q="
}
}
},
"vscode-languageserver-protocol": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.4.1.tgz",
"integrity": "sha1-lrfIo+1opOvTCkB7BCenY3k1/h8=",
"requires": {
"vscode-jsonrpc": "3.4.0",
"vscode-languageserver-types": "3.4.0"
}
},
"vscode-languageserver-types": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.4.0.tgz",
"integrity": "sha1-UEOuR+5KwWrwe7PQylYSNeDA0vo="
},
"vscode-uri": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-1.0.1.tgz",
"integrity": "sha1-Eahr7+rDxKo+wIYjZRo8gabQu8g="
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-1.0.3.tgz",
"integrity": "sha1-Yxvb9xbcyrDmUpGo3CXCMjIIWlI="
}
}
}