All Downloads are FREE. Search and download functionalities are using the official Maven repository.

mill.define.Task.scala Maven / Gradle / Ivy

The newest version!
package mill.define

import mill.api.{CompileProblemReporter, Logger, PathRef, Result, TestReporter}
import mill.define.Applicative.Applyable
import upickle.default.{ReadWriter => RW, 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 tasks does this task depend on?
   */
  val inputs: Seq[Task[_]]

  /**
   * Evaluate this task
   */
  def evaluate(args: mill.api.Ctx): Result[T]

  /**
   * Even if this task's inputs did not change, does it need to re-evaluate
   * anyway?
   */
  def sideHash: Int = 0

  /**
   * Whether this [[Task]] deletes the `T.dest` folder between runs
   */
  def flushDest: Boolean = true

  def asTarget: Option[Target[T]] = None
  def asCommand: Option[Command[T]] = None
  def asWorker: Option[Worker[T]] = None
  def self: Task[T] = this
  def isExclusiveCommand: Boolean = this match {
    case c: Command[_] if c.exclusive => true
    case _ => false
  }
}

object Task extends TaskBase {

  /**
   * A specialization of [[InputImpl]] defined via `Task.Sources`, [[SourcesImpl]]
   * uses [[PathRef]]s to compute a signature for a set of source files and
   * folders.
   *
   * This is most used when detecting changes in source code: when you edit a
   * file and run `mill compile`, it is the `Task.Sources` that re-computes the
   * signature for you source files/folders and decides whether downstream
   * [[TargetImpl]]s need to be invalidated and re-computed.
   */
  def Sources(values: Result[os.Path]*)(implicit ctx: mill.define.Ctx): Target[Seq[PathRef]] =
    macro Target.Internal.sourcesImpl1

  def Sources(values: Result[Seq[PathRef]])(implicit ctx: mill.define.Ctx): Target[Seq[PathRef]] =
    macro Target.Internal.sourcesImpl2

  /**
   * Similar to [[Source]], but only for a single source file or folder. Defined
   * using `Task.Source`.
   */
  def Source(value: Result[os.Path])(implicit ctx: mill.define.Ctx): Target[PathRef] =
    macro Target.Internal.sourceImpl1

  def Source(value: Result[PathRef])(implicit ctx: mill.define.Ctx): Target[PathRef] =
    macro Target.Internal.sourceImpl2

  /**
   * [[InputImpl]]s, normally defined using `Task.Input`, are [[NamedTask]]s that
   * re-evaluate every time Mill is run. This is in contrast to [[TargetImpl]]s
   * which only re-evaluate when upstream tasks change.
   *
   * [[InputImpl]]s are useful when you want to capture some input to the Mill
   * build graph that comes from outside: maybe from an environment variable, a
   * JVM system property, the hash returned by `git rev-parse HEAD`. Reading
   * these external mutable variables inside a `Task{...}` [[TargetImpl]] will
   * incorrectly cache them forever. Reading them inside a `Task.Input{...}`
   * will re-compute them every time, and only if the value changes would it
   * continue to invalidate downstream [[TargetImpl]]s
   *
   * The most common case of [[InputImpl]] is [[SourceImpl]] and [[SourcesImpl]],
   * used for detecting changes to source files.
   */
  def Input[T](value: Result[T])(implicit
      w: upickle.default.Writer[T],
      ctx: mill.define.Ctx
  ): Target[T] =
    macro Target.Internal.inputImpl[T]

  /**
   * [[Command]]s are only [[NamedTask]]s defined using
   * `def foo() = Task.Command{...}` and are typically called from the
   * command-line. Unlike other [[NamedTask]]s, [[Command]]s can be defined to
   * take arguments that are automatically converted to command-line
   * arguments, as long as an implicit [[mainargs.TokensReader]] is available.
   */
  def Command[T](t: Result[T])(implicit
      w: W[T],
      ctx: mill.define.Ctx,
      cls: EnclosingClass
  ): Command[T] = macro Target.Internal.commandImpl[T]

