All Downloads are FREE. Search and download functionalities are using the official Maven repository.

mill.define.Discover.scala Maven / Gradle / Ivy

The newest version!
package mill.define

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

/**
 * Macro to walk the module tree and generate `mainargs` entrypoints for any
 * `Task.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 `Task.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 private (
    value: Map[
      Class[_],
      (Seq[String], Seq[mainargs.MainData[_, _]], Seq[String])
    ],
    dummy: Int = 0 /* avoid conflict with Discover.apply(value: Map) below*/
) {
  @deprecated("Binary compatibility shim", "Mill 0.11.4")
  private[define] def this(value: Map[Class[_], Seq[mainargs.MainData[_, _]]]) =
    this(value.view.mapValues((Nil, _, Nil)).toMap)
  // Explicit copy, as we also need to provide an override for bin-compat reasons
  def copy[T](
      value: Map[
        Class[_],
        (Seq[String], Seq[mainargs.MainData[_, _]], Seq[String])
      ] = value,
      dummy: Int = dummy /* avoid conflict with Discover.apply(value: Map) below*/
  ): Discover = new Discover(value, dummy)
  @deprecated("Binary compatibility shim", "Mill 0.11.4")
  private[define] def copy(value: Map[Class[_], Seq[mainargs.MainData[_, _]]]): Discover = {
    new Discover(value.view.mapValues((Nil, _, Nil)).toMap, dummy)
  }
}

object Discover {
  def apply2[T](value: Map[Class[_], (Seq[String], Seq[mainargs.MainData[_, _]], Seq[String])])
      : Discover =
    new Discover(value)

  @deprecated("Binary compatibility shim", "Mill 0.11.4")
  def apply[T](value: Map[Class[_], Seq[mainargs.MainData[_, _]]]): Discover =
    new Discover(value.view.mapValues((Nil, _, Nil)).toMap)

  def apply[T]: Discover = macro Router.applyImpl[T]

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

    def applyImpl[T: WeakTypeTag]: Expr[Discover] = {
      val seen = mutable.Set.empty[Type]
      def rec(tpe: Type): Unit = {
        if (!seen(tpe)) {
          seen.add(tpe)
          for {
            m <- tpe.members.toList.sortBy(_.name.toString)
            if !m.isType
            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 && !(m.returnType <:< weakTypeOf[Nothing])
            }
            .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")
              )
            }
        }
      }

      // Make sure we sort the types and methods to keep the output deterministic;
      // otherwise the compiler likes to give us stuff in random orders, which
      // causes the code to be generated in random order resulting in code hashes
      // changing unnecessarily
      val mapping = for {
        discoveredModuleType <- seen.toSeq.sortBy(_.typeSymbol.fullName)
        curCls = discoveredModuleType
        methods = getValsOrMeths(curCls)
        declMethods = curCls.decls.toList.collect {
          case m: MethodSymbol if !m.isSynthetic && m.isPublic => m
        }
        overridesRoutes = {
          assertParamListCounts(
            methods,
            (weakTypeOf[mill.define.Command[_]], 1, "`Task.Command`"),
            (weakTypeOf[mill.define.Target[_]], 0, "Target")
          )

          Tuple3(
            for {
              m <- methods.toList.sortBy(_.fullName)
              if m.returnType <:< weakTypeOf[mill.define.NamedTask[_]]
            } yield m.name.decoded,
            for {
              m <- methods.toList.sortBy(_.fullName)
              if m.returnType <:< weakTypeOf[mill.define.Command[_]]
            } yield extractMethod(
              m.name,
              m.paramLists.flatten,
              m.pos,
              m.annotations.find(_.tree.tpe =:= typeOf[mainargs.main]),
              curCls,
              weakTypeOf[Any]
            ),
            for {
              m <- declMethods.sortBy(_.fullName)
              if m.returnType <:< weakTypeOf[mill.define.Task[_]]
            } yield m.name.decodedName.toString
          )
        }
        if overridesRoutes._1.nonEmpty || overridesRoutes._2.nonEmpty || overridesRoutes._3.nonEmpty
      } yield {
        val lhs = q"classOf[${discoveredModuleType.typeSymbol.asClass.toType}]"

        // 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)()"
        q"$lhs -> $overridesLambda"
      }

      c.Expr[Discover](
        q"import mill.api.JsonFormatters._; _root_.mill.define.Discover.apply2(_root_.scala.collection.immutable.Map(..$mapping))"
      )
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy