diff --git a/build.sbt b/build.sbt index 4ffda8ebdc..15d9b9dc0b 100644 --- a/build.sbt +++ b/build.sbt @@ -113,6 +113,7 @@ lazy val core = myCrossProject("core") libraryDependencies ++= Seq( Dependencies.bcprovJdk15to18, Dependencies.betterFiles, + Dependencies.bsp4j, Dependencies.catsCore, Dependencies.catsEffect, Dependencies.catsParse, @@ -184,7 +185,9 @@ lazy val core = myCrossProject("core") BuildInfoKey("mainBranch" -> mainBranch), BuildInfoKey.map(git.gitHeadCommit) { case (k, v) => k -> v.getOrElse(mainBranch) }, BuildInfoKey("millPluginArtifactName" -> Dependencies.scalaStewardMillPluginArtifactName), - BuildInfoKey("millPluginVersion" -> Dependencies.scalaStewardMillPlugin.revision) + BuildInfoKey("millPluginVersion" -> Dependencies.scalaStewardMillPlugin.revision), + BuildInfoKey("bsp4jVersion" -> Dependencies.bsp4j.revision), + BuildInfoKey("bazelBsp" -> Dependencies.bazelBsp) ), buildInfoPackage := moduleRootPkg.value, initialCommands += s""" @@ -272,6 +275,7 @@ lazy val dummy = myCrossProject("dummy") .settings(noPublishSettings) .settings( libraryDependencies ++= Seq( + Dependencies.bazelBsp, Dependencies.scalaStewardMillPlugin ) ) diff --git a/modules/core/src/main/scala/org/scalasteward/core/application/Context.scala b/modules/core/src/main/scala/org/scalasteward/core/application/Context.scala index cb18337892..1011d75fb2 100644 --- a/modules/core/src/main/scala/org/scalasteward/core/application/Context.scala +++ b/modules/core/src/main/scala/org/scalasteward/core/application/Context.scala @@ -25,6 +25,10 @@ import org.http4s.client.Client import org.http4s.headers.`User-Agent` import org.scalasteward.core.application.Config.ForgeCfg import org.scalasteward.core.buildtool.BuildToolDispatcher +import org.scalasteward.core.buildtool.bazel.BazelAlg +import org.scalasteward.core.buildtool.bleep.BleepAlg +import org.scalasteward.core.buildtool.bsp.BspExtractor +import org.scalasteward.core.buildtool.gradle.GradleAlg import org.scalasteward.core.buildtool.maven.MavenAlg import org.scalasteward.core.buildtool.mill.MillAlg import org.scalasteward.core.buildtool.sbt.SbtAlg @@ -181,8 +185,13 @@ object Context { implicit val updateAlg: UpdateAlg[F] = new UpdateAlg[F] implicit val mavenAlg: MavenAlg[F] = new MavenAlg[F](config) implicit val sbtAlg: SbtAlg[F] = new SbtAlg[F](config) - implicit val scalaCliAlg: ScalaCliAlg[F] = new ScalaCliAlg[F] + implicit val bspExtractor: BspExtractor[F] = + new BspExtractor[F](config.defaultResolver, config.processCfg.processTimeout) + implicit val bazelAlg: BazelAlg[F] = new BazelAlg[F] + implicit val bleepAlg: BleepAlg[F] = new BleepAlg[F] + implicit val gradleAlg: GradleAlg[F] = new GradleAlg[F] implicit val millAlg: MillAlg[F] = new MillAlg[F](config.defaultResolver) + implicit val scalaCliAlg: ScalaCliAlg[F] = new ScalaCliAlg[F] implicit val buildToolDispatcher: BuildToolDispatcher[F] = new BuildToolDispatcher[F] implicit val refreshErrorAlg: RefreshErrorAlg[F] = new RefreshErrorAlg[F](refreshErrorStore, config.refreshBackoffPeriod) diff --git a/modules/core/src/main/scala/org/scalasteward/core/buildtool/BuildToolAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/buildtool/BuildToolAlg.scala index 4dacef0227..48d7da3b61 100644 --- a/modules/core/src/main/scala/org/scalasteward/core/buildtool/BuildToolAlg.scala +++ b/modules/core/src/main/scala/org/scalasteward/core/buildtool/BuildToolAlg.scala @@ -22,6 +22,8 @@ import org.typelevel.log4cats.Logger import scala.annotation.nowarn trait BuildToolAlg[F[_]] { + def enabled: Boolean = true + def name: String def containsBuild(buildRoot: BuildRoot): F[Boolean] diff --git a/modules/core/src/main/scala/org/scalasteward/core/buildtool/BuildToolDispatcher.scala b/modules/core/src/main/scala/org/scalasteward/core/buildtool/BuildToolDispatcher.scala index 80538d4b1a..7426b8fd9c 100644 --- a/modules/core/src/main/scala/org/scalasteward/core/buildtool/BuildToolDispatcher.scala +++ b/modules/core/src/main/scala/org/scalasteward/core/buildtool/BuildToolDispatcher.scala @@ -18,6 +18,9 @@ package org.scalasteward.core.buildtool import cats.Monad import cats.syntax.all._ +import org.scalasteward.core.buildtool.bazel.BazelAlg +import org.scalasteward.core.buildtool.bleep.BleepAlg +import org.scalasteward.core.buildtool.gradle.GradleAlg import org.scalasteward.core.buildtool.maven.MavenAlg import org.scalasteward.core.buildtool.mill.MillAlg import org.scalasteward.core.buildtool.sbt.SbtAlg @@ -29,6 +32,9 @@ import org.scalasteward.core.scalafmt.ScalafmtAlg import org.typelevel.log4cats.Logger final class BuildToolDispatcher[F[_]](implicit + bazelAlg: BazelAlg[F], + bleepAlg: BleepAlg[F], + gradleAlg: GradleAlg[F], logger: Logger[F], mavenAlg: MavenAlg[F], millAlg: MillAlg[F], @@ -53,7 +59,8 @@ final class BuildToolDispatcher[F[_]](implicit buildTools.traverse_(_.runMigration(buildRoot, migration)) }) - private val allBuildTools = List(mavenAlg, millAlg, sbtAlg, scalaCliAlg) + private val allBuildTools = + List(bazelAlg, bleepAlg, gradleAlg, mavenAlg, millAlg, sbtAlg, scalaCliAlg).filter(_.enabled) private val fallbackBuildTool = List(sbtAlg) private def findBuildTools(buildRoot: BuildRoot): F[(BuildRoot, List[BuildToolAlg[F]])] = diff --git a/modules/core/src/main/scala/org/scalasteward/core/buildtool/bazel/BazelAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/buildtool/bazel/BazelAlg.scala new file mode 100644 index 0000000000..a0f9224e2a --- /dev/null +++ b/modules/core/src/main/scala/org/scalasteward/core/buildtool/bazel/BazelAlg.scala @@ -0,0 +1,45 @@ +/* + * Copyright 2018-2023 Scala Steward contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.scalasteward.core.buildtool.bazel + +import cats.Monad +import cats.syntax.all._ +import org.scalasteward.core.buildtool.bsp.{BspExtractor, BspServerType} +import org.scalasteward.core.buildtool.{BuildRoot, BuildToolAlg} +import org.scalasteward.core.data.Scope.Dependencies +import org.scalasteward.core.io.{FileAlg, WorkspaceAlg} +import org.typelevel.log4cats.Logger + +final class BazelAlg[F[_]](implicit + bspExtractor: BspExtractor[F], + fileAlg: FileAlg[F], + override protected val logger: Logger[F], + workspaceAlg: WorkspaceAlg[F], + F: Monad[F] +) extends BuildToolAlg[F] { + override def enabled: Boolean = false + + override def name: String = "Bazel" + + override def containsBuild(buildRoot: BuildRoot): F[Boolean] = + workspaceAlg + .buildRootDir(buildRoot) + .flatMap(buildRootDir => fileAlg.isRegularFile(buildRootDir / "BUILD")) + + override def getDependencies(buildRoot: BuildRoot): F[List[Dependencies]] = + bspExtractor.getDependencies(BspServerType.Bazel, buildRoot) +} diff --git a/modules/core/src/main/scala/org/scalasteward/core/buildtool/bleep/BleepAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/buildtool/bleep/BleepAlg.scala new file mode 100644 index 0000000000..4ecc6fae0a --- /dev/null +++ b/modules/core/src/main/scala/org/scalasteward/core/buildtool/bleep/BleepAlg.scala @@ -0,0 +1,45 @@ +/* + * Copyright 2018-2023 Scala Steward contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.scalasteward.core.buildtool.bleep + +import cats.Monad +import cats.syntax.all._ +import org.scalasteward.core.buildtool.bsp.{BspExtractor, BspServerType} +import org.scalasteward.core.buildtool.{BuildRoot, BuildToolAlg} +import org.scalasteward.core.data.Scope.Dependencies +import org.scalasteward.core.io.{FileAlg, WorkspaceAlg} +import org.typelevel.log4cats.Logger + +final class BleepAlg[F[_]](implicit + bspExtractor: BspExtractor[F], + fileAlg: FileAlg[F], + override protected val logger: Logger[F], + workspaceAlg: WorkspaceAlg[F], + F: Monad[F] +) extends BuildToolAlg[F] { + override def enabled: Boolean = false + + override def name: String = "bleep" + + override def containsBuild(buildRoot: BuildRoot): F[Boolean] = + workspaceAlg + .buildRootDir(buildRoot) + .flatMap(buildRootDir => fileAlg.isRegularFile(buildRootDir / "bleep.yaml")) + + override def getDependencies(buildRoot: BuildRoot): F[List[Dependencies]] = + bspExtractor.getDependencies(BspServerType.Bleep, buildRoot) +} diff --git a/modules/core/src/main/scala/org/scalasteward/core/buildtool/bsp/BspClient.scala b/modules/core/src/main/scala/org/scalasteward/core/buildtool/bsp/BspClient.scala new file mode 100644 index 0000000000..a935cd05a4 --- /dev/null +++ b/modules/core/src/main/scala/org/scalasteward/core/buildtool/bsp/BspClient.scala @@ -0,0 +1,29 @@ +/* + * Copyright 2018-2023 Scala Steward contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.scalasteward.core.buildtool.bsp + +import ch.epfl.scala.bsp4j._ + +class BspClient extends BuildClient { + override def onBuildShowMessage(params: ShowMessageParams): Unit = () + override def onBuildLogMessage(params: LogMessageParams): Unit = () + override def onBuildTaskStart(params: TaskStartParams): Unit = () + override def onBuildTaskProgress(params: TaskProgressParams): Unit = () + override def onBuildTaskFinish(params: TaskFinishParams): Unit = () + override def onBuildPublishDiagnostics(params: PublishDiagnosticsParams): Unit = () + override def onBuildTargetDidChange(params: DidChangeBuildTarget): Unit = () +} diff --git a/modules/core/src/main/scala/org/scalasteward/core/buildtool/bsp/BspConnectionDetails.scala b/modules/core/src/main/scala/org/scalasteward/core/buildtool/bsp/BspConnectionDetails.scala new file mode 100644 index 0000000000..6e476cdb17 --- /dev/null +++ b/modules/core/src/main/scala/org/scalasteward/core/buildtool/bsp/BspConnectionDetails.scala @@ -0,0 +1,29 @@ +/* + * Copyright 2018-2023 Scala Steward contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.scalasteward.core.buildtool.bsp + +import io.circe.Decoder +import io.circe.generic.semiauto.deriveDecoder +import org.scalasteward.core.util.Nel + +// https://build-server-protocol.github.io/docs/overview/server-discovery#the-bsp-connection-details +final case class BspConnectionDetails(argv: Nel[String]) + +object BspConnectionDetails { + implicit val bspConnectionDetailsDecoder: Decoder[BspConnectionDetails] = + deriveDecoder +} diff --git a/modules/core/src/main/scala/org/scalasteward/core/buildtool/bsp/BspExtractor.scala b/modules/core/src/main/scala/org/scalasteward/core/buildtool/bsp/BspExtractor.scala new file mode 100644 index 0000000000..10230de524 --- /dev/null +++ b/modules/core/src/main/scala/org/scalasteward/core/buildtool/bsp/BspExtractor.scala @@ -0,0 +1,139 @@ +/* + * Copyright 2018-2023 Scala Steward contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.scalasteward.core.buildtool.bsp + +import better.files.File +import cats.effect.Async +import cats.effect.syntax.all._ +import cats.syntax.all._ +import ch.epfl.scala.bsp4j.{ + BuildClientCapabilities, + DependencyModulesParams, + DependencyModulesResult, + InitializeBuildParams +} +import io.circe.parser.decode +import java.util.Collections +import java.util.concurrent.CompletableFuture +import org.scalasteward.core.buildtool.BuildRoot +import org.scalasteward.core.data._ +import org.scalasteward.core.io.{FileAlg, ProcessAlg, WorkspaceAlg} +import scala.concurrent.duration.FiniteDuration +import scala.jdk.CollectionConverters._ + +final class BspExtractor[F[_]](defaultResolver: Resolver, processTimeout: FiniteDuration)(implicit + fileAlg: FileAlg[F], + processAlg: ProcessAlg[F], + workspaceAlg: WorkspaceAlg[F], + F: Async[F] +) { + def getDependencies( + bspServerType: BspServerType, + buildRoot: BuildRoot + ): F[List[Scope.Dependencies]] = + for { + buildRootDir <- workspaceAlg.buildRootDir(buildRoot) + connectionDetailsFile <- createConnectionDetailsFile(bspServerType, buildRoot) + connectionDetails <- readConnectionDetailsFile(connectionDetailsFile) + bspDependencies <- getBspDependencies(buildRootDir, connectionDetails) + } yield transform(bspDependencies) + + private def createConnectionDetailsFile( + bspServerType: BspServerType, + buildRoot: BuildRoot + ): F[File] = + for { + buildRootDir <- workspaceAlg.buildRootDir(buildRoot) + _ <- processAlg.execSandboxed(bspServerType.connectionDetailsCommand, buildRootDir) + } yield buildRootDir / ".bsp" / bspServerType.connectionDetailsName + + private def readConnectionDetailsFile(connectionDetailsFile: File): F[BspConnectionDetails] = + fileAlg.readFile(connectionDetailsFile).flatMap { + case Some(content) => + decode[BspConnectionDetails](content) match { + case Right(connectionDetails) => F.pure(connectionDetails) + case Left(error) => F.raiseError(error) + } + case None => + F.raiseError(new Throwable(s"${connectionDetailsFile.pathAsString} does not exist")) + } + + private def getBspDependencies( + buildRootDir: File, + connectionDetails: BspConnectionDetails + ): F[DependencyModulesResult] = + BspProcess + .run(connectionDetails.argv, buildRootDir) + .use { p => + val result = for { + initBuildResult <- lift(p.buildInitialize(initBuildParams(buildRootDir))) + _ <- F.blocking(p.onBuildInitialized()) + isDependencyModulesProvider = + Option(initBuildResult.getCapabilities.getDependencyModulesProvider) + .exists(_.booleanValue()) + _ <- F.raiseWhen(!isDependencyModulesProvider) { + new Throwable(s"${initBuildResult.getDisplayName} is not a dependency modules provider") + } + buildTargetsResult <- lift(p.workspaceBuildTargets()) + dependencyModulesParams = + new DependencyModulesParams(buildTargetsResult.getTargets.asScala.map(_.getId).asJava) + dependencyModulesResult <- lift(p.buildTargetDependencyModules(dependencyModulesParams)) + } yield dependencyModulesResult + result.guarantee(lift(p.buildShutdown()) >> F.blocking(p.onBuildExit())) + } + .timeoutAndForget(processTimeout) + + private def initBuildParams(buildRootDir: File): InitializeBuildParams = + new InitializeBuildParams( + "Scala Steward", + org.scalasteward.core.BuildInfo.version, + org.scalasteward.core.BuildInfo.bsp4jVersion, + buildRootDir.uri.toString, + new BuildClientCapabilities(Collections.emptyList()) + ) + + private def lift[A](fut: => CompletableFuture[A]): F[A] = + F.fromCompletableFuture(F.blocking(fut)) + + private def transform(bspDependencies: DependencyModulesResult): List[Scope.Dependencies] = + bspDependencies.getItems.asScala.toList.map { item => + val dependencies = item.getModules.asScala.toList.mapFilter { module => + if ( + module.getDataKind === "maven" && module.getData.isInstanceOf[com.google.gson.JsonObject] + ) { + val data = module.getData.asInstanceOf[com.google.gson.JsonObject] + val g = GroupId(data.get("organization").getAsString) + val a = ArtifactId.from(data.get("name").getAsString) + val v = Version(data.get("version").getAsString) + Dependency(g, a, v).some + } else if (module.getName.contains(':')) { + module.getName.split(':') match { + case Array(groupId, artifactId) => + val g = GroupId(groupId) + val a = ArtifactId.from(artifactId) + val v = Version(module.getVersion) + Dependency(g, a, v).some + case _ => None + } + } else None + } + // The BSP does not yet provide resolvers, so we use the default resolver here. + // See https://github.com/build-server-protocol/build-server-protocol/discussions/500 + // for a proposal to add resolvers to BSP. + Scope(dependencies, List(defaultResolver)) + } +} diff --git a/modules/core/src/main/scala/org/scalasteward/core/buildtool/bsp/BspProcess.scala b/modules/core/src/main/scala/org/scalasteward/core/buildtool/bsp/BspProcess.scala new file mode 100644 index 0000000000..8a4ac45aec --- /dev/null +++ b/modules/core/src/main/scala/org/scalasteward/core/buildtool/bsp/BspProcess.scala @@ -0,0 +1,64 @@ +/* + * Copyright 2018-2023 Scala Steward contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.scalasteward.core.buildtool.bsp + +import better.files.File +import cats.effect.implicits._ +import cats.effect.{Async, Resource} +import cats.syntax.all._ +import ch.epfl.scala.bsp4j._ +import java.util +import java.util.Collections +import java.util.concurrent.{AbstractExecutorService, ExecutorService, TimeUnit} +import org.eclipse.lsp4j.jsonrpc.Launcher +import org.scalasteward.core.util.Nel +import scala.concurrent.ExecutionContext + +object BspProcess { + def run[F[_]]( + command: Nel[String], + workingDirectory: File + )(implicit F: Async[F]): Resource[F, BuildServer] = + for { + process <- Resource.make(F.blocking { + val pb = new ProcessBuilder(command.toList: _*) + pb.directory(workingDirectory.toJava) + pb.start() + })(process => F.blocking(process.waitFor(1L, TimeUnit.SECONDS)).void) + ec <- Resource.executionContext + launcher <- Resource.eval(F.blocking { + new Launcher.Builder[BuildServer]() + .setOutput(process.getOutputStream) + .setInput(process.getInputStream) + .setExecutorService(executorServiceFrom(ec)) + .setLocalService(new BspClient) + .setRemoteInterface(classOf[BuildServer]) + .create() + }) + _ <- F.blocking(launcher.startListening()).background + } yield launcher.getRemoteProxy + + private def executorServiceFrom(ec: ExecutionContext): ExecutorService = + new AbstractExecutorService { + override def shutdown(): Unit = () + override def shutdownNow(): util.List[Runnable] = Collections.emptyList() + override def isShutdown: Boolean = false + override def isTerminated: Boolean = false + override def awaitTermination(timeout: Long, unit: TimeUnit): Boolean = false + override def execute(command: Runnable): Unit = ec.execute(command) + } +} diff --git a/modules/core/src/main/scala/org/scalasteward/core/buildtool/bsp/BspServerType.scala b/modules/core/src/main/scala/org/scalasteward/core/buildtool/bsp/BspServerType.scala new file mode 100644 index 0000000000..d36caa09db --- /dev/null +++ b/modules/core/src/main/scala/org/scalasteward/core/buildtool/bsp/BspServerType.scala @@ -0,0 +1,80 @@ +/* + * Copyright 2018-2023 Scala Steward contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.scalasteward.core.buildtool.bsp + +import org.scalasteward.core.util.Nel + +sealed abstract class BspServerType( + val connectionDetailsCommand: Nel[String], + val connectionDetailsName: String +) + +object BspServerType { + // https://github.com/JetBrains/bazel-bsp#easy-way-coursier + // Status: waiting for https://youtrack.jetbrains.com/issue/BAZEL-616 + case object Bazel + extends BspServerType( + connectionDetailsCommand = Nel.of( + "cs", + "launch", + org.scalasteward.core.BuildInfo.bazelBsp, + "-M", + "org.jetbrains.bsp.bazel.install.Install" + ), + connectionDetailsName = "bazelbsp.json" + ) + + // https://bleep.build/ + // Status: waiting for https://github.com/scalacenter/bloop/pull/2197 to propagate to bleep + case object Bleep + extends BspServerType( + connectionDetailsCommand = Nel.of("bleep", "setup-ide"), + connectionDetailsName = "bleep.json" + ) + + // https://github.com/microsoft/build-server-for-gradle + // Status: waiting for https://github.com/microsoft/build-server-for-gradle/issues/115 + case object Gradle + extends BspServerType( + connectionDetailsCommand = Nel.one("???"), + connectionDetailsName = "???" + ) + + // https://com-lihaoyi.github.io/mill/mill/Plugin_BSP.html + // Status: working + case object Mill + extends BspServerType( + connectionDetailsCommand = Nel.of("mill", "mill.bsp.BSP/install"), + connectionDetailsName = "mill-bsp.json" + ) + + // https://www.scala-sbt.org + // Status: waiting for https://github.com/sbt/sbt/issues/6957 + case object Sbt + extends BspServerType( + connectionDetailsCommand = Nel.of("sbt", "bspConfig"), + connectionDetailsName = "sbt.json" + ) + + // https://scala-cli.virtuslab.org/docs/commands/setup-ide#ide-support-internals + // Status: working + case object ScalaCli + extends BspServerType( + connectionDetailsCommand = Nel.of("scala-cli", "setup-ide", "."), + connectionDetailsName = "scala-cli.json" + ) +} diff --git a/modules/core/src/main/scala/org/scalasteward/core/buildtool/gradle/GradleAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/buildtool/gradle/GradleAlg.scala new file mode 100644 index 0000000000..28fe41fa4c --- /dev/null +++ b/modules/core/src/main/scala/org/scalasteward/core/buildtool/gradle/GradleAlg.scala @@ -0,0 +1,46 @@ +/* + * Copyright 2018-2023 Scala Steward contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.scalasteward.core.buildtool.gradle + +import cats.Monad +import cats.syntax.all._ +import org.scalasteward.core.buildtool.bsp.{BspExtractor, BspServerType} +import org.scalasteward.core.buildtool.{BuildRoot, BuildToolAlg} +import org.scalasteward.core.data.Scope.Dependencies +import org.scalasteward.core.io.{FileAlg, WorkspaceAlg} +import org.typelevel.log4cats.Logger + +final class GradleAlg[F[_]](implicit + bspExtractor: BspExtractor[F], + fileAlg: FileAlg[F], + override protected val logger: Logger[F], + workspaceAlg: WorkspaceAlg[F], + F: Monad[F] +) extends BuildToolAlg[F] { + override def enabled: Boolean = false + + override def name: String = "Gradle" + + override def containsBuild(buildRoot: BuildRoot): F[Boolean] = + workspaceAlg.buildRootDir(buildRoot).flatMap { buildRootDir => + List("build.gradle", "build.gradle.kts", "settings.gradle", "settings.gradle.kts") + .existsM(name => fileAlg.isRegularFile(buildRootDir / name)) + } + + override def getDependencies(buildRoot: BuildRoot): F[List[Dependencies]] = + bspExtractor.getDependencies(BspServerType.Gradle, buildRoot) +} diff --git a/modules/core/src/main/scala/org/scalasteward/core/buildtool/mill/MillAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/buildtool/mill/MillAlg.scala index a769448fee..d51ad18b0e 100644 --- a/modules/core/src/main/scala/org/scalasteward/core/buildtool/mill/MillAlg.scala +++ b/modules/core/src/main/scala/org/scalasteward/core/buildtool/mill/MillAlg.scala @@ -19,6 +19,7 @@ package org.scalasteward.core.buildtool.mill import better.files.File import cats.effect.MonadCancelThrow import cats.syntax.all._ +import org.scalasteward.core.buildtool.bsp.{BspExtractor, BspServerType} import org.scalasteward.core.buildtool.mill.MillAlg._ import org.scalasteward.core.buildtool.{BuildRoot, BuildToolAlg} import org.scalasteward.core.data._ @@ -27,6 +28,7 @@ import org.scalasteward.core.util.Nel import org.typelevel.log4cats.Logger final class MillAlg[F[_]](defaultResolver: Resolver)(implicit + bspExtractor: BspExtractor[F], fileAlg: FileAlg[F], override protected val logger: Logger[F], processAlg: ProcessAlg[F], @@ -57,7 +59,10 @@ final class MillAlg[F[_]](defaultResolver: Resolver)(implicit for { buildRootDir <- workspaceAlg.buildRootDir(buildRoot) millBuildVersion <- getMillVersion(buildRootDir) - dependencies <- getProjectDependencies(buildRootDir, millBuildVersion) + useBsp = false + dependencies <- + if (useBsp) bspExtractor.getDependencies(BspServerType.Mill, buildRoot) + else getProjectDependencies(buildRootDir, millBuildVersion) millBuildDeps = millBuildVersion.toSeq.map(version => Scope(List(millMainArtifact(version)), List(defaultResolver)) ) diff --git a/modules/core/src/main/scala/org/scalasteward/core/buildtool/scalacli/ScalaCliAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/buildtool/scalacli/ScalaCliAlg.scala index 10e3da4015..21061cb18a 100644 --- a/modules/core/src/main/scala/org/scalasteward/core/buildtool/scalacli/ScalaCliAlg.scala +++ b/modules/core/src/main/scala/org/scalasteward/core/buildtool/scalacli/ScalaCliAlg.scala @@ -18,6 +18,7 @@ package org.scalasteward.core.buildtool.scalacli import cats.Monad import cats.syntax.all._ +import org.scalasteward.core.buildtool.bsp.{BspExtractor, BspServerType} import org.scalasteward.core.buildtool.sbt.SbtAlg import org.scalasteward.core.buildtool.{BuildRoot, BuildToolAlg} import org.scalasteward.core.data.Scope @@ -50,6 +51,7 @@ object ScalaCliAlg { } final class ScalaCliAlg[F[_]](implicit + bspExtractor: BspExtractor[F], fileAlg: FileAlg[F], gitAlg: GitAlg[F], override protected val logger: Logger[F], @@ -68,8 +70,11 @@ final class ScalaCliAlg[F[_]](implicit .map(_.exists(path => path.startsWith(buildRootPath) && extensions.exists(path.endsWith))) } - override def getDependencies(buildRoot: BuildRoot): F[List[Scope.Dependencies]] = - getDependenciesViaSbtExport(buildRoot) + override def getDependencies(buildRoot: BuildRoot): F[List[Scope.Dependencies]] = { + val useBsp = false + if (useBsp) bspExtractor.getDependencies(BspServerType.ScalaCli, buildRoot) + else getDependenciesViaSbtExport(buildRoot) + } private def getDependenciesViaSbtExport(buildRoot: BuildRoot): F[List[Scope.Dependencies]] = for { diff --git a/modules/core/src/main/scala/org/scalasteward/core/data/ArtifactId.scala b/modules/core/src/main/scala/org/scalasteward/core/data/ArtifactId.scala index 4126844ace..0254c52fe6 100644 --- a/modules/core/src/main/scala/org/scalasteward/core/data/ArtifactId.scala +++ b/modules/core/src/main/scala/org/scalasteward/core/data/ArtifactId.scala @@ -33,6 +33,13 @@ object ArtifactId { def apply(name: String, crossName: String): ArtifactId = ArtifactId(name, Some(crossName)) + def from(maybeCrossName: String): ArtifactId = + maybeCrossName.split('_').toList match { + case Nil => ArtifactId(maybeCrossName) + case _ :: Nil => ArtifactId(maybeCrossName) + case list @ x :: _ => ArtifactId(list.find(_.nonEmpty).getOrElse(x), maybeCrossName) + } + implicit val artifactIdCodec: Codec[ArtifactId] = deriveCodec diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 819b7993c0..218df8bee3 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -1,8 +1,10 @@ import sbt._ object Dependencies { + val bazelBsp = "org.jetbrains.bsp" % "bazel-bsp" % "3.1.0" val bcprovJdk15to18 = "org.bouncycastle" % "bcprov-jdk15to18" % "1.77" val betterFiles = "com.github.pathikrit" %% "better-files" % "3.9.2" + val bsp4j = "ch.epfl.scala" % "bsp4j" % "2.1.0-M4" val catsEffect = "org.typelevel" %% "cats-effect" % "3.5.2" val catsCore = "org.typelevel" %% "cats-core" % "2.10.0" val catsLaws = "org.typelevel" %% "cats-laws" % catsCore.revision diff --git a/repos.md b/repos.md index 39136251d2..44aa1e9979 100644 --- a/repos.md +++ b/repos.md @@ -10,5 +10,7 @@ All lines that do not start with a hyphen and space are ignored. - scala-steward-org/sbt-plugin - scala-steward-org/scala-steward - scala-steward-org/test-repo-1 +- scala-steward-org/test-repo-1:bleep-test - scala-steward-org/test-repo-1:scala-cli-test - scala-steward-org/test-repo-2 +- scala-steward-org/test-repo-2:bazel