summaryrefslogtreecommitdiff
path: root/main/core/src/define/Cross.scala
blob: aa730e0da37e0dd7c94757092e1bcdb340e3ac51 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
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]
  }
}