  /**
   * @param exclusive Exclusive commands run serially at the end of an evaluation,
   *                  without any other tasks running parallel, and without the
   *                  terminal logging prefixes that are applied to normal tasks.
   *                  These are normally used for "top level" commands which are
   *                  run directly to perform some action or display some output
   *                  to the user.
   */
  def Command(
      t: NamedParameterOnlyDummy = new NamedParameterOnlyDummy,
      exclusive: Boolean = false
  ): CommandFactory = new CommandFactory(exclusive)
  class CommandFactory private[mill] (val exclusive: Boolean) extends TaskBase.TraverseCtxHolder {
    def apply[T](t: Result[T])(implicit
        w: W[T],
        ctx: mill.define.Ctx,
        cls: EnclosingClass
    ): Command[T] = macro Target.Internal.serialCommandImpl[T]
  }

  /**
   * [[Worker]] is a [[NamedTask]] that lives entirely in-memory, defined using
   * `Task.Worker{...}`. The value returned by `Task.Worker{...}` is long-lived,
   * persisting as long as the Mill process is kept alive (e.g. via `--watch`,
   * or via its default `MillServerMain` server process). This allows the user to
   * perform in-memory caching that is even more aggressive than the disk-based
   * caching enabled by [[PersistentImpl]]: your [[Worker]] can cache running
   * sub-processes, JVM Classloaders with JITed code, and all sorts of things
   * that do not easily serialize to JSON on disk.
   *
   * Like [[PersistentImpl]], The user defining a [[Worker]] assumes the
   * responsibility of ensuring the implementation is idempotent regardless of
   * what in-memory state the worker may have.
   */
  def Worker[T](t: Result[T])(implicit ctx: mill.define.Ctx): Worker[T] =
    macro Target.Internal.workerImpl2[T]

  /**
   * Creates an anonymous `Task`. These depend on other tasks and
   * be-depended-upon by other tasks, but cannot be run directly from the
   * command line and do not perform any caching. Typically used as helpers to
   * implement `Task{...}` targets.
   */
  def Anon[T](t: Result[T]): Task[T] = macro Applicative.impl[Task, T, mill.api.Ctx]

  @deprecated(
    "Creating a target from a task is deprecated. You most likely forgot a parenthesis pair `()`",
    "Mill after 0.12.0-RC1"
  )
  def apply[T](t: Task[T])(implicit rw: RW[T], ctx: mill.define.Ctx): Target[T] =
    macro Target.Internal.targetTaskImpl[T]

  def apply[T](t: T)(implicit rw: RW[T], ctx: mill.define.Ctx): Target[T] =
    macro Target.Internal.targetImpl[T]

  def apply[T](t: Result[T])(implicit rw: RW[T], ctx: mill.define.Ctx): Target[T] =
    macro Target.Internal.targetResultImpl[T]

  /**
   * Persistent tasks are defined using
   * the `Task(persistent = true){...}` syntax. The main difference is that while
   * [[TargetImpl]] deletes the `T.dest` folder in between runs,
   * [[PersistentImpl]] preserves it. This lets the user make use of files on
   * disk that persistent between runs of the task, e.g. to implement their own
   * fine-grained caching beyond what Mill provides by default.
   *
   * Note that the user defining a `Task(persistent = true)` task is taking on the
   * responsibility of ensuring that their implementation is idempotent, i.e.
   * that it computes the same result whether there is data in `T.dest`.
   * Violating that invariant can result in confusing mis-behaviors
   */
  def apply(
      t: NamedParameterOnlyDummy = new NamedParameterOnlyDummy,
      persistent: Boolean = false
  ): ApplyFactory = new ApplyFactory(persistent)
  class ApplyFactory private[mill] (val persistent: Boolean) extends TaskBase.TraverseCtxHolder {
    def apply[T](t: Result[T])(implicit
        rw: RW[T],
        ctx: mill.define.Ctx
    ): Target[T] = macro Target.Internal.persistentTargetResultImpl[T]
  }

  abstract class Ops[+T] { this: Task[T] =>
    def map[V](f: T => V): Task[V] = new Task.Mapped(this, f)
    def filter(f: T => Boolean): Task[T] = this
    def withFilter(f: T => Boolean): Task[T] = this
    def zip[V](other: Task[V]): Task[(T, V)] = new Task.Zipped(this, other)

  }

  private[define] class Sequence[+T](inputs0: Seq[Task[T]]) extends Task[Seq[T]] {
    val inputs: Seq[Task[_]] = inputs0
    def evaluate(ctx: mill.api.Ctx): Result[Seq[T]] = {
      for (i <- 0 until ctx.args.length)
        yield ctx.args(i).asInstanceOf[T]
    }
  }
  private[define] class TraverseCtx[+T, V](
      inputs0: Seq[Task[T]],
      f: (IndexedSeq[T], mill.api.Ctx) => Result[V]
  ) extends Task[V] {
    val inputs: Seq[Task[_]] = inputs0
    def evaluate(ctx: mill.api.Ctx): Result[V] = {
      f(
        for (i <- 0 until ctx.args.length)
          yield ctx.args(i).asInstanceOf[T],
        ctx
      )
    }
  }

  private[define] class Mapped[+T, +V](source: Task[T], f: T => V) extends Task[V] {
    def evaluate(ctx: mill.api.Ctx): Result[V] = f(ctx.arg(0))
    val inputs: Seq[Task[_]] = List(source)
  }

  private[define] class Zipped[+T, +V](source1: Task[T], source2: Task[V]) extends Task[(T, V)] {
    def evaluate(ctx: mill.api.Ctx): Result[(T, V)] = (ctx.arg(0), ctx.arg(1))
    val inputs: Seq[Task[_]] = List(source1, source2)
  }
}

/**
 * Represents a task that can be referenced by its path segments. `Task{...}`
 * targets, `Task.Input`, `Task.Worker`, etc. but not including anonymous
 * `Task.Anon` or `T.traverse` etc. instances
 */
trait NamedTask[+T] extends Task[T] {

  /**
   * The implementation task wrapped by this named task
   */
  def t: Task[T]
  def ctx0: mill.define.Ctx
  def isPrivate: Option[Boolean]
  def label: String = ctx.segment match {
    case Segment.Label(v) => v
    case Segment.Cross(_) => throw new IllegalArgumentException(
        "NamedTask only support a ctx with a Label segment, but found a Cross."
      )
  }
  override def toString = ctx.segments.render

  def evaluate(ctx: mill.api.Ctx): Result[T] = ctx.arg[T](0)

  val ctx: Ctx = ctx0.withSegments(segments = ctx0.segments ++ Seq(ctx0.segment))
  val inputs: Seq[Task[_]] = Seq(t)

  def readWriterOpt: Option[upickle.default.ReadWriter[_]] = None

  def writerOpt: Option[upickle.default.Writer[_]] = readWriterOpt.orElse(None)
}

/**
 * A Target is a [[NamedTask]] that is cached on disk; either a
 * [[TargetImpl]] or an [[InputImpl]]
 */
trait Target[+T] extends NamedTask[T]

object Target extends TaskBase {
  @deprecated("Use Task(persistent = true){...} instead", "Mill after 0.12.0-RC1")
  def persistent[T](t: Result[T])(implicit rw: RW[T], ctx: mill.define.Ctx): Target[T] =
    macro Target.Internal.persistentImpl[T]

  @deprecated("Use Task.Sources instead", "Mill after 0.12.0-RC1")
  def sources(values: Result[os.Path]*)(implicit ctx: mill.define.Ctx): Target[Seq[PathRef]] =
    macro Target.Internal.sourcesImpl1
  @deprecated("Use Task.Sources instead", "Mill after 0.12.0-RC1")
  def sources(values: Result[Seq[PathRef]])(implicit ctx: mill.define.Ctx): Target[Seq[PathRef]] =
    macro Target.Internal.sourcesImpl2

  @deprecated("Use Task.Source instead", "Mill after 0.12.0-RC1")
  def source(value: Result[os.Path])(implicit ctx: mill.define.Ctx): Target[PathRef] =
    macro Target.Internal.sourceImpl1

  @deprecated("Use Task.Source instead", "Mill after 0.12.0-RC1")
  def source(value: Result[PathRef])(implicit ctx: mill.define.Ctx): Target[PathRef] =
    macro Target.Internal.sourceImpl2

  @deprecated("Use Task.Input instead", "Mill after 0.12.0-RC1")
  def input[T](value: Result[T])(implicit
      w: upickle.default.Writer[T],
      ctx: mill.define.Ctx
  ): Target[T] =
    macro Target.Internal.inputImpl[T]

  @deprecated(
    "Creating a command from a task is deprecated. You most likely forgot a parenthesis pair `()`",
    "Mill after 0.12.0-RC1"
  )
  def command[T](t: Task[T])(implicit
      ctx: mill.define.Ctx,
      w: W[T],
      cls: EnclosingClass
  ): Command[T] = macro Target.Internal.commandFromTask[T]

  @deprecated("Use Task.Command instead", "Mill after 0.12.0-RC1")
  def command[T](t: Result[T])(implicit
      w: W[T],
      ctx: mill.define.Ctx,
      cls: EnclosingClass
  ): Command[T] = macro Target.Internal.commandImpl[T]

  @deprecated(
    "Creating a worker from a task is deprecated. You most likely forgot a parenthesis pair `()`",
    "Mill after 0.12.0-RC1"
  )
  def worker[T](t: Task[T])(implicit ctx: mill.define.Ctx): Worker[T] =
    macro Target.Internal.workerImpl1[T]

  @deprecated("Use Task.Worker instead", "Mill after 0.12.0-RC1")
  def worker[T](t: Result[T])(implicit ctx: mill.define.Ctx): Worker[T] =
    macro Target.Internal.workerImpl2[T]

  @deprecated("Use Task.Anon instead", "Mill after 0.12.0-RC2")
  def task[T](t: Result[T]): Task[T] = macro Applicative.impl[Task, T, mill.api.Ctx]

  @deprecated(
    "Creating a target from a task is deprecated. You most likely forgot a parenthesis pair `()`",
    "Mill after 0.12.0-RC1"
  )
  def apply[T](t: Task[T])(implicit rw: RW[T], ctx: mill.define.Ctx): Target[T] =
    macro Target.Internal.targetTaskImpl[T]

  /**
   * A target is the most common [[Task]] a user would encounter, commonly
   * defined using the `def foo = Task {...}` syntax. [[TargetImpl]]s require that their
   * return type is JSON serializable. In return, they automatically cache their
   * return value to disk, only re-computing if upstream [[Task]]s change
   */
  implicit def apply[T](t: T)(implicit rw: RW[T], ctx: mill.define.Ctx): Target[T] =
    macro Internal.targetImpl[T]

  implicit def apply[T](t: Result[T])(implicit rw: RW[T], ctx: mill.define.Ctx): Target[T] =
    macro Internal.targetResultImpl[T]

  object Internal {
    private def isPrivateTargetOption(c: Context): c.Expr[Option[Boolean]] = {
      import c.universe._
      if (c.internal.enclosingOwner.isPrivate) reify(Some(true))
      else reify(Some(false))
    }

    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 taskIsPrivate = isPrivateTargetOption(c)

      val lhs = Applicative.impl0[Task, T, mill.api.Ctx](c)(reify(Result.create(t.splice)).tree)

      mill.define.Cacher.impl0[Target[T]](c)(
        reify(
          new TargetImpl[T](
            lhs.splice,
            ctx.splice,
            rw.splice,
            taskIsPrivate.splice
          )
        )
      )
    }

    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._

      val taskIsPrivate = isPrivateTargetOption(c)

