Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Target type abstract to allow overriding by different concrete implementations #2402

Merged
merged 18 commits into from
Apr 3, 2023
Prev Previous commit
Next Next commit
merge
  • Loading branch information
lihaoyi committed Apr 3, 2023
commit a534e90ca012c21fd5a7644ed32a5f3b477f5b97
33 changes: 0 additions & 33 deletions example/misc/1-other-task-types/build.sc

This file was deleted.

Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@ object InvalidMetaModuleTests extends IntegrationTestSuite {
test("success"){
val res = evalStdout("resolve", "_")
assert(res.isSuccess == false)
assert(res.err.contains("Top-level module in mill-build/build.sc must be of class mill.runner.MillBuildRootModule"))
assert(res.err.contains("Root module in mill-build/build.sc must be of class mill.runner.MillBuildRootModule"))
}
}
}
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@ object MultipleTopLevelModulesTests extends IntegrationTestSuite {
test("success"){
val res = evalStdout("resolve", "_")
assert(!res.isSuccess)
assert(res.err.contains("Only one BaseModule can be defined in a build, not 2: millbuild.build$bar$,millbuild.build$foo$"))
assert(res.err.contains("Only one RootModule can be defined in a build, not 2: millbuild.build$bar$,millbuild.build$foo$"))
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

def invalidTarget = T{ "..." }

object invalidModule extends Module

object bar extends RootModule
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package mill.integration

import utest._

import scala.util.matching.Regex

object ThingsOutsideTopLevelModuleTests extends IntegrationTestSuite {
val tests = Tests {
val workspaceRoot = initWorkspace()

test("success"){
val res = evalStdout("resolve", "_")
assert(!res.isSuccess)
assert(
res.err.contains(
"RootModule bar$ cannot have other modules defined outside of it: invalidModule"
)
)
}
}
}
106 changes: 105 additions & 1 deletion main/core/src/mill/define/Discover.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
package mill.define

import language.experimental.macros
import scala.collection.mutable
import scala.reflect.macros.blackbox

/**
* Macro to walk the module tree and generate `mainargs` entrypoints for any
* `T.command` methods that it finds.
*
* Note that unlike the rest of Mill's module-handling logic which uses Java
* reflection, generation of entrypoints requires typeclass resolution, and so
* needs to be done at compile time. Thus we walk the entire module tree,
* collecting all the module `Class[_]`s we can find, and for each one generate
* the `mainargs.MainData` containing metadata and resolved typeclasses for all
* the `T.command` methods we find. This mapping from `Class[_]` to `MainData`
* can then be used later to look up the `MainData` for any module.
*/
case class Discover[T] private (value: Map[Class[_], Seq[(Int, mainargs.MainData[_, _])]]) {
private[mill] def copy(value: Map[Class[_], Seq[(Int, mainargs.MainData[_, _])]] = value)
: Discover[T] =
@@ -10,7 +24,97 @@ case class Discover[T] private (value: Map[Class[_], Seq[(Int, mainargs.MainData
object Discover {
def apply[T](value: Map[Class[_], Seq[(Int, mainargs.MainData[_, _])]]): Discover[T] =
new Discover[T](value)
def apply[T]: Discover[T] = macro mill.define.Router.applyImpl[T]
def apply[T]: Discover[T] = macro Router.applyImpl[T]

private def unapply[T](discover: Discover[T])
: Option[Map[Class[_], Seq[(Int, mainargs.MainData[_, _])]]] = Some(discover.value)

class Router(val ctx: blackbox.Context) extends mainargs.Macros(ctx) {
import c.universe._

def applyImpl[T: WeakTypeTag]: Expr[Discover[T]] = {
val seen = mutable.Set.empty[Type]
def rec(tpe: Type): Unit = {
if (!seen(tpe)) {
seen.add(tpe)
for {
m <- tpe.members
memberTpe = m.typeSignature
if memberTpe.resultType <:< typeOf[mill.define.Module] && memberTpe.paramLists.isEmpty
} rec(memberTpe.resultType)

if (tpe <:< typeOf[mill.define.Cross[_]]) {
val inner = typeOf[Cross[_]]
.typeSymbol
.asClass
.typeParams
.head
.asType
.toType
.asSeenFrom(tpe, typeOf[Cross[_]].typeSymbol)

rec(inner)
}
}
}
rec(weakTypeOf[T])

def assertParamListCounts(
methods: Iterable[MethodSymbol],
cases: (Type, Int, String)*
): Unit = {
for (m <- methods.toList) {
cases
.find{case (tt, n, label) => m.returnType <:< tt}
.foreach{case (tt, n, label) =>
if (m.paramLists.length != n) c.abort(
m.pos,
s"$label definitions must have $n parameter list" + (if (n == 1) "" else "s")
)
}
}
}
val mapping = for {
discoveredModuleType <- seen
curCls = discoveredModuleType
methods = getValsOrMeths(curCls)
overridesRoutes = {
assertParamListCounts(
methods,
(weakTypeOf[mill.define.Command[_]], 1, "`T.command`"),
(weakTypeOf[mill.define.Target[_]], 0, "Target"),
)

for {
m <- methods.toList
if m.returnType <:< weakTypeOf[mill.define.Command[_]]
} yield (
m.overrides.length,
extractMethod(
m.name,
m.paramLists.flatten,
m.pos,
m.annotations.find(_.tree.tpe =:= typeOf[mainargs.main]),
curCls,
weakTypeOf[Any]
)
)

}
if overridesRoutes.nonEmpty
} yield {
// by wrapping the `overridesRoutes` in a lambda function we kind of work around
// the problem of generating a *huge* macro method body that finally exceeds the
// JVM's maximum allowed method size
val overridesLambda = q"(() => $overridesRoutes)()"
val lhs = q"classOf[${discoveredModuleType.typeSymbol.asClass}]"
q"$lhs -> $overridesLambda"
}

c.Expr[Discover[T]](
q"_root_.mill.define.Discover(_root_.scala.collection.immutable.Map(..$mapping))"
)
}
}

}
93 changes: 0 additions & 93 deletions main/core/src/mill/define/Router.scala

This file was deleted.

28 changes: 19 additions & 9 deletions runner/src/mill/runner/MillBuildBootstrap.scala
Original file line number Diff line number Diff line change
@@ -92,19 +92,29 @@ class MillBuildBootstrap(projectRoot: os.Path,
.millModuleDirectChildren
.collect { case b: RootModule => b }

val baseModuleOrErr = childRootModules match {
val rootModuleOrErr = childRootModules match {
case Seq() => Right(rootModule0)
case Seq(child) => Right(child)
case Seq(child) =>
val invalidChildModules = rootModule0.millModuleDirectChildren.filter(_ ne child)
if (invalidChildModules.isEmpty) Right(child)
else Left(
// We can't get use `child.toString` here, because as a RootModule
// it's segments are empty and it's toString is ""
s"RootModule ${child.getClass.getSimpleName} cannot have other " +
s"modules defined outside of it: ${invalidChildModules.mkString(",")}"
)


case multiple =>
Left(
s"Only one BaseModule can be defined in a build, not " +
s"Only one RootModule can be defined in a build, not " +
s"${multiple.size}: ${multiple.map(_.getClass.getName).mkString(",")}"
)
}

val validatedRootModuleOrErr = baseModuleOrErr.filterOrElse( baseModule =>
depth == 0 || baseModule.isInstanceOf[MillBuildRootModule],
s"Top-level module in ${recRoot(depth).relativeTo(projectRoot)}/build.sc must be of ${classOf[mill.runner.MillBuildRootModule]}"
val validatedRootModuleOrErr = rootModuleOrErr.filterOrElse(rootModule =>
depth == 0 || rootModule.isInstanceOf[MillBuildRootModule],
s"Root module in ${recRoot(depth).relativeTo(projectRoot)}/build.sc must be of ${classOf[mill.runner.MillBuildRootModule]}"
)

validatedRootModuleOrErr match{
@@ -138,7 +148,7 @@ class MillBuildBootstrap(projectRoot: os.Path,
/**
* Handles the compilation of `build.sc` or one of the meta-builds. These
* cases all only need us to run evaluate `runClasspath` and
* `scriptImportGraph` to instantiate their classloader/`BaseModule` to feed
* `scriptImportGraph` to instantiate their classloader/`RootModule` to feed
* into the next level's [[Evaluator]].
*
* Note that if the `runClasspath` doesn't change, we re-use the previous
@@ -233,7 +243,7 @@ class MillBuildBootstrap(projectRoot: os.Path,

def makeEvaluator(workerCache: Map[Segments, (Int, Any)],
scriptImportGraph: Map[Path, (Int, Seq[Path])],
baseModule: RootModule,
rootModule: RootModule,
millClassloaderSigHash: Int,
depth: Int) = {

@@ -245,7 +255,7 @@ class MillBuildBootstrap(projectRoot: os.Path,
config.home,
recOut(depth),
recOut(depth),
baseModule,
rootModule,
PrefixLogger(logger, "", tickerContext = bootLogPrefix),
millClassloaderSigHash
)
You are viewing a condensed version of this merge commit. You can view the full changes here.