Skip to content
Open
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
cc110dd
Initial plan
Copilot Sep 12, 2025
71a3394
Update a TODO comment
ShreckYe Sep 12, 2025
852b470
Convert generic type parameters to abstract type members in BuildGenBase
Copilot Sep 12, 2025
98a7dd3
Rename `optional` in `BuildGenUtil` to `renderIfArgsNonEmpty`
ShreckYe Sep 12, 2025
5f421c2
Address all review comments: remove TODO, fix type parameters, format…
Copilot Sep 12, 2025
a309e67
Rename `Config` to `MavenAndGradleCommonConfig` in `BuildGenUtil` to …
ShreckYe Sep 12, 2025
5111c97
[autofix.ci] apply automated fixes
autofix-ci[bot] Sep 12, 2025
8098b5d
Remove redundant explicit inherited type members
ShreckYe Sep 12, 2025
3308601
Merge branch 'copilot/fix-9475931d-2457-49c4-9a71-fd7144c9d4ef' of ht…
ShreckYe Sep 12, 2025
7cf17b2
Reformat
ShreckYe Sep 12, 2025
75bb185
[autofix.ci] apply automated fixes
autofix-ci[bot] Sep 12, 2025
8b5fb6a
Remove `moduleOptionTree` which seems redundant
ShreckYe Sep 12, 2025
3572715
Review the TODO `// TODO consider filtering out projects without the …
ShreckYe Sep 13, 2025
26f1f55
Merge pull request #1 from ShreckYe/copilot/fix-9475931d-2457-49c4-9a…
ShreckYe Sep 13, 2025
2259cab
Add a missing backtick in a comment
ShreckYe Sep 13, 2025
4ab8c14
Remove the "filter out Gradle projects without the `java` plugin" TODO
ShreckYe Sep 13, 2025
855c022
Replace all Yoda conditions concerning `null`
ShreckYe Sep 13, 2025
403d495
Find and replace more Yoda conditions using GitHub Copilot
ShreckYe Sep 13, 2025
e7a8240
Fix some mistakes in commit 855c0222fd8690981b455f8748ac8d1eded4bbc0
ShreckYe Sep 15, 2025
dfe3d14
Merge branch 'BuildGenUtil-rename-Config-to-MavenAndGradleCommonConfi…
ShreckYe Sep 15, 2025
b6154dd
Merge branch 'BuildGenUtil-rename-optional-to-renderIfArgsNonEmpty' i…
ShreckYe Sep 15, 2025
c3b9483
Merge branch 'remove-redundant-moduleOptionTree' into improve-init-gu…
ShreckYe Sep 15, 2025
e781428
Merge branch 'build-gen-base-convert-type-parameters-to-abstract-type…
ShreckYe Sep 15, 2025
99f28bd
Merge branch 'sbt-ExportBuildPlugin-add-missing-backtick' into improv…
ShreckYe Sep 15, 2025
8b838ec
Merge branch 'remove-filter-out-Gradle-projects-without-java-plugin-t…
ShreckYe Sep 15, 2025
4672eef
Merge branch 'replace-yoda-conditions' into improve-init-guildgen
ShreckYe Sep 15, 2025
ae7ea49
Rename `IrBuild` to `IrModuleBuild`
ShreckYe Sep 16, 2025
a39cd8d
Update a TODO comment to an ordinary comment
ShreckYe Sep 16, 2025
ab843e4
Rename `IrTrait` to `IrBaseInfo` and replace the original references …
ShreckYe Sep 16, 2025
fb6f970
Remove remaining occurrences of `IrTrait`
ShreckYe Sep 16, 2025
53499b1
Replace remaining occurrences of `IrBuild`
ShreckYe Sep 16, 2025
d8860ef
Rename `extractScopedDeps` to `extractConfigurationDeps` in `GradleBu…
ShreckYe Sep 16, 2025
43bf2da
Update a TODO to an ordinary comment
ShreckYe Sep 16, 2025
d1f801c
Change `I` to `(BuildExport, Tree[Node[Option[Project]]])` in `SbtBui…
ShreckYe Sep 16, 2025
4932766
Fix format
ShreckYe Sep 16, 2025
d787684
Fix format
ShreckYe Sep 16, 2025
a1e2a85
[autofix.ci] apply automated fixes
autofix-ci[bot] Sep 16, 2025
cc2cacc
Remove the `D` type member
ShreckYe Sep 16, 2025
53ea96f
Rename the type members to be more specific
ShreckYe Sep 16, 2025
09027fa
Rename `_java` to `javaModel`
ShreckYe Sep 16, 2025
4d8d00a
Merge remote-tracking branch 'origin/improve-init-buildgen' into impr…
ShreckYe Sep 16, 2025
45813a1
[autofix.ci] apply automated fixes
autofix-ci[bot] Sep 16, 2025
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
41 changes: 20 additions & 21 deletions libs/init/buildgen/src/mill/main/buildgen/BuildGenBase.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ import mill.main.buildgen.BuildGenUtil.{compactBuildTree, writeBuildObject}

import scala.collection.immutable.{SortedMap, SortedSet}

/*
TODO Can we just convert all generic type parameters to abstract type members?
See https://stackoverflow.com/a/1154727/5082913.
I think abstract type members are preferred in this case.
*/
trait BuildGenBase[M, D, I] {
trait BuildGenBase {
type M
type D
type I
type C
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have more descriptive names here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, they are respectively Module, Dependency, Input, and Config.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also just noticed that D/Dependency is no longer used in the current implementation. Shall I remove it or just keep it for possible use and better readability?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's unused, remove it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have removed D and renamed M and I. C is kept as renaming it would conflict with the Config nested classes. I guess it can be renamed to something like ConfigT if needed, but I am not sure whether this is conventional in Scala.


def convertWriteOut(cfg: C, shared: BuildGenUtil.BasicConfig, input: I): Unit = {
val output = convert(input, cfg, shared)
writeBuildObject(
Expand All @@ -37,17 +36,17 @@ trait BuildGenBase[M, D, I] {
shared: BuildGenUtil.BasicConfig
): Tree[Node[BuildObject]] = {
val moduleTree = getModuleTree(input)
val moduleOptionTree = moduleTree.map(node => node.copy(value = node.value))

// for resolving moduleDeps
val moduleNodes =
moduleOptionTree.nodes().flatMap(node => node.value.map(m => node.copy(value = m))).toSeq
moduleTree.nodes().flatMap(node => node.value.map(m => node.copy(value = m))).toSeq
val moduleRefMap = getModuleFqnMap(moduleNodes)

val baseInfo =
shared.baseModule.fold(IrBaseInfo()) { getBaseInfo(input, cfg, _, moduleNodes.size) }
val baseInfo = shared.baseModule.map {
getBaseInfo(input, cfg, _, moduleNodes.size)
}

moduleOptionTree.map(optionalBuild =>
moduleTree.map(optionalBuild =>
optionalBuild.copy(value =
optionalBuild.value.fold(
BuildObject(SortedSet("mill._"), SortedMap.empty, Seq("Module"), "", "")
Expand All @@ -56,7 +55,7 @@ trait BuildGenBase[M, D, I] {
println(s"converting module $name")

val build = optionalBuild.copy(value = moduleModel)
val inner = extractIrBuild(cfg, build, moduleRefMap)
val inner = extractIrModuleBuild(cfg, build, moduleRefMap)

val isNested = optionalBuild.dirs.nonEmpty
BuildObject(
Expand All @@ -71,10 +70,10 @@ trait BuildGenBase[M, D, I] {
SortedMap((name, SortedMap(inner.scopedDeps.namedMvnDeps.toSeq*)))
),
supertypes = getSupertypes(cfg, baseInfo, build),
inner = BuildGenUtil.renderIrBuild(inner, baseInfo),
inner = BuildGenUtil.renderIrModuleBuild(inner, baseInfo),
outer =
if (isNested || baseInfo.moduleTypedef == null) ""
else BuildGenUtil.renderIrTrait(baseInfo.moduleTypedef)
if (isNested || baseInfo.isEmpty) ""
else BuildGenUtil.renderIrBaseInfo(baseInfo.get)
)
})
)
Expand All @@ -83,7 +82,7 @@ trait BuildGenBase[M, D, I] {

def extraImports: Seq[String]

def getSupertypes(cfg: C, baseInfo: IrBaseInfo, build: Node[M]): Seq[String]
def getSupertypes(cfg: C, baseInfo: Option[IrBaseInfo], build: Node[M]): Seq[String]

def getBaseInfo(
input: I,
Expand All @@ -94,18 +93,18 @@ trait BuildGenBase[M, D, I] {

def getArtifactId(moduleModel: M): String

def extractIrBuild(
def extractIrModuleBuild(
cfg: C,
// baseInfo: IrBaseInfo, // `baseInfo` is no longer needed as we compare the `IrBuild` with `IrBaseInfo`/`IrTrait` in common code now.
// baseInfo: IrBaseInfo, // `baseInfo` is no longer needed as we compare the `IrModuleBuild` with `IrBaseInfo` in common code now.
build: Node[M],
moduleFqnMap: ModuleFqnMap
): IrBuild
): IrModuleBuild
}

object BuildGenBase {
trait MavenAndGradle[M, D] extends BuildGenBase[M, D, Tree[Node[M]]] {
trait MavenAndGradle extends BuildGenBase {
type I = Tree[Node[M]]
override def getModuleTree(input: Tree[Node[M]]): Tree[Node[Option[M]]] =
// TODO consider filtering out projects without the `java` plugin applied in Gradle too
input.map(node => node.copy(value = Some(node.value)))
override def extraImports: Seq[String] = Seq()
}
Expand Down
85 changes: 39 additions & 46 deletions libs/init/buildgen/src/mill/main/buildgen/BuildGenUtil.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import scala.util.boundary
@internal
object BuildGenUtil {

def renderIrTrait(value: IrTrait): String = {
def renderIrBaseInfo(value: IrBaseInfo): String = {
import value.*

s"""trait $baseModule ${renderExtends(moduleSupertypes)} {
Expand Down Expand Up @@ -49,8 +49,7 @@ object BuildGenUtil {
/**
* @param baseInfo to compare with [[build]] and render the values only if they are different.
*/
def renderIrBuild(build: IrBuild, baseInfo: IrBaseInfo): String = {
val baseTrait = baseInfo.moduleTypedef
def renderIrModuleBuild(build: IrModuleBuild, baseInfo: Option[IrBaseInfo]): String = {
import build.*
val testModuleTypedef =
if (!hasTest) ""
Expand Down Expand Up @@ -82,22 +81,13 @@ object BuildGenUtil {

s"""${renderArtifactName(projectName, dirs)}
|
|${renderJavacOptions(
javacOptions,
if (baseTrait != null) baseTrait.javacOptions else Seq.empty
)}
|${renderJavacOptions(javacOptions, baseInfo.fold(Seq.empty)(_.javacOptions))}
|
|${renderScalaVersion(scalaVersion, if (baseTrait != null) baseTrait.scalaVersion else None)}
|${renderScalaVersion(scalaVersion, baseInfo.flatMap(_.scalaVersion))}
|
|${renderScalacOptions(
scalacOptions,
if (baseTrait != null) baseTrait.scalacOptions else None
)}
|${renderScalacOptions(scalacOptions, baseInfo.flatMap(_.scalacOptions))}
|
|${renderRepositories(
repositories,
if (baseTrait != null) baseTrait.repositories else Seq.empty
)}
|${renderRepositories(repositories, baseInfo.fold(Seq.empty)(_.repositories))}
|
|${renderBomMvnDeps(scopedDeps.mainBomMvnDeps)}
|
Expand All @@ -114,15 +104,12 @@ object BuildGenUtil {
|${renderRunModuleDeps(scopedDeps.mainRunModuleDeps)}
|
|${
if (pomSettings != (if (baseTrait != null) baseTrait.pomSettings else null))
if (pomSettings != baseInfo.map(_.pomSettings).orNull)
renderPomSettings(renderIrPom(pomSettings))
else ""
}
|
|${renderPublishVersion(
publishVersion,
if (baseTrait != null) baseTrait.publishVersion else null
)}
|${renderPublishVersion(publishVersion, baseInfo.map(_.publishVersion).orNull)}
|
|${renderPomPackaging(packaging)}
|
Expand Down Expand Up @@ -219,7 +206,7 @@ object BuildGenUtil {

childCompanions.foreach { case entry @ (objectName, childConstants) =>
val parentConstants = mergedParentCompanions.getOrElse(objectName, null)
if (null == parentConstants) mergedParentCompanions += entry
if (parentConstants == null) mergedParentCompanions += entry
else {
if (childConstants.exists { case (k, v) => v != parentConstants.getOrElse(k, v) })
boundary.break(null)
Expand All @@ -237,7 +224,7 @@ object BuildGenUtil {
children.iterator.foreach {
case child @ Tree(Node(_ :+ dir, nested), Seq()) if nested.outer.isEmpty =>
val mergedCompanions = merge(module.companions, nested.companions)
if (null == mergedCompanions) unmerged += child
if (mergedCompanions == null) unmerged += child
else {
val mergedImports = module.imports ++ nested.imports
val mergedInner = {
Expand Down Expand Up @@ -271,7 +258,7 @@ object BuildGenUtil {
pprint.Util.literalize(if (value == null) "" else value)

def escapeOption(value: String): String =
if (null == value) "None" else s"Some(\"$value\")"
if (value == null) "None" else s"Some(\"$value\")"

def renderMvnString(
group: String,
Expand All @@ -291,7 +278,7 @@ object BuildGenUtil {
}
}
val sepVersion =
if (null == version) {
if (version == null) {
println(
s"assuming $group:$artifact is a BOM dependency; if not, please specify version in the generated build file"
)
Expand Down Expand Up @@ -319,7 +306,7 @@ object BuildGenUtil {
groupArtifactVersion._2.endsWith("-bom")

def isNullOrEmpty(value: String | Null): Boolean =
null == value || value.isEmpty
value == null || value.isEmpty

val linebreak: String =
"""
Expand Down Expand Up @@ -356,11 +343,16 @@ object BuildGenUtil {
def renderVersionControl(vc: IrVersionControl): String =
s"VersionControl(${escapeOption(vc.url)}, ${escapeOption(vc.connection)}, ${escapeOption(vc.devConnection)}, ${escapeOption(vc.tag)})"

// TODO consider renaming to `renderOptionalDef` or `renderIfArgsNonEmpty`?
def optional(construct: String, args: IterableOnce[String]): String =
optional(construct + "(", args, ",", ")")
// TODO more alternative names: `renderOptional(Def)` or `render(Def)IfArgsNonEmpty`?
def renderIfArgsNonEmpty(construct: String, args: IterableOnce[String]): String =
renderIfArgsNonEmpty(construct + "(", args, ",", ")")

def optional(start: String, args: IterableOnce[String], sep: String, end: String): String = {
def renderIfArgsNonEmpty(
start: String,
args: IterableOnce[String],
sep: String,
end: String
): String = {
val itr = args.iterator
if (itr.isEmpty) ""
else itr.mkString(start, sep, end)
Expand Down Expand Up @@ -406,25 +398,25 @@ object BuildGenUtil {
else s"def artifactName = ${escape(name)}"

def renderBomMvnDeps(args: IterableOnce[String]): String =
optional("def bomMvnDeps = super.bomMvnDeps() ++ Seq", args)
renderIfArgsNonEmpty("def bomMvnDeps = super.bomMvnDeps() ++ Seq", args)

def renderMvnDeps(args: IterableOnce[String]): String =
optional("def mvnDeps = Seq", args)
renderIfArgsNonEmpty("def mvnDeps = Seq", args)

def renderModuleDeps(args: IterableOnce[String]): String =
optional("def moduleDeps = super.moduleDeps ++ Seq", args)
renderIfArgsNonEmpty("def moduleDeps = super.moduleDeps ++ Seq", args)

def renderCompileMvnDeps(args: IterableOnce[String]): String =
optional("def compileMvnDeps = Seq", args)
renderIfArgsNonEmpty("def compileMvnDeps = Seq", args)

def renderCompileModuleDeps(args: IterableOnce[String]): String =
optional("def compileModuleDeps = super.compileModuleDeps ++ Seq", args)
renderIfArgsNonEmpty("def compileModuleDeps = super.compileModuleDeps ++ Seq", args)

def renderRunMvnDeps(args: IterableOnce[String]): String =
optional("def runMvnDeps = Seq", args)
renderIfArgsNonEmpty("def runMvnDeps = Seq", args)

def renderRunModuleDeps(args: IterableOnce[String]): String =
optional("def runModuleDeps = super.runModuleDeps ++ Seq", args)
renderIfArgsNonEmpty("def runModuleDeps = super.runModuleDeps ++ Seq", args)

def renderJavacOptions(args: Seq[String], superArgs: Seq[String] = Seq.empty): String =
renderSeqTaskDefWithSuper("javacOptions", args, superArgs, "String", escape).getOrElse("")
Expand Down Expand Up @@ -455,7 +447,7 @@ object BuildGenUtil {
).getOrElse("")

def renderResources(args: IterableOnce[os.SubPath]): String =
optional(
renderIfArgsNonEmpty(
"""def resources = Task { super.resources() ++ customResources() }
|def customResources = Task.Sources(""".stripMargin,
args.iterator.map(sub => escape(sub.toString())),
Expand All @@ -464,9 +456,9 @@ object BuildGenUtil {
)

def renderPomPackaging(packaging: String): String =
if (isNullOrEmpty(packaging) || "jar" == packaging) "" // skip default
if (isNullOrEmpty(packaging) || packaging == "jar") "" // skip default
else {
val pkg = if ("pom" == packaging) "PackagingType.Pom" else escape(packaging)
val pkg = if (packaging == "pom") "PackagingType.Pom" else escape(packaging)
s"def pomPackagingType = $pkg"
}

Expand All @@ -491,7 +483,7 @@ object BuildGenUtil {
args: Seq[(String, String)]
): String = {
val tuples = args.iterator.map { case (k, v) => s"(${escape(k)}, ${escape(v)})" }
optional("def publishProperties = super.publishProperties() ++ Map", tuples)
renderIfArgsNonEmpty("def publishProperties = super.publishProperties() ++ Map", tuples)
}

def renderJvmWorker(moduleName: String): String =
Expand Down Expand Up @@ -557,15 +549,16 @@ object BuildGenUtil {
object BasicConfig {
implicit def parser: mainargs.ParserForClass[BasicConfig] = mainargs.ParserForClass[BasicConfig]
}
// TODO alternative names: `MavenAndGradleConfig`, `MavenAndGradleSharedConfig`

// TODO other alternative names to consider: `MavenAndGradleConfig`, `MavenAndGradleSharedConfig`
@mainargs.main
case class Config(
case class MavenAndGradleCommonConfig(
basicConfig: BasicConfig,
@arg(doc = "capture Maven publish properties", short = 'p')
publishProperties: Flag = Flag()
)

object Config {
implicit def configParser: mainargs.ParserForClass[Config] = mainargs.ParserForClass[Config]
object MavenAndGradleCommonConfig {
implicit def configParser: mainargs.ParserForClass[MavenAndGradleCommonConfig] =
mainargs.ParserForClass[MavenAndGradleCommonConfig]
}
}
28 changes: 6 additions & 22 deletions libs/init/buildgen/src/mill/main/buildgen/ir.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ object BuildObject {
@mill.api.experimental
case class Node[T](dirs: Seq[String], value: T)

case class IrTrait(
case class IrBaseInfo(
jvmId: Option[String],
baseModule: String,
moduleSupertypes: Seq[String],
Expand Down Expand Up @@ -77,9 +77,8 @@ case class IrLicense(
distribution: String = "repo"
)

// TODO Consider renaming to `IrModule(Build)` to disambiguate? `sbt`, for example, uses `ThisBuild` and `buildSettings` to refer to the whole build.
// TODO reuse the members in `IrTrait`?
case class IrBuild(
// TODO reuse the members in `IrBaseInfo`?
case class IrModuleBuild(
scopedDeps: IrScopedDeps,
testModule: String,
testModuleMainType: String,
Expand All @@ -101,9 +100,9 @@ case class IrBuild(
testForkDir: Option[String]
)

object IrBuild {
// TODO not used
def empty(dirs: Seq[String]) = IrBuild(
object IrModuleBuild {
// This function is not used currently but temporarily kept for possible use cases. Remove if it's ensured that it's not going to be used.
def empty(dirs: Seq[String]) = IrModuleBuild(
IrScopedDeps(),
null,
null,
Expand Down Expand Up @@ -144,21 +143,6 @@ case class IrScopedDeps(
testCompileModuleDeps: SortedSet[String] = SortedSet()
)

// TODO remove `IrBaseInfo` and just use `IrTrait` directly?
case class IrBaseInfo(
/*
javacOptions: Seq[String] = Nil,
scalaVersion: Option[String] = None,
scalacOptions: Option[Seq[String]] = None,
repositories: Seq[String] = Nil,
noPom: Boolean = true,
publishVersion: String = "",
publishProperties: Seq[(String, String)] = Nil,
*/
// TODO consider renaming directly to `trait` or `baseTrait`?
moduleTypedef: IrTrait | Null = null
)

sealed class IrDependencyType
object IrDependencyType {
case object Default extends IrDependencyType
Expand Down
Loading
Loading