summaryrefslogtreecommitdiff
path: root/contrib/bsp/src/mill/contrib/BSP.scala
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/bsp/src/mill/contrib/BSP.scala')
-rw-r--r--contrib/bsp/src/mill/contrib/BSP.scala137
1 files changed, 137 insertions, 0 deletions
diff --git a/contrib/bsp/src/mill/contrib/BSP.scala b/contrib/bsp/src/mill/contrib/BSP.scala
new file mode 100644
index 00000000..f214ed9a
--- /dev/null
+++ b/contrib/bsp/src/mill/contrib/BSP.scala
@@ -0,0 +1,137 @@
+package mill.contrib
+
+import java.io.PrintWriter
+import java.nio.file.FileAlreadyExistsException
+import java.util.concurrent.Executors
+
+import ch.epfl.scala.bsp4j._
+import mill._
+import mill.define.{Command, Discover, ExternalModule}
+import mill.eval.Evaluator
+import org.eclipse.lsp4j.jsonrpc.Launcher
+import upickle.default._
+
+import scala.collection.JavaConverters._
+import scala.concurrent.CancellationException
+
+case class BspConfigJson(name: String,
+ argv: Seq[String],
+ version: String,
+ bspVersion: String,
+ languages: Seq[String])
+ extends BspConnectionDetails(name, argv.asJava, version, bspVersion, languages.asJava) {
+}
+
+object BspConfigJson {
+ implicit val rw: ReadWriter[BspConfigJson] = macroRW
+}
+
+object BSP extends ExternalModule {
+
+ implicit def millScoptEvaluatorReads[T] = new mill.main.EvaluatorScopt[T]()
+
+ lazy val millDiscover: Discover[BSP.this.type] = Discover[this.type]
+ val version = "1.0.0"
+ val bspProtocolVersion = "2.0.0"
+ val languages = List("scala", "java")
+
+ /**
+ * Installs the mill-bsp server. It creates a json file
+ * with connection details in the ./.bsp directory for
+ * a potential client to find.
+ *
+ * If a .bsp folder with a connection file already
+ * exists in the working directory, it will be
+ * overwritten and a corresponding message will be displayed
+ * in stdout.
+ *
+ * If the creation of the .bsp folder fails due to any other
+ * reason, the message and stacktrace of the exception will be
+ * printed to stdout.
+ *
+ */
+ def install(ev: Evaluator): Command[Unit] = T.command {
+ val bspDirectory = os.pwd / ".bsp"
+ if (!os.exists(bspDirectory)) os.makeDir.all(bspDirectory)
+ try {
+ os.write(bspDirectory / "mill.json", createBspConnectionJson())
+ } catch {
+ case e: FileAlreadyExistsException =>
+ println("The bsp connection json file probably exists already - will be overwritten")
+ os.remove(bspDirectory / "mill.json")
+ os.write(bspDirectory / "mill.json", createBspConnectionJson())
+ case e: Exception =>
+ println("An exception occurred while installing mill-bsp")
+ e.printStackTrace()
+ }
+
+ }
+
+ // creates a Json with the BSP connection details
+ def createBspConnectionJson(): String = {
+ val millPath = scala.sys.props.get("MILL_CLASSPATH").getOrElse(System.getProperty("MILL_CLASSPATH"))
+ val millVersion = scala.sys.props.get("MILL_VERSION").getOrElse(System.getProperty("MILL_VERSION"))
+ write(BspConfigJson("mill-bsp",
+ List(whichJava,
+ s"-DMILL_CLASSPATH=$millPath",
+ s"-DMILL_VERSION=$millVersion",
+ "-Djna.nosys=true",
+ "-cp",
+ millPath,
+ "mill.MillMain",
+ "mill.contrib.BSP/start"),
+ version,
+ bspProtocolVersion,
+ languages))
+ }
+
+ // computes the path to the java executable
+ def whichJava: String = {
+ if (scala.sys.props.contains("JAVA_HOME")) scala.sys.props("JAVA_HOME") else "java"
+ }
+
+ /**
+ * Computes a mill command which starts the mill-bsp
+ * server and establishes connection to client. Waits
+ * until a client connects and ends the connection
+ * after the client sent an "exit" notification
+ *
+ * @param ev Environment, used by mill to evaluate commands
+ * @return: mill.Command which executes the starting of the
+ * server
+ */
+ def start(ev: Evaluator): Command[Unit] = T.command {
+ val eval = new Evaluator(ev.home, ev.outPath, ev.externalOutPath, ev.rootModule, ev.log, ev.classLoaderSig,
+ ev.workerCache, ev.env, false)
+ val millServer = new mill.contrib.bsp.MillBuildServer(eval, bspProtocolVersion, version, languages)
+ val executor = Executors.newCachedThreadPool()
+
+ val stdin = System.in
+ val stdout = System.out
+ try {
+ val launcher = new Launcher.Builder[BuildClient]()
+ .setOutput(stdout)
+ .setInput(stdin)
+ .setLocalService(millServer)
+ .setRemoteInterface(classOf[BuildClient]).
+ traceMessages(new PrintWriter((os.pwd / "bsp.log").toIO))
+ .setExecutorService(executor)
+ .create()
+ millServer.onConnectWithClient(launcher.getRemoteProxy)
+ val listening = launcher.startListening()
+ millServer.cancelator = () => listening.cancel(true)
+ val voidFuture = listening.get()
+ } catch {
+ case _: CancellationException => System.err.println("The mill server was shut down.")
+ case e: Exception =>
+ System.err.println("An exception occured while connecting to the client.")
+ System.err.println("Cause: " + e.getCause)
+ System.err.println("Message: " + e.getMessage)
+ System.err.println("Exception class: " + e.getClass)
+ System.err.println("Stack Trace: " + e.getStackTrace)
+ } finally {
+ System.err.println("Shutting down executor")
+ executor.shutdown()
+ }
+ }
+}