From ee8ce1240163e1a074674b350f890d6d862d1b3e Mon Sep 17 00:00:00 2001 From: Ondra Pelech Date: Mon, 18 Nov 2024 00:44:56 +0100 Subject: [PATCH 1/7] Mill support: enable importing projects which contain build.mill(.scala) but not wrapper script Not all Mill projects necessarily have a wrapper script in the root. The wrapper script may be on the PATH. But _all_ Mill projects contain `build.mill` or `build.mill.scala`, so that's _the_ robust way to recognize them. --- .../bsp/project/importing/MillProjectInstaller.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bsp/src/org/jetbrains/bsp/project/importing/MillProjectInstaller.scala b/bsp/src/org/jetbrains/bsp/project/importing/MillProjectInstaller.scala index bd9846c2425..1f317d3d248 100644 --- a/bsp/src/org/jetbrains/bsp/project/importing/MillProjectInstaller.scala +++ b/bsp/src/org/jetbrains/bsp/project/importing/MillProjectInstaller.scala @@ -16,7 +16,7 @@ class MillProjectInstaller extends BspProjectInstallProvider { override def canImport(workspace: File): Boolean = Option(workspace) match { - case Some(directory) if directory.isDirectory => isBspCompatible(directory) || isLegacyBspCompatible(directory) + case Some(directory) if directory.isDirectory => isBspCompatible(directory) || isLegacyBspCompatible(directory) || BspUtil.findFileByName(directory, "build.mill").isDefined || BspUtil.findFileByName(directory, "build.mill.scala").isDefined case _ => false } @@ -34,7 +34,8 @@ class MillProjectInstaller extends BspProjectInstallProvider { Success(Seq(file.getAbsolutePath, "-i", "mill.bsp.BSP/install")) case Some(file) if isLegacyMill => Success(Seq(file.getAbsolutePath, "-i", "mill.contrib.BSP/install")) - case _ => Failure(new IllegalStateException("Unable to install BSP as this is not a Mill project")) + case None => + Success(Seq("mill", "-i", "mill.bsp.BSP/install")) } } From 1bbdcca19280dc267b618dcb59f1fb98bd108c40 Mon Sep 17 00:00:00 2001 From: Ondra Pelech Date: Thu, 21 Nov 2024 13:09:10 +0100 Subject: [PATCH 2/7] build.sc --- .../jetbrains/bsp/project/importing/MillProjectInstaller.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bsp/src/org/jetbrains/bsp/project/importing/MillProjectInstaller.scala b/bsp/src/org/jetbrains/bsp/project/importing/MillProjectInstaller.scala index 1f317d3d248..ee541adc5f3 100644 --- a/bsp/src/org/jetbrains/bsp/project/importing/MillProjectInstaller.scala +++ b/bsp/src/org/jetbrains/bsp/project/importing/MillProjectInstaller.scala @@ -16,7 +16,7 @@ class MillProjectInstaller extends BspProjectInstallProvider { override def canImport(workspace: File): Boolean = Option(workspace) match { - case Some(directory) if directory.isDirectory => isBspCompatible(directory) || isLegacyBspCompatible(directory) || BspUtil.findFileByName(directory, "build.mill").isDefined || BspUtil.findFileByName(directory, "build.mill.scala").isDefined + case Some(directory) if directory.isDirectory => isBspCompatible(directory) || isLegacyBspCompatible(directory) || BspUtil.findFileByName(directory, "build.mill").isDefined || BspUtil.findFileByName(directory, "build.mill.scala").isDefined || BspUtil.findFileByName(directory, "build.sc").isDefined case _ => false } From 68210b23857c577ba82e3581112e5c79fab1aa6c Mon Sep 17 00:00:00 2001 From: Ondra Pelech Date: Tue, 7 Jan 2025 22:58:40 +0100 Subject: [PATCH 3/7] Update MillProjectInstaller.scala --- .../bsp/project/importing/MillProjectInstaller.scala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/bsp/src/org/jetbrains/bsp/project/importing/MillProjectInstaller.scala b/bsp/src/org/jetbrains/bsp/project/importing/MillProjectInstaller.scala index ee541adc5f3..38c674a5fe5 100644 --- a/bsp/src/org/jetbrains/bsp/project/importing/MillProjectInstaller.scala +++ b/bsp/src/org/jetbrains/bsp/project/importing/MillProjectInstaller.scala @@ -16,7 +16,11 @@ class MillProjectInstaller extends BspProjectInstallProvider { override def canImport(workspace: File): Boolean = Option(workspace) match { - case Some(directory) if directory.isDirectory => isBspCompatible(directory) || isLegacyBspCompatible(directory) || BspUtil.findFileByName(directory, "build.mill").isDefined || BspUtil.findFileByName(directory, "build.mill.scala").isDefined || BspUtil.findFileByName(directory, "build.sc").isDefined + case Some(directory) if directory.isDirectory => + isBspCompatible(directory) || + isLegacyBspCompatible(directory) || + BspUtil.findFileByName(directory, "build.mill").isDefined || + BspUtil.findFileByName(directory, "build.mill.scala").isDefined case _ => false } @@ -34,7 +38,7 @@ class MillProjectInstaller extends BspProjectInstallProvider { Success(Seq(file.getAbsolutePath, "-i", "mill.bsp.BSP/install")) case Some(file) if isLegacyMill => Success(Seq(file.getAbsolutePath, "-i", "mill.contrib.BSP/install")) - case None => + case _ => Success(Seq("mill", "-i", "mill.bsp.BSP/install")) } } From 4fbcc5508ee844bf043ef46cbfd531e922c9852b Mon Sep 17 00:00:00 2001 From: Ondra Pelech Date: Tue, 7 Jan 2025 23:18:58 +0100 Subject: [PATCH 4/7] streamline the logic --- .../importing/MillProjectInstaller.scala | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/bsp/src/org/jetbrains/bsp/project/importing/MillProjectInstaller.scala b/bsp/src/org/jetbrains/bsp/project/importing/MillProjectInstaller.scala index 38c674a5fe5..cfef5e31957 100644 --- a/bsp/src/org/jetbrains/bsp/project/importing/MillProjectInstaller.scala +++ b/bsp/src/org/jetbrains/bsp/project/importing/MillProjectInstaller.scala @@ -17,10 +17,10 @@ class MillProjectInstaller extends BspProjectInstallProvider { override def canImport(workspace: File): Boolean = Option(workspace) match { case Some(directory) if directory.isDirectory => - isBspCompatible(directory) || - isLegacyBspCompatible(directory) || - BspUtil.findFileByName(directory, "build.mill").isDefined || - BspUtil.findFileByName(directory, "build.mill.scala").isDefined + BspUtil.findFileByName(directory, "build.mill").isDefined || + BspUtil.findFileByName(directory, "build.mill.scala").isDefined || + isBspCompatible(directory) || + isLegacyBspCompatible(directory) case _ => false } @@ -28,18 +28,18 @@ class MillProjectInstaller extends BspProjectInstallProvider { override def serverName: String = "Mill" - override def installCommand(workspace: File): Try[Seq[String]] = { + override def installCommand(workspace: File): Try[Seq[String]] = Try { // note: The legacy part is only executed for mill bootstrap script so it is not applicable for Windows. // Maybe it could be, but we decided to support mill.bat file only for the newer bsp approach val isLegacyMill = !SystemInfo.isWindows && isLegacyBspCompatible(workspace) val millFileOpt = getMillFile(workspace) millFileOpt match { - case Some(file) if isMillFileBspCompatible(file, workspace) => - Success(Seq(file.getAbsolutePath, "-i", "mill.bsp.BSP/install")) - case Some(file) if isLegacyMill => - Success(Seq(file.getAbsolutePath, "-i", "mill.contrib.BSP/install")) + case Some(file) if isLegacyMill && !isMillFileBspCompatible(file, workspace) => + Seq(file.getAbsolutePath, "-i", "mill.contrib.BSP/install") + case Some(file) => // executes if isMillFileBspCompatible + Seq(file.getAbsolutePath, "-i", "mill.bsp.BSP/install") case _ => - Success(Seq("mill", "-i", "mill.bsp.BSP/install")) + Seq("mill", "-i", "mill.bsp.BSP/install") } } From f989902895bd0de580d98efed6bba2bc64290d0d Mon Sep 17 00:00:00 2001 From: Ondra Pelech Date: Wed, 8 Jan 2025 22:41:04 +0100 Subject: [PATCH 5/7] Improve comments --- .../bsp/project/importing/MillProjectInstaller.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bsp/src/org/jetbrains/bsp/project/importing/MillProjectInstaller.scala b/bsp/src/org/jetbrains/bsp/project/importing/MillProjectInstaller.scala index cfef5e31957..7c1569c6a0d 100644 --- a/bsp/src/org/jetbrains/bsp/project/importing/MillProjectInstaller.scala +++ b/bsp/src/org/jetbrains/bsp/project/importing/MillProjectInstaller.scala @@ -35,10 +35,13 @@ class MillProjectInstaller extends BspProjectInstallProvider { val millFileOpt = getMillFile(workspace) millFileOpt match { case Some(file) if isLegacyMill && !isMillFileBspCompatible(file, workspace) => + // run this only if we're confident this is legacy Mill Seq(file.getAbsolutePath, "-i", "mill.contrib.BSP/install") - case Some(file) => // executes if isMillFileBspCompatible + case Some(file) => + // otherwise run the normal BSP install command Seq(file.getAbsolutePath, "-i", "mill.bsp.BSP/install") case _ => + // as a fallback, use Mill from PATH in case we couldn't find launcher in the project root Seq("mill", "-i", "mill.bsp.BSP/install") } } From 054629b29519b0e3c19acc2addb98b8c7675ad4e Mon Sep 17 00:00:00 2001 From: Aleksandra Zdrojowa Date: Thu, 23 Jan 2025 12:37:56 +0100 Subject: [PATCH 6/7] [Mill]: optimise the finding of Mill build files --- bsp/src/org/jetbrains/bsp/BspUtil.scala | 7 +++++++ .../bsp/project/importing/MillProjectInstaller.scala | 3 +-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/bsp/src/org/jetbrains/bsp/BspUtil.scala b/bsp/src/org/jetbrains/bsp/BspUtil.scala index 8bd421fbe9e..f91ea0014db 100644 --- a/bsp/src/org/jetbrains/bsp/BspUtil.scala +++ b/bsp/src/org/jetbrains/bsp/BspUtil.scala @@ -121,4 +121,11 @@ object BspUtil { .getOrElse(Array.empty) .find(x => x.getName == name && !x.isDirectory) + /** + * Checks whether a specified directory contains at least one file with a name from a given sequence of file names. + */ + def directoryContainsFile(directory: File, fileNames: String*): Boolean = + Option(directory.listFiles()) + .getOrElse(Array.empty) + .exists(x => !x.isDirectory && fileNames.contains(x.getName)) } diff --git a/bsp/src/org/jetbrains/bsp/project/importing/MillProjectInstaller.scala b/bsp/src/org/jetbrains/bsp/project/importing/MillProjectInstaller.scala index 7c1569c6a0d..55084c5910c 100644 --- a/bsp/src/org/jetbrains/bsp/project/importing/MillProjectInstaller.scala +++ b/bsp/src/org/jetbrains/bsp/project/importing/MillProjectInstaller.scala @@ -17,8 +17,7 @@ class MillProjectInstaller extends BspProjectInstallProvider { override def canImport(workspace: File): Boolean = Option(workspace) match { case Some(directory) if directory.isDirectory => - BspUtil.findFileByName(directory, "build.mill").isDefined || - BspUtil.findFileByName(directory, "build.mill.scala").isDefined || + BspUtil.directoryContainsFile(directory, "build.mill", "build.mill.scala") || isBspCompatible(directory) || isLegacyBspCompatible(directory) case _ => false From 7b16afccb6ceca93ffd0c89af96da709de9abb2a Mon Sep 17 00:00:00 2001 From: Aleksandra Zdrojowa Date: Thu, 23 Jan 2025 12:39:12 +0100 Subject: [PATCH 7/7] [Mill]: check if Mill executable is in the PATH before calling the mill command --- bsp/src/org/jetbrains/bsp/BspUtil.scala | 10 ++++++++++ .../importing/MillProjectInstaller.scala | 18 ++++++++++++------ .../org/jetbrains/scalaCli/ScalaCliUtils.scala | 11 ++--------- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/bsp/src/org/jetbrains/bsp/BspUtil.scala b/bsp/src/org/jetbrains/bsp/BspUtil.scala index f91ea0014db..219e99ca127 100644 --- a/bsp/src/org/jetbrains/bsp/BspUtil.scala +++ b/bsp/src/org/jetbrains/bsp/BspUtil.scala @@ -5,6 +5,7 @@ import java.net.URI import java.nio.file.{Path, Paths} import java.util.concurrent.CompletableFuture import com.intellij.build.events.impl.{FailureResultImpl, SkippedResultImpl, SuccessResultImpl} +import com.intellij.execution.configurations.GeneralCommandLine import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil import com.intellij.openapi.module.{Module, ModuleManager} import com.intellij.openapi.project.{Project, ProjectUtil} @@ -128,4 +129,13 @@ object BspUtil { Option(directory.listFiles()) .getOrElse(Array.empty) .exists(x => !x.isDirectory && fileNames.contains(x.getName)) + + def checkIfToolIsInstalled(workspace: File, toolCommand: String): Boolean = + Try { + val generalCommandLine = new GeneralCommandLine(toolCommand, "version") + .withWorkDirectory(workspace) + val process = generalCommandLine.toProcessBuilder.start() + val exitValue = process.waitFor() + exitValue == 0 + }.getOrElse(false) } diff --git a/bsp/src/org/jetbrains/bsp/project/importing/MillProjectInstaller.scala b/bsp/src/org/jetbrains/bsp/project/importing/MillProjectInstaller.scala index 55084c5910c..02847d60715 100644 --- a/bsp/src/org/jetbrains/bsp/project/importing/MillProjectInstaller.scala +++ b/bsp/src/org/jetbrains/bsp/project/importing/MillProjectInstaller.scala @@ -27,7 +27,7 @@ class MillProjectInstaller extends BspProjectInstallProvider { override def serverName: String = "Mill" - override def installCommand(workspace: File): Try[Seq[String]] = Try { + override def installCommand(workspace: File): Try[Seq[String]] = { // note: The legacy part is only executed for mill bootstrap script so it is not applicable for Windows. // Maybe it could be, but we decided to support mill.bat file only for the newer bsp approach val isLegacyMill = !SystemInfo.isWindows && isLegacyBspCompatible(workspace) @@ -35,16 +35,22 @@ class MillProjectInstaller extends BspProjectInstallProvider { millFileOpt match { case Some(file) if isLegacyMill && !isMillFileBspCompatible(file, workspace) => // run this only if we're confident this is legacy Mill - Seq(file.getAbsolutePath, "-i", "mill.contrib.BSP/install") + Success(Seq(file.getAbsolutePath, "-i", "mill.contrib.BSP/install")) case Some(file) => // otherwise run the normal BSP install command - Seq(file.getAbsolutePath, "-i", "mill.bsp.BSP/install") - case _ => - // as a fallback, use Mill from PATH in case we couldn't find launcher in the project root - Seq("mill", "-i", "mill.bsp.BSP/install") + Success(Seq(file.getAbsolutePath, "-i", "mill.bsp.BSP/install")) + //TODO: consider verifying Mill's installation in the #canImport to prevent its + // display in BspSetupConfigStepUi if not installed (the same in ScalaCliProjectInstaller) + case _ if isMillInstalled(workspace) => + // If the launcher is not found in the project root but Mill is available in the PATH, then we can use it. + Success(Seq("mill", "-i", "mill.bsp.BSP/install")) + case _ => Failure(new IllegalStateException("Installation of BSP is unable to proceed as the Mill executable is missing from both the project root and the PATH.")) } } + private def isMillInstalled(workspace: File): Boolean = + BspUtil.checkIfToolIsInstalled(workspace, "mill") + private def getMillFile(workspace: File): Option[File] = if (SystemInfo.isWindows) BspUtil.findFileByName(workspace, "mill.bat") else BspUtil.findFileByName(workspace, "mill") diff --git a/scala-cli/src/org/jetbrains/scalaCli/ScalaCliUtils.scala b/scala-cli/src/org/jetbrains/scalaCli/ScalaCliUtils.scala index 74599fdc17f..c063e780033 100644 --- a/scala-cli/src/org/jetbrains/scalaCli/ScalaCliUtils.scala +++ b/scala-cli/src/org/jetbrains/scalaCli/ScalaCliUtils.scala @@ -1,12 +1,11 @@ package org.jetbrains.scalaCli -import com.intellij.execution.configurations.GeneralCommandLine import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.project.Project +import org.jetbrains.bsp.BspUtil import org.jetbrains.bsp.project.BspExternalSystemUtil import java.io.File -import scala.util.Try object ScalaCliUtils { @@ -18,13 +17,7 @@ object ScalaCliUtils { } def isScalaCliInstalled(workspace: File): Boolean = - Try { - val generalCommandLine = new GeneralCommandLine(getScalaCliCommand, "version") - .withWorkDirectory(workspace) - val process = generalCommandLine.toProcessBuilder.start() - val exitValue = process.waitFor() - exitValue == 0 - }.getOrElse(false) + BspUtil.checkIfToolIsInstalled(workspace, getScalaCliCommand) /** * If these are tests, the Scala CLI is not installed globally - the script is only available in the project root directory,