From b5d3b0a89abb8b0a945aba65fd95b572b0f295ea Mon Sep 17 00:00:00 2001 From: Frank Thomas Date: Fri, 10 Jan 2025 13:43:18 +0100 Subject: [PATCH] Extract dependencies from Gradle Version Catalogs This adds partial support for Gradle builds that use a [version catalog](https://docs.gradle.org/current/userguide/version_catalogs.html) (i.e. a `gradle/libs.versions.toml` file). Dependencies are extracted from the version catalog just by parsing the `libs.versions.toml` file. Since the version catalog only contains libraries and no resolvers, the default resolver is used for the `Scope` of these libraries. This is one reason why this Gradle support is only partial. The other is that additional dependencies and plugins that are defined in other Gradle build files are also ignored. Closes: #3534 --- build.sbt | 1 + docs/repo-specific-configuration.md | 2 +- .../core/application/Context.scala | 3 + .../core/buildtool/BuildToolDispatcher.scala | 4 +- .../core/buildtool/gradle/GradleAlg.scala | 52 +++++++++++ .../core/buildtool/gradle/gradleParser.scala | 88 +++++++++++++++++++ .../core/buildtool/gradle/package.scala | 26 ++++++ .../org/scalasteward/core/data/package.scala | 8 +- .../core/repocache/RepoCache.scala | 4 +- .../core/repoconfig/UpdatesConfig.scala | 15 ++-- .../scalasteward/core/scalafmt/package.scala | 2 +- .../buildtool/BuildToolDispatcherTest.scala | 4 +- .../core/buildtool/gradle/GradleAlgTest.scala | 33 +++++++ .../buildtool/gradle/gradleParserTest.scala | 59 +++++++++++++ .../scalasteward/core/edit/RewriteTest.scala | 38 ++++++++ project/Dependencies.scala | 1 + 16 files changed, 323 insertions(+), 17 deletions(-) create mode 100644 modules/core/src/main/scala/org/scalasteward/core/buildtool/gradle/GradleAlg.scala create mode 100644 modules/core/src/main/scala/org/scalasteward/core/buildtool/gradle/gradleParser.scala create mode 100644 modules/core/src/main/scala/org/scalasteward/core/buildtool/gradle/package.scala create mode 100644 modules/core/src/test/scala/org/scalasteward/core/buildtool/gradle/GradleAlgTest.scala create mode 100644 modules/core/src/test/scala/org/scalasteward/core/buildtool/gradle/gradleParserTest.scala diff --git a/build.sbt b/build.sbt index b053eda9bd..a13e7c59a2 100644 --- a/build.sbt +++ b/build.sbt @@ -149,6 +149,7 @@ lazy val core = myCrossProject("core") Dependencies.monocleCore, Dependencies.refined, Dependencies.scalacacheCaffeine, + Dependencies.tomlj, Dependencies.logbackClassic % Runtime, Dependencies.catsLaws % Test, Dependencies.circeLiteral % Test, diff --git a/docs/repo-specific-configuration.md b/docs/repo-specific-configuration.md index 44670f058b..e1d23c7a71 100644 --- a/docs/repo-specific-configuration.md +++ b/docs/repo-specific-configuration.md @@ -130,7 +130,7 @@ updates.allowPreReleases = [ { groupId = "com.example", artifactId="foo" } ] updates.limit = 5 # The extensions of files that should be updated. -# Default: [".mill",".sbt",".sbt.shared",".sc",".scala",".scalafmt.conf",".sdkmanrc",".yml","build.properties","mill-version","pom.xml"] +# Default: [".mill",".sbt",".sbt.shared",".sc",".scala",".scalafmt.conf",".sdkmanrc",".yml","build.properties","libs.versions.toml","mill-version","pom.xml"] updates.fileExtensions = [".scala", ".sbt", ".sbt.shared", ".sc", ".yml", ".md", ".markdown", ".txt"] # If "on-conflicts", Scala Steward will update the PR it created to resolve conflicts as 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 09ed1ac883..0c76bfa787 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,7 @@ 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.gradle.GradleAlg import org.scalasteward.core.buildtool.maven.MavenAlg import org.scalasteward.core.buildtool.mill.MillAlg import org.scalasteward.core.buildtool.sbt.SbtAlg @@ -61,6 +62,7 @@ final class Context[F[_]](implicit val filterAlg: FilterAlg[F], val forgeRepoAlg: ForgeRepoAlg[F], val gitAlg: GitAlg[F], + val gradleAlg: GradleAlg[F], val hookExecutor: HookExecutor[F], val httpJsonClient: HttpJsonClient[F], val logger: Logger[F], @@ -176,6 +178,7 @@ object Context { implicit val versionsCache: VersionsCache[F] = new VersionsCache[F](config.cacheTtl, versionsStore) implicit val updateAlg: UpdateAlg[F] = new UpdateAlg[F] + implicit val gradleAlg: GradleAlg[F] = new GradleAlg[F](config.defaultResolver) 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] 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 6635bbb170..be868f6f09 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,7 @@ package org.scalasteward.core.buildtool import cats.Monad import cats.syntax.all.* +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 +30,7 @@ import org.scalasteward.core.scalafmt.ScalafmtAlg import org.typelevel.log4cats.Logger final class BuildToolDispatcher[F[_]](implicit + gradleAlg: GradleAlg[F], logger: Logger[F], mavenAlg: MavenAlg[F], millAlg: MillAlg[F], @@ -53,7 +55,7 @@ final class BuildToolDispatcher[F[_]](implicit buildTools.traverse_(_.runMigration(buildRoot, migration)) }) - private val allBuildTools = List(mavenAlg, millAlg, sbtAlg, scalaCliAlg) + private val allBuildTools = List(gradleAlg, mavenAlg, millAlg, sbtAlg, scalaCliAlg) 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/gradle/GradleAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/buildtool/gradle/GradleAlg.scala new file mode 100644 index 0000000000..292ca5182a --- /dev/null +++ b/modules/core/src/main/scala/org/scalasteward/core/buildtool/gradle/GradleAlg.scala @@ -0,0 +1,52 @@ +/* + * Copyright 2018-2025 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 better.files.File +import cats.Monad +import cats.syntax.all.* +import org.scalasteward.core.buildtool.{BuildRoot, BuildToolAlg} +import org.scalasteward.core.data.Scope.Dependencies +import org.scalasteward.core.data.{Resolver, Scope} +import org.scalasteward.core.io.{FileAlg, WorkspaceAlg} +import org.typelevel.log4cats.Logger + +final class GradleAlg[F[_]](defaultResolver: Resolver)(implicit + fileAlg: FileAlg[F], + override protected val logger: Logger[F], + workspaceAlg: WorkspaceAlg[F], + F: Monad[F] +) extends BuildToolAlg[F] { + override def name: String = "Gradle" + + override def containsBuild(buildRoot: BuildRoot): F[Boolean] = + libsVersionsToml(buildRoot).flatMap(fileAlg.isRegularFile) + + override def getDependencies(buildRoot: BuildRoot): F[List[Dependencies]] = + libsVersionsToml(buildRoot) + .flatMap(fileAlg.readFile) + .map(_.getOrElse("")) + .map(gradleParser.parseDependenciesAndPlugins) + .map { case (dependencies, plugins) => + val ds = Option.when(dependencies.nonEmpty)(Scope(dependencies, List(defaultResolver))) + val ps = Option.when(plugins.nonEmpty)(Scope(plugins, List(pluginsResolver))) + ds.toList ++ ps.toList + } + + private def libsVersionsToml(buildRoot: BuildRoot): F[File] = + workspaceAlg.buildRootDir(buildRoot).map(_ / "gradle" / libsVersionsTomlName) +} diff --git a/modules/core/src/main/scala/org/scalasteward/core/buildtool/gradle/gradleParser.scala b/modules/core/src/main/scala/org/scalasteward/core/buildtool/gradle/gradleParser.scala new file mode 100644 index 0000000000..2670df6fa6 --- /dev/null +++ b/modules/core/src/main/scala/org/scalasteward/core/buildtool/gradle/gradleParser.scala @@ -0,0 +1,88 @@ +/* + * Copyright 2018-2025 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.implicits.* +import org.scalasteward.core.data.{ArtifactId, Dependency, GroupId, Module, Version} +import org.tomlj.{Toml, TomlTable} +import scala.jdk.CollectionConverters.* + +object gradleParser { + def parseDependenciesAndPlugins(input: String): (List[Dependency], List[Dependency]) = { + val parsed = Toml.parse(input) + val versionsTable = getTableSafe(parsed, "versions") + val librariesTable = getTableSafe(parsed, "libraries") + val pluginsTable = getTableSafe(parsed, "plugins") + + val dependencies = collectEntries(librariesTable, parseDependency(_, versionsTable)) + val plugins = collectEntries(pluginsTable, parsePlugin(_, versionsTable)) + + (dependencies, plugins) + } + + private def collectEntries[A: Ordering](table: TomlTable, f: TomlTable => Option[A]): List[A] = { + val aSet = table.entrySet().asScala.map(_.getValue).flatMap { + case t: TomlTable => f(t) + case _ => None + } + aSet.toList.sorted + } + + private def parseDependency(lib: TomlTable, versions: TomlTable): Option[Dependency] = + for { + case (groupId, artifactId) <- parseModuleObj(lib).orElse(parseModuleString(lib)) + version <- parseVersion(lib, versions) + } yield Dependency(groupId, artifactId, version) + + private def parseModuleObj(lib: TomlTable): Option[Module] = + for { + groupId <- getStringSafe(lib, "group").map(GroupId(_)) + artifactId <- getStringSafe(lib, "name").map(ArtifactId(_)) + } yield (groupId, artifactId) + + private def parseModuleString(lib: TomlTable): Option[Module] = + getStringSafe(lib, "module").flatMap { + _.split(':') match { + case Array(g, a) => Some((GroupId(g), ArtifactId(a))) + case _ => None + } + } + + private def parsePlugin(plugin: TomlTable, versions: TomlTable): Option[Dependency] = + for { + id <- getStringSafe(plugin, "id") + groupId = GroupId(id) + artifactId = ArtifactId(s"$id.gradle.plugin") + version <- parseVersion(plugin, versions) + } yield Dependency(groupId, artifactId, version) + + private def parseVersion(table: TomlTable, versions: TomlTable): Option[Version] = { + def versionString = getStringSafe(table, "version") + def versionRef = getStringSafe(table, "version.ref").flatMap(getStringSafe(versions, _)) + versionString.orElse(versionRef).map(Version.apply) + } + + private def getTableSafe(table: TomlTable, key: String): TomlTable = + Option + .when(table.contains(key) && table.isTable(key))(table.getTableOrEmpty(key)) + .getOrElse(emptyTable) + + private val emptyTable: TomlTable = Toml.parse("") + + private def getStringSafe(table: TomlTable, key: String): Option[String] = + Option.when(table.contains(key) && table.isString(key))(table.getString(key)) +} diff --git a/modules/core/src/main/scala/org/scalasteward/core/buildtool/gradle/package.scala b/modules/core/src/main/scala/org/scalasteward/core/buildtool/gradle/package.scala new file mode 100644 index 0000000000..a98de6153d --- /dev/null +++ b/modules/core/src/main/scala/org/scalasteward/core/buildtool/gradle/package.scala @@ -0,0 +1,26 @@ +/* + * Copyright 2018-2025 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 + +import org.scalasteward.core.data.Resolver + +package object gradle { + val libsVersionsTomlName = "libs.versions.toml" + + val pluginsResolver: Resolver.MavenRepository = + Resolver.MavenRepository("gradle-plugins", "https://plugins.gradle.org/m2/", None, None) +} diff --git a/modules/core/src/main/scala/org/scalasteward/core/data/package.scala b/modules/core/src/main/scala/org/scalasteward/core/data/package.scala index 1ee447fc37..a22d303cc7 100644 --- a/modules/core/src/main/scala/org/scalasteward/core/data/package.scala +++ b/modules/core/src/main/scala/org/scalasteward/core/data/package.scala @@ -17,9 +17,11 @@ package org.scalasteward.core package object data { + type Module = (GroupId, ArtifactId) + val scalaLangGroupId: GroupId = GroupId("org.scala-lang") - val scala2LangModules: List[(GroupId, ArtifactId)] = + val scala2LangModules: List[Module] = List( (scalaLangGroupId, ArtifactId("scala-compiler")), (scalaLangGroupId, ArtifactId("scala-library")), @@ -27,7 +29,7 @@ package object data { (scalaLangGroupId, ArtifactId("scalap")) ) - val scala3LangModules: List[(GroupId, ArtifactId)] = + val scala3LangModules: List[Module] = List( "scala3-compiler", "scala3-library", @@ -42,7 +44,7 @@ package object data { "tasty-core" ).map(artifactId => (scalaLangGroupId, ArtifactId(artifactId))) - val scalaLangModules: List[(GroupId, ArtifactId)] = + val scalaLangModules: List[Module] = scala2LangModules ++ scala3LangModules val scalaNextMinVersion: Version = Version("3.4.0-NIGHTLY") diff --git a/modules/core/src/main/scala/org/scalasteward/core/repocache/RepoCache.scala b/modules/core/src/main/scala/org/scalasteward/core/repocache/RepoCache.scala index e1d098db43..3bfdb43302 100644 --- a/modules/core/src/main/scala/org/scalasteward/core/repocache/RepoCache.scala +++ b/modules/core/src/main/scala/org/scalasteward/core/repocache/RepoCache.scala @@ -19,7 +19,7 @@ package org.scalasteward.core.repocache import cats.syntax.all.* import io.circe.Codec import io.circe.generic.semiauto.* -import org.scalasteward.core.data.{ArtifactId, DependencyInfo, GroupId, Scope} +import org.scalasteward.core.data.{DependencyInfo, Module, Scope} import org.scalasteward.core.git.Sha1 import org.scalasteward.core.repoconfig.RepoConfig @@ -29,7 +29,7 @@ final case class RepoCache( maybeRepoConfig: Option[RepoConfig], maybeRepoConfigParsingError: Option[String] ) { - def dependsOn(modules: List[(GroupId, ArtifactId)]): Boolean = + def dependsOn(modules: List[Module]): Boolean = dependencyInfos.exists(_.value.exists { info => modules.exists { case (groupId, artifactId) => info.dependency.groupId === groupId && diff --git a/modules/core/src/main/scala/org/scalasteward/core/repoconfig/UpdatesConfig.scala b/modules/core/src/main/scala/org/scalasteward/core/repoconfig/UpdatesConfig.scala index 235df83ac9..20fd3bfd4d 100644 --- a/modules/core/src/main/scala/org/scalasteward/core/repoconfig/UpdatesConfig.scala +++ b/modules/core/src/main/scala/org/scalasteward/core/repoconfig/UpdatesConfig.scala @@ -22,11 +22,9 @@ import eu.timepit.refined.types.numeric.NonNegInt import io.circe.generic.semiauto.deriveCodec import io.circe.refined.* import io.circe.{Codec, Decoder} -import org.scalasteward.core.buildtool.maven.pomXmlName -import org.scalasteward.core.buildtool.mill.MillAlg -import org.scalasteward.core.buildtool.sbt.buildPropertiesName +import org.scalasteward.core.buildtool.{gradle, maven, mill, sbt} import org.scalasteward.core.data.{GroupId, Update} -import org.scalasteward.core.scalafmt.scalafmtConfName +import org.scalasteward.core.scalafmt import org.scalasteward.core.update.FilterAlg.{ FilterResult, IgnoredByConfig, @@ -106,16 +104,17 @@ object UpdatesConfig { val defaultFileExtensions: Set[String] = Set( ".mill", - MillAlg.millVersionName, ".sbt", ".sbt.shared", ".sc", ".scala", - scalafmtConfName, ".sdkmanrc", ".yml", - buildPropertiesName, - pomXmlName + gradle.libsVersionsTomlName, + maven.pomXmlName, + mill.MillAlg.millVersionName, + sbt.buildPropertiesName, + scalafmt.scalafmtConfName ) val defaultLimit: Option[NonNegInt] = None diff --git a/modules/core/src/main/scala/org/scalasteward/core/scalafmt/package.scala b/modules/core/src/main/scala/org/scalasteward/core/scalafmt/package.scala index 141890d15e..55456d0673 100644 --- a/modules/core/src/main/scala/org/scalasteward/core/scalafmt/package.scala +++ b/modules/core/src/main/scala/org/scalasteward/core/scalafmt/package.scala @@ -29,7 +29,7 @@ package object scalafmt { ArtifactId(core, s"${core}_$defaultScalaBinaryVersion") } - val scalafmtModule: (GroupId, ArtifactId) = + val scalafmtModule: Module = (scalafmtGroupId, scalafmtArtifactId) def isScalafmtCoreUpdate(update: Update.Single): Boolean = diff --git a/modules/core/src/test/scala/org/scalasteward/core/buildtool/BuildToolDispatcherTest.scala b/modules/core/src/test/scala/org/scalasteward/core/buildtool/BuildToolDispatcherTest.scala index 9a5bc67634..d89d9e5e90 100644 --- a/modules/core/src/test/scala/org/scalasteward/core/buildtool/BuildToolDispatcherTest.scala +++ b/modules/core/src/test/scala/org/scalasteward/core/buildtool/BuildToolDispatcherTest.scala @@ -41,12 +41,14 @@ class BuildToolDispatcherTest extends FunSuite { } val expectedState = initial.copy(trace = - Cmd("test", "-f", s"$repoDir/pom.xml") +: + Cmd("test", "-f", s"$repoDir/gradle/libs.versions.toml") +: + Cmd("test", "-f", s"$repoDir/pom.xml") +: Cmd("test", "-f", s"$repoDir/build.sc") +: Cmd("test", "-f", s"$repoDir/build.mill") +: Cmd("test", "-f", s"$repoDir/build.mill.scala") +: Cmd("test", "-f", s"$repoDir/build.sbt") +: allGreps ++: + Cmd("test", "-f", s"$repoDir/mvn-build/gradle/libs.versions.toml") +: Cmd("test", "-f", s"$repoDir/mvn-build/pom.xml") +: Cmd("test", "-f", s"$repoDir/mvn-build/build.sc") +: Cmd("test", "-f", s"$repoDir/mvn-build/build.mill") +: diff --git a/modules/core/src/test/scala/org/scalasteward/core/buildtool/gradle/GradleAlgTest.scala b/modules/core/src/test/scala/org/scalasteward/core/buildtool/gradle/GradleAlgTest.scala new file mode 100644 index 0000000000..31118ff8fc --- /dev/null +++ b/modules/core/src/test/scala/org/scalasteward/core/buildtool/gradle/GradleAlgTest.scala @@ -0,0 +1,33 @@ +package org.scalasteward.core.buildtool.gradle + +import munit.CatsEffectSuite +import org.scalasteward.core.TestSyntax.* +import org.scalasteward.core.buildtool.BuildRoot +import org.scalasteward.core.data.{Repo, Scope} +import org.scalasteward.core.mock.MockContext.context.* +import org.scalasteward.core.mock.{MockEffOps, MockState} + +class GradleAlgTest extends CatsEffectSuite { + test("getDependencies") { + val repo = Repo("gradle-alg", "test-getDependencies") + val buildRoot = BuildRoot(repo, ".") + val buildRootDir = workspaceAlg.buildRootDir(buildRoot).unsafeRunSync() + + val initial = MockState.empty.addFiles( + buildRootDir / "gradle" / libsVersionsTomlName -> + """|[libraries] + |tomlj = { group = "org.tomlj", name = "tomlj", version = "1.1.1" } + |[plugins] + |kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version = "2.1.20-Beta1" } + |""".stripMargin + ) + val obtained = initial.flatMap(gradleAlg.getDependencies(buildRoot).runA) + val kotlinJvm = + "org.jetbrains.kotlin.jvm".g % "org.jetbrains.kotlin.jvm.gradle.plugin".a % "2.1.20-Beta1" + val expected = List( + List("org.tomlj".g % "tomlj".a % "1.1.1").withMavenCentral, + Scope(List(kotlinJvm), List(pluginsResolver)) + ) + assertIO(obtained, expected) + } +} diff --git a/modules/core/src/test/scala/org/scalasteward/core/buildtool/gradle/gradleParserTest.scala b/modules/core/src/test/scala/org/scalasteward/core/buildtool/gradle/gradleParserTest.scala new file mode 100644 index 0000000000..8b10095b9a --- /dev/null +++ b/modules/core/src/test/scala/org/scalasteward/core/buildtool/gradle/gradleParserTest.scala @@ -0,0 +1,59 @@ +package org.scalasteward.core.buildtool.gradle + +import munit.FunSuite +import org.scalasteward.core.TestSyntax.* + +class gradleParserTest extends FunSuite { + test("parseDependenciesAndPlugins: valid input") { + val input = + """|[versions] + |groovy = "3.0.5" + |checkstyle = "8.37" + | + |[libraries] + |groovy-core = { module = "org.codehaus.groovy:groovy", version.ref = "groovy" } + |groovy-json = { module = "org.codehaus.groovy:groovy-json", version.ref = "groovy" } + |groovy-nio = { module = "org.codehaus.groovy:groovy-nio", version.ref = "groovy" } + |commons-lang3 = { group = "org.apache.commons", name = "commons-lang3", version = { strictly = "[3.8, 4.0[", prefer="3.9" } } + |tomlj = { group = "org.tomlj", name = "tomlj", version = "1.1.1" } + | + |[bundles] + |groovy = ["groovy-core", "groovy-json", "groovy-nio"] + | + |[plugins] + |versions = { id = "com.github.ben-manes.versions", version = "0.45.0" } + |""".stripMargin + val obtained = gradleParser.parseDependenciesAndPlugins(input) + val expected = ( + List( + "org.codehaus.groovy".g % "groovy".a % "3.0.5", + "org.codehaus.groovy".g % "groovy-json".a % "3.0.5", + "org.codehaus.groovy".g % "groovy-nio".a % "3.0.5", + "org.tomlj".g % "tomlj".a % "1.1.1" + ), + List( + "com.github.ben-manes.versions".g % "com.github.ben-manes.versions.gradle.plugin".a % "0.45.0" + ) + ) + assertEquals(obtained, expected) + } + + test("parseDependenciesAndPlugins: empty input") { + val obtained = gradleParser.parseDependenciesAndPlugins("") + assertEquals(obtained, (List.empty, List.empty)) + } + + test("parseDependenciesAndPlugins: malformed input") { + val input = + """|versions] + |groovy = "3.0.5" + |[libraries] + |groovy-core = { module = "org.codehaus.groovy:groovy", version.ref = "groovy" + |foo = { module = "bar:qux:foo", version = "1" } + |[plugins] + |foo = "" + |""".stripMargin + val obtained = gradleParser.parseDependenciesAndPlugins(input) + assertEquals(obtained, (List.empty, List.empty)) + } +} diff --git a/modules/core/src/test/scala/org/scalasteward/core/edit/RewriteTest.scala b/modules/core/src/test/scala/org/scalasteward/core/edit/RewriteTest.scala index 5034112012..68d153a204 100644 --- a/modules/core/src/test/scala/org/scalasteward/core/edit/RewriteTest.scala +++ b/modules/core/src/test/scala/org/scalasteward/core/edit/RewriteTest.scala @@ -947,6 +947,44 @@ class RewriteTest extends FunSuite { runApplyUpdate(update, original, expected) } + test("Gradle Version Catalog") { + val update = ("org.tomlj".g % "tomlj".a % "1.0.0" %> "1.1.1").single + val original = Map( + "gradle/libs.versions.toml" -> + """|[libraries] + |tomlj = { group = "org.tomlj", name = "tomlj", version = "1.0.0" } + |""".stripMargin + ) + val expected = Map( + "gradle/libs.versions.toml" -> + """|[libraries] + |tomlj = { group = "org.tomlj", name = "tomlj", version = "1.1.1" } + |""".stripMargin + ) + runApplyUpdate(update, original, expected) + } + + test("Gradle Version Catalog with version.ref") { + val update = ("org.tomlj".g % "tomlj".a % "1.0.0" %> "1.1.1").single + val original = Map( + "gradle/libs.versions.toml" -> + """|[versions] + |tomlj = "1.0.0" + |[libraries] + |tomlj = { group = "org.tomlj", name = "tomlj", version.ref = "tomlj" } + |""".stripMargin + ) + val expected = Map( + "gradle/libs.versions.toml" -> + """|[versions] + |tomlj = "1.1.1" + |[libraries] + |tomlj = { group = "org.tomlj", name = "tomlj", version.ref = "tomlj" } + |""".stripMargin + ) + runApplyUpdate(update, original, expected) + } + private def runApplyUpdate( update: Update.Single, files: Map[String, String], diff --git a/project/Dependencies.scala b/project/Dependencies.scala index fd64f5920d..0d411b236a 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -44,4 +44,5 @@ object Dependencies { val scalaStewardMillPluginArtifactName = "scala-steward-mill-plugin" val scalaStewardMillPlugin = "org.scala-steward" % s"${scalaStewardMillPluginArtifactName}_mill0.10_2.13" % "0.18.0" + val tomlj = "org.tomlj" % "tomlj" % "1.1.1" }