diff options
Diffstat (limited to 'main/core/src/define')
-rw-r--r-- | main/core/src/define/Applicative.scala | 108 | ||||
-rw-r--r-- | main/core/src/define/BaseModule.scala | 56 | ||||
-rw-r--r-- | main/core/src/define/Caller.scala | 13 | ||||
-rw-r--r-- | main/core/src/define/Cross.scala | 90 | ||||
-rw-r--r-- | main/core/src/define/Ctx.scala | 100 | ||||
-rw-r--r-- | main/core/src/define/Discover.scala | 92 | ||||
-rw-r--r-- | main/core/src/define/Graph.scala | 72 | ||||
-rw-r--r-- | main/core/src/define/Module.scala | 96 | ||||
-rw-r--r-- | main/core/src/define/Task.scala | 344 |
9 files changed, 971 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..c7dab54c --- /dev/null +++ b/main/core/src/define/Discover.scala @@ -0,0 +1,92 @@ +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 { + // 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"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..5b29bd7b --- /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.api.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) + } +} |