summaryrefslogtreecommitdiff
path: root/main/core/src/define
diff options
context:
space:
mode:
Diffstat (limited to 'main/core/src/define')
-rw-r--r--main/core/src/define/Applicative.scala108
-rw-r--r--main/core/src/define/BaseModule.scala56
-rw-r--r--main/core/src/define/Caller.scala13
-rw-r--r--main/core/src/define/Cross.scala90
-rw-r--r--main/core/src/define/Ctx.scala100
-rw-r--r--main/core/src/define/Discover.scala89
-rw-r--r--main/core/src/define/Graph.scala72
-rw-r--r--main/core/src/define/Module.scala96
-rw-r--r--main/core/src/define/Task.scala344
9 files changed, 968 insertions, 0 deletions
diff --git a/main/core/src/define/Applicative.scala b/main/core/src/define/Applicative.scala
new file mode 100644
index 00000000..5e63b1cc
--- /dev/null
+++ b/main/core/src/define/Applicative.scala
@@ -0,0 +1,108 @@
+package mill.define
+
+import scala.annotation.{StaticAnnotation, compileTimeOnly}
+import scala.language.higherKinds
+import scala.reflect.macros.blackbox.Context
+
+/**
+ * A generic Applicative-functor macro: translates calls to
+ *
+ * Applier.apply{ ... applyable1.apply() ... applyable2.apply() ... }
+ *
+ * into
+ *
+ * Applier.zipMap(applyable1, applyable2){ (a1, a2, ctx) => ... a1 ... a2 ... }
+ */
+object Applicative {
+ trait ApplyHandler[M[+_]]{
+ def apply[T](t: M[T]): T
+ }
+ object ApplyHandler{
+ @compileTimeOnly("Target#apply() can only be used with a T{...} block")
+ implicit def defaultApplyHandler[M[+_]]: ApplyHandler[M] = ???
+ }
+ trait Applyable[M[+_], +T]{
+ def self: M[T]
+ def apply()(implicit handler: ApplyHandler[M]): T = handler(self)
+ }
+
+ type Id[+T] = T
+
+ trait Applyer[W[_], T[_], Z[_], Ctx] extends ApplyerGenerated[T, Z, Ctx] {
+ def ctx()(implicit c: Ctx) = c
+ def underlying[A](v: W[A]): T[_]
+
+ def zipMap[R]()(cb: Ctx => Z[R]) = mapCtx(zip()){ (_, ctx) => cb(ctx)}
+ def zipMap[A, R](a: T[A])(f: (A, Ctx) => Z[R]) = mapCtx(a)(f)
+ def zip(): T[Unit]
+ def zip[A](a: T[A]): T[Tuple1[A]]
+ }
+
+ def impl[M[_], T: c.WeakTypeTag, Ctx: c.WeakTypeTag](c: Context)
+ (t: c.Expr[T]): c.Expr[M[T]] = {
+ impl0(c)(t.tree)(implicitly[c.WeakTypeTag[T]], implicitly[c.WeakTypeTag[Ctx]])
+ }
+ def impl0[M[_], T: c.WeakTypeTag, Ctx: c.WeakTypeTag](c: Context)
+ (t: c.Tree): c.Expr[M[T]] = {
+ import c.universe._
+ def rec(t: Tree): Iterator[c.Tree] = Iterator(t) ++ t.children.flatMap(rec(_))
+
+ val bound = collection.mutable.Buffer.empty[(c.Tree, ValDef)]
+ val targetApplySym = typeOf[Applyable[Nothing, _]].member(TermName("apply"))
+
+ // Derived from @olafurpg's
+ // https://gist.github.com/olafurpg/596d62f87bf3360a29488b725fbc7608
+ val defs = rec(t).filter(_.isDef).map(_.symbol).toSet
+
+ val ctxName = TermName(c.freshName("ctx"))
+ val ctxSym = c.internal.newTermSymbol(c.internal.enclosingOwner, ctxName)
+ c.internal.setInfo(ctxSym, weakTypeOf[Ctx])
+
+ val transformed = c.internal.typingTransform(t) {
+ case (t @ q"$fun.apply()($handler)", api) if t.symbol == targetApplySym =>
+
+ val localDefs = rec(fun).filter(_.isDef).map(_.symbol).toSet
+ val banned = rec(t).filter(x => defs(x.symbol) && !localDefs(x.symbol))
+
+ if (banned.hasNext){
+ val banned0 = banned.next()
+ c.abort(
+ banned0.pos,
+ "Target#apply() call cannot use `" + banned0.symbol + "` defined within the T{...} block"
+ )
+ }
+ val tempName = c.freshName(TermName("tmp"))
+ val tempSym = c.internal.newTermSymbol(c.internal.enclosingOwner, tempName)
+ c.internal.setInfo(tempSym, t.tpe)
+ val tempIdent = Ident(tempSym)
+ c.internal.setType(tempIdent, t.tpe)
+ c.internal.setFlag(tempSym, (1L << 44).asInstanceOf[c.universe.FlagSet])
+ bound.append((q"${c.prefix}.underlying($fun)", c.internal.valDef(tempSym)))
+ tempIdent
+ case (t, api)
+ if t.symbol != null
+ && t.symbol.annotations.exists(_.tree.tpe =:= typeOf[mill.api.Ctx.ImplicitStub]) =>
+
+ val tempIdent = Ident(ctxSym)
+ c.internal.setType(tempIdent, t.tpe)
+ c.internal.setFlag(ctxSym, (1L << 44).asInstanceOf[c.universe.FlagSet])
+ tempIdent
+
+ case (t, api) => api.default(t)
+ }
+
+ val (exprs, bindings) = bound.unzip
+
+
+ val ctxBinding = c.internal.valDef(ctxSym)
+
+ val callback = c.typecheck(q"(..$bindings, $ctxBinding) => $transformed ")
+
+ val res = q"${c.prefix}.zipMap(..$exprs){ $callback }"
+
+ c.internal.changeOwner(transformed, c.internal.enclosingOwner, callback.symbol)
+
+ c.Expr[M[T]](res)
+ }
+
+}
diff --git a/main/core/src/define/BaseModule.scala b/main/core/src/define/BaseModule.scala
new file mode 100644
index 00000000..cd79f73e
--- /dev/null
+++ b/main/core/src/define/BaseModule.scala
@@ -0,0 +1,56 @@
+package mill.define
+
+
+object BaseModule{
+ case class Implicit(value: BaseModule)
+}
+
+abstract class BaseModule(millSourcePath0: os.Path,
+ external0: Boolean = false,
+ foreign0 : Boolean = false)
+ (implicit millModuleEnclosing0: sourcecode.Enclosing,
+ millModuleLine0: sourcecode.Line,
+ millName0: sourcecode.Name,
+ millFile0: sourcecode.File,
+ caller: Caller)
+ extends Module()(
+ mill.define.Ctx.make(
+ implicitly,
+ implicitly,
+ implicitly,
+ BasePath(millSourcePath0),
+ Segments(),
+ mill.util.Router.Overrides(0),
+ Ctx.External(external0),
+ Ctx.Foreign(foreign0),
+ millFile0,
+ caller
+ )
+ ){
+ // A BaseModule should provide an empty Segments list to it's children, since
+ // it is the root of the module tree, and thus must not include it's own
+ // sourcecode.Name as part of the list,
+ override implicit def millModuleSegments: Segments = Segments()
+ override def millSourcePath = millOuterCtx.millSourcePath
+ override implicit def millModuleBasePath: BasePath = BasePath(millSourcePath)
+ implicit def millImplicitBaseModule: BaseModule.Implicit = BaseModule.Implicit(this)
+ def millDiscover: Discover[this.type]
+}
+
+
+abstract class ExternalModule(implicit millModuleEnclosing0: sourcecode.Enclosing,
+ millModuleLine0: sourcecode.Line,
+ millName0: sourcecode.Name)
+ extends BaseModule(ammonite.ops.pwd, external0 = true, foreign0 = false)(
+ implicitly, implicitly, implicitly, implicitly, Caller(())
+ ){
+
+ implicit def millDiscoverImplicit: Discover[_] = millDiscover
+ assert(
+ !" #".exists(millModuleEnclosing0.value.contains(_)),
+ "External modules must be at a top-level static path, not " + millModuleEnclosing0.value
+ )
+ override implicit def millModuleSegments = {
+ Segments(millModuleEnclosing0.value.split('.').map(Segment.Label):_*)
+ }
+}
diff --git a/main/core/src/define/Caller.scala b/main/core/src/define/Caller.scala
new file mode 100644
index 00000000..6d2d4d1d
--- /dev/null
+++ b/main/core/src/define/Caller.scala
@@ -0,0 +1,13 @@
+package mill.define
+
+import sourcecode.Compat.Context
+import language.experimental.macros
+case class Caller(value: Any)
+object Caller {
+ def apply()(implicit c: Caller) = c.value
+ implicit def generate: Caller = macro impl
+ def impl(c: Context): c.Tree = {
+ import c.universe._
+ q"new _root_.mill.define.Caller(this)"
+ }
+} \ No newline at end of file
diff --git a/main/core/src/define/Cross.scala b/main/core/src/define/Cross.scala
new file mode 100644
index 00000000..aa730e0d
--- /dev/null
+++ b/main/core/src/define/Cross.scala
@@ -0,0 +1,90 @@
+package mill.define
+import language.experimental.macros
+import scala.reflect.macros.blackbox
+
+
+object Cross{
+ case class Factory[T](make: (Product, mill.define.Ctx) => T)
+
+ object Factory{
+ implicit def make[T]: Factory[T] = macro makeImpl[T]
+ def makeImpl[T: c.WeakTypeTag](c: blackbox.Context): c.Expr[Factory[T]] = {
+ import c.universe._
+ val tpe = weakTypeOf[T]
+
+ val primaryConstructorArgs =
+ tpe.typeSymbol.asClass.primaryConstructor.typeSignature.paramLists.head
+
+ val argTupleValues =
+ for((a, n) <- primaryConstructorArgs.zipWithIndex)
+ yield q"v.productElement($n).asInstanceOf[${a.info}]"
+
+ val instance = c.Expr[(Product, mill.define.Ctx) => T](
+ q"{ (v, ctx0) => new $tpe(..$argTupleValues){ override def millOuterCtx = ctx0 } }"
+ )
+
+ reify { mill.define.Cross.Factory[T](instance.splice) }
+ }
+ }
+
+ trait Resolver[-T]{
+ def resolve[V <: T](c: Cross[V]): V
+ }
+}
+
+/**
+ * Models "cross-builds": sets of duplicate builds which differ only in the
+ * value of one or more "case" variables whose values are determined at runtime.
+ * Used via:
+ *
+ * object foo extends Cross[FooModule]("bar", "baz", "qux")
+ * class FooModule(v: String) extends Module{
+ * ...
+ * }
+ */
+class Cross[T](cases: Any*)
+ (implicit ci: Cross.Factory[T],
+ ctx: mill.define.Ctx) extends mill.define.Module()(ctx) {
+
+ override lazy val millModuleDirectChildren =
+ this.millInternal.reflectNestedObjects[Module] ++
+ items.collect{case (k, v: mill.define.Module) => v}
+
+ val items = for(c0 <- cases.toList) yield{
+ val c = c0 match{
+ case p: Product => p
+ case v => Tuple1(v)
+ }
+ val crossValues = c.productIterator.toList
+ val relPath = ctx.segment.pathSegments
+ val sub = ci.make(
+ c,
+ ctx.copy(
+ segments = ctx.segments ++ Seq(ctx.segment),
+ millSourcePath = ctx.millSourcePath / relPath,
+ segment = Segment.Cross(crossValues)
+ )
+ )
+ (crossValues, sub)
+ }
+ val itemMap = items.toMap
+
+ /**
+ * Fetch the cross module corresponding to the given cross values
+ */
+ def get(args: Seq[Any]) = itemMap(args.toList)
+
+ /**
+ * Fetch the cross module corresponding to the given cross values
+ */
+ def apply(arg0: Any, args: Any*) = itemMap(arg0 :: args.toList)
+
+ /**
+ * Fetch the relevant cross module given the implicit resolver you have in
+ * scope. This is often the first cross module whose cross-version is
+ * compatible with the current module.
+ */
+ def apply[V >: T]()(implicit resolver: Cross.Resolver[V]): T = {
+ resolver.resolve(this.asInstanceOf[Cross[V]]).asInstanceOf[T]
+ }
+} \ No newline at end of file
diff --git a/main/core/src/define/Ctx.scala b/main/core/src/define/Ctx.scala
new file mode 100644
index 00000000..c21e53b4
--- /dev/null
+++ b/main/core/src/define/Ctx.scala
@@ -0,0 +1,100 @@
+package mill.define
+
+
+import scala.annotation.implicitNotFound
+
+sealed trait Segment{
+ def pathSegments: Seq[String] = this match{
+ case Segment.Label(s) => List(s)
+ case Segment.Cross(vs) => vs.map(_.toString)
+ }
+}
+object Segment{
+ case class Label(value: String) extends Segment{
+ assert(!value.contains('.'))
+ }
+ case class Cross(value: Seq[Any]) extends Segment
+}
+
+case class BasePath(value: os.Path)
+
+
+/**
+ * Models a path with the Mill build hierarchy, e.g.
+ *
+ * amm.util[2.11].test.compile
+ *
+ * .-separated segments are [[Segment.Label]]s, while []-delimited
+ * segments are [[Segment.Cross]]s
+ */
+case class Segments(value: Segment*){
+ def ++(other: Seq[Segment]): Segments = Segments(value ++ other:_*)
+ def ++(other: Segments): Segments = Segments(value ++ other.value:_*)
+ def parts = value.toList match {
+ case Nil => Nil
+ case Segment.Label(head) :: rest =>
+ val stringSegments = rest.flatMap{
+ case Segment.Label(s) => Seq(s)
+ case Segment.Cross(vs) => vs.map(_.toString)
+ }
+ head +: stringSegments
+ }
+ def last : Segments = Segments(value.last)
+ def render = value.toList match {
+ case Nil => ""
+ case Segment.Label(head) :: rest =>
+ val stringSegments = rest.map{
+ case Segment.Label(s) => "." + s
+ case Segment.Cross(vs) => "[" + vs.mkString(",") + "]"
+ }
+ head + stringSegments.mkString
+ }
+}
+
+object Segments {
+
+ def labels(values : String*) : Segments =
+ Segments(values.map(Segment.Label):_*)
+
+}
+
+@implicitNotFound("Modules, Targets and Commands can only be defined within a mill Module")
+case class Ctx(enclosing: String,
+ lineNum: Int,
+ segment: Segment,
+ millSourcePath: os.Path,
+ segments: Segments,
+ overrides: Int,
+ external: Boolean,
+ foreign: Boolean,
+ fileName: String,
+ enclosingCls: Class[_]){
+}
+
+object Ctx{
+ case class External(value: Boolean)
+ case class Foreign(value : Boolean)
+ implicit def make(implicit millModuleEnclosing0: sourcecode.Enclosing,
+ millModuleLine0: sourcecode.Line,
+ millName0: sourcecode.Name,
+ millModuleBasePath0: BasePath,
+ segments0: Segments,
+ overrides0: mill.util.Router.Overrides,
+ external0: External,
+ foreign0: Foreign,
+ fileName: sourcecode.File,
+ enclosing: Caller): Ctx = {
+ Ctx(
+ millModuleEnclosing0.value,
+ millModuleLine0.value,
+ Segment.Label(millName0.value),
+ millModuleBasePath0.value,
+ segments0,
+ overrides0.value,
+ external0.value,
+ foreign0.value,
+ fileName.value,
+ enclosing.value.getClass
+ )
+ }
+}
diff --git a/main/core/src/define/Discover.scala b/main/core/src/define/Discover.scala
new file mode 100644
index 00000000..f0c668e6
--- /dev/null
+++ b/main/core/src/define/Discover.scala
@@ -0,0 +1,89 @@
+package mill.define
+import mill.util.Router.EntryPoint
+
+import language.experimental.macros
+import sourcecode.Compat.Context
+
+import scala.collection.mutable
+import scala.reflect.macros.blackbox
+
+
+
+case class Discover[T](value: Map[Class[_], Seq[(Int, EntryPoint[_])]])
+object Discover {
+ def apply[T]: Discover[T] = macro applyImpl[T]
+
+ def applyImpl[T: c.WeakTypeTag](c: blackbox.Context): c.Expr[Discover[T]] = {
+ import c.universe._
+ import compat._
+ 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[router.c.universe.MethodSymbol],
+ cases: (c.Type, Int, String)*) = {
+ for (m <- methods.toList){
+ for ((tt, n, label) <- cases){
+ if (m.returnType <:< tt.asInstanceOf[router.c.Type] &&
+ m.paramLists.length != n){
+ c.abort(
+ m.pos.asInstanceOf[c.Position],
+ s"$label definitions must have $n parameter list" + (if (n == 1) "" else "s")
+ )
+ }
+ }
+ }
+ }
+ val router = new mill.util.Router(c)
+ val mapping = for{
+ discoveredModuleType <- seen
+ val curCls = discoveredModuleType.asInstanceOf[router.c.Type]
+ val methods = router.getValsOrMeths(curCls)
+ val overridesRoutes = {
+ assertParamListCounts(
+ methods,
+ (weakTypeOf[mill.define.Sources], 0, "`T.sources`"),
+ (weakTypeOf[mill.define.Input[_]], 0, "`T.input`"),
+ (weakTypeOf[mill.define.Persistent[_]], 0, "`T.persistent`"),
+ (weakTypeOf[mill.define.Target[_]], 0, "`T{...}`"),
+ (weakTypeOf[mill.define.Command[_]], 1, "`T.command`")
+ )
+
+ for{
+ m <- methods.toList
+ if m.returnType <:< weakTypeOf[mill.define.Command[_]].asInstanceOf[router.c.Type]
+ } yield (m.overrides.length, router.extractMethod(m, curCls).asInstanceOf[c.Tree])
+
+ }
+ if overridesRoutes.nonEmpty
+ } yield {
+ val lhs = q"classOf[${discoveredModuleType.typeSymbol.asClass}]"
+ val rhs = q"scala.Seq[(Int, mill.util.Router.EntryPoint[_])](..$overridesRoutes)"
+ q"$lhs -> $rhs"
+ }
+
+ c.Expr[Discover[T]](q"mill.define.Discover(scala.collection.immutable.Map(..$mapping))")
+ }
+}
diff --git a/main/core/src/define/Graph.scala b/main/core/src/define/Graph.scala
new file mode 100644
index 00000000..3119f2fb
--- /dev/null
+++ b/main/core/src/define/Graph.scala
@@ -0,0 +1,72 @@
+package mill.define
+
+import mill.eval.Tarjans
+import mill.util.MultiBiMap
+import mill.util.Strict.Agg
+
+object Graph {
+
+ /**
+ * The `values` [[Agg]] is guaranteed to be topological sorted and cycle free.
+ * That's why the constructor is package private.
+ * @see [[Graph.topoSorted]]
+ */
+ class TopoSorted private[Graph] (val values: Agg[Task[_]])
+
+ def groupAroundImportantTargets[T](topoSortedTargets: TopoSorted)
+ (important: PartialFunction[Task[_], T]): MultiBiMap[T, Task[_]] = {
+
+ val output = new MultiBiMap.Mutable[T, Task[_]]()
+ for ((target, t) <- topoSortedTargets.values.flatMap(t => important.lift(t).map((t, _)))) {
+
+ val transitiveTargets = new Agg.Mutable[Task[_]]
+ def rec(t: Task[_]): Unit = {
+ if (transitiveTargets.contains(t)) () // do nothing
+ else if (important.isDefinedAt(t) && t != target) () // do nothing
+ else {
+ transitiveTargets.append(t)
+ t.inputs.foreach(rec)
+ }
+ }
+ rec(target)
+ output.addAll(t, topoSorted(transitiveTargets).values)
+ }
+ output
+ }
+
+ /**
+ * Collects all transitive dependencies (targets) of the given targets,
+ * including the given targets.
+ */
+ def transitiveTargets(sourceTargets: Agg[Task[_]]): Agg[Task[_]] = {
+ val transitiveTargets = new Agg.Mutable[Task[_]]
+ def rec(t: Task[_]): Unit = {
+ if (transitiveTargets.contains(t)) () // do nothing
+ else {
+ transitiveTargets.append(t)
+ t.inputs.foreach(rec)
+ }
+ }
+
+ sourceTargets.items.foreach(rec)
+ transitiveTargets
+ }
+ /**
+ * Takes the given targets, finds all the targets they transitively depend
+ * on, and sort them topologically. Fails if there are dependency cycles
+ */
+ def topoSorted(transitiveTargets: Agg[Task[_]]): TopoSorted = {
+
+ val indexed = transitiveTargets.indexed
+ val targetIndices = indexed.zipWithIndex.toMap
+
+ val numberedEdges =
+ for(t <- transitiveTargets.items)
+ yield t.inputs.collect(targetIndices)
+
+ val sortedClusters = Tarjans(numberedEdges)
+ val nonTrivialClusters = sortedClusters.filter(_.length > 1)
+ assert(nonTrivialClusters.isEmpty, nonTrivialClusters)
+ new TopoSorted(Agg.from(sortedClusters.flatten.map(indexed)))
+ }
+}
diff --git a/main/core/src/define/Module.scala b/main/core/src/define/Module.scala
new file mode 100644
index 00000000..a8fc5be7
--- /dev/null
+++ b/main/core/src/define/Module.scala
@@ -0,0 +1,96 @@
+package mill.define
+
+import java.lang.reflect.Modifier
+
+import mill.util.ParseArgs
+
+import scala.language.experimental.macros
+import scala.reflect.ClassTag
+import scala.reflect.NameTransformer.decode
+
+
+/**
+ * `Module` is a class meant to be extended by `trait`s *only*, in order to
+ * propagate the implicit parameters forward to the final concrete
+ * instantiation site so they can capture the enclosing/line information of
+ * the concrete instance.
+ */
+class Module(implicit outerCtx0: mill.define.Ctx)
+ extends mill.moduledefs.Cacher{ outer =>
+
+ /**
+ * Miscellaneous machinery around traversing & querying the build hierarchy,
+ * that should not be needed by normal users of Mill
+ */
+ object millInternal extends Module.Internal(this)
+
+ lazy val millModuleDirectChildren = millInternal.reflectNestedObjects[Module].toSeq
+ def millOuterCtx = outerCtx0
+ def millSourcePath: os.Path = millOuterCtx.millSourcePath / millOuterCtx.segment.pathSegments
+ implicit def millModuleExternal: Ctx.External = Ctx.External(millOuterCtx.external)
+ implicit def millModuleShared: Ctx.Foreign = Ctx.Foreign(millOuterCtx.foreign)
+ implicit def millModuleBasePath: BasePath = BasePath(millSourcePath)
+ implicit def millModuleSegments: Segments = {
+ millOuterCtx.segments ++ Seq(millOuterCtx.segment)
+ }
+ override def toString = millModuleSegments.render
+}
+
+object Module{
+ class Internal(outer: Module){
+ def traverse[T](f: Module => Seq[T]): Seq[T] = {
+ def rec(m: Module): Seq[T] = f(m) ++ m.millModuleDirectChildren.flatMap(rec)
+ rec(outer)
+ }
+
+ lazy val modules = traverse(Seq(_))
+ lazy val segmentsToModules = modules.map(m => (m.millModuleSegments, m)).toMap
+
+ lazy val targets = traverse{_.millInternal.reflectAll[Target[_]]}.toSet
+
+ lazy val segmentsToTargets = targets
+ .map(t => (t.ctx.segments, t))
+ .toMap
+
+ // Ensure we do not propagate the implicit parameters as implicits within
+ // the body of any inheriting class/trait/objects, as it would screw up any
+ // one else trying to use sourcecode.{Enclosing,Line} to capture debug info
+ lazy val millModuleEnclosing = outer.millOuterCtx.enclosing
+ lazy val millModuleLine = outer.millOuterCtx.lineNum
+
+ private def reflect[T: ClassTag](filter: (String) => Boolean): Array[T] = {
+ val runtimeCls = implicitly[ClassTag[T]].runtimeClass
+ for{
+ m <- outer.getClass.getMethods.sortBy(_.getName)
+ n = decode(m.getName)
+ if
+ filter(n) &&
+ ParseArgs.isLegalIdentifier(n) &&
+ m.getParameterCount == 0 &&
+ (m.getModifiers & Modifier.STATIC) == 0 &&
+ (m.getModifiers & Modifier.ABSTRACT) == 0 &&
+ runtimeCls.isAssignableFrom(m.getReturnType)
+ } yield m.invoke(outer).asInstanceOf[T]
+ }
+
+ def reflectAll[T: ClassTag]: Array[T] = reflect(Function.const(true))
+
+ def reflectSingle[T: ClassTag](label: String): Option[T] = reflect(_ == label).headOption
+
+ // For some reason, this fails to pick up concrete `object`s nested directly within
+ // another top-level concrete `object`. This is fine for now, since Mill's Ammonite
+ // script/REPL runner always wraps user code in a wrapper object/trait
+ def reflectNestedObjects[T: ClassTag] = {
+ (reflectAll[T] ++
+ outer
+ .getClass
+ .getClasses
+ .filter(implicitly[ClassTag[T]].runtimeClass isAssignableFrom _)
+ .flatMap(c => c.getFields.find(_.getName == "MODULE$").map(_.get(c).asInstanceOf[T]))
+ ).distinct
+ }
+ }
+}
+trait TaskModule extends Module {
+ def defaultCommandName(): String
+}
diff --git a/main/core/src/define/Task.scala b/main/core/src/define/Task.scala
new file mode 100644
index 00000000..a464bf18
--- /dev/null
+++ b/main/core/src/define/Task.scala
@@ -0,0 +1,344 @@
+package mill.define
+
+import ammonite.main.Router.Overrides
+import mill.define.Applicative.Applyable
+import mill.eval.{PathRef, Result}
+import mill.util.EnclosingClass
+import sourcecode.Compat.Context
+import upickle.default.{ReadWriter => RW, Reader => R, Writer => W}
+
+import scala.language.experimental.macros
+import scala.reflect.macros.blackbox.Context
+
+
+/**
+ * Models a single node in the Mill build graph, with a list of inputs and a
+ * single output of type [[T]].
+ *
+ * Generally not instantiated manually, but instead constructed via the
+ * [[Target.apply]] & similar macros.
+ */
+abstract class Task[+T] extends Task.Ops[T] with Applyable[Task, T]{
+ /**
+ * What other Targets does this Target depend on?
+ */
+ val inputs: Seq[Task[_]]
+
+ /**
+ * Evaluate this target
+ */
+ def evaluate(args: mill.api.Ctx): Result[T]
+
+ /**
+ * Even if this target's inputs did not change, does it need to re-evaluate
+ * anyway?
+ */
+ def sideHash: Int = 0
+
+ def flushDest: Boolean = true
+
+ def asTarget: Option[Target[T]] = None
+ def asCommand: Option[Command[T]] = None
+ def asWorker: Option[Worker[T]] = None
+ def self = this
+}
+
+trait NamedTask[+T] extends Task[T]{
+ def ctx: mill.define.Ctx
+ def label = ctx.segment match{case Segment.Label(v) => v}
+ override def toString = ctx.segments.render
+}
+trait Target[+T] extends NamedTask[T]{
+ override def asTarget = Some(this)
+ def readWrite: RW[_]
+}
+
+object Target extends TargetGenerated with Applicative.Applyer[Task, Task, Result, mill.api.Ctx] {
+
+ implicit def apply[T](t: T)
+ (implicit rw: RW[T],
+ ctx: mill.define.Ctx): Target[T] = macro targetImpl[T]
+
+ def targetImpl[T: c.WeakTypeTag](c: Context)
+ (t: c.Expr[T])
+ (rw: c.Expr[RW[T]],
+ ctx: c.Expr[mill.define.Ctx]): c.Expr[Target[T]] = {
+ import c.universe._
+ val lhs = Applicative.impl0[Task, T, mill.api.Ctx](c)(reify(Result.Success(t.splice)).tree)
+
+ mill.moduledefs.Cacher.impl0[TargetImpl[T]](c)(
+ reify(
+ new TargetImpl[T](lhs.splice, ctx.splice, rw.splice)
+ )
+ )
+ }
+
+ implicit def apply[T](t: Result[T])
+ (implicit rw: RW[T],
+ ctx: mill.define.Ctx): Target[T] = macro targetResultImpl[T]
+
+ def targetResultImpl[T: c.WeakTypeTag](c: Context)
+ (t: c.Expr[Result[T]])
+ (rw: c.Expr[RW[T]],
+ ctx: c.Expr[mill.define.Ctx]): c.Expr[Target[T]] = {
+ import c.universe._
+ mill.moduledefs.Cacher.impl0[Target[T]](c)(
+ reify(
+ new TargetImpl[T](
+ Applicative.impl0[Task, T, mill.api.Ctx](c)(t.tree).splice,
+ ctx.splice,
+ rw.splice
+ )
+ )
+ )
+ }
+
+ def apply[T](t: Task[T])
+ (implicit rw: RW[T],
+ ctx: mill.define.Ctx): Target[T] = macro targetTaskImpl[T]
+
+ def targetTaskImpl[T: c.WeakTypeTag](c: Context)
+ (t: c.Expr[Task[T]])
+ (rw: c.Expr[RW[T]],
+ ctx: c.Expr[mill.define.Ctx]): c.Expr[Target[T]] = {
+ import c.universe._
+ mill.moduledefs.Cacher.impl0[Target[T]](c)(
+ reify(
+ new TargetImpl[T](t.splice, ctx.splice, rw.splice)
+ )
+ )
+ }
+
+ def sources(values: Result[os.Path]*)
+ (implicit ctx: mill.define.Ctx): Sources = macro sourcesImpl1
+
+ def sourcesImpl1(c: Context)
+ (values: c.Expr[Result[os.Path]]*)
+ (ctx: c.Expr[mill.define.Ctx]): c.Expr[Sources] = {
+ import c.universe._
+ val wrapped =
+ for (value <- values.toList)
+ yield Applicative.impl0[Task, PathRef, mill.api.Ctx](c)(
+ reify(value.splice.map(PathRef(_))).tree
+ ).tree
+
+ mill.moduledefs.Cacher.impl0[Sources](c)(
+ reify(
+ new Sources(
+ Task.sequence(c.Expr[List[Task[PathRef]]](q"scala.List(..$wrapped)").splice),
+ ctx.splice
+ )
+ )
+ )
+ }
+
+ def sources(values: Result[Seq[PathRef]])
+ (implicit ctx: mill.define.Ctx): Sources = macro sourcesImpl2
+
+ def sourcesImpl2(c: Context)
+ (values: c.Expr[Result[Seq[PathRef]]])
+ (ctx: c.Expr[mill.define.Ctx]): c.Expr[Sources] = {
+ import c.universe._
+
+
+ mill.moduledefs.Cacher.impl0[Sources](c)(
+ reify(
+ new Sources(
+ Applicative.impl0[Task, Seq[PathRef], mill.api.Ctx](c)(values.tree).splice,
+ ctx.splice
+ )
+ )
+ )
+ }
+ def input[T](value: Result[T])
+ (implicit rw: RW[T],
+ ctx: mill.define.Ctx): Input[T] = macro inputImpl[T]
+
+ def inputImpl[T: c.WeakTypeTag](c: Context)
+ (value: c.Expr[T])
+ (rw: c.Expr[RW[T]],
+ ctx: c.Expr[mill.define.Ctx]): c.Expr[Input[T]] = {
+ import c.universe._
+
+ mill.moduledefs.Cacher.impl0[Input[T]](c)(
+ reify(
+ new Input[T](
+ Applicative.impl[Task, T, mill.api.Ctx](c)(value).splice,
+ ctx.splice,
+ rw.splice
+ )
+ )
+ )
+ }
+
+ def command[T](t: Task[T])
+ (implicit ctx: mill.define.Ctx,
+ w: W[T],
+ cls: EnclosingClass,
+ overrides: Overrides): Command[T] = {
+ new Command(t, ctx, w, cls.value, overrides.value)
+ }
+
+ def command[T](t: Result[T])
+ (implicit w: W[T],
+ ctx: mill.define.Ctx,
+ cls: EnclosingClass,
+ overrides: Overrides): Command[T] = macro commandImpl[T]
+
+ def commandImpl[T: c.WeakTypeTag](c: Context)
+ (t: c.Expr[T])
+ (w: c.Expr[W[T]],
+ ctx: c.Expr[mill.define.Ctx],
+ cls: c.Expr[EnclosingClass],
+ overrides: c.Expr[Overrides]): c.Expr[Command[T]] = {
+ import c.universe._
+ reify(
+ new Command[T](
+ Applicative.impl[Task, T, mill.api.Ctx](c)(t).splice,
+ ctx.splice,
+ w.splice,
+ cls.splice.value,
+ overrides.splice.value
+ )
+ )
+ }
+
+ def worker[T](t: Task[T])
+ (implicit ctx: mill.define.Ctx): Worker[T] = new Worker(t, ctx)
+
+ def worker[T](t: Result[T])
+ (implicit ctx: mill.define.Ctx): Worker[T] = macro workerImpl[T]
+
+ def workerImpl[T: c.WeakTypeTag](c: Context)
+ (t: c.Expr[T])
+ (ctx: c.Expr[mill.define.Ctx]): c.Expr[Worker[T]] = {
+ import c.universe._
+ reify(
+ new Worker[T](Applicative.impl[Task, T, mill.api.Ctx](c)(t).splice, ctx.splice)
+ )
+ }
+
+ def task[T](t: Result[T]): Task[T] = macro Applicative.impl[Task, T, mill.api.Ctx]
+
+ def persistent[T](t: Result[T])(implicit rw: RW[T],
+ ctx: mill.define.Ctx): Persistent[T] = macro persistentImpl[T]
+
+ def persistentImpl[T: c.WeakTypeTag](c: Context)
+ (t: c.Expr[T])
+ (rw: c.Expr[RW[T]],
+ ctx: c.Expr[mill.define.Ctx]): c.Expr[Persistent[T]] = {
+ import c.universe._
+
+
+ mill.moduledefs.Cacher.impl0[Persistent[T]](c)(
+ reify(
+ new Persistent[T](
+ Applicative.impl[Task, T, mill.api.Ctx](c)(t).splice,
+ ctx.splice,
+ rw.splice
+ )
+ )
+ )
+ }
+
+ type TT[+X] = Task[X]
+ def makeT[X](inputs0: Seq[TT[_]], evaluate0: mill.api.Ctx => Result[X]) = new Task[X] {
+ val inputs = inputs0
+ def evaluate(x: mill.api.Ctx) = evaluate0(x)
+ }
+
+ def underlying[A](v: Task[A]) = v
+ def mapCtx[A, B](t: Task[A])(f: (A, mill.api.Ctx) => Result[B]) = t.mapDest(f)
+ def zip() = new Task.Task0(())
+ def zip[A](a: Task[A]) = a.map(Tuple1(_))
+ def zip[A, B](a: Task[A], b: Task[B]) = a.zip(b)
+}
+
+abstract class NamedTaskImpl[+T](ctx0: mill.define.Ctx, t: Task[T]) extends NamedTask[T]{
+ def evaluate(args: mill.api.Ctx) = args[T](0)
+ val ctx = ctx0.copy(segments = ctx0.segments ++ Seq(ctx0.segment))
+ val inputs = Seq(t)
+}
+
+class TargetImpl[+T](t: Task[T],
+ ctx0: mill.define.Ctx,
+ val readWrite: RW[_]) extends NamedTaskImpl[T](ctx0, t) with Target[T] {
+}
+
+class Command[+T](t: Task[T],
+ ctx0: mill.define.Ctx,
+ val writer: W[_],
+ val cls: Class[_],
+ val overrides: Int) extends NamedTaskImpl[T](ctx0, t) {
+ override def asCommand = Some(this)
+}
+
+class Worker[+T](t: Task[T], ctx0: mill.define.Ctx) extends NamedTaskImpl[T](ctx0, t) {
+ override def flushDest = false
+ override def asWorker = Some(this)
+}
+class Persistent[+T](t: Task[T],
+ ctx0: mill.define.Ctx,
+ readWrite: RW[_])
+ extends TargetImpl[T](t, ctx0, readWrite) {
+
+ override def flushDest = false
+}
+class Input[T](t: Task[T],
+ ctx0: mill.define.Ctx,
+ val readWrite: RW[_]) extends NamedTaskImpl[T](ctx0, t) with Target[T]{
+ override def sideHash = util.Random.nextInt()
+}
+class Sources(t: Task[Seq[PathRef]],
+ ctx0: mill.define.Ctx) extends Input[Seq[PathRef]](
+ t,
+ ctx0,
+ RW.join(
+ upickle.default.SeqLikeReader[Seq, PathRef],
+ upickle.default.SeqLikeWriter[Seq, PathRef]
+ )
+)
+object Task {
+
+ class Task0[T](t: T) extends Task[T]{
+ lazy val t0 = t
+ val inputs = Nil
+ def evaluate(args: mill.api.Ctx) = t0
+ }
+
+ abstract class Ops[+T]{ this: Task[T] =>
+ def map[V](f: T => V) = new Task.Mapped(this, f)
+ def mapDest[V](f: (T, mill.api.Ctx) => Result[V]) = new Task.MappedDest(this, f)
+
+ def filter(f: T => Boolean) = this
+ def withFilter(f: T => Boolean) = this
+ def zip[V](other: Task[V]) = new Task.Zipped(this, other)
+
+ }
+
+ def traverse[T, V](source: Seq[T])(f: T => Task[V]) = {
+ new Sequence[V](source.map(f))
+ }
+ def sequence[T](source: Seq[Task[T]]) = new Sequence[T](source)
+
+ class Sequence[+T](inputs0: Seq[Task[T]]) extends Task[Seq[T]]{
+ val inputs = inputs0
+ def evaluate(args: mill.api.Ctx) = {
+ for (i <- 0 until args.length)
+ yield args(i).asInstanceOf[T]
+ }
+
+ }
+ class Mapped[+T, +V](source: Task[T], f: T => V) extends Task[V]{
+ def evaluate(args: mill.api.Ctx) = f(args(0))
+ val inputs = List(source)
+ }
+ class MappedDest[+T, +V](source: Task[T], f: (T, mill.api.Ctx) => Result[V]) extends Task[V]{
+ def evaluate(args: mill.api.Ctx) = f(args(0), args)
+ val inputs = List(source)
+ }
+ class Zipped[+T, +V](source1: Task[T], source2: Task[V]) extends Task[(T, V)]{
+ def evaluate(args: mill.api.Ctx) = (args(0), args(1))
+ val inputs = List(source1, source2)
+ }
+}