      mill.define.Cacher.impl0[Target[T]](c)(
        reify(
          new TargetImpl[T](
            Applicative.impl0[Task, T, mill.api.Ctx](c)(t.tree).splice,
            ctx.splice,
            rw.splice,
            taskIsPrivate.splice
          )
        )
      )
    }
    def persistentTargetResultImpl[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._

      val taskIsPrivate = isPrivateTargetOption(c)

      mill.define.Cacher.impl0[Target[T]](c)(
        reify {
          val s1 = Applicative.impl0[Task, T, mill.api.Ctx](c)(t.tree).splice
          val c1 = ctx.splice
          val r1 = rw.splice
          val t1 = taskIsPrivate.splice
          if (c.prefix.splice.asInstanceOf[Task.ApplyFactory].persistent) {
            new PersistentImpl[T](s1, c1, r1, t1)
          } else {
            new TargetImpl[T](s1, c1, r1, t1)
          }
        }
      )
    }

    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._

      val taskIsPrivate = isPrivateTargetOption(c)

      mill.define.Cacher.impl0[Target[T]](c)(
        reify(
          new TargetImpl[T](
            t.splice,
            ctx.splice,
            rw.splice,
            taskIsPrivate.splice
          )
        )
      )
    }

    def sourcesImpl1(c: Context)(values: c.Expr[Result[os.Path]]*)(ctx: c.Expr[mill.define.Ctx])
        : c.Expr[Target[Seq[PathRef]]] = {
      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

      val taskIsPrivate = isPrivateTargetOption(c)

      mill.define.Cacher.impl0[SourcesImpl](c)(
        reify(
          new SourcesImpl(
            Target.sequence(c.Expr[List[Task[PathRef]]](q"_root_.scala.List(..$wrapped)").splice),
            ctx.splice,
            taskIsPrivate.splice
          )
        )
      )
    }

    def sourcesImpl2(c: Context)(values: c.Expr[Result[Seq[PathRef]]])(ctx: c.Expr[mill.define.Ctx])
        : c.Expr[Target[Seq[PathRef]]] = {
      import c.universe._

      val taskIsPrivate = isPrivateTargetOption(c)

      mill.define.Cacher.impl0[SourcesImpl](c)(
        reify(
          new SourcesImpl(
            Applicative.impl0[Task, Seq[PathRef], mill.api.Ctx](c)(values.tree).splice,
            ctx.splice,
            taskIsPrivate.splice
          )
        )
      )
    }

    def sourceImpl1(c: Context)(value: c.Expr[Result[os.Path]])(ctx: c.Expr[mill.define.Ctx])
        : c.Expr[Target[PathRef]] = {
      import c.universe._

      val wrapped =
        Applicative.impl0[Task, PathRef, mill.api.Ctx](c)(
          reify(value.splice.map(PathRef(_))).tree
        )

      val taskIsPrivate = isPrivateTargetOption(c)

      mill.define.Cacher.impl0[Target[PathRef]](c)(
        reify(
          new SourceImpl(
            wrapped.splice,
            ctx.splice,
            taskIsPrivate.splice
          )
        )
      )
    }

    def sourceImpl2(c: Context)(value: c.Expr[Result[PathRef]])(ctx: c.Expr[mill.define.Ctx])
        : c.Expr[Target[PathRef]] = {
      import c.universe._

      val taskIsPrivate = isPrivateTargetOption(c)

      mill.define.Cacher.impl0[Target[PathRef]](c)(
        reify(
          new SourceImpl(
            Applicative.impl0[Task, PathRef, mill.api.Ctx](c)(value.tree).splice,
            ctx.splice,
            taskIsPrivate.splice
          )
        )
      )
    }

    def inputImpl[T: c.WeakTypeTag](c: Context)(value: c.Expr[T])(
        w: c.Expr[upickle.default.Writer[T]],
        ctx: c.Expr[mill.define.Ctx]
    ): c.Expr[Target[T]] = {
      import c.universe._

      val taskIsPrivate = isPrivateTargetOption(c)

      mill.define.Cacher.impl0[InputImpl[T]](c)(
        reify(
          new InputImpl[T](
            Applicative.impl[Task, T, mill.api.Ctx](c)(value).splice,
            ctx.splice,
            w.splice,
            taskIsPrivate.splice
          )
        )
      )
    }

    def commandFromTask[T: c.WeakTypeTag](c: Context)(t: c.Expr[Task[T]])(
        ctx: c.Expr[mill.define.Ctx],
        w: c.Expr[W[T]],
        cls: c.Expr[EnclosingClass]
    ): c.Expr[Command[T]] = {
      import c.universe._

      val taskIsPrivate = isPrivateTargetOption(c)

      reify(
        new Command[T](
          t.splice,
          ctx.splice,
          w.splice,
          cls.splice.value,
          taskIsPrivate.splice
        )
      )
    }

    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]
    ): c.Expr[Command[T]] = {
      import c.universe._

      val taskIsPrivate = isPrivateTargetOption(c)

      reify(
        new Command[T](
          Applicative.impl[Task, T, mill.api.Ctx](c)(t).splice,
          ctx.splice,
          w.splice,
          cls.splice.value,
          taskIsPrivate.splice
        )
      )
    }

    def serialCommandImpl[T: c.WeakTypeTag](c: Context)(t: c.Expr[T])(
        w: c.Expr[W[T]],
        ctx: c.Expr[mill.define.Ctx],
        cls: c.Expr[EnclosingClass]
    ): c.Expr[Command[T]] = {
      import c.universe._

      val taskIsPrivate = isPrivateTargetOption(c)

      reify(
        new Command[T](
          Applicative.impl[Task, T, mill.api.Ctx](c)(t).splice,
          ctx.splice,
          w.splice,
          cls.splice.value,
          taskIsPrivate.splice,
          exclusive = c.prefix.splice.asInstanceOf[Task.CommandFactory].exclusive
        )
      )
    }

    def workerImpl1[T: c.WeakTypeTag](c: Context)(t: c.Expr[Task[T]])(ctx: c.Expr[mill.define.Ctx])
        : c.Expr[Worker[T]] = {
      import c.universe._

      val taskIsPrivate = isPrivateTargetOption(c)

      mill.define.Cacher.impl0[Worker[T]](c)(
        reify(
          new Worker[T](t.splice, ctx.splice, taskIsPrivate.splice)
        )
      )
    }

    def workerImpl2[T: c.WeakTypeTag](c: Context)(t: c.Expr[T])(ctx: c.Expr[mill.define.Ctx])
        : c.Expr[Worker[T]] = {
      import c.universe._

      val taskIsPrivate = isPrivateTargetOption(c)

      mill.define.Cacher.impl0[Worker[T]](c)(
        reify(
          new Worker[T](
            Applicative.impl[Task, T, mill.api.Ctx](c)(t).splice,
            ctx.splice,
            taskIsPrivate.splice
          )
        )
      )
    }

    def persistentImpl[T: c.WeakTypeTag](c: Context)(t: c.Expr[T])(
        rw: c.Expr[RW[T]],
        ctx: c.Expr[mill.define.Ctx]
    ): c.Expr[PersistentImpl[T]] = {
      import c.universe._

      val taskIsPrivate = isPrivateTargetOption(c)

      mill.define.Cacher.impl0[PersistentImpl[T]](c)(
        reify(
          new PersistentImpl[T](
            Applicative.impl[Task, T, mill.api.Ctx](c)(t).splice,
            ctx.splice,
            rw.splice,
            taskIsPrivate.splice
          )
        )
      )
    }
  }

}

