From cf82b87826dc9eee773d2e8742ca26846a69917e Mon Sep 17 00:00:00 2001 From: David Gregory <2992938+DavidGregory084@users.noreply.github.com> Date: Mon, 31 Jul 2023 13:32:42 +0100 Subject: [PATCH] Try using sbt-scalafix for Scalafix build migrations --- .../core/buildtool/sbt/SbtAlg.scala | 60 ++++-- .../core/buildtool/sbt/SbtAlgTest.scala | 178 ++++++++++++++++++ 2 files changed, 225 insertions(+), 13 deletions(-) diff --git a/modules/core/src/main/scala/org/scalasteward/core/buildtool/sbt/SbtAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/buildtool/sbt/SbtAlg.scala index 19460a3cfd..aa63728e4f 100644 --- a/modules/core/src/main/scala/org/scalasteward/core/buildtool/sbt/SbtAlg.scala +++ b/modules/core/src/main/scala/org/scalasteward/core/buildtool/sbt/SbtAlg.scala @@ -25,15 +25,15 @@ import org.scalasteward.core.buildtool.sbt.command._ import org.scalasteward.core.buildtool.{BuildRoot, BuildToolAlg} import org.scalasteward.core.coursier.VersionsCache import org.scalasteward.core.data.{Dependency, Scope, Version} -import org.scalasteward.core.edit.scalafix.{ScalafixCli, ScalafixMigration} +import org.scalasteward.core.edit.scalafix.ScalafixMigration import org.scalasteward.core.io.process.SlurpOptions import org.scalasteward.core.io.{FileAlg, FileData, ProcessAlg, WorkspaceAlg} import org.scalasteward.core.util.Nel +import org.scalasteward.core.buildtool.sbt.scalaStewardSbtScalafix final class SbtAlg[F[_]](config: Config)(implicit fileAlg: FileAlg[F], processAlg: ProcessAlg[F], - scalafixCli: ScalafixCli[F], workspaceAlg: WorkspaceAlg[F], versionsCache: VersionsCache[F], F: Concurrent[F] @@ -129,18 +129,52 @@ final class SbtAlg[F[_]](config: Config)(implicit .getVersions(Scope(sbtScalafixDependency, List(config.defaultResolver)), None) .map(_.lastOption) + private def addScalafixPluginTemporarily( + buildRootDir: File, + pluginVersion: Version, + metaBuilds: Int + ): Resource[F, Unit] = { + val plugin = scalaStewardSbtScalafix(pluginVersion) + List + .iterate(buildRootDir / project / project, metaBuilds + 1)(_ / project) + .collectFold(fileAlg.createTemporarily(_, plugin)) + } + + private def addScalacOptionsTemporarily( + buildRootDir: File, + scalacOptions: Option[Nel[String]], + metaBuilds: Int + ): Resource[F, Unit] = + scalacOptions.fold(Resource.unit[F]) { opts => + val options = scalaStewardScalafixOptions(opts.toList) + List + .iterate(buildRootDir / project, metaBuilds + 1)(_ / project) + .collectFold(fileAlg.createTemporarily(_, options)) + } + private def runBuildMigration(buildRoot: BuildRoot, migration: ScalafixMigration): F[Unit] = - for { - buildRootDir <- workspaceAlg.buildRootDir(buildRoot) - projectDir = buildRootDir / project - files0 <- ( - fileAlg.walk(buildRootDir, 1).filter(_.extension.contains(".sbt")) ++ - fileAlg.walk(projectDir, 3).filter(_.extension.exists(Set(".sbt", ".scala"))) - ).compile.toList - _ <- Nel.fromList(files0).fold(F.unit) { files1 => - scalafixCli.runMigration(buildRootDir, files1, migration) - } - } yield () + OptionT(latestSbtScalafixVersion).foreachF { pluginVersion => + for { + buildRootDir <- workspaceAlg.buildRootDir(buildRoot) + metaBuilds <- metaBuildsCount(buildRootDir) + _ <- addScalafixPluginTemporarily(buildRootDir, pluginVersion, metaBuilds).surround { + addScalacOptionsTemporarily(buildRootDir, migration.scalacOptions, metaBuilds).surround { + val scalafixCmds = migration.rewriteRules.map(rule => s"$scalafixAll $rule").toList + val slurpOptions = SlurpOptions.ignoreBufferOverflow + val commands = Nel.fromList( + List.fill(metaBuilds + 1)(List(reloadPlugins, scalafixEnable) ++ scalafixCmds).flatten + ) + commands.fold(F.unit) { cmds => + sbt( + cmds, + buildRootDir, + slurpOptions + ).void + } + } + } + } yield () + } private def sbt( sbtCommands: Nel[String], diff --git a/modules/core/src/test/scala/org/scalasteward/core/buildtool/sbt/SbtAlgTest.scala b/modules/core/src/test/scala/org/scalasteward/core/buildtool/sbt/SbtAlgTest.scala index f065d8916a..a22b7b064f 100644 --- a/modules/core/src/test/scala/org/scalasteward/core/buildtool/sbt/SbtAlgTest.scala +++ b/modules/core/src/test/scala/org/scalasteward/core/buildtool/sbt/SbtAlgTest.scala @@ -125,6 +125,184 @@ class SbtAlgTest extends FunSuite { assertEquals(state, expected) } + test("runMigrations: build migration") { + val repo = Repo("sbt-alg", "test-build-migration-1") + val buildRoot = BuildRoot(repo, ".") + val repoDir = workspaceAlg.repoDir(repo).unsafeRunSync() + val migration = ScalafixMigration( + groupId = GroupId("io.github.davidgregory084"), + artifactIds = Nel.of("sbt-tpolecat"), + newVersion = Version("0.5.0"), + rewriteRules = Nel.of("github:typelevel/sbt-tpolecat/v0_5?sha=v0.5.0"), + target = Some(ScalafixMigration.Target.Build) + ) + val initialState = MockState.empty + .addFiles( + workspace / s"store/versions/v2/https/repo1.maven.org/maven2/ch/epfl/scala/sbt-scalafix_2.12_1.0/versions.json" -> sbtScalafixVersionJson + ) + .unsafeRunSync() + val state = sbtAlg.runMigration(buildRoot, migration).runS(initialState).unsafeRunSync() + val expected = initialState.copy( + trace = Vector( + Cmd( + "read", + s"$workspace/store/versions/v2/https/repo1.maven.org/maven2/ch/epfl/scala/sbt-scalafix_2.12_1.0/versions.json" + ), + Cmd("test", "-d", s"$repoDir/project"), + Cmd("write", s"$repoDir/project/project/scala-steward-sbt-scalafix.sbt"), + Cmd.execSandboxed( + repoDir, + "sbt", + "-Dsbt.color=false", + "-Dsbt.log.noformat=true", + "-Dsbt.supershell=false", + "-Dsbt.server.forcestart=true", + s";$reloadPlugins;$scalafixEnable;$scalafixAll github:typelevel/sbt-tpolecat/v0_5?sha=v0.5.0" + ), + Cmd("rm", "-rf", s"$repoDir/project/project/scala-steward-sbt-scalafix.sbt") + ) + ) + assertEquals(state, expected) + } + + test("runMigrations: build migration with meta-build") { + val repo = Repo("sbt-alg", "test-build-migration-2") + val buildRoot = BuildRoot(repo, ".") + val repoDir = workspaceAlg.repoDir(repo).unsafeRunSync() + val migration = ScalafixMigration( + groupId = GroupId("io.github.davidgregory084"), + artifactIds = Nel.of("sbt-tpolecat"), + newVersion = Version("0.5.0"), + rewriteRules = Nel.of("github:typelevel/sbt-tpolecat/v0_5?sha=v0.5.0"), + target = Some(ScalafixMigration.Target.Build) + ) + val initialState = MockState.empty + .addFiles( + repoDir / "project" / "Dependencies.scala" -> "object Dependencies", // put anything at all into the meta-build + workspace / s"store/versions/v2/https/repo1.maven.org/maven2/ch/epfl/scala/sbt-scalafix_2.12_1.0/versions.json" -> sbtScalafixVersionJson + ) + .unsafeRunSync() + val state = sbtAlg.runMigration(buildRoot, migration).runS(initialState).unsafeRunSync() + val expected = initialState.copy( + trace = Vector( + Cmd( + "read", + s"$workspace/store/versions/v2/https/repo1.maven.org/maven2/ch/epfl/scala/sbt-scalafix_2.12_1.0/versions.json" + ), + Cmd("test", "-d", s"$repoDir/project"), + Cmd("test", "-d", s"$repoDir/project/project"), + Cmd("write", s"$repoDir/project/project/scala-steward-sbt-scalafix.sbt"), + Cmd("write", s"$repoDir/project/project/project/scala-steward-sbt-scalafix.sbt"), + Cmd.execSandboxed( + repoDir, + "sbt", + "-Dsbt.color=false", + "-Dsbt.log.noformat=true", + "-Dsbt.supershell=false", + "-Dsbt.server.forcestart=true", + s";$reloadPlugins;$scalafixEnable;$scalafixAll github:typelevel/sbt-tpolecat/v0_5?sha=v0.5.0" + + s";$reloadPlugins;$scalafixEnable;$scalafixAll github:typelevel/sbt-tpolecat/v0_5?sha=v0.5.0" + ), + Cmd("rm", "-rf", s"$repoDir/project/project/project/scala-steward-sbt-scalafix.sbt"), + Cmd("rm", "-rf", s"$repoDir/project/project/scala-steward-sbt-scalafix.sbt") + ) + ) + assertEquals(state, expected) + } + + test("runMigrations: build migration with scalacOptions") { + val repo = Repo("sbt-alg", "test-build-migration-3") + val buildRoot = BuildRoot(repo, ".") + val repoDir = workspaceAlg.repoDir(repo).unsafeRunSync() + val migration = ScalafixMigration( + groupId = GroupId("io.github.davidgregory084"), + artifactIds = Nel.of("sbt-tpolecat"), + newVersion = Version("0.5.0"), + rewriteRules = Nel.of("github:typelevel/sbt-tpolecat/v0_5?sha=v0.5.0"), + scalacOptions = Some(Nel.of("-P:semanticdb:synthetics:on")), + target = Some(ScalafixMigration.Target.Build) + ) + val initialState = MockState.empty + .addFiles( + workspace / s"store/versions/v2/https/repo1.maven.org/maven2/ch/epfl/scala/sbt-scalafix_2.12_1.0/versions.json" -> sbtScalafixVersionJson + ) + .unsafeRunSync() + val state = sbtAlg.runMigration(buildRoot, migration).runS(initialState).unsafeRunSync() + val expected = initialState.copy( + trace = Vector( + Cmd( + "read", + s"$workspace/store/versions/v2/https/repo1.maven.org/maven2/ch/epfl/scala/sbt-scalafix_2.12_1.0/versions.json" + ), + Cmd("test", "-d", s"$repoDir/project"), + Cmd("write", s"$repoDir/project/project/scala-steward-sbt-scalafix.sbt"), + Cmd("write", s"$repoDir/project/scala-steward-scalafix-options.sbt"), + Cmd.execSandboxed( + repoDir, + "sbt", + "-Dsbt.color=false", + "-Dsbt.log.noformat=true", + "-Dsbt.supershell=false", + "-Dsbt.server.forcestart=true", + s";$reloadPlugins;$scalafixEnable;$scalafixAll github:typelevel/sbt-tpolecat/v0_5?sha=v0.5.0" + ), + Cmd("rm", "-rf", s"$repoDir/project/scala-steward-scalafix-options.sbt"), + Cmd("rm", "-rf", s"$repoDir/project/project/scala-steward-sbt-scalafix.sbt") + ) + ) + assertEquals(state, expected) + } + + test("runMigrations: build migration with scalacOptions and meta-build") { + val repo = Repo("sbt-alg", "test-build-migration-4") + val buildRoot = BuildRoot(repo, ".") + val repoDir = workspaceAlg.repoDir(repo).unsafeRunSync() + val migration = ScalafixMigration( + groupId = GroupId("io.github.davidgregory084"), + artifactIds = Nel.of("sbt-tpolecat"), + newVersion = Version("0.5.0"), + rewriteRules = Nel.of("github:typelevel/sbt-tpolecat/v0_5?sha=v0.5.0"), + scalacOptions = Some(Nel.of("-P:semanticdb:synthetics:on")), + target = Some(ScalafixMigration.Target.Build) + ) + val initialState = MockState.empty + .addFiles( + repoDir / "project" / "Dependencies.scala" -> "object Dependencies", // put anything at all into the meta-build + workspace / s"store/versions/v2/https/repo1.maven.org/maven2/ch/epfl/scala/sbt-scalafix_2.12_1.0/versions.json" -> sbtScalafixVersionJson + ) + .unsafeRunSync() + val state = sbtAlg.runMigration(buildRoot, migration).runS(initialState).unsafeRunSync() + val expected = initialState.copy( + trace = Vector( + Cmd( + "read", + s"$workspace/store/versions/v2/https/repo1.maven.org/maven2/ch/epfl/scala/sbt-scalafix_2.12_1.0/versions.json" + ), + Cmd("test", "-d", s"$repoDir/project"), + Cmd("test", "-d", s"$repoDir/project/project"), + Cmd("write", s"$repoDir/project/project/scala-steward-sbt-scalafix.sbt"), + Cmd("write", s"$repoDir/project/project/project/scala-steward-sbt-scalafix.sbt"), + Cmd("write", s"$repoDir/project/scala-steward-scalafix-options.sbt"), + Cmd("write", s"$repoDir/project/project/scala-steward-scalafix-options.sbt"), + Cmd.execSandboxed( + repoDir, + "sbt", + "-Dsbt.color=false", + "-Dsbt.log.noformat=true", + "-Dsbt.supershell=false", + "-Dsbt.server.forcestart=true", + s";$reloadPlugins;$scalafixEnable;$scalafixAll github:typelevel/sbt-tpolecat/v0_5?sha=v0.5.0" + + s";$reloadPlugins;$scalafixEnable;$scalafixAll github:typelevel/sbt-tpolecat/v0_5?sha=v0.5.0" + ), + Cmd("rm", "-rf", s"$repoDir/project/project/scala-steward-scalafix-options.sbt"), + Cmd("rm", "-rf", s"$repoDir/project/scala-steward-scalafix-options.sbt"), + Cmd("rm", "-rf", s"$repoDir/project/project/project/scala-steward-sbt-scalafix.sbt"), + Cmd("rm", "-rf", s"$repoDir/project/project/scala-steward-sbt-scalafix.sbt") + ) + ) + assertEquals(state, expected) + } + private def sbtScalafixVersionJson = s"""|{ | "updatedAt" : 9999999999999,