mirror of https://github.com/sbt/sbt.git
commit
36c3215320
15
build.sbt
15
build.sbt
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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" =>
|
||||
|
|
|
|||
|
|
@ -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) =>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.")
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue