summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexandra Dima <alexandra.dima@jetbrains.com>2019-06-21 18:39:34 +0200
committerSamvel Abrahamyan <samvel1024@gmail.com>2019-10-12 14:32:36 +0200
commit91bfa49804673bc337be002a537cb5d41b0c74ce (patch)
treeb6311674dc92c84d34c1a857e5aaea1a3f91c650
parenta6adf55cf37ff6aa05dd111a13595b7bd1b6cc92 (diff)
downloadmill-91bfa49804673bc337be002a537cb5d41b0c74ce.tar.gz
mill-91bfa49804673bc337be002a537cb5d41b0c74ce.tar.bz2
mill-91bfa49804673bc337be002a537cb5d41b0c74ce.zip
Started integrating mill with Build Server Protocol
-rw-r--r--.gitignore3
-rw-r--r--contrib/bsp/.bsp/mill-bsp.json1
-rw-r--r--contrib/bsp/src/mill/contrib/MainMillBuildServer.scala216
-rw-r--r--contrib/bsp/src/mill/contrib/bsp/MillBuildServer.scala303
-rw-r--r--contrib/bsp/src/mill/contrib/bsp/ModuleUtils.scala129
5 files changed, 652 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index d79d325d..53cea62c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,6 @@ output/
out/
/.bloop/
/.metals/
+contrib/bsp/mill-external-bs
+contrib/bsp/mill-out-bs
+mill.iml \ No newline at end of file
diff --git a/contrib/bsp/.bsp/mill-bsp.json b/contrib/bsp/.bsp/mill-bsp.json
new file mode 100644
index 00000000..880e7454
--- /dev/null
+++ b/contrib/bsp/.bsp/mill-bsp.json
@@ -0,0 +1 @@
+{"name":"mill-bsp","argv":["java","-DMILL_CLASSPATH=/usr/local/bin/mill","-DMILL_VERSION=0.4.0","-Djna.nosys=true","-cp","/usr/local/bin/mill","mill.MillMain mill.contrib.MainMillBuildServer/startServer"],"version":"1.0.0","bspVersion":"2.0.0-M4","languages":["scala","java"]} \ No newline at end of file
diff --git a/contrib/bsp/src/mill/contrib/MainMillBuildServer.scala b/contrib/bsp/src/mill/contrib/MainMillBuildServer.scala
new file mode 100644
index 00000000..6a1594ba
--- /dev/null
+++ b/contrib/bsp/src/mill/contrib/MainMillBuildServer.scala
@@ -0,0 +1,216 @@
+package mill.contrib
+
+import java.io.{BufferedReader, File, InputStreamReader}
+
+import play.api.libs.json._
+import java.nio.file.FileAlreadyExistsException
+import java.util.concurrent.{CompletableFuture, Executors, Future}
+
+import upickle.default._
+import ch.epfl.scala.bsp4j.{BspConnectionDetails, BuildClient}
+import mill._
+import mill.contrib.bsp.ModuleUtils
+import mill.define.{Discover, ExternalModule, Target, Task}
+import mill.eval.Evaluator
+import mill.scalalib._
+import mill.util.DummyLogger
+import org.eclipse.lsp4j.jsonrpc.{CompletableFutures, Launcher}
+import requests.Compress.None
+import upickle.default
+
+import scala.collection.JavaConverters._
+
+
+object MainMillBuildServer extends ExternalModule {
+
+ implicit def millScoptEvaluatorReads[T] = new mill.main.EvaluatorScopt[T]()
+
+ lazy val millDiscover: Discover[MainMillBuildServer.this.type] = Discover[this.type]
+ val version = "1.0.0"
+ val bspVersion = "2.0.0-M4"
+ val languages = List("scala", "java")
+
+ // returns the mill installation path in the user's system
+ def whichMill(): String = {
+ import sys.process._
+ "which mill"!!
+ }
+
+ // creates a Json with the BSP connection details
+ def createBspConnectionJson(): JsValue = {
+
+ implicit val connectionWrites = new Writes[BspConnectionDetails] {
+ def writes(connection: BspConnectionDetails) = Json.obj(
+ "name" -> connection.getName,
+ "argv" -> new JsArray(connection.getArgv.asScala.map(string => JsString(string)).toIndexedSeq),
+ "version" -> connection.getVersion,
+ "bspVersion" -> connection.getBspVersion,
+ "languages" -> new JsArray(connection.getLanguages.asScala.map(string => JsString(string)).toIndexedSeq)
+ )
+ }
+ val millPath = whichMill().replaceAll("\n", "")
+ Json.toJson(new BspConnectionDetails("mill-bsp",
+ List("java","-DMILL_CLASSPATH=" + millPath,
+ "-DMILL_VERSION=0.4.0", "-Djna.nosys=true", "-cp",
+ millPath,
+ "mill.MillMain mill.contrib.MainMillBuildServer/startServer").asJava,
+ version,
+ bspVersion,
+ languages.asJava))
+ }
+
+ /**
+ * 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 installMillBsp(): Unit = {
+ val bspDirecotry = os.pwd / "contrib" / "bsp" / ".bsp"
+
+ try {
+ os.makeDir(bspDirecotry)
+ os.write(bspDirecotry / "mill-bsp.json", Json.stringify(createBspConnectionJson()))
+ } catch {
+ case e: FileAlreadyExistsException => {
+ println("The bsp connection json file probably exists already - will be overwritten")
+ os.remove.all(bspDirecotry)
+ installMillBsp()
+ }
+ //TODO: Do I want to catch this or throw the exception?
+ case e: Exception => println("An exception occurred while installing mill-bsp: " + e.getMessage +
+ " " + e.getStackTrace.toString)
+ }
+
+ }
+
+ /**
+ * Computes a mill task for resolving all JavaModules
+ * defined in the build.sc file of the project to build.
+ * This file should be in the working directory of the client.
+ * @param ev: Environment, used by mill to evaluate tasks
+ * @return: mill.Task which evaluates to a sequence of all
+ * the JavaModules defined for a project
+ */
+ def modules(ev: Evaluator): Task[Seq[JavaModule]] = T.task{
+ ev.rootModule.millInternal.segmentsToModules.values.
+ collect {
+ case m: scalalib.JavaModule => m
+ }.toSeq
+ }
+
+ /**
+ * 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 startServer(ev: Evaluator) = T.command {
+
+ val millServer = new mill.contrib.bsp.MillBuildServer(modules(ev)(), ev, bspVersion, 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])
+ .setExecutorService(executor)
+ .create()
+ millServer.onConnectWithClient(launcher.getRemoteProxy)
+ val listening = launcher.startListening()
+ millServer.cancelator = () => listening.cancel(true)
+ val voidFuture = listening.get()
+ } catch {
+ case e: Exception =>
+ System.err.println("An exception occured while connecting to the client.")
+ System.err.println(e.getMessage)
+ e.printStackTrace()
+ } finally {
+ System.err.println("Shutting down executor")
+ executor.shutdown()
+ }
+ }
+
+ def experiment: Unit = {
+ val index = foo.bar.test.millModuleSegments.parts.length
+ println((os.pwd / "out" / foo.bar.test.millModuleSegments.parts
+ / "compile" / "dest").toNIO.toAbsolutePath.toUri.toString )
+ println(foo.bar.test.millModuleSegments.parts)
+ println(foo.bar.test.millSourcePath)
+ println(foo.bar.millOuterCtx.fileName)
+ }
+
+ /**
+ * Allows minimal testing and installing the build server
+ * from the command line.
+ * @param args: can be - exp: executes code in the experiment
+ * method, helps with testing the server
+ * - install: installs the mill-bsp server in the
+ * working directory.
+ */
+ def main(args: Array[String]) {
+ args(0) match {
+ case "exp" => experiment
+ case "install" => installMillBsp() //TODO: Do I want to make this a mill command instead?
+ case e: String => println("Wrong command, you can only use:\n " +
+ "install - creates the bsp connection json file\n")
+ }
+
+ }
+}
+
+object foo extends mill.define.ExternalModule {
+
+ implicit def millScoptEvaluatorReads[T] = new mill.main.EvaluatorScopt[T]()
+ lazy val millDiscover: Discover[foo.this.type] = Discover[this.type]
+
+ object bar extends ScalaModule {
+ def scalaVersion = "2.12.4"
+ override def ivyDeps = Agg(ivy"org.scalameta::metals:0.5.2")
+ override def moduleDeps = Seq(baz)
+ object test extends TestModule {
+ override def ivyDeps = Agg(ivy"com.lihaoyi::utest:0.6.0")
+ def testFrameworks: Target[Seq[String]] = Seq("utest.runner.Framework")
+ }
+ }
+
+ object baz extends scalajslib.ScalaJSModule {
+ def scalaJSVersion = "1.0.0-M8"
+ def scalaVersion = "2.12.8"
+
+ }
+
+ object mill_exercise extends ScalaModule {
+ def scalaVersion = "2.12.8"
+
+ override def scalacPluginIvyDeps = Agg(ivy"org.scala-lang:scala-compiler:2.12.8")
+ override def ivyDeps = Agg(
+ ivy"org.scala-lang:scala-reflect:2.12.8",
+ ivy"org.scalameta::metals:0.5.2"
+ )
+
+ object test extends Tests {
+ override def ivyDeps = Agg(//ivy"org.scalameta::metals:0.5.2",
+ ivy"com.lihaoyi::utest:0.6.0",
+ ivy"org.scalactic::scalactic:3.0.5")
+
+ def testFrameworks: Target[Seq[String]] = Seq("utest.runner.Framework")
+ }
+ }
+} \ No newline at end of file
diff --git a/contrib/bsp/src/mill/contrib/bsp/MillBuildServer.scala b/contrib/bsp/src/mill/contrib/bsp/MillBuildServer.scala
new file mode 100644
index 00000000..4ae68f15
--- /dev/null
+++ b/contrib/bsp/src/mill/contrib/bsp/MillBuildServer.scala
@@ -0,0 +1,303 @@
+package mill.contrib.bsp
+
+import java.util.{Calendar, Collections}
+import java.util.concurrent.CompletableFuture
+
+import ch.epfl.scala.bsp4j._
+import mill._
+import mill.api.Strict
+import mill.contrib.bsp.ModuleUtils._
+import mill.eval.Evaluator
+import mill.scalalib._
+import mill.scalalib.api.CompilationResult
+
+import scala.collection.mutable.Map
+import scala.collection.JavaConverters._
+
+
+class MillBuildServer(modules: Seq[JavaModule],
+ evaluator: Evaluator,
+ _bspVersion: String,
+ serverVersion:String,
+ languages: List[String]) extends BuildServer with ScalaBuildServer {
+
+ val bspVersion: String = _bspVersion
+ val supportedLanguages: List[String] = languages
+ val millServerVersion: String = serverVersion
+ var cancelator: () => Unit = () => ()
+
+ var millModules: Seq[JavaModule] = modules
+ var client: BuildClient = _
+ var moduleToTargetId: Predef.Map[JavaModule, BuildTargetIdentifier] = ModuleUtils.getModuleTargetIdMap(millModules)
+ var targetIdToModule: Predef.Map[BuildTargetIdentifier, JavaModule] = targetToModule(moduleToTargetId)
+ var moduleToTarget: Predef.Map[JavaModule, BuildTarget] =
+ ModuleUtils.millModulesToBspTargets(millModules, List("scala", "java"))
+
+ var millEvaluator: Evaluator = evaluator
+ var clientInitialized = false
+
+ override def onConnectWithClient(server: BuildClient): Unit =
+ client = server
+
+ override def buildInitialize(params: InitializeBuildParams): CompletableFuture[InitializeBuildResult] = {
+
+ val capabilities = new BuildServerCapabilities
+ capabilities.setCompileProvider(new CompileProvider(List("java", "scala").asJava))
+ capabilities.setRunProvider(new RunProvider(List("java", "scala").asJava))
+ capabilities.setTestProvider(new TestProvider(List("java", "scala").asJava))
+ capabilities.setDependencySourcesProvider(true)
+ capabilities.setInverseSourcesProvider(true)
+ capabilities.setResourcesProvider(true)
+ capabilities.setBuildTargetChangedProvider(false) //TODO: for now it's false, but will try to support this later
+ val future = new CompletableFuture[InitializeBuildResult]()
+ future.complete(new InitializeBuildResult("mill-bsp", millServerVersion, bspVersion, capabilities))
+ future
+ }
+
+ override def onBuildInitialized(): Unit = {
+ clientInitialized = true
+ }
+
+ override def buildShutdown(): CompletableFuture[Object] = {
+ clientInitialized match {
+ case true => val future = new CompletableFuture[AnyRef]()
+ future.complete("shut down this server")
+ future
+ case false => throw new Error("Can not send any other request before the initialize request")
+ }
+
+ }
+
+ override def onBuildExit(): Unit = {
+ cancelator()
+ }
+
+ override def workspaceBuildTargets(): CompletableFuture[WorkspaceBuildTargetsResult] = {
+ val future = new CompletableFuture[WorkspaceBuildTargetsResult]()
+ val result = new WorkspaceBuildTargetsResult(moduleToTarget.values.toList.asJava)
+ future.complete(result)
+ future
+ }
+
+ private[this] def getSourceFiles(sources: Seq[os.Path]): Iterable[os.Path] = {
+ var files = Seq.empty[os.Path]
+
+ for (source <- sources) {
+ if (os.exists(source)) (if (os.isDir(source)) os.walk(source) else Seq(source))
+ .foreach(path => if (os.isFile(path) && List("scala", "java").contains(path.ext) &&
+ !path.last.startsWith(".")) {
+ files ++= Seq(path)
+ })
+ }
+
+ files
+ }
+
+ override def buildTargetSources(sourcesParams: SourcesParams): CompletableFuture[SourcesResult] = {
+
+ def computeSourcesResult: SourcesResult = {
+ var items = List[SourcesItem]()
+
+ for (targetId <- sourcesParams.getTargets.asScala) {
+ var itemSources = List[SourceItem]()
+
+ val sources = evaluateInformativeTask(targetIdToModule(targetId).sources).left.get.map(pathRef => pathRef.path)
+ val generatedSources = evaluateInformativeTask(targetIdToModule(targetId).generatedSources).left.get
+ .map(pathRef => pathRef.path)
+
+ for (file <- getSourceFiles(sources)) {
+ itemSources ++= List(new SourceItem(file.toNIO.toAbsolutePath.toUri.toString, SourceItemKind.FILE, false))
+ }
+
+ for (file <- getSourceFiles(generatedSources)) {
+ itemSources ++= List(new SourceItem(file.toNIO.toAbsolutePath.toUri.toString, SourceItemKind.FILE, true))
+ }
+
+ items ++= List(new SourcesItem(targetId, itemSources.asJava))
+ }
+
+ new SourcesResult(items.asJava)
+ }
+
+ val future = new CompletableFuture[SourcesResult]()
+ future.complete(computeSourcesResult)
+ future
+ }
+
+ override def buildTargetInverseSources(inverseSourcesParams: InverseSourcesParams):
+ CompletableFuture[InverseSourcesResult] = {
+
+ def getInverseSourcesResult: InverseSourcesResult = {
+ val textDocument = inverseSourcesParams.getTextDocument
+
+ val targets = (for (targetId <- targetIdToModule.keys
+ if buildTargetSources(new SourcesParams(Collections.singletonList(targetId))).
+ get.getItems.asScala.head.getSources.asScala.
+ exists(item => item.getUri.equals(textDocument.getUri)))
+ yield targetId).toList.asJava
+ new InverseSourcesResult(targets)
+ }
+
+ val future = new CompletableFuture[InverseSourcesResult]()
+ future.complete(getInverseSourcesResult)
+ future
+ }
+
+ override def buildTargetDependencySources(dependencySourcesParams: DependencySourcesParams):
+ CompletableFuture[DependencySourcesResult] = {
+ def getDependencySources: DependencySourcesResult = {
+ var items = List[DependencySourcesItem]()
+
+ for (targetId <- dependencySourcesParams.getTargets.asScala) {
+ val millModule = targetIdToModule(targetId)
+ var sources = evaluateInformativeTask(millModule.resolveDeps(millModule.transitiveIvyDeps)).
+ left.get ++
+ evaluateInformativeTask(millModule.resolveDeps(millModule.compileIvyDeps)).
+ left.get
+ millModule match {
+ case m: ScalaModule => sources ++= evaluateInformativeTask(
+ millModule.resolveDeps(millModule.asInstanceOf[ScalaModule].scalaLibraryIvyDeps)).left.get
+ case m: JavaModule => sources ++= List()
+ }
+ items ++= List(new DependencySourcesItem(targetId, sources.
+ map(pathRef => pathRef.path.toNIO.toAbsolutePath.toUri.toString).
+ toList.asJava))
+ }
+
+ new DependencySourcesResult(items.asJava)
+ }
+
+ val future = new CompletableFuture[DependencySourcesResult]()
+ future.complete(getDependencySources)
+ future
+ }
+
+ override def buildTargetResources(resourcesParams: ResourcesParams): CompletableFuture[ResourcesResult] = {
+
+ def getResources: ResourcesResult = {
+ var items = List[ResourcesItem]()
+
+ for (targetId <- resourcesParams.getTargets.asScala) {
+ val millModule = targetIdToModule(targetId)
+ val resources = evaluateInformativeTask(millModule.resources).left.get.
+ flatMap(pathRef => os.walk(pathRef.path)).
+ map(path => path.toNIO.toAbsolutePath.toUri.toString).
+ toList.asJava
+ items ++= List(new ResourcesItem(targetId, resources))
+ }
+
+ new ResourcesResult(items.asJava)
+ }
+
+ val future = new CompletableFuture[ResourcesResult]()
+ future.complete(getResources)
+ future
+ }
+
+ //TODO: send task notifications - start, progress and finish
+ //TODO: if the client wants to give compilation arguments and the module
+ // already has some from the build file, what to do?
+ //TODO: Send notification if compilation fails
+ override def buildTargetCompile(compileParams: CompileParams): CompletableFuture[CompileResult] = {
+
+ def getCompileResult: CompileResult = {
+
+ var numFailures = 0
+ var compileTime = 0
+ for (targetId <- compileParams.getTargets.asScala) {
+ if (moduleToTarget(targetIdToModule(targetId)).getCapabilities.getCanCompile) {
+ var millModule = targetIdToModule(targetId)
+ val compileTask = millModule.compile
+ // send notification to client that compilation of this target started
+ val taskStartParams = new TaskStartParams(new TaskId(compileTask.hashCode().toString))
+ taskStartParams.setEventTime(System.currentTimeMillis())
+ taskStartParams.setMessage("Compiling target: " + targetId)
+ taskStartParams.setDataKind("compile-task")
+ taskStartParams.setData(new CompileTask(targetId))
+ client.onBuildTaskStart(taskStartParams)
+
+ val result = millEvaluator.evaluate(Strict.Agg(compileTask))
+ val endTime = System.currentTimeMillis()
+ compileTime += result.timings.map(timingTuple => timingTuple._2).sum
+ var statusCode = StatusCode.OK
+
+ if (result.failing.keyCount > 0) {
+ statusCode = StatusCode.ERROR
+ numFailures += result.failing.keyCount
+ }
+
+ // send notification to client that compilation of this target ended => compilation report
+ val taskFinishParams = new TaskFinishParams(new TaskId(compileTask.hashCode().toString), statusCode)
+ taskFinishParams.setEventTime(endTime)
+ taskFinishParams.setMessage("Finished compiling target: " +
+ moduleToTarget(targetIdToModule(targetId)).getDisplayName)
+ taskFinishParams.setDataKind("compile-report")
+ val compileReport = new CompileReport(targetId, numFailures, 0)
+ compileReport.setOriginId(compileParams.getOriginId)
+ compileReport.setTime(compileTime)
+ taskFinishParams.setData(compileReport)
+ client.onBuildTaskFinish(taskFinishParams)
+ }
+ }
+
+ var overallStatusCode = StatusCode.OK
+ if (numFailures > 0) {
+ overallStatusCode = StatusCode.ERROR
+ }
+ val compileResult = new CompileResult(overallStatusCode)
+ compileResult.setOriginId(compileParams.getOriginId)
+ compileResult //TODO: See what form IntelliJ expects data about products of compilation in order to set data field
+ }
+
+ val future = new CompletableFuture[CompileResult]()
+ future.complete(getCompileResult)
+ future
+ }
+
+ override def buildTargetRun(runParams: RunParams): CompletableFuture[RunResult] = ???
+
+ override def buildTargetTest(testParams: TestParams): CompletableFuture[TestResult] = ???
+
+ override def buildTargetCleanCache(cleanCacheParams: CleanCacheParams): CompletableFuture[CleanCacheResult] = ???
+
+ override def buildTargetScalacOptions(scalacOptionsParams: ScalacOptionsParams):
+ CompletableFuture[ScalacOptionsResult] = {
+ def getScalacOptionsResult: ScalacOptionsResult = {
+ var targetScalacOptions = List.empty[ScalacOptionsItem]
+ for (targetId <- scalacOptionsParams.getTargets.asScala) {
+ val module = targetIdToModule(targetId)
+ module match {
+ case m: ScalaModule =>
+ val options = evaluateInformativeTask(m.scalacOptions).left.get.toList
+ val classpath = evaluateInformativeTask(m.compileClasspath).left.get.
+ map(pathRef => pathRef.path.toNIO.toAbsolutePath.toUri.toString).toList
+ val index = m.millModuleSegments.parts.length
+
+ val classDirectory = m.millOuterCtx.fileName//.toNIO.toAbsolutePath.toUri.toString
+
+ targetScalacOptions ++= List(new ScalacOptionsItem(targetId, options.asJava, classpath.asJava, classDirectory))
+ case m: JavaModule => targetScalacOptions ++= List()
+ }
+
+ }
+ new ScalacOptionsResult(targetScalacOptions.asJava)
+ }
+
+ val future = new CompletableFuture[ScalacOptionsResult]()
+ future.complete(getScalacOptionsResult)
+ future
+ }
+
+ override def buildTargetScalaMainClasses(scalaMainClassesParams: ScalaMainClassesParams):
+ CompletableFuture[ScalaMainClassesResult] = ???
+
+ override def buildTargetScalaTestClasses(scalaTestClassesParams: ScalaTestClassesParams):
+ CompletableFuture[ScalaTestClassesResult] = ???
+
+
+ private[this] def targetToModule(moduleToTargetId: Predef.Map[JavaModule, BuildTargetIdentifier]):
+ Predef.Map[BuildTargetIdentifier, JavaModule] = {
+ moduleToTargetId.keys.map(mod => (moduleToTargetId(mod), mod)).toMap
+
+ }
+}
diff --git a/contrib/bsp/src/mill/contrib/bsp/ModuleUtils.scala b/contrib/bsp/src/mill/contrib/bsp/ModuleUtils.scala
new file mode 100644
index 00000000..750d070e
--- /dev/null
+++ b/contrib/bsp/src/mill/contrib/bsp/ModuleUtils.scala
@@ -0,0 +1,129 @@
+package mill.contrib.bsp
+
+import java.util.Collections
+import scala.collection.JavaConverters._
+import ch.epfl.scala.bsp4j._
+import mill.api.{Loose, Strict}
+import mill.define.{Discover, Task}
+import mill.eval
+import mill.eval.Evaluator
+import mill.scalajslib.ScalaJSModule
+import mill.scalalib.api.Util
+import mill.scalanativelib._
+import mill.scalalib.{JavaModule, ScalaModule, TestModule}
+import mill.util.DummyLogger
+
+object ModuleUtils {
+
+ object dummyModule extends mill.define.ExternalModule {
+ lazy val millDiscover: Discover[dummyModule.this.type] = Discover[this.type]
+ }
+
+ val dummyEvalautor: Evaluator = new Evaluator(os.pwd / "contrib" / "bsp" / "mill-bs",
+ os.pwd / "contrib" / "bsp" / "mill-out-bs",
+ os.pwd / "contrib" / "bsp" / "mill-external-bs",
+ dummyModule, DummyLogger)
+
+ def millModulesToBspTargets(modules: Seq[JavaModule],
+ supportedLanguages: List[String]): Predef.Map[JavaModule, BuildTarget] = {
+
+ val moduleIdMap = getModuleTargetIdMap(modules)
+ var moduleToTarget = Predef.Map[JavaModule, BuildTarget]()
+
+ for ( module <- modules ) {
+ val dataBuildTarget = computeScalaBuildTarget(module)
+
+ val capabilities = getModuleCapabilities(module)
+ val buildTargetTag: String = module match {
+ case m: TestModule => BuildTargetTag.TEST
+ case m: JavaModule => "-"
+ }
+
+ val dependencies = module match {
+ case m: JavaModule => m.moduleDeps.map(dep => moduleIdMap(dep)).toList.asJava
+ }
+
+ val buildTarget = new BuildTarget(moduleIdMap(module),
+ Collections.singletonList(buildTargetTag),
+ supportedLanguages.asJava,
+ dependencies,
+ capabilities)
+ buildTarget.setData(dataBuildTarget)
+ buildTarget.setDisplayName(module.millModuleSegments.last.value.toList.head.pathSegments.head)
+ buildTarget.setBaseDirectory(module.millOuterCtx.fileName)
+ moduleToTarget ++= Map(module -> buildTarget)
+
+ }
+
+ moduleToTarget
+ }
+
+ def getModuleCapabilities(module: JavaModule): BuildTargetCapabilities = {
+ val canRun = evaluateInformativeTask(module.finalMainClass, success = false) match {
+ case result: Left[String, eval.Result[String]] => true
+ case result: Right[String, eval.Result[String]] => false
+ }
+ val canTest = module match {
+ case module: TestModule => true
+ case module: JavaModule => false
+ }
+
+ new BuildTargetCapabilities(true, canRun, canTest)
+ }
+
+ //TODO: I think here I need to look at scalaLibraryIvyDeps, ivyDeps that contain
+ // "scala-compiler" and "scala-reflect" and at scalacPluginIvyDeps
+ def computeScalaBuildTarget(module: JavaModule): Any = {
+ module match {
+ case m: ScalaModule =>
+ val scalaVersion = evaluateInformativeTask(m.scalaVersion).left.get
+ new ScalaBuildTarget(
+ evaluateInformativeTask(m.scalaOrganization).left.get,
+ scalaVersion,
+ Util.scalaBinaryVersion(scalaVersion),
+ getScalaTargetPlatform(m),
+ computeScalaLangDependencies(m).
+ map(pathRef => pathRef.path.toNIO.toAbsolutePath.toString).
+ toList.asJava)
+
+ case m: JavaModule => "This is just a test or java target"
+ }
+ }
+
+ def evaluateInformativeTask[T](task: Task[T], success: Boolean = true): Either[T, eval.Result[Any]] = {
+ if (success) {
+ Left(dummyEvalautor.evaluate(Strict.Agg(task)).results(task).asSuccess.get.value.asInstanceOf[T])
+ } else {
+ Right(dummyEvalautor.evaluate(Strict.Agg(task)).results(task))
+ }
+
+ }
+
+ def computeScalaLangDependencies(module: ScalaModule): Loose.Agg[eval.PathRef] = {
+ evaluateInformativeTask(module.scalacPluginClasspath).left.get ++
+ evaluateInformativeTask(module.resolveDeps(module.ivyDeps)).left.get.
+ filter(pathRef => pathRef.path.toNIO.toAbsolutePath.toString.contains("scala-compiler") ||
+ pathRef.path.toNIO.toAbsolutePath.toString.contains("scala-reflect") ||
+ pathRef.path.toNIO.toAbsolutePath.toString.contains("scala-library"))
+ }
+
+ def getScalaTargetPlatform(module: ScalaModule): ScalaPlatform = {
+ module match {
+ case m: ScalaNativeModule => ScalaPlatform.NATIVE
+ case m: ScalaJSModule => ScalaPlatform.JS
+ case m: ScalaModule => ScalaPlatform.JVM
+ }
+ }
+
+ def getModuleTargetIdMap(modules: Seq[JavaModule]): Predef.Map[JavaModule, BuildTargetIdentifier] = {
+ var moduleToTarget = Map[JavaModule, BuildTargetIdentifier]()
+
+ for ( module <- modules ) {
+ moduleToTarget ++= Map(module -> new BuildTargetIdentifier(
+ module.millSourcePath.toNIO.toAbsolutePath.toUri.toString
+ ))
+ }
+
+ moduleToTarget
+ }
+}