Skip to content

Commit d1f7a11

Browse files
authored
Merge pull request #5133 from dotty-staging/launchIDE-no-connection
sbt-dotty: Make launchIDE work offline
2 parents 999d1cb + 1073a79 commit d1f7a11

File tree

2 files changed

+63
-18
lines changed

2 files changed

+63
-18
lines changed

project/Build.scala

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import xerial.sbt.pack.PackPlugin
1616
import xerial.sbt.pack.PackPlugin.autoImport._
1717

1818
import dotty.tools.sbtplugin.DottyPlugin.autoImport._
19-
import dotty.tools.sbtplugin.DottyIDEPlugin.{ prepareCommand, runProcess }
19+
import dotty.tools.sbtplugin.DottyIDEPlugin.{ installCodeExtension, prepareCommand, runProcess }
2020
import dotty.tools.sbtplugin.DottyIDEPlugin.autoImport._
2121

2222
import sbtbuildinfo.BuildInfoPlugin
@@ -56,7 +56,7 @@ object Build {
5656

5757
val sbtDottyName = "sbt-dotty"
5858
val sbtDottyVersion = {
59-
val base = "0.2.4"
59+
val base = "0.2.5"
6060
if (isRelease) base else base + "-SNAPSHOT"
6161
}
6262

@@ -988,27 +988,27 @@ object Build {
988988
val coursier = workingDir / "out" / "coursier"
989989
val packageJson = workingDir / "package.json"
990990
if (!coursier.exists || packageJson.lastModified > coursier.lastModified)
991-
runProcess(Seq("npm", "install"), wait = true, directory = workingDir)
991+
runProcess(Seq("npm", "install"), wait = true, directory = Some(workingDir))
992992
val tsc = workingDir / "node_modules" / ".bin" / "tsc"
993993
runProcess(Seq(tsc.getAbsolutePath, "--pretty", "--project", workingDir.getAbsolutePath), wait = true)
994994

995995
// Currently, vscode-dotty depends on daltonjorge.scala for syntax highlighting,
996996
// this is not automatically installed when starting the extension in development mode
997997
// (--extensionDevelopmentPath=...)
998-
runProcess(codeCommand.value ++ Seq("--install-extension", "daltonjorge.scala"), wait = true)
998+
installCodeExtension(codeCommand.value, "daltonjorge.scala")
999999

10001000
sbt.internal.inc.Analysis.Empty
10011001
}.dependsOn(managedResources in Compile).value,
10021002
sbt.Keys.`package`:= {
1003-
runProcess(Seq("vsce", "package"), wait = true, directory = baseDirectory.value)
1003+
runProcess(Seq("vsce", "package"), wait = true, directory = Some(baseDirectory.value))
10041004

10051005
baseDirectory.value / s"dotty-${version.value}.vsix"
10061006
},
10071007
unpublish := {
1008-
runProcess(Seq("vsce", "unpublish"), wait = true, directory = baseDirectory.value)
1008+
runProcess(Seq("vsce", "unpublish"), wait = true, directory = Some(baseDirectory.value))
10091009
},
10101010
publish := {
1011-
runProcess(Seq("vsce", "publish"), wait = true, directory = baseDirectory.value)
1011+
runProcess(Seq("vsce", "publish"), wait = true, directory = Some(baseDirectory.value))
10121012
},
10131013
run := Def.inputTask {
10141014
val inputArgs = spaceDelimited("<arg>").parsed

sbt-dotty/src/dotty/tools/sbtplugin/DottyIDEPlugin.scala

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import sbt.Def.Initialize
55
import sbt.Keys._
66
import java.io._
77
import java.lang.ProcessBuilder
8+
import java.lang.ProcessBuilder.Redirect
89
import scala.collection.mutable
910
import scala.util.Properties.{ isWin, isMac }
1011

@@ -146,23 +147,66 @@ object DottyIDEPlugin extends AutoPlugin {
146147
if (isWin) Seq("cmd.exe", "/C") ++ cmd
147148
else cmd
148149

149-
/** Run `cmd`.
150-
* @param wait If true, wait for `cmd` to return and throw an exception if the exit code is non-zero.
151-
* @param directory If not null, run `cmd` in this directory.
150+
/** Run the command `cmd`.
151+
*
152+
* @param wait If true, wait for the command to return and throw an exception if the exit code is non-zero.
153+
* @param directory If not None, run the command in this directory.
154+
* @param outputCallback If not None, pass the command output to this callback instead of writing it to stdout.
152155
*/
153-
def runProcess(cmd: Seq[String], wait: Boolean = false, directory: File = null): Unit = {
154-
val pb = new ProcessBuilder(prepareCommand(cmd): _*).inheritIO()
155-
if (directory != null) pb.directory(directory)
156+
def runProcess(cmd: Seq[String], wait: Boolean = false, directory: Option[File] = None, outputCallback: Option[BufferedReader => Unit] = None): Unit = {
157+
val pb = new ProcessBuilder(prepareCommand(cmd): _*)
158+
159+
directory match {
160+
case Some(dir) =>
161+
pb.directory(dir)
162+
case None =>
163+
}
164+
165+
pb.redirectInput(Redirect.INHERIT)
166+
.redirectError(Redirect.INHERIT)
167+
.redirectOutput(
168+
outputCallback match {
169+
case Some(_) =>
170+
Redirect.PIPE
171+
case None =>
172+
Redirect.INHERIT
173+
})
174+
175+
val process = pb.start()
176+
outputCallback match {
177+
case Some(callback) =>
178+
callback(new BufferedReader(new InputStreamReader(process.getInputStream)))
179+
case None =>
180+
}
156181
if (wait) {
157-
val exitCode = pb.start().waitFor()
182+
val exitCode = process.waitFor()
158183
if (exitCode != 0) {
159184
val cmdString = cmd.mkString(" ")
160185
val description = if (directory != null) s""" in directory "$directory"""" else ""
161186
throw new MessageOnlyException(s"""Running command "${cmdString}"${description} failed.""")
162187
}
163188
}
164-
else
165-
pb.start()
189+
}
190+
191+
/** Install or upgrade Code extension `name`.
192+
*
193+
* We start by trying to install or upgrade the extension. If this fails we
194+
* check if an existing version of the extension exists. If this also fails
195+
* we throw an exception. This ensures that we're always running the latest
196+
* version of the extension but that we can still work offline.
197+
*/
198+
def installCodeExtension(codeCmd: Seq[String], name: String): Unit = {
199+
try {
200+
runProcess(codeCmd ++ Seq("--install-extension", name), wait = true)
201+
} catch {
202+
case e: Exception =>
203+
var alreadyInstalled: Boolean = false
204+
runProcess(codeCmd ++ Seq("--list-extensions"), wait = true, outputCallback = Some({ br =>
205+
alreadyInstalled = br.lines.filter(_ == name).findFirst.isPresent
206+
}))
207+
if (!alreadyInstalled)
208+
throw e
209+
}
166210
}
167211

168212
private val projectConfig = taskKey[Option[ProjectConfig]]("")
@@ -270,8 +314,9 @@ object DottyIDEPlugin extends AutoPlugin {
270314

271315
runCode := {
272316
try {
273-
runProcess(codeCommand.value ++ Seq("--install-extension", "lampepfl.dotty"), wait = true)
274-
runProcess(codeCommand.value ++ Seq("."), directory = baseDirectory.value)
317+
installCodeExtension(codeCommand.value, "lampepfl.dotty")
318+
319+
runProcess(codeCommand.value ++ Seq("."), directory = Some(baseDirectory.value))
275320
} catch {
276321
case ioex: IOException if ioex.getMessage.startsWith("""Cannot run program "code"""") =>
277322
val log = streams.value.log

0 commit comments

Comments
 (0)