/**
 * The [[mill.define.Target]] companion object, usually aliased as [[T]],
 * provides most of the helper methods and macros used to build task graphs.
 * methods like `T.`[[apply]], `T.`[[sources]], `T.`[[command]] allow you to
 * define the tasks, while methods like `T.`[[dest]], `T.`[[log]] or
 * `T.`[[env]] provide the core APIs that are provided to a task implementation
 */
class TaskBase extends Applicative.Applyer[Task, Task, Result, mill.api.Ctx]
    with TaskBase.TraverseCtxHolder {

  /**
   * `T.dest` is a unique `os.Path` (e.g. `out/classFiles.dest/` or `out/run.dest/`)
   * that is assigned to every Target or Command. It is cleared before your
   * task runs, and you can use it as a scratch space for temporary files or
   * a place to put returned artifacts. This is guaranteed to be unique for
   * every Target or Command, so you can be sure that you will not collide or
   * interfere with anyone else writing to those same paths.
   */
  def dest(implicit ctx: mill.api.Ctx.Dest): os.Path = ctx.dest

  /**
   * `T.log` is the default logger provided for every task. While your task is running,
   * `System.out` and `System.in` are also redirected to this logger. The logs for a
   * task are streamed to standard out/error as you would expect, but each task's
   * specific output is also streamed to a log file on disk, e.g. `out/run.log` or
   * `out/classFiles.log` for you to inspect later.
   *
   * Messages logged with `log.debug` appear by default only in the log files.
   * You can use the `--debug` option when running mill to show them on the console too.
   */
  def log(implicit ctx: mill.api.Ctx.Log): Logger = ctx.log

  /**
   * Returns the implicit [[mill.api.Ctx.Home.home]] in scope.
   */
  def home(implicit ctx: mill.api.Ctx.Home): os.Path = ctx.home

  /**
   * `T.env` is the environment variable map passed to the Mill command when
   * it is run; typically used inside a `Task.Input` to ensure any changes in
   * the env vars are properly detected.
   *
   * Note that you should not use `sys.env`, as Mill's long-lived server
   * process means that `sys.env` variables may not be up to date.
   */
  def env(implicit ctx: mill.api.Ctx.Env): Map[String, String] = ctx.env

  /**
   * Returns the implicit [[mill.api.Ctx.Args.args]] in scope.
   */
  def args(implicit ctx: mill.api.Ctx.Args): IndexedSeq[_] = ctx.args

  /**
   * Report test results to BSP for IDE integration
   */
  def testReporter(implicit ctx: mill.api.Ctx): TestReporter = ctx.testReporter

  /**
   * Report build results to BSP for IDE integration
   */
  def reporter(implicit ctx: mill.api.Ctx): Int => Option[CompileProblemReporter] = ctx.reporter

  /**
   * This is the `os.Path` pointing to the project root directory.
   *
   * This is the preferred access to the project directory, and should
   * always be preferred over `os.pwd`* (which might also point to the
   * project directory in classic cli scenarios, but might not in other
   * use cases like BSP or LSP server usage).
   */
  def workspace(implicit ctx: mill.api.Ctx): os.Path = ctx.workspace

  /**
   * Provides the `.fork.async` and `.fork.await` APIs for spawning and joining
   * async futures within your task in a Mill-friendly manner.
   */
  def fork(implicit ctx: mill.api.Ctx): mill.api.Ctx.Fork.Api = ctx.fork

  /**
   * Converts a `Seq[Task[T]]` into a `Task[Seq[T]]`
   */
  def sequence[T](source: Seq[Task[T]]): Task[Seq[T]] = new Task.Sequence[T](source)

  /**
   * Converts a `Seq[T]` into a `Task[Seq[V]]` using the given `f: T => Task[V]`
   */
  def traverse[T, V](source: Seq[T])(f: T => Task[V]): Task[Seq[V]] = {
    new Task.Sequence[V](source.map(f))
  }
}

