Repository: scala-steward-org/scala-steward
Pull request #2821 (2022-12-13 17:05:33)

Refactored MillAlg to use external plugin project
We now generate a version specific predef file, to make
sure we load the correct plugin artifact.

Removed internal mill-plugin module code.

This PR finalizes #2704 .


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4700fa0e..199afb4d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -68,7 +68,7 @@ jobs:
         uses: codecov/codecov-action@v3
 
       - name: Compress target directories
-        run: tar cf targets.tar target modules/docs/.jvm/target modules/mill-plugin/.jvm/target modules/sbt-plugin/.jvm/target modules/core/.jvm/target modules/benchmark/.jvm/target project/target
+        run: tar cf targets.tar target modules/docs/.jvm/target modules/sbt-plugin/.jvm/target modules/core/.jvm/target modules/benchmark/.jvm/target project/target
 
       - name: Upload target directories
         uses: actions/upload-artifact@v2
diff --git a/build.sbt b/build.sbt
index f1bf05c4..d7a0b06f 100644
--- a/build.sbt
+++ b/build.sbt
@@ -70,7 +70,7 @@ ThisBuild / evictionErrorLevel := Level.Info
 
 lazy val root = project
   .in(file("."))
-  .aggregate(benchmark.jvm, core.jvm, docs.jvm, `sbt-plugin`.jvm, `mill-plugin`.jvm)
+  .aggregate(benchmark.jvm, core.jvm, docs.jvm, `sbt-plugin`.jvm)
   .settings(commonSettings)
   .settings(noPublishSettings)
 
@@ -162,12 +162,9 @@ lazy val core = myCrossProject("core")
       BuildInfoKey.map(`sbt-plugin`.jvm / moduleRootPkg) { case (_, v) =>
         "sbtPluginModuleRootPkg" -> v
       },
