summaryrefslogblamecommitdiff
path: root/core/src/main/scala/forge/Target.scala
blob: ae5b488bf2eb0ef5cf9c161323b361f58e10100d (plain) (tree)
1
2
3
4
5
6
7
8
             
 
 
                               
                                           
                                                 

                                       



                                      
                                               
     










                                                                              
             
      
                       
 

                                                                         


              
               
                                                                                        



                                                                                          
                                                                 

     
                                           
                   
                    

                                  
                                           
                                               






                                                                                
                       













                                                                                     


                                       
                                                   
                                      
 
                                                    
 
                                                                                  




                                                                

                               
   
 

                                                      
 

                                          
                                                                 
 
   
 
                                             
                           
   
                                                                       





                                    
                                                                     


                                         
                                        
                                                                                

                                                 

   
                                                    
                                                              
                              

                                             

                    
 
                                              
                                                                                    

                                
                      

                                                           
                                                            

                                    
                                                   


                    



                                                                           
   
 
package forge


import ammonite.ops.{ls, mkdir}
import forge.util.{Args, LocalDef, PathRef}
import play.api.libs.json.{Format, JsValue, Json}

import scala.annotation.compileTimeOnly
import language.experimental.macros
import reflect.macros.blackbox.Context
import scala.collection.mutable

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 T[T](t: T)
                            (implicit l: LocalDef): Target[T] = macro localDefImpl[T]
    protected[this] def T[T](t: => Target[T])
                            (implicit c: sourcecode.Enclosing, l: LocalDef): Target[T] = {
      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] = t
  def apply[T](t: T): Target[T] = macro impl[T]
  def localDefImpl[T: c.WeakTypeTag](c: Context)
                                    (t: c.Expr[T])
                                    (l: c.Expr[LocalDef]): c.Expr[Target[T]] = {
    impl(c)(t)
  }
  def impl[T: c.WeakTypeTag](c: Context)
                            (t: c.Expr[T]): c.Expr[Target[T]] = {
    import c.universe._
    val bound = collection.mutable.Buffer.empty[(c.Tree, Symbol)]
    val OptionGet = c.universe.typeOf[Target[_]].member(TermName("apply"))
    object transformer extends c.universe.Transformer {
      // Derived from @olafurpg's
      // https://gist.github.com/olafurpg/596d62f87bf3360a29488b725fbc7608
      override def transform(tree: c.Tree): c.Tree = tree match {
        case t @ q"$fun.apply()" if t.symbol == OptionGet =>
          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)
          bound.append((fun, tempSym))
          tempIdent
        case _ => super.transform(tree)
      }
    }
    val transformed = transformer.transform(t.tree)
    val (exprs, symbols) = bound.unzip

    val bindings = symbols.map(c.internal.valDef(_))

    val newTargetTree = q"forge.zipMap(..$exprs){ (..$bindings) => $transformed }"

    val embedded =
      if (!(c.prefix.tree.tpe <:< typeOf[Cacher])) newTargetTree
      else q"${c.prefix}.T($newTargetTree)"


    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{
      implicit val tsFormat: Format[Target.Subprocess.Result] = Json.format
    }
  }
}