Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make BSP 1st class citizen #969

Merged
merged 2 commits into from
Oct 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,37 +1,22 @@
package mill.contrib
package mill.bsp

import ch.epfl.scala.bsp4j.BuildClient
import java.io.PrintWriter
import java.nio.file.FileAlreadyExistsException
import java.util.concurrent.Executors
import ch.epfl.scala.bsp4j._
import mill._
import mill.contrib.bsp.MillBuildServer
import mill.{BuildInfo, T}
import mill.bsp.{BspConfigJson, MillBuildServer}
import mill.define.{Command, Discover, ExternalModule}
import mill.eval.Evaluator
import mill.modules.Util
import org.eclipse.lsp4j.jsonrpc.Launcher
import upickle.default._
import scala.collection.JavaConverters._
import scala.concurrent.CancellationException
import scala.util.Try

case class BspConfigJson(
name: String,
argv: Seq[String],
millVersion: String,
bspVersion: String,
languages: Seq[String]
) extends BspConnectionDetails(name, argv.asJava, millVersion, bspVersion, languages.asJava)

object BspConfigJson {
implicit val rw: ReadWriter[BspConfigJson] = macroRW
}
import upickle.default.write

object BSP extends ExternalModule {

implicit def millScoptEvaluatorReads[T] = new mill.main.EvaluatorScopt[T]()

lazy val millDiscover: Discover[BSP.this.type] = Discover[this.type]
lazy val millDiscover: Discover[this.type] = Discover[this.type]
val bspProtocolVersion = "2.0.0"
val languages = Seq("scala", "java")

Expand Down Expand Up @@ -73,15 +58,16 @@ object BSP extends ExternalModule {
def createBspConnectionJson(): String = {
val millPath = sys.props
.get("java.class.path")
.getOrElse(throw new IllegalStateException("System property java.class.path not set"))
.getOrElse(throw new IllegalStateException(
"System property java.class.path not set"))

write(
BspConfigJson(
"mill-bsp",
Seq(
"sh",
"-c",
s"env ${sys.env.map { case (k, v) => s""""$k=$v"""" }.toSeq.mkString(" ")} $millPath -i mill.contrib.BSP/start"
s"env ${sys.env.map { case (k, v) => s""""$k=$v"""" }.toSeq.mkString(" ")} $millPath -i ${BSP.getClass.getCanonicalName.split("\\$").last}/start"
Copy link
Contributor

@ckipp01 ckipp01 Oct 28, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joan38 So I've started to play around a bit with this in Metals, and one thing that surprised me a bit was that this captures all env variables and adds them to the mill.json. My worry is, especially in Metals if we automatically generate this for a mill build is that a user may accidentally commit this. I've fairly positive this will happen. I already see it happening a bunch with the new .bsp/sbt.json files that sbt has been producing. Unknowingly I expect users will be committing this with env variables such as passwords etc that they may obviously not want committed.

In my experience with bsp discovery and the other build servers, I haven't seen this before. Is this a limitation of Mill, is this really necessary to include to start the server?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these env varibables explicitly set? Is it solving some known issue, e.g. a mill specific env varibale? In that case, we might better special handle a know case than bulk materializing a given state of env variables into this file.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We initially copied all the env vars to pick stuff such as COURSIER_REPOSITORY or JAVA_OPTS and make sure the BSP compilation is equal to a cmd compilation.
That can be reverted easily.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto #984

),
Util.millProperty("MILL_VERSION").getOrElse(BuildInfo.millVersion),
bspProtocolVersion,
Expand Down Expand Up @@ -113,7 +99,10 @@ object BSP extends ExternalModule {
ev.env,
false
)
val millServer = new MillBuildServer(evaluator, bspProtocolVersion, BuildInfo.millVersion)
val millServer = new MillBuildServer(
evaluator,
bspProtocolVersion,
BuildInfo.millVersion)
val executor = Executors.newCachedThreadPool()

val stdin = System.in
Expand Down
17 changes: 17 additions & 0 deletions bsp/src/mill/bsp/BspConfigJson.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package mill.bsp

import ch.epfl.scala.bsp4j.BspConnectionDetails
import scala.jdk.CollectionConverters._
import upickle.default._

case class BspConfigJson(
name: String,
argv: Seq[String],
millVersion: String,
bspVersion: String,
languages: Seq[String]
) extends BspConnectionDetails(name, argv.asJava, millVersion, bspVersion, languages.asJava)

object BspConfigJson {
implicit val rw: ReadWriter[BspConfigJson] = macroRW
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package mill.contrib.bsp
package mill.bsp

import java.io.File
import java.util.concurrent.ConcurrentHashMap
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package mill.contrib.bsp
package mill.bsp

import java.io.{PrintWriter, StringWriter}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package mill.contrib.bsp
package mill.bsp

import ch.epfl.scala.bsp4j._
import mill.api.Logger
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package mill.contrib.bsp
package mill.bsp

import ammonite.runtime.SpecialClassLoader
import ch.epfl.scala.bsp4j._
import com.google.gson.JsonObject
import java.util.concurrent.CompletableFuture
import mill._
import mill.api.{DummyTestReporter, Result, Strict}
import mill.contrib.bsp.ModuleUtils._
import mill.contrib.bsp.Utils._
import mill.bsp.ModuleUtils._
import mill.bsp.Utils._
import mill.define.Segment.Label
import mill.define.{Discover, ExternalModule}
import mill.eval.Evaluator
Expand Down Expand Up @@ -133,12 +133,9 @@ class MillBuildServer(evaluator: Evaluator, bspVersion: String, serverVersion: S

val items = dependencySourcesParams.getTargets.asScala
.foldLeft(Seq.empty[DependencySourcesItem]) { (items, targetId) =>
val all = if (targetId == millBuildTargetId) {
Try(getClass.getClassLoader.asInstanceOf[SpecialClassLoader]).fold(
_ => Seq.empty,
_.allJars.filter(url => isSourceJar(url) && exists(Path(url.getFile))).map(_.toURI.toString)
)
} else {
val all = if (targetId == millBuildTargetId)
getMillBuildClasspath(evaluator, source = true)
else {
val module = getModule(targetId, modules)
val sources = evaluateInformativeTask(
evaluator,
Expand Down Expand Up @@ -375,10 +372,7 @@ class MillBuildServer(evaluator: Evaluator, bspVersion: String, serverVersion: S
.foldLeft(Seq.empty[ScalacOptionsItem]) { (items, targetId) =>
val newItem =
if (targetId == millBuildTargetId) {
val classpath = Try(getClass.getClassLoader.asInstanceOf[SpecialClassLoader]).fold(
_ => Seq.empty,
_.allJars.filter(url => !isSourceJar(url) && exists(Path(url.getFile))).map(_.toURI.toString)
)
val classpath = getMillBuildClasspath(evaluator, source = false)
Some(new ScalacOptionsItem(
targetId,
Seq.empty.asJava,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package mill.contrib.bsp
package mill.bsp

import ammonite.runtime.SpecialClassLoader
import ch.epfl.scala.bsp4j._
import java.net.URL
import java.net.URLClassLoader
import mill._
import mill.api.Result.Success
import mill.api.{PathRef, Strict}
Expand All @@ -13,7 +15,9 @@ import mill.scalalib.api.Util
import mill.scalalib.{JavaModule, ScalaModule, TestModule}
import mill.scalanativelib._
import mill.util.Ctx
import os.{Path, exists}
import scala.collection.JavaConverters._
import scala.util.Try

/**
* Utilities for translating the mill build into
Expand Down Expand Up @@ -101,6 +105,17 @@ object ModuleUtils {
target
}

def getMillBuildClasspath(evaluator: Evaluator, source: Boolean): Seq[String] = {
val all = Try(evaluator.rootModule.getClass.getClassLoader.asInstanceOf[SpecialClassLoader]).fold(
_ => Seq.empty,
_.allJars
)
val filtered =
if (source) all.filter(url => isSourceJar(url))
else all.filter(url => !isSourceJar(url))
filtered.filter(url => exists(Path(url.getFile))).map(_.toURI.toString)
}

/**
* Compute the BuildTarget associated with the given mill
* JavaModule, which is any module present in the working
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package mill.contrib.bsp
package mill.bsp

import ch.epfl.scala.bsp4j.{BuildTargetIdentifier, CompileParams, RunParams, TestParams}

Expand Down Expand Up @@ -124,4 +124,4 @@ object TaskParameters {
def fromTestParams(testParams: TestParams): Parameters = {
TParams(testParams)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package mill.contrib.bsp
package mill.bsp

import ch.epfl.scala.bsp4j._
import mill._
import mill.api.Result.{Skipped, Success}
import mill.api.{BuildProblemReporter, Result}
import mill.contrib.bsp.ModuleUtils._
import mill.bsp.ModuleUtils._
import mill.eval.Evaluator
import mill.modules.Jvm
import mill.scalalib.Lib.discoverTests
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
package mill.contrib.bsp
package mill.bsp

import mill.util.ScriptTestSuite
import os._
import utest._

object BspInstallTests extends ScriptTestSuite(false) {
override def workspaceSlug: String = "gen-idea-hello-world"
override def scriptSourcePath: Path = os.pwd / "contrib" / "bsp" / "test" / "resources" / workspaceSlug
override def scriptSourcePath: Path = os.pwd / "bsp" / "test" / "resources" / workspaceSlug

def tests: Tests = Tests {
"BSP install" - {
val workspacePath = initWorkspace()
eval("mill.contrib.BSP/install")
eval("mill.bsp.BSP/install")

assert(exists(workspacePath / ".bsp" /"mill.json"))
assert(exists(workspacePath / ".bsp" / "mill.json"))
}
}
}
23 changes: 10 additions & 13 deletions build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,7 @@ object Deps {
def scalaReflect(scalaVersion: String) = ivy"org.scala-lang:scala-reflect:${scalaVersion}"
def scalacScoveragePlugin = ivy"org.scoverage::scalac-scoverage-plugin:1.4.1"
val sourcecode = ivy"com.lihaoyi::sourcecode:0.2.1"
val ujsonCirce = ivy"com.lihaoyi::ujson-circe:1.2.0"
val upickle = ivy"com.lihaoyi::upickle:1.2.0"
val upickle = ivy"com.lihaoyi::upickle:1.2.1"
val utest = ivy"com.lihaoyi::utest:0.7.4"
val zinc = ivy"org.scala-sbt::zinc:1.4.0-M1"
val bsp = ivy"ch.epfl.scala:bsp4j:2.0.0-M4"
Expand Down Expand Up @@ -480,16 +479,6 @@ object contrib extends MillModule {
def testArgs = T(scalanativelib.testArgs())
}

object bsp extends MillModule {

override def compileModuleDeps = Seq(scalalib, scalajslib, main, scalanativelib)
def ivyDeps = Agg(
Deps.bsp,
Deps.ujsonCirce,
Deps.sbtTestInterface
)
}

object artifactory extends MillModule {
override def compileModuleDeps = Seq(scalalib)
}
Expand Down Expand Up @@ -548,6 +537,14 @@ object scalanativelib extends MillModule {
}
}

object bsp extends MillModule {
override def compileModuleDeps = Seq(scalalib, scalajslib, scalanativelib)
def ivyDeps = Agg(
Deps.bsp,
Deps.sbtTestInterface
)
}

def testRepos = T{
Seq(
"MILL_ACYCLIC_REPO" ->
Expand Down Expand Up @@ -677,7 +674,7 @@ def launcherScript(shellJvmArgs: Seq[String],
}

object dev extends MillModule{
def moduleDeps = Seq(scalalib, scalajslib, scalanativelib)
def moduleDeps = Seq(scalalib, scalajslib, scalanativelib, bsp)


def forkArgs =
Expand Down
11 changes: 9 additions & 2 deletions docs/pages/1 - Intro to Mill.md
Original file line number Diff line number Diff line change
Expand Up @@ -701,9 +701,16 @@ mill mill.scalalib.Dependency/showUpdates
mill mill.scalalib.Dependency/showUpdates --allowPreRelease true # also show pre-release versions
```

## IntelliJ Support
## IDE Support

Mill supports IntelliJ by default. Use `mill mill.scalalib.GenIdea/idea` to
Mill supports any IDE that is compatible with [BSP](https://build-server-protocol.github.io/), such as IntelliJ.
Use `mill mill.bsp.BSP/install` to generate the BSP project config for your build.

It also enables Intellij to provide navigation & code-completion features within your build file itself.

## IntelliJ Support (legacy)

Mill supports IntelliJ configuration generation. Use `mill mill.scalalib.GenIdea/idea` to
generate an IntelliJ project config for your build.

This also configures IntelliJ to allow easy navigate & code-completion within
Expand Down
19 changes: 3 additions & 16 deletions docs/pages/9 - Contrib Modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,24 +175,11 @@ object project extends BuildInfo {

## BSP - Build Server Protocol

The contrib.bsp module was created in order to integrate the Mill build tool
with IntelliJ IDEA via the Build Server Protocol (BSP). It implements most of
the server side functionality described in BSP, and can therefore connect to a
BSP client, including the one behind IntelliJ IDEA. This allows a lot of mill
tasks to be executed from the IDE.

### Importing an existing mill project in IntelliJ via BSP

1) Add the following import statement in the `build.sc` of your project:

```scala
import $ivy.`com.lihaoyi::mill-contrib-bsp:$MILL_VERSION`
```

2) Run the following command in the working directory of your project:
The contrib.bsp module is now included in mill by default and will eventually replace GenIdea.

Run the following command in the working directory of your project to generate the BSP config:
```
mill mill.contrib.BSP/install
mill mill.bsp.BSP/install
```

### Known Issues:
Expand Down