Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
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
6 changes: 5 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ lazy val core = myCrossProject("core")
libraryDependencies ++= Seq(
Dependencies.bcprovJdk15to18,
Dependencies.betterFiles,
Dependencies.bsp4j,
Dependencies.catsCore,
Dependencies.catsEffect,
Dependencies.catsParse,
Expand Down Expand Up @@ -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"""
Expand Down Expand Up @@ -272,6 +275,7 @@ lazy val dummy = myCrossProject("dummy")
.settings(noPublishSettings)
.settings(
libraryDependencies ++= Seq(
Dependencies.bazelBsp,
Dependencies.scalaStewardMillPlugin
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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],
Expand All @@ -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]])] =
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}
Original file line number Diff line number Diff line change
@@ -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)
}
Original file line number Diff line number Diff line change
@@ -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 = ()
}
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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))
}
}
Loading
Loading