-      BuildInfoKey.map(`mill-plugin`.jvm / moduleName) { case (_, v) =>
-        "millPluginModuleName" -> v
-      },
-      BuildInfoKey.map(`mill-plugin`.jvm / moduleRootPkg) { case (_, v) =>
-        "millPluginModuleRootPkg" -> v
-      }
+      BuildInfoKey("millPluginArtifactName" -> Dependencies.scalaStewardMillPluginArtifactName),
+      BuildInfoKey("millPluginVersion" -> Dependencies.scalaStewardMillPlugin.revision),
+      BuildInfoKey("millPluginModuleRootPkg" -> "org.scalasteward.mill.plugin")
     ),
     buildInfoPackage := moduleRootPkg.value,
     initialCommands += s"""
@@ -240,13 +237,6 @@ lazy val `sbt-plugin` = myCrossProject("sbt-plugin")
     sbtPlugin := true
   )
 
-lazy val `mill-plugin` = myCrossProject("mill-plugin")
-  .settings(
-    crossScalaVersions := Seq(Scala213, Scala212),
-    libraryDependencies += Dependencies.millScalalib.value % Provided,
-    scalacOptions -= "-Xfatal-warnings"
-  )
-
 /// settings
 
 def myCrossProject(name: String): CrossProject =
@@ -321,7 +311,7 @@ lazy val dockerSettings = Def.settings(
     val sbtTgz = s"sbt-$sbtVer.tgz"
     val sbtUrl = s"https://github.com/sbt/sbt/releases/download/v$sbtVer/$sbtTgz"
     val millBin = s"$binDir/mill"
-    val millVer = Dependencies.millVersion.value
+    val millVer = Dependencies.millVersion
     val millUrl =
       s"https://github.com/lihaoyi/mill/releases/download/${millVer.split("-").head}/$millVer"
     val coursierBin = s"$binDir/coursier"
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 fa46b8e4..200c19f0 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
@@ -43,7 +43,8 @@ final class MillAlg[F[_]](implicit
     for {
       buildRootDir <- workspaceAlg.buildRootDir(buildRoot)
       predef = buildRootDir / "scala-steward.sc"
-      extracted <- fileAlg.createTemporarily(predef, content).surround {
+      millBuildVersion <- getMillVersion(buildRootDir)
+      extracted <- fileAlg.createTemporarily(predef, content(millBuildVersion)).surround {
         val command = Nel("mill", List("-i", "-p", predef.toString, "show", extractDeps))
         processAlg.execSandboxed(command, buildRootDir)
       }
@@ -51,7 +52,6 @@ final class MillAlg[F[_]](implicit
         parser.parseModules(extracted.dropWhile(!_.startsWith("{")).mkString("\n"))
       )
       dependencies = parsed.map(module => Scope(module.dependencies, module.repositories))
-      millBuildVersion <- getMillVersion(buildRootDir)
       millBuildDeps = millBuildVersion.toSeq.map(version =>
         Scope(List(millMainArtifact(version)), List(millMainResolver))
       )
@@ -83,14 +83,28 @@ final class MillAlg[F[_]](implicit
 }
 
 object MillAlg {
-  private val content =
-    s"""|import coursierapi.MavenRepository
-        |
-        |interp.repositories() ++= Seq(
-        |  MavenRepository.of("https://oss.sonatype.org/content/repositories/snapshots/")
-        |)
-        |interp.load.ivy("${org.scalasteward.core.BuildInfo.organization}" %% "${org.scalasteward.core.BuildInfo.millPluginModuleName}" % "${org.scalasteward.core.BuildInfo.version}")
-        |""".stripMargin
+  private[mill] def content(millVersion: Option[Version]) = {
+    def rawContent(millBinPlatform: String) =
+      s"""|import coursierapi.MavenRepository
+          |
+          |interp.repositories() ++= Seq(
+          |  MavenRepository.of("https://oss.sonatype.org/content/repositories/snapshots/")
+          |)
+          |interp.load.ivy("${org.scalasteward.core.BuildInfo.organization}" %% "${org.scalasteward.core.BuildInfo.millPluginArtifactName}_mill${millBinPlatform}" % "${org.scalasteward.core.BuildInfo.millPluginVersion}")
+          |""".stripMargin
+
+    millVersion match {
+      case None => rawContent("$MILL_BIN_PLATFORM")
+      case Some(millVersion) =>
+        millVersion.value.trim.split("[.]", 3).take(2) match {
+          // We support these platform, but we can't take the $$MILL_BIN_PLATFORM support for granted
+          case Array("0", "6")       => rawContent("0.6")
+          case Array("0", "7" | "8") => rawContent("0.7")
+          case Array("0", "9")       => rawContent("0.9")
+          case _                     => rawContent("$MILL_BIN_PLATFORM")
+        }
+    }
+  }
 
   val extractDeps: String =
     s"${org.scalasteward.core.BuildInfo.millPluginModuleRootPkg}.StewardPlugin/extractDeps"
diff --git a/modules/mill-plugin/src/main/scala-2.12/org/scalasteward/mill/plugin/package.scala b/modules/mill-plugin/src/main/scala-2.12/org/scalasteward/mill/plugin/package.scala
deleted file mode 100644
index 4e41512c..00000000
--- a/modules/mill-plugin/src/main/scala-2.12/org/scalasteward/mill/plugin/package.scala
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright 2018-2022 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.mill
-
-package object plugin {
-  implicit class AggOps[A](agg: mill.api.Loose.Agg[A]) {
-    def iterator = agg.toSeq.iterator
-  }
-}
diff --git a/modules/mill-plugin/src/main/scala/org/scalasteward/mill/plugin/StewardPlugin.scala b/modules/mill-plugin/src/main/scala/org/scalasteward/mill/plugin/StewardPlugin.scala
deleted file mode 100644
index c3b211a7..00000000
--- a/modules/mill-plugin/src/main/scala/org/scalasteward/mill/plugin/StewardPlugin.scala
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright 2018-2022 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.mill.plugin
-
-import coursier.core.{Authentication, Repository}
-import coursier.ivy.IvyRepository
-import coursier.maven.MavenRepository
-import mill._
-import mill.define.{Discover, ExternalModule}
-import mill.eval.Evaluator
-import mill.scalalib._
-import ujson._
-
-object StewardPlugin extends ExternalModule {
-
-  implicit def millScoptEvaluatorReads[E]: mill.main.EvaluatorScopt[E] =
-    new mill.main.EvaluatorScopt[E]()
-
-  def extractDeps(ev: Evaluator) =
-    T.command {
-      val modules = T.traverse(findModules(ev))(toModuleDep)
-      T.task {
-        val m = modules()
-        Obj(
-          "modules" -> Arr.from(m.map(_.toJson))
-        )
-      }
-    }
-
-  def findModules(ev: Evaluator) =
-    ev.rootModule.millInternal.modules.collect { case j: JavaModule => j }
-
-  def toModuleDep(m: JavaModule) = {
-    val artifactMods = m match {
-      case scalaMod: ScalaModule =>
-        T.task(
-          Some(
-            (scalaMod.artifactScalaVersion(), scalaMod.scalaVersion(), scalaMod.platformSuffix())
-          )
-        )
-      case _ => T.task(None)
-    }
-    val dependencies = T.task {
-      val ivy = m.ivyDeps()
-      val mod = artifactMods()
-      ivy.iterator.toSeq.map(Dependency.fromDep(_, mod))
-    }
-
-    T.task {
-      val resolvers = m.repositories.map(Repo).filterNot(_.isLocal)
-      val deps = dependencies()
-      ModuleDependencies(m.millModuleSegments.render, resolvers, deps)
-    }
-  }
-
-  lazy val millDiscover: Discover[StewardPlugin.this.type] = Discover[this.type]
-
-  case class ArtifactId(
-      name: String,
-      maybeCrossName: Option[String]
-  ) {
-    def toJson =
-      Obj(
-        "name" -> Str(name),
-        "maybeCrossName" -> maybeCrossName.map(Str).getOrElse(Null)
-      )
-  }
-
-  case class Dependency(
-      groupId: String,
-      artifactId: ArtifactId,
-      version: String
-  ) {
-    def toJson =
-      Obj(
-        "groupId" -> Str(groupId),
-        "artifactId" -> artifactId.toJson,
-        "version" -> Str(version)
-      )
-  }
-
-  object Dependency {
-    def fromDep(dep: Dep, modifiers: Option[(String, String, String)]) = {
-      val artifactId = ArtifactId(
-        dep.dep.module.name.value,
-        modifiers.map { case (binary, full, platform) =>
-          dep.artifactName(binary, full, platform)
-        }
-      )
-      Dependency(dep.dep.module.organization.value, artifactId, dep.dep.version)
-    }
-  }
-
-  case class Repo(repository: Repository) {
-    def isLocal =
-      repository match {
-        case repository: IvyRepository   => repository.pattern.string.startsWith("file")
-        case repository: MavenRepository => repository.root.startsWith("file")
-        case _                           => true
-      }
-
-    val headerJson: Function1[(String, String), Obj] = { case ((key, value)) =>
-      Obj(
-        "key" -> key,
-        "value" -> value
-      )
-    }
-
-    val authJson = (a: Authentication) =>
-      Obj(
-        "user" -> Str(a.user),
-        "pass" -> a.passwordOpt.map(Str).getOrElse(Null),
-        "realm" -> a.realmOpt.map(Str).getOrElse(Null)
-      )
-
-    def toJson =
-      repository match {
-        case m: MavenRepository =>
-          Obj(
-            "url" -> Str(m.root),
-            "type" -> Str("maven"),
-            "auth" -> m.authentication
-              .map(authJson)
-              .getOrElse(Null),
-            "headers" -> m.authentication
-              .map(headers => Arr(headers.httpHeaders.map(headerJson)))
-              .getOrElse(Null)
-          )
-        case ivy: IvyRepository =>
-          Obj(
-            "pattern" -> Str(ivy.pattern.string),
-            "type" -> Str("ivy"),
-            "auth" -> ivy.authentication
-              .map(authJson)
-              .getOrElse(Null),
-            "headers" -> ivy.authentication
-              .map(headers => Arr(headers.httpHeaders.map(headerJson)))
-              .getOrElse(Null)
-          )
-        case _ => Null
-      }
-  }
-
-  case class ModuleDependencies(
-      name: String,
-      resolvers: Seq[Repo],
-      dependencies: Seq[Dependency]
-  ) {
-    def toJson =
-      Obj(
-        "name" -> Str(name),
-        "repositories" -> Arr.from(resolvers.map(_.toJson).filterNot(_.isNull)),
-        "dependencies" -> Arr.from(dependencies.map(_.toJson))
-      )
-  }
-
-}
diff --git a/project/Dependencies.scala b/project/Dependencies.scala
index aa4a242e..e9fdf6e6 100644
--- a/project/Dependencies.scala
+++ b/project/Dependencies.scala
@@ -33,8 +33,10 @@ object Dependencies {
   val jjwtApi = "io.jsonwebtoken" % "jjwt-api" % "0.11.5"
   val jjwtImpl = "io.jsonwebtoken" % "jjwt-impl" % jjwtApi.revision
   val jjwtJackson = "io.jsonwebtoken" % "jjwt-jackson" % jjwtApi.revision
-  val millVersion = Def.setting(if (scalaBinaryVersion.value == "2.12") "0.6.3" else "0.10.10")
-  val millScalalib = Def.setting("com.lihaoyi" %% "mill-scalalib" % millVersion.value)
+  val millVersion = "0.10.10"
+  val scalaStewardMillPluginArtifactName = "scala-steward-mill-plugin"
+  val scalaStewardMillPlugin: ModuleID =
+    "org.scalasteward" % s"${scalaStewardMillPlugin}_mill10_2.13" % "0.17.0"
   val monocleCore = "dev.optics" %% "monocle-core" % "3.1.0"
   val munit = "org.scalameta" %% "munit" % "0.7.29"
   val munitCatsEffect = "org.typelevel" %% "munit-cats-effect-3" % "1.0.7"


Test patch:
diff --git a/modules/core/src/test/scala/org/scalasteward/core/buildtool/mill/MillAlgTest.scala b/modules/core/src/test/scala/org/scalasteward/core/buildtool/mill/MillAlgTest.scala
index 20b67ece..678dc2aa 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/buildtool/mill/MillAlgTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/buildtool/mill/MillAlgTest.scala
@@ -3,6 +3,7 @@ package org.scalasteward.core.buildtool.mill
 import cats.effect.unsafe.implicits.global
 import munit.FunSuite
 import org.scalasteward.core.buildtool.mill.MillAlg.extractDeps
+import org.scalasteward.core.data.Version
 import org.scalasteward.core.mock.MockConfig.config
 import org.scalasteward.core.mock.MockContext.context.millAlg
 import org.scalasteward.core.mock.MockState
@@ -32,12 +33,20 @@ class MillAlgTest extends FunSuite {
     val state = millAlg.getDependencies(buildRoot).runS(initial).unsafeRunSync()
     val expected = initial.copy(
       trace = Vector(
+        Cmd("read", s"$repoDir/.mill-version"),
         Cmd("write", predef),
         Cmd(repoDir.toString :: millCmd),
-        Cmd("rm", "-rf", predef),
-        Cmd("read", s"$repoDir/.mill-version")
+        Cmd("rm", "-rf", predef)
       )
     )
     assertEquals(state, expected)
   }
+  test("predef-content") {
+    assert(MillAlg.content(None).contains("_mill$MILL_BIN_PLATFORM"))
+    assert(MillAlg.content(Some(Version("0.6.1"))).contains("_mill0.6"))
+    assert(MillAlg.content(Some(Version("0.7.0"))).contains("_mill0.7"))
+    assert(MillAlg.content(Some(Version("0.8.0"))).contains("_mill0.7"))
+    assert(MillAlg.content(Some(Version("0.9.14"))).contains("_mill0.9"))
+    assert(MillAlg.content(Some(Version("0.10.0"))).contains("_mill$MILL_BIN_PLATFORM"))
+  }
 }