diff options
author | Li Haoyi <haoyi.sg@gmail.com> | 2017-11-05 06:24:14 -0800 |
---|---|---|
committer | Li Haoyi <haoyi.sg@gmail.com> | 2017-11-05 06:24:14 -0800 |
commit | 2fb5b569d30ac09cfda7ce6450c54d0fff6f32ae (patch) | |
tree | 74ca947565a623fb7ea801973617888fb36af0c6 /core/src/main/scala/forge/define | |
parent | dfe12f27063874ca6ff86dee98c85b82236e1762 (diff) | |
download | mill-2fb5b569d30ac09cfda7ce6450c54d0fff6f32ae.tar.gz mill-2fb5b569d30ac09cfda7ce6450c54d0fff6f32ae.tar.bz2 mill-2fb5b569d30ac09cfda7ce6450c54d0fff6f32ae.zip |
Re-organize `forge/` folder according to ordering of build phases: target definition, target discovery, and target evaluation
Diffstat (limited to 'core/src/main/scala/forge/define')
-rw-r--r-- | core/src/main/scala/forge/define/Target.scala | 175 | ||||
-rw-r--r-- | core/src/main/scala/forge/define/ZipTarget.scala | 40 |
2 files changed, 215 insertions, 0 deletions
diff --git a/core/src/main/scala/forge/define/Target.scala b/core/src/main/scala/forge/define/Target.scala new file mode 100644 index 00000000..1d8ecdee --- /dev/null +++ b/core/src/main/scala/forge/define/Target.scala @@ -0,0 +1,175 @@ +package forge.define + +import ammonite.ops.{CommandResult, mkdir} +import forge.eval.PathRef +import forge.util.{Args, JsonFormatters} +import play.api.libs.json.{Format, Json} + +import scala.annotation.compileTimeOnly +import scala.collection.mutable +import scala.language.experimental.macros +import scala.reflect.macros.blackbox.Context + +abstract class Target[T] extends Target.Ops[T]{ + /** + * What other Targets does this Target depend on? + */ + val inputs: Seq[Target[_]] + + /** + * Evaluate this target + */ + def evaluate(args: Args): T + + /** + * Even if this target's inputs did not change, does it need to re-evaluate + * anyway? + */ + def sideHash: Int = 0 + + @compileTimeOnly("Target#apply() can only be used with a T{...} block") + def apply(): T = ??? +} + +object Target{ + trait Cacher{ + private[this] val cacherLazyMap = mutable.Map.empty[sourcecode.Enclosing, Target[_]] + protected[this] def cachedTarget[T](t: => Target[T]) + (implicit c: sourcecode.Enclosing): Target[T] = synchronized{ + cacherLazyMap.getOrElseUpdate(c, t).asInstanceOf[Target[T]] + } + } + class Target0[T](t: T) extends Target[T]{ + lazy val t0 = t + val inputs = Nil + def evaluate(args: Args) = t0 + } + def apply[T](t: Target[T]): Target[T] = macro impl0[T] + def apply[T](t: T): Target[T] = macro impl[T] + def impl0[T: c.WeakTypeTag](c: Context)(t: c.Expr[Target[T]]): c.Expr[Target[T]] = { + wrapCached(c)(t.tree) + } + def impl[T: c.WeakTypeTag](c: Context)(t: c.Expr[T]): c.Expr[Target[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, Symbol)] + val targetApplySym = c.universe.typeOf[Target[_]].member(TermName("apply")) + // Derived from @olafurpg's + // https://gist.github.com/olafurpg/596d62f87bf3360a29488b725fbc7608 + + val (startPos, endPos) = rec(t.tree) + .map(t => (t.pos.start, t.pos.end)) + .reduce[(Int, Int)]{ case ((s1, e1), (s2, e2)) => (math.min(s1, s2), math.max(e1, e2))} + + val macroSource = t.tree.pos.source + val transformed = c.internal.typingTransform(t.tree) { + case (t @ q"$fun.apply()", api) if t.symbol == targetApplySym => + + val used = rec(t) + val banned = used.filter(x => + x.symbol.pos.source == macroSource && + x.symbol.pos.start >= startPos && + x.symbol.pos.end <= endPos + ) + 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(api.currentOwner, tempName) + c.internal.setInfo(tempSym, t.tpe) + val tempIdent = Ident(tempSym) + c.internal.setType(tempIdent, t.tpe) + bound.append((fun, tempSym)) + tempIdent + case (t, api) => api.default(t) + } + + val (exprs, symbols) = bound.unzip + + val bindings = symbols.map(c.internal.valDef(_)) + + wrapCached(c)(q"forge.zipMap(..$exprs){ (..$bindings) => $transformed }") + } + def wrapCached[T](c: Context)(t: c.Tree) = { + import c.universe._ + val owner = c.internal.enclosingOwner + val ownerIsCacherClass = + owner.owner.isClass && + owner.owner.asClass.baseClasses.exists(_.fullName == "forge.define.Target.Cacher") + + if (ownerIsCacherClass && !owner.isMethod){ + c.abort( + c.enclosingPosition, + "T{} members defined in a Cacher class/trait/object body must be defs" + ) + }else{ + val embedded = + if (!ownerIsCacherClass) t + else q"this.cachedTarget($t)" + + c.Expr[Target[T]](embedded) + } + } + + abstract class Ops[T]{ this: Target[T] => + def map[V](f: T => V) = new Target.Mapped(this, f) + + def filter(f: T => Boolean) = this + def withFilter(f: T => Boolean) = this + def zip[V](other: Target[V]) = new Target.Zipped(this, other) + + } + + def traverse[T](source: Seq[Target[T]]) = { + new Traverse[T](source) + } + class Traverse[T](val inputs: Seq[Target[T]]) extends Target[Seq[T]]{ + def evaluate(args: Args) = { + for (i <- 0 until args.length) + yield args(i).asInstanceOf[T] + } + + } + class Mapped[T, V](source: Target[T], f: T => V) extends Target[V]{ + def evaluate(args: Args) = f(args(0)) + val inputs = List(source) + } + class Zipped[T, V](source1: Target[T], + source2: Target[V]) extends Target[(T, V)]{ + def evaluate(args: Args) = (args(0), args(1)) + val inputs = List(source1, source2) + } + + def path(path: ammonite.ops.Path) = new Path(path) + class Path(path: ammonite.ops.Path) extends Target[PathRef]{ + def handle = PathRef(path) + def evaluate(args: Args) = handle + override def sideHash = handle.hashCode() + val inputs = Nil + } + + class Subprocess(val inputs: Seq[Target[_]], + command: Args => Seq[String]) extends Target[Subprocess.Result] { + + def evaluate(args: Args) = { + mkdir(args.dest) + import ammonite.ops._ + implicit val path = ammonite.ops.Path(args.dest, pwd) + val toTarget = () // Shadow the implicit conversion :/ + val output = %%(command(args)) + assert(output.exitCode == 0) + Subprocess.Result(output, PathRef(args.dest)) + } + } + object Subprocess{ + case class Result(result: ammonite.ops.CommandResult, dest: PathRef) + object Result{ + private implicit val crFormat: Format[CommandResult] = JsonFormatters.crFormat + implicit val tsFormat: Format[Target.Subprocess.Result] = Json.format + } + } +} diff --git a/core/src/main/scala/forge/define/ZipTarget.scala b/core/src/main/scala/forge/define/ZipTarget.scala new file mode 100644 index 00000000..1faef07e --- /dev/null +++ b/core/src/main/scala/forge/define/ZipTarget.scala @@ -0,0 +1,40 @@ +package forge.define + +import forge.util.Args + +object ZipTarget +trait ZipTarget { + val T = Target + type T[V] = Target[V] + def zipMap[R]()(f: () => R) = new Target.Target0(f()) + def zipMap[A, R](a: T[A])(f: A => R) = a.map(f) + def zipMap[A, B, R](a: T[A], b: T[B])(f: (A, B) => R) = zip(a, b).map(f.tupled) + def zipMap[A, B, C, R](a: T[A], b: T[B], c: T[C])(f: (A, B, C) => R) = zip(a, b, c).map(f.tupled) + def zipMap[A, B, C, D, R](a: T[A], b: T[B], c: T[C], d: T[D])(f: (A, B, C, D) => R) = zip(a, b, c, d).map(f.tupled) + def zipMap[A, B, C, D, E, R](a: T[A], b: T[B], c: T[C], d: T[D], e: T[E])(f: (A, B, C, D, E) => R) = zip(a, b, c, d, e).map(f.tupled) + def zipMap[A, B, C, D, E, F, R](a: T[A], b: T[B], c: T[C], d: T[D], e: T[E], f: T[F])(cb: (A, B, C, D, E, F) => R) = zip(a, b, c, d, e, f).map(cb.tupled) + def zipMap[A, B, C, D, E, F, G, R](a: T[A], b: T[B], c: T[C], d: T[D], e: T[E], f: T[F], g: T[G])(cb: (A, B, C, D, E, F, G) => R) = zip(a, b, c, d, e, f, g).map(cb.tupled) + def zip() = new Target.Target0(()) + def zip[A](a: T[A]) = a.map(Tuple1(_)) + def zip[A, B](a: T[A], b: T[B]) = a.zip(b) + def zip[A, B, C](a: T[A], b: T[B], c: T[C]) = new T[(A, B, C)]{ + val inputs = Seq(a, b, c) + def evaluate(args: Args) = (args[A](0), args[B](1), args[C](2)) + } + def zip[A, B, C, D](a: T[A], b: T[B], c: T[C], d: T[D]) = new T[(A, B, C, D)]{ + val inputs = Seq(a, b, c, d) + def evaluate(args: Args) = (args[A](0), args[B](1), args[C](2), args[D](3)) + } + def zip[A, B, C, D, E](a: T[A], b: T[B], c: T[C], d: T[D], e: T[E]) = new T[(A, B, C, D, E)]{ + val inputs = Seq(a, b, c, d, e) + def evaluate(args: Args) = (args[A](0), args[B](1), args[C](2), args[D](3), args[E](4)) + } + def zip[A, B, C, D, E, F](a: T[A], b: T[B], c: T[C], d: T[D], e: T[E], f: T[F]) = new T[(A, B, C, D, E, F)]{ + val inputs = Seq(a, b, c, d, e, f) + def evaluate(args: Args) = (args[A](0), args[B](1), args[C](2), args[D](3), args[E](4), args[F](5)) + } + def zip[A, B, C, D, E, F, G](a: T[A], b: T[B], c: T[C], d: T[D], e: T[E], f: T[F], g: T[G]) = new T[(A, B, C, D, E, F, G)]{ + val inputs = Seq(a, b, c, d, e, f, g) + def evaluate(args: Args) = (args[A](0), args[B](1), args[C](2), args[D](3), args[E](4), args[F](5), args[G](6)) + } +} |