Skip to content

Commit a767592

Browse files
committed
Added new implementation for SBT importer with support for cross-platform and cross version projects
Cross-platform support is limited to projects using `sbt-crossproject` plugin and was achieved with custom layout supertypes that replicate the project structure used by the plugin. Project settings are imported by running a custom script in the SBT shell. This means that the values imported are for the default Scala version used in the project. Post import, running Mill tasks with a different version may result in failures. For now, a warning message is logged notifying the user of this possibility.
1 parent f6c29a9 commit a767592

33 files changed

+1523
-98
lines changed
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package mill.init.migrate
2+
3+
import upickle.default.{ReadWriter, macroRW}
4+
5+
sealed trait ModuleConfig
6+
object ModuleConfig {
7+
implicit val rw: ReadWriter[ModuleConfig] = macroRW
8+
}
9+
10+
case class CoursierModuleConfig(repositories: Seq[String] = Nil) extends ModuleConfig
11+
object CoursierModuleConfig {
12+
implicit val rw: ReadWriter[CoursierModuleConfig] = macroRW
13+
}
14+
15+
case class JavaModuleConfig(
16+
mvnDeps: Seq[String] = Nil,
17+
compileMvnDeps: Seq[String] = Nil,
18+
runMvnDeps: Seq[String] = Nil,
19+
moduleDeps: Seq[JavaModuleConfig.ModuleDep] = Nil,
20+
compileModuleDeps: Seq[JavaModuleConfig.ModuleDep] = Nil,
21+
runModuleDeps: Seq[JavaModuleConfig.ModuleDep] = Nil,
22+
javacOptions: Seq[String] = Nil
23+
) extends ModuleConfig
24+
object JavaModuleConfig {
25+
case class ModuleDep(segments: Seq[String], crossArgs: Map[Int, String] = Map())
26+
object ModuleDep {
27+
implicit val rw: ReadWriter[ModuleDep] = macroRW
28+
}
29+
implicit val rw: ReadWriter[JavaModuleConfig] = macroRW
30+
}
31+
32+
case class PublishModuleConfig(
33+
pomSettings: PublishModuleConfig.PomSettings,
34+
publishVersion: String,
35+
versionScheme: Option[String] = None
36+
) extends ModuleConfig
37+
object PublishModuleConfig {
38+
case class Artifact(group: String, id: String, verison: String)
39+
object Artifact {
40+
implicit val rw: ReadWriter[Artifact] = macroRW
41+
}
42+
case class License(
43+
id: String,
44+
name: String,
45+
url: String = "",
46+
isOsiApproved: Boolean = false,
47+
isFsfLibre: Boolean = false,
48+
distribution: String = ""
49+
)
50+
object License {
51+
implicit val rw: ReadWriter[License] = macroRW
52+
}
53+
case class VersionControl(
54+
browsableRepository: Option[String] = None,
55+
connection: Option[String] = None,
56+
developerConnection: Option[String] = None,
57+
tag: Option[String] = None
58+
)
59+
object VersionControl {
60+
implicit val rw: ReadWriter[VersionControl] = macroRW
61+
}
62+
case class Developer(
63+
id: String,
64+
name: String,
65+
url: String,
66+
organization: Option[String] = None,
67+
organizationUrl: Option[String] = None
68+
)
69+
object Developer {
70+
implicit val rw: ReadWriter[Developer] = macroRW
71+
}
72+
case class PomSettings(
73+
description: String,
74+
organization: String,
75+
url: String,
76+
licenses: Seq[License] = Nil,
77+
versionControl: VersionControl = VersionControl(),
78+
developers: Seq[Developer] = Nil
79+
)
80+
object PomSettings {
81+
implicit val rw: ReadWriter[PomSettings] = macroRW
82+
}
83+
sealed trait VersionScheme
84+
object VersionScheme {
85+
case object Early
86+
}
87+
88+
implicit val rw: ReadWriter[PublishModuleConfig] = macroRW
89+
}
90+
91+
case class ScalaModuleConfig(
92+
scalaVersion: String = "",
93+
scalacOptions: Seq[String] = Nil,
94+
scalacPluginMvnDeps: Seq[String] = Nil
95+
) extends ModuleConfig
96+
object ScalaModuleConfig {
97+
implicit val rw: ReadWriter[ScalaModuleConfig] = macroRW
98+
}
99+
100+
case class ScalaJSModuleConfig(scalaJSVersion: String = "") extends ModuleConfig
101+
object ScalaJSModuleConfig {
102+
implicit val rw: ReadWriter[ScalaJSModuleConfig] = macroRW
103+
}
104+
105+
case class ScalaNativeModuleConfig(scalaNativeVersion: String = "") extends ModuleConfig
106+
object ScalaNativeModuleConfig {
107+
implicit val rw: ReadWriter[ScalaNativeModuleConfig] = macroRW
108+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package mill.init.migrate
2+
3+
import upickle.default.{ReadWriter, macroRW}
4+
5+
case class ModuleRepr(main: ModuleTypedef = ModuleTypedef(), test: Option[ModuleTypedef] = None)
6+
object ModuleRepr {
7+
implicit val rw: ReadWriter[ModuleRepr] = macroRW
8+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package mill.init.migrate
2+
3+
import upickle.default.{ReadWriter, macroRW}
4+
5+
case class ModuleTypedef(name: String, supertypes: Seq[String], configs: Seq[ModuleConfig]) {
6+
7+
def extend(supertypes: String*): ModuleTypedef =
8+
copy(supertypes = this.supertypes ++ supertypes)
9+
}
10+
object ModuleTypedef {
11+
12+
def apply(name: String = "package", configs: Seq[ModuleConfig] = Nil): ModuleTypedef =
13+
apply(name, computeSupertypes(configs), configs)
14+
15+
def computeSupertypes(configs: Seq[ModuleConfig]) = {
16+
var supertypes = configs.collectFirst {
17+
case _: ScalaJSModuleConfig => "ScalaJSModule"
18+
case _: ScalaNativeModuleConfig => "ScalaNativeModule"
19+
}.toSeq
20+
if (supertypes.isEmpty && configs.exists(_.isInstanceOf[ScalaModuleConfig]))
21+
supertypes :+= "ScalaModule"
22+
if (configs.exists(_.isInstanceOf[PublishModuleConfig])) supertypes :+= "PublishModule"
23+
if (supertypes.isEmpty && configs.exists(_.isInstanceOf[JavaModuleConfig]))
24+
supertypes :+= "JavaModule"
25+
if (supertypes.isEmpty && configs.exists(_.isInstanceOf[CoursierModuleConfig]))
26+
supertypes :+= "CoursierModule"
27+
if (supertypes.isEmpty) Seq("Module") else supertypes
28+
}
29+
30+
def computeTestSupertypes(mainSupertypes: Seq[String]) = {
31+
var supertypes = mainSupertypes.collectFirst {
32+
case "ScalaJSModule" => "ScalaJSTests"
33+
case "ScalaNativeModule" => "ScalaNativeTests"
34+
}.toSeq
35+
if (supertypes.isEmpty && mainSupertypes.contains("ScalaModule"))
36+
supertypes :+= "ScalaTests"
37+
if (supertypes.isEmpty && mainSupertypes.contains("JavaModule"))
38+
supertypes :+= "JavaTests"
39+
supertypes
40+
}
41+
42+
val testSupertypeByDepOrg = Map(
43+
"junit" -> "TestModule.Junit4",
44+
"org.junit.jupiter" -> "TestModule.Junit5",
45+
"org.testng" -> "TestModule.TestNg",
46+
"org.scalatest" -> "TestModule.ScalaTest",
47+
"org.specs2" -> "TestModule.Specs2",
48+
"com.lihaoyi" -> "TestModule.Utest",
49+
"org.scalameta" -> "TestModule.Munit",
50+
"com.disneystreaming" -> "TestModule.Weaver",
51+
"dev.zio" -> "TestModule.ZioTest",
52+
"org.scalacheck" -> "TestModule.ScalaCheck"
53+
)
54+
55+
implicit val rw: ReadWriter[ModuleTypedef] = macroRW
56+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package mill.init.migrate
2+
3+
import upickle.default.{ReadWriter, macroRW}
4+
5+
case class Tree[+A](root: A, children: Seq[Tree[A]] = Nil) {
6+
7+
def iterator: Iterator[A] = Iterator(root) ++ children.iterator.flatMap(_.iterator)
8+
9+
def map[B](f: A => B): Tree[B] = Tree(f(root), children.map(_.map(f)))
10+
11+
def transform[B](f: (A, Seq[Tree[B]]) => Tree[B]): Tree[B] = f(root, children.map(_.transform(f)))
12+
}
13+
object Tree {
14+
15+
def gen[S, A](init: S)(step: S => (A, Seq[S])): Tree[A] = {
16+
def recurse(state: S): Tree[A] = {
17+
val (a, states) = step(state)
18+
Tree(a, states.map(recurse))
19+
}
20+
recurse(init)
21+
}
22+
23+
implicit def rw[A: ReadWriter]: ReadWriter[Tree[A]] = macroRW
24+
}

libs/init/migrate/package.mill

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package build.libs.init.migrate
2+
3+
import mill.api.{Cross, PathRef, Task}
4+
import mill.contrib.buildinfo.BuildInfo
5+
import mill.scalalib.Assembly.Rule
6+
import mill.scalalib.ScalaModule
7+
import millbuild.*
8+
9+
object `package` extends MillPublishScalaModule { migrate =>
10+
11+
def moduleDeps = Seq(api(Deps.scalaVersion), build.core.internal, build.libs.util)
12+
13+
object api extends Cross[ApiModule](Deps.sbtScalaVersion212, Deps.scalaVersion)
14+
trait ApiModule extends MillPublishCrossScalaModule {
15+
def mvnDeps = Seq(Deps.upickle)
16+
}
17+
18+
object sbt extends MillPublishScalaModule with BuildInfo {
19+
def moduleDeps = Seq(api(Deps.scalaVersion), migrate)
20+
def sbtScriptJarResource = Task {
21+
os.copy(exportscript.assembly().path, Task.dest / "exportscript-assembly.jar")
22+
PathRef(Task.dest)
23+
}
24+
def resources = Task { super.resources() ++ Seq(sbtScriptJarResource()) }
25+
def buildInfoPackageName = "mill.init.migrate.sbt"
26+
def buildInfoMembers = Seq(
27+
BuildInfo.Value("exportscriptAssemblyResource", "/exportscript-assembly.jar")
28+
)
29+
30+
object api extends Cross[ApiModule](Deps.sbtScalaVersion212, Deps.scalaVersion)
31+
trait ApiModule extends MillPublishCrossScalaModule {
32+
def moduleDeps = Seq(migrate.api())
33+
}
34+
35+
object exportscript extends ScalaModule {
36+
def scalaVersion = Deps.sbtScalaVersion212
37+
def mvnDeps = Seq(Deps.osLib)
38+
def moduleDeps = Seq(api(Deps.sbtScalaVersion212))
39+
def compileMvnDeps = Seq(Deps.sbt)
40+
def assemblyRules = Seq(Rule.ExcludePattern("scala\\.*"))
41+
}
42+
}
43+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package mill.init.migrate
2+
package sbt
3+
4+
import upickle.default.{ReadWriter, macroRW}
5+
6+
case class SbtModule(
7+
moduleDir: Seq[String],
8+
baseDir: Seq[String],
9+
platformCrossType: Option[String],
10+
crossScalaVersions: Seq[String],
11+
module: ModuleRepr
12+
)
13+
14+
object SbtModule {
15+
16+
def apply(
17+
moduleDir: Seq[String],
18+
baseDir: Seq[String],
19+
platformCrossType: Option[String],
20+
crossScalaVersions: Seq[String],
21+
useVersionRanges: Boolean,
22+
mainConfigs: Seq[ModuleConfig],
23+
testModuleName: String,
24+
testModuleBase: Option[String],
25+
testConfigs: Seq[ModuleConfig]
26+
): SbtModule = {
27+
val isCrossPlatform = moduleDir != baseDir
28+
val isCrossVersion = crossScalaVersions.length > 1
29+
val layoutType =
30+
if (isCrossPlatform && isCrossVersion) "CrossSbtPlatformModule"
31+
else if (isCrossPlatform) "SbtPlatformModule"
32+
else if (isCrossVersion) "CrossSbtModule"
33+
else "SbtModule"
34+
var main = if (isCrossPlatform) ModuleTypedef(baseDir.last, mainConfigs)
35+
else ModuleTypedef(configs = mainConfigs)
36+
main = main.extend(layoutType)
37+
if (isCrossVersion && useVersionRanges) main = main.extend("CrossScalaVersionRanges")
38+
val test = testModuleBase.map(testSupertype0 =>
39+
ModuleTypedef(
40+
testModuleName,
41+
ModuleTypedef.computeTestSupertypes(main.supertypes),
42+
testConfigs
43+
).extend(testSupertypeByLayout(layoutType), testSupertype0)
44+
)
45+
SbtModule(
46+
moduleDir,
47+
baseDir,
48+
platformCrossType,
49+
crossScalaVersions,
50+
ModuleRepr(main, test)
51+
)
52+
}
53+
54+
val testSupertypeByLayout = Map(
55+
"SbtModule" -> "SbtTests",
56+
"CrossSbtModule" -> "CrossSbtTests",
57+
"SbtPlatformModule" -> "SbtPlatformTests",
58+
"CrossSbtPlatformModule" -> "CrossSbtPlatformTests"
59+
)
60+
61+
implicit val rw: ReadWriter[SbtModule] = macroRW
62+
}

0 commit comments

Comments
 (0)