Skip to content

Commit 05498c2

Browse files
committed
sbt-dotty: Make launchIDE work offline
For each Code extension we require, we start by trying to install or upgrade the extension. If this fails we check if an existing version of the extension exists. If this also fails we throw an exception. This ensures that we're always running the latest version of the extension but that we can still work offline.
1 parent a2a1112 commit 05498c2

File tree

2 files changed

+61
-16
lines changed

2 files changed

+61
-16
lines changed

project/Build.scala

Lines changed: 6 additions & 6 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
@@ -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: 55 additions & 10 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 = {
156+
def runProcess(cmd: Seq[String], wait: Boolean = false, directory: Option[File] = None, outputCallback: Option[BufferedReader => Unit] = None): Unit = {
154157
val pb = new ProcessBuilder(prepareCommand(cmd): _*).inheritIO()
155-
if (directory != null) pb.directory(directory)
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)