object TaskBase {
  trait TraverseCtxHolder {

    /**
     * A variant of [[traverse]] that also provides the [[mill.api.Ctx]] to the
     * function [[f]]
     */
    def traverseCtx[I, R](xs: Seq[Task[I]])(f: (IndexedSeq[I], mill.api.Ctx) => Result[R])
        : Task[R] = {
      new Task.TraverseCtx[I, R](xs, f)
    }
  }
}

class TargetImpl[+T](
    val t: Task[T],
    val ctx0: mill.define.Ctx,
    val readWriter: RW[_],
    val isPrivate: Option[Boolean]
) extends Target[T] {
  override def asTarget: Option[Target[T]] = Some(this)
  // FIXME: deprecated return type: Change to Option
  override def readWriterOpt: Some[RW[_]] = Some(readWriter)
}

class PersistentImpl[+T](
    t: Task[T],
    ctx0: mill.define.Ctx,
    readWriter: RW[_],
    isPrivate: Option[Boolean]
) extends TargetImpl[T](t, ctx0, readWriter, isPrivate) {
  override def flushDest = false
}

class Command[+T](
    val t: Task[T],
    val ctx0: mill.define.Ctx,
    val writer: W[_],
    val cls: Class[_],
    val isPrivate: Option[Boolean],
    val exclusive: Boolean
) extends NamedTask[T] {
  def this(
      t: Task[T],
      ctx0: mill.define.Ctx,
      writer: W[_],
      cls: Class[_],
      isPrivate: Option[Boolean]
  ) = this(t, ctx0, writer, cls, isPrivate, false)
  override def asCommand: Some[Command[T]] = Some(this)
  // FIXME: deprecated return type: Change to Option
  override def writerOpt: Some[W[_]] = Some(writer)
}

class Worker[+T](val t: Task[T], val ctx0: mill.define.Ctx, val isPrivate: Option[Boolean])
    extends NamedTask[T] {
  override def flushDest = false
  override def asWorker: Some[Worker[T]] = Some(this)
}

class InputImpl[T](
    val t: Task[T],
    val ctx0: mill.define.Ctx,
    val writer: upickle.default.Writer[_],
    val isPrivate: Option[Boolean]
) extends Target[T] {
  override def sideHash: Int = util.Random.nextInt()
  // FIXME: deprecated return type: Change to Option
  override def writerOpt: Some[W[_]] = Some(writer)
}

class SourcesImpl(t: Task[Seq[PathRef]], ctx0: mill.define.Ctx, isPrivate: Option[Boolean])
    extends InputImpl[Seq[PathRef]](
      t,
      ctx0,
      upickle.default.readwriter[Seq[PathRef]],
      isPrivate
    ) {
  override def readWriterOpt: Some[RW[Seq[PathRef]]] =
    Some(upickle.default.readwriter[Seq[PathRef]])
}

class SourceImpl(t: Task[PathRef], ctx0: mill.define.Ctx, isPrivate: Option[Boolean])
    extends InputImpl[PathRef](
      t,
      ctx0,
      upickle.default.readwriter[PathRef],
      isPrivate
    ) {
  override def readWriterOpt: Some[RW[PathRef]] = Some(upickle.default.readwriter[PathRef])
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy