From 01d7f400d7ef1c21e03f95a30c3cd344617811b5 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Tue, 4 Jan 2022 02:48:36 -0500 Subject: [PATCH] Port Task Std --- build.sbt | 1 + .../sbt/internal/util/appmacro/Cont.scala | 13 +- .../src/main/scala/sbt/Action.scala | 132 ------------- tasks-standard/src/main/scala/sbt/Task.scala | 59 ++++++ .../src/main/scala/sbt/internal/Action.scala | 83 +++++++++ .../src/main/scala/sbt/std/TaskExtra.scala | 175 +++++++++++------- .../src/main/scala/sbt/std/Transform.scala | 78 ++++---- tasks-standard/src/test/scala/TaskGen.scala | 6 +- .../src/test/scala/TaskSerial.scala | 6 +- tasks-standard/src/test/scala/Test.scala | 26 +-- .../main/scala/sbt/CompletionService.scala | 4 + tasks/src/main/scala/sbt/Execute.scala | 8 +- tasks/src/main/scala/sbt/Node.scala | 13 +- .../main/scala/sbt/internal/util/AList.scala | 4 +- .../sbt/internal/util/TypeFunctions.scala | 17 +- 15 files changed, 353 insertions(+), 272 deletions(-) delete mode 100644 tasks-standard/src/main/scala/sbt/Action.scala create mode 100644 tasks-standard/src/main/scala/sbt/Task.scala create mode 100644 tasks-standard/src/main/scala/sbt/internal/Action.scala diff --git a/build.sbt b/build.sbt index c291e9643..9c73c4874 100644 --- a/build.sbt +++ b/build.sbt @@ -190,6 +190,7 @@ lazy val sbtRoot: Project = (project in file(".")) logicProj, utilCache, taskProj, + stdTaskProj, ) .settings( minimalSettings, diff --git a/core-macros/src/main/scala/sbt/internal/util/appmacro/Cont.scala b/core-macros/src/main/scala/sbt/internal/util/appmacro/Cont.scala index 09b50914f..b619cab90 100644 --- a/core-macros/src/main/scala/sbt/internal/util/appmacro/Cont.scala +++ b/core-macros/src/main/scala/sbt/internal/util/appmacro/Cont.scala @@ -8,6 +8,7 @@ import scala.reflect.TypeTest import scala.quoted.* import sbt.util.Applicative import sbt.util.Monad +import Types.Id /** * Implementation of a macro that provides a direct syntax for applicative functors and monads. It @@ -197,11 +198,13 @@ trait Cont: case '[inputTypeTpe] => '{ given Applicative[F] = $instanceExpr - AList.tuple.mapN[F, A1, inputTypeTpe & Tuple](${ - br.tupleExpr.asInstanceOf[Expr[Tuple.Map[inputTypeTpe & Tuple, F]]] - })( - ${ lambda.asExprOf[inputTypeTpe => A1] } - ) + AList + .tuple[inputTypeTpe & Tuple] + .mapN[F, A1](${ + br.tupleExpr.asInstanceOf[Expr[Tuple.Map[inputTypeTpe & Tuple, F]]] + })( + ${ lambda.asExprOf[Tuple.Map[inputTypeTpe & Tuple, Id] => A1] } + ) } eitherTree match diff --git a/tasks-standard/src/main/scala/sbt/Action.scala b/tasks-standard/src/main/scala/sbt/Action.scala deleted file mode 100644 index 6c6889c78..000000000 --- a/tasks-standard/src/main/scala/sbt/Action.scala +++ /dev/null @@ -1,132 +0,0 @@ -/* - * sbt - * Copyright 2011 - 2018, Lightbend, Inc. - * Copyright 2008 - 2010, Mark Harrah - * Licensed under Apache License 2.0 (see LICENSE) - */ - -package sbt - -import sbt.internal.util.Types._ -import sbt.internal.util.{ ~>, AList, AttributeKey, AttributeMap } -import ConcurrentRestrictions.{ Tag, TagMap, tagsKey } - -// Action, Task, and Info are intentionally invariant in their type parameter. -// Various natural transformations used, such as PMap, require invariant type constructors for correctness - -/** Defines a task computation */ -sealed trait Action[T] { - // TODO: remove after deprecated InputTask constructors are removed - private[sbt] def mapTask(f: Task ~> Task): Action[T] -} - -/** - * A direct computation of a value. If `inline` is true, `f` will be evaluated on the scheduler - * thread without the overhead of normal scheduling when possible. This is intended as an - * optimization for already evaluated values or very short pure computations. - */ -final case class Pure[T](f: () => T, `inline`: Boolean) extends Action[T] { - private[sbt] def mapTask(f: Task ~> Task) = this -} - -/** Applies a function to the result of evaluating a heterogeneous list of other tasks. */ -final case class Mapped[T, K[L[x]]](in: K[Task], f: K[Result] => T, alist: AList[K]) - extends Action[T] { - private[sbt] def mapTask(g: Task ~> Task) = Mapped[T, K](alist.transform(in, g), f, alist) -} - -/** Computes another task to evaluate based on results from evaluating other tasks. */ -final case class FlatMapped[T, K[L[x]]](in: K[Task], f: K[Result] => Task[T], alist: AList[K]) - extends Action[T] { - private[sbt] def mapTask(g: Task ~> Task) = - FlatMapped[T, K](alist.transform(in, g), g.fn[T] compose f, alist) -} - -/** A computation `in` that requires other tasks `deps` to be evaluated first. */ -final case class DependsOn[T](in: Task[T], deps: Seq[Task[_]]) extends Action[T] { - private[sbt] def mapTask(g: Task ~> Task) = DependsOn[T](g(in), deps.map(t => g(t))) -} - -/** - * A computation that operates on the results of a homogeneous list of other tasks. It can either - * return another task to be evaluated or the final value. - */ -final case class Join[T, U](in: Seq[Task[U]], f: Seq[Result[U]] => Either[Task[T], T]) - extends Action[T] { - private[sbt] def mapTask(g: Task ~> Task) = - Join[T, U](in.map(g.fn[U]), sr => f(sr).left.map(g.fn[T])) -} - -/** - * A computation that conditionally falls back to a second transformation. This can be used to - * encode `if` conditions. - */ -final case class Selected[A, B](fab: Task[Either[A, B]], fin: Task[A => B]) extends Action[B] { - private def ml = AList.single[Either[A, B]] - type K[L[x]] = L[Either[A, B]] - - private[sbt] def mapTask(g: Task ~> Task) = - Selected[A, B](g(fab), g(fin)) - - /** - * Encode this computation as a flatMap. - */ - private[sbt] def asFlatMapped: FlatMapped[B, K] = { - val f: Either[A, B] => Task[B] = { - case Right(b) => std.TaskExtra.task(b) - case Left(a) => std.TaskExtra.singleInputTask(fin).map(_(a)) - } - FlatMapped[B, K]( - fab, { - f compose std.TaskExtra.successM - }, - ml - ) - } -} - -/** Combines metadata `info` and a computation `work` to define a task. */ -final case class Task[T](info: Info[T], work: Action[T]) { - override def toString = info.name getOrElse ("Task(" + info + ")") - override def hashCode = info.hashCode - - def tag(tags: Tag*): Task[T] = tagw(tags.map(t => (t, 1)): _*) - def tagw(tags: (Tag, Int)*): Task[T] = { - val tgs: TagMap = info.get(tagsKey).getOrElse(TagMap.empty) - val value = tags.foldLeft(tgs)((acc, tag) => acc + tag) - val nextInfo = info.set(tagsKey, value) - copy(info = nextInfo) - } - - def tags: TagMap = info get tagsKey getOrElse TagMap.empty -} - -/** - * Used to provide information about a task, such as the name, description, and tags for controlling - * concurrent execution. - * @param attributes - * Arbitrary user-defined key/value pairs describing this task - * @param post - * a transformation that takes the result of evaluating this task and produces user-defined - * key/value pairs. - */ -final case class Info[T]( - attributes: AttributeMap = AttributeMap.empty, - post: T => AttributeMap = Info.defaultAttributeMap -) { - import Info._ - def name = attributes.get(Name) - def description = attributes.get(Description) - def setName(n: String) = set(Name, n) - def setDescription(d: String) = set(Description, d) - def set[A](key: AttributeKey[A], value: A) = copy(attributes = this.attributes.put(key, value)) - def get[A](key: AttributeKey[A]): Option[A] = attributes.get(key) - def postTransform(f: (T, AttributeMap) => AttributeMap) = copy(post = (t: T) => f(t, post(t))) - - override def toString = if (attributes.isEmpty) "_" else attributes.toString -} -object Info { - val Name = AttributeKey[String]("name") - val Description = AttributeKey[String]("description") - val defaultAttributeMap = const(AttributeMap.empty) -} diff --git a/tasks-standard/src/main/scala/sbt/Task.scala b/tasks-standard/src/main/scala/sbt/Task.scala new file mode 100644 index 000000000..c89893f4a --- /dev/null +++ b/tasks-standard/src/main/scala/sbt/Task.scala @@ -0,0 +1,59 @@ +/* + * sbt + * Copyright 2011 - 2018, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under Apache License 2.0 (see LICENSE) + */ + +package sbt + +import sbt.internal.util.Types._ +import sbt.internal.util.{ ~>, AList, AttributeKey, AttributeMap } +import ConcurrentRestrictions.{ Tag, TagMap, tagsKey } +import sbt.internal.Action + +/** Combines metadata `info` and a computation `work` to define a task. */ +final case class Task[A](info: Info[A], work: Action[A]) { + override def toString = info.name getOrElse ("Task(" + info + ")") + override def hashCode = info.hashCode + + def tag(tags: Tag*): Task[A] = tagw(tags.map(t => (t, 1)): _*) + def tagw(tags: (Tag, Int)*): Task[A] = { + val tgs: TagMap = info.get(tagsKey).getOrElse(TagMap.empty) + val value = tags.foldLeft(tgs)((acc, tag) => acc + tag) + val nextInfo = info.set(tagsKey, value) + copy(info = nextInfo) + } + + def tags: TagMap = info get tagsKey getOrElse TagMap.empty +} + +/** + * Used to provide information about a task, such as the name, description, and tags for controlling + * concurrent execution. + * @param attributes + * Arbitrary user-defined key/value pairs describing this task + * @param post + * a transformation that takes the result of evaluating this task and produces user-defined + * key/value pairs. + */ +final case class Info[T]( + attributes: AttributeMap = AttributeMap.empty, + post: T => AttributeMap = Info.defaultAttributeMap +) { + import Info._ + def name = attributes.get(Name) + def description = attributes.get(Description) + def setName(n: String) = set(Name, n) + def setDescription(d: String) = set(Description, d) + def set[A](key: AttributeKey[A], value: A) = copy(attributes = this.attributes.put(key, value)) + def get[A](key: AttributeKey[A]): Option[A] = attributes.get(key) + def postTransform(f: (T, AttributeMap) => AttributeMap) = copy(post = (t: T) => f(t, post(t))) + + override def toString = if (attributes.isEmpty) "_" else attributes.toString +} +object Info { + val Name = AttributeKey[String]("name") + val Description = AttributeKey[String]("description") + val defaultAttributeMap = const(AttributeMap.empty) +} diff --git a/tasks-standard/src/main/scala/sbt/internal/Action.scala b/tasks-standard/src/main/scala/sbt/internal/Action.scala new file mode 100644 index 000000000..59220889f --- /dev/null +++ b/tasks-standard/src/main/scala/sbt/internal/Action.scala @@ -0,0 +1,83 @@ +/* + * sbt + * Copyright 2011 - 2018, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under Apache License 2.0 (see LICENSE) + */ + +package sbt +package internal + +import sbt.internal.util.AList + +// Action, Task, and Info are intentionally invariant in their type parameter. +// Various natural transformations used, such as PMap, require invariant type constructors for correctness + +/** Defines a task computation */ +enum Action[A]: + // TODO: remove after deprecated InputTask constructors are removed + // private[sbt] def mapTask(f: [A1] => Task[A1] => Task[A1]): Action[A] + + /** + * A direct computation of a value. If `inline` is true, `f` will be evaluated on the scheduler + * thread without the overhead of normal scheduling when possible. This is intended as an + * optimization for already evaluated values or very short pure computations. + */ + case Pure[A](f: () => A, `inline`: Boolean) extends Action[A] + // private[sbt] def mapTask(f: [A1] => Task[A1] => Task[A1]) = this + + /** Applies a function to the result of evaluating a heterogeneous list of other tasks. */ + case Mapped[A, K[F[_]]](in: K[Task], f: K[Result] => A, alist: AList[K]) extends Action[A] +// private[sbt] def mapTask(g: Task ~> Task) = Mapped[A, K](alist.transform(in, g), f, alist) + + /** Computes another task to evaluate based on results from evaluating other tasks. */ + case FlatMapped[A, K[F[_]]]( + in: K[Task], + f: K[Result] => Task[A], + alist: AList[K], + ) extends Action[A] + // private[sbt] def mapTask(g: Task ~> Task) = + // FlatMapped[A, K](alist.transform(in, g), g.fn[A] compose f, alist) + + /** A computation `in` that requires other tasks `deps` to be evaluated first. */ + case DependsOn[A](in: Task[A], deps: Seq[Task[_]]) extends Action[A] + // private[sbt] def mapTask(g: Task ~> Task) = DependsOn[A](g(in), deps.map(t => g(t))) + + /** + * A computation that operates on the results of a homogeneous list of other tasks. It can either + * return another task to be evaluated or the final value. + */ + case Join[A, U](in: Seq[Task[U]], f: Seq[Result[U]] => Either[Task[A], A]) extends Action[A] + // private[sbt] def mapTask(g: Task ~> Task) = + // Join[A, U](in.map(g.fn[U]), sr => f(sr).left.map(g.fn[A])) + + /** + * A computation that conditionally falls back to a second transformation. This can be used to + * encode `if` conditions. + */ + case Selected[A1, A2](fab: Task[Either[A1, A2]], fin: Task[A1 => A2]) extends Action[A2] + +// private[sbt] def mapTask(g: Task ~> Task) = +// Selected[A, B](g(fab), g(fin)) + +end Action + +object Action: + + /** + * Encode this computation as a flatMap. + */ + private[sbt] def asFlatMapped[A1, A2]( + s: Action.Selected[A1, A2] + ): Action.FlatMapped[A2, [F[_]] =>> Tuple1[F[Either[A1, A2]]]] = + val alist = AList.tuple[Tuple1[Either[A1, A2]]] + val f: Either[A1, A2] => Task[A2] = { + case Right(b) => std.TaskExtra.task(b) + case Left(a) => std.TaskExtra.singleInputTask(s.fin).map(_(a)) + } + Action.FlatMapped[A2, [F[_]] =>> Tuple1[F[Either[A1, A2]]]]( + Tuple1(s.fab), + { case Tuple1(r) => (f compose std.TaskExtra.successM)(r) }, + alist, + ) +end Action diff --git a/tasks-standard/src/main/scala/sbt/std/TaskExtra.scala b/tasks-standard/src/main/scala/sbt/std/TaskExtra.scala index 4b749b8f4..f6b2809e5 100644 --- a/tasks-standard/src/main/scala/sbt/std/TaskExtra.scala +++ b/tasks-standard/src/main/scala/sbt/std/TaskExtra.scala @@ -14,14 +14,15 @@ import sbt.internal.util.{ AList, AttributeMap } import sbt.internal.util.Types._ import java.io.{ BufferedInputStream, BufferedReader, File, InputStream } import sbt.io.IO +import sbt.internal.Action -sealed trait MultiInTask[K[L[x]]] { - def flatMap[T](f: K[Id] => Task[T]): Task[T] - def flatMapR[T](f: K[Result] => Task[T]): Task[T] - def map[T](f: K[Id] => T): Task[T] - def mapR[T](f: K[Result] => T): Task[T] - def flatFailure[T](f: Seq[Incomplete] => Task[T]): Task[T] - def mapFailure[T](f: Seq[Incomplete] => T): Task[T] +sealed trait MultiInTask[K[F[_]]] { + def flatMap[A](f: K[Id] => Task[A]): Task[A] + def flatMapR[A](f: K[Result] => Task[A]): Task[A] + def map[A](f: K[Id] => A): Task[A] + def mapR[A](f: K[Result] => A): Task[A] + def flatFailure[A](f: Seq[Incomplete] => Task[A]): Task[A] + def mapFailure[A](f: Seq[Incomplete] => A): Task[A] } sealed trait SingleInTask[S] { @@ -99,7 +100,7 @@ trait TaskExtra0 { joinTasks0[Any](existToAny(in)) private[sbt] def joinTasks0[S](in: Seq[Task[S]]): JoinTask[S, Seq] = new JoinTask[S, Seq] { def join: Task[Seq[S]] = - Task[Seq[S]](Info(), new Join(in, (s: Seq[Result[S]]) => Right(TaskExtra.all(s)))) + Task[Seq[S]](Info(), Action.Join(in, (s: Seq[Result[S]]) => Right(TaskExtra.all(s)))) def reduced(f: (S, S) => S): Task[S] = TaskExtra.reduced(in.toIndexedSeq, f) } private[sbt] def existToAny(in: Seq[Task[_]]): Seq[Task[Any]] = in.asInstanceOf[Seq[Task[Any]]] @@ -109,9 +110,9 @@ trait TaskExtra extends TaskExtra0 { final def nop: Task[Unit] = constant(()) final def constant[T](t: T): Task[T] = task(t) - final def task[T](f: => T): Task[T] = toTask(f _) - final implicit def toTask[T](f: () => T): Task[T] = Task(Info(), new Pure(f, false)) - final def inlineTask[T](value: T): Task[T] = Task(Info(), new Pure(() => value, true)) + final def task[T](f: => T): Task[T] = toTask(() => f) + final implicit def toTask[T](f: () => T): Task[T] = Task(Info(), Action.Pure(f, false)) + final def inlineTask[T](value: T): Task[T] = Task(Info(), Action.Pure(() => value, true)) final implicit def upcastTask[A >: B, B](t: Task[B]): Task[A] = t map { x => x: A @@ -126,61 +127,83 @@ trait TaskExtra extends TaskExtra0 { final implicit def joinTasks[S](in: Seq[Task[S]]): JoinTask[S, Seq] = new JoinTask[S, Seq] { def join: Task[Seq[S]] = - Task[Seq[S]](Info(), new Join(in, (s: Seq[Result[S]]) => Right(TaskExtra.all(s)))) + Task[Seq[S]](Info(), Action.Join(in, (s: Seq[Result[S]]) => Right(TaskExtra.all(s)))) def reduced(f: (S, S) => S): Task[S] = TaskExtra.reduced(in.toIndexedSeq, f) } - final implicit def multT2Task[A, B](in: (Task[A], Task[B])) = - multInputTask[λ[L[x] => (L[A], L[B])]](in)(AList.tuple2[A, B]) + final implicit def multT2Task[A1, A2]( + in: (Task[A1], Task[A2]) + ): MultiInTask[[F[_]] =>> Tuple.Map[(A1, A2), F]] = + given AList[[F[_]] =>> Tuple.Map[(A1, A2), F]] = AList.tuple[(A1, A2)] + multInputTask[[F[_]] =>> Tuple.Map[(A1, A2), F]](in) - final implicit def multInputTask[K[L[X]]](tasks: K[Task])(implicit a: AList[K]): MultiInTask[K] = - new MultiInTask[K] { - def flatMapR[T](f: K[Result] => Task[T]): Task[T] = - Task(Info(), new FlatMapped[T, K](tasks, f, a)) - def flatMap[T](f: K[Id] => Task[T]): Task[T] = - Task(Info(), new FlatMapped[T, K](tasks, f compose allM, a)) - def flatFailure[T](f: Seq[Incomplete] => Task[T]): Task[T] = - Task(Info(), new FlatMapped[T, K](tasks, f compose anyFailM, a)) + final implicit def multInputTask[K[F[_]]: AList](tasks: K[Task]): MultiInTask[K] = + new MultiInTask[K]: + override def flatMap[A](f: K[Id] => Task[A]): Task[A] = + Task(Info(), Action.FlatMapped[A, K](tasks, f compose allM, AList[K])) + override def flatMapR[A](f: K[Result] => Task[A]): Task[A] = + Task(Info(), Action.FlatMapped[A, K](tasks, f, AList[K])) + override def map[A](f: K[Id] => A): Task[A] = + Task(Info(), Action.Mapped[A, K](tasks, f compose allM, AList[K])) + override def mapR[A](f: K[Result] => A): Task[A] = + Task(Info(), Action.Mapped[A, K](tasks, f, AList[K])) + override def flatFailure[A](f: Seq[Incomplete] => Task[A]): Task[A] = + Task(Info(), Action.FlatMapped[A, K](tasks, f compose anyFailM, AList[K])) + override def mapFailure[A](f: Seq[Incomplete] => A): Task[A] = + Task(Info(), Action.Mapped[A, K](tasks, f compose anyFailM, AList[K])) - def mapR[T](f: K[Result] => T): Task[T] = Task(Info(), new Mapped[T, K](tasks, f, a)) - def map[T](f: K[Id] => T): Task[T] = Task(Info(), new Mapped[T, K](tasks, f compose allM, a)) - def mapFailure[T](f: Seq[Incomplete] => T): Task[T] = - Task(Info(), new Mapped[T, K](tasks, f compose anyFailM, a)) - } + final implicit def singleInputTask[S](in: Task[S]): SingleInTask[S] = + new SingleInTask[S]: + // type K[L[x]] = L[S] + given alist: AList[[F[_]] =>> Tuple.Map[Tuple1[S], F]] = AList.tuple[Tuple1[S]] - final implicit def singleInputTask[S](in: Task[S]): SingleInTask[S] = new SingleInTask[S] { - type K[L[x]] = L[S] - private def ml = AList.single[S] + def failure: Task[Incomplete] = mapFailure(idFun) + def result: Task[Result[S]] = mapR(idFun) - def failure: Task[Incomplete] = mapFailure(idFun) - def result: Task[Result[S]] = mapR(idFun) + private def newInfo[A]: Info[A] = TaskExtra.newInfo(in.info) - private def newInfo[A]: Info[A] = TaskExtra.newInfo(in.info) + override def flatMapR[A](f: Result[S] => Task[A]): Task[A] = + Task( + newInfo, + Action.FlatMapped[A, [F[_]] =>> Tuple.Map[Tuple1[S], F]]( + AList.toTuple(in), + AList.fromTuple(f), + alist, + ) + ) - def flatMapR[T](f: Result[S] => Task[T]): Task[T] = - Task(newInfo, new FlatMapped[T, K](in, f, ml)) - def mapR[T](f: Result[S] => T): Task[T] = Task(newInfo, new Mapped[T, K](in, f, ml)) - def dependsOn(tasks: Task[_]*): Task[S] = Task(newInfo, new DependsOn(in, tasks)) + override def mapR[A](f: Result[S] => A): Task[A] = + Task( + newInfo, + Action.Mapped[A, [F[_]] =>> Tuple.Map[Tuple1[S], F]]( + AList.toTuple(in), + AList.fromTuple(f), + alist, + ) + ) - def flatMap[T](f: S => Task[T]): Task[T] = flatMapR(f compose successM) - def flatFailure[T](f: Incomplete => Task[T]): Task[T] = flatMapR(f compose failM) + override def dependsOn(tasks: Task[_]*): Task[S] = Task(newInfo, Action.DependsOn(in, tasks)) - def map[T](f: S => T): Task[T] = mapR(f compose successM) - def mapFailure[T](f: Incomplete => T): Task[T] = mapR(f compose failM) + override def flatMap[T](f: S => Task[T]): Task[T] = flatMapR(f compose successM) - def andFinally(fin: => Unit): Task[S] = mapR(x => Result.tryValue[S]({ fin; x })) - def doFinally(t: Task[Unit]): Task[S] = - flatMapR(x => - t.result.map { tx => - Result.tryValues[S](tx :: Nil, x) - } - ) - def ||[T >: S](alt: Task[T]): Task[T] = flatMapR { - case Value(v) => task(v: T) - case Inc(_) => alt - } - def &&[T](alt: Task[T]): Task[T] = flatMap(_ => alt) - } + override def flatFailure[T](f: Incomplete => Task[T]): Task[T] = flatMapR(f compose failM) + + override def map[T](f: S => T): Task[T] = mapR(f compose successM) + + override def mapFailure[T](f: Incomplete => T): Task[T] = mapR(f compose failM) + + def andFinally(fin: => Unit): Task[S] = mapR(x => Result.tryValue[S]({ fin; x })) + def doFinally(t: Task[Unit]): Task[S] = + flatMapR(x => + t.result.map { tx => + Result.tryValues[S](tx :: Nil, x) + } + ) + def ||[T >: S](alt: Task[T]): Task[T] = flatMapR { + case Result.Value(v) => task(v: T) + case Result.Inc(_) => alt + } + def &&[T](alt: Task[T]): Task[T] = flatMap(_ => alt) final implicit def toTaskInfo[S](in: Task[S]): TaskInfo[S] = new TaskInfo[S] { def describedAs(s: String): Task[S] = in.copy(info = in.info.setDescription(s)) @@ -263,35 +286,49 @@ object TaskExtra extends TaskExtra { reducePair(reduced(a, f), reduced(b, f), f) } - def reducePair[S](a: Task[S], b: Task[S], f: (S, S) => S): Task[S] = - multInputTask[λ[L[x] => (L[S], L[S])]]((a, b))(AList.tuple2[S, S]) map f.tupled + def reducePair[A1](a: Task[A1], b: Task[A1], f: (A1, A1) => A1): Task[A1] = + given AList[[F[_]] =>> Tuple.Map[(A1, A1), F]] = AList.tuple[(A1, A1)] + multInputTask[[F[_]] =>> Tuple.Map[(A1, A1), F]]((a, b)) map f.tupled - def anyFailM[K[L[x]]](implicit a: AList[K]): K[Result] => Seq[Incomplete] = in => { - val incs = failuresM(a)(in) - if (incs.isEmpty) expectedFailure else incs + def anyFailM[K[F[_]]: AList]: K[Result] => Seq[Incomplete] = in => { + val incs = failuresM[K](AList[K])(in) + if incs.isEmpty then expectedFailure + else incs + } + + def failM[A]: Result[A] => Incomplete = { + case Result.Inc(i) => i + case _ => expectedFailure } - def failM[T]: Result[T] => Incomplete = { case Inc(i) => i; case _ => expectedFailure } def expectedFailure = throw Incomplete(None, message = Some("Expected dependency to fail.")) - def successM[T]: Result[T] => T = { case Inc(i) => throw i; case Value(t) => t } - def allM[K[L[x]]](implicit a: AList[K]): K[Result] => K[Id] = in => { - val incs = failuresM(a)(in) - if (incs.isEmpty) a.transform(in, Result.tryValue) else throw incompleteDeps(incs) + def successM[A]: Result[A] => A = { + case Result.Inc(i) => throw i + case Result.Value(a) => a } - def failuresM[K[L[x]]](implicit a: AList[K]): K[Result] => Seq[Incomplete] = - x => failures[Any](a.toList(x)) + + def allM[K[F[_]]: AList]: K[Result] => K[Id] = in => { + val incs = failuresM[K](AList[K])(in) + if incs.isEmpty then AList[K].transform[Result, Id](in)(Result.tryValue) // .asInstanceOf + else throw incompleteDeps(incs) + } + def failuresM[K[F[_]]: AList]: K[Result] => Seq[Incomplete] = x => + failures[Any](AList[K].toList(x)) def all[D](in: Seq[Result[D]]): Seq[D] = { val incs = failures(in) - if (incs.isEmpty) in.map(Result.tryValue.fn[D]) else throw incompleteDeps(incs) + if incs.isEmpty then in.map(Result.tryValue[D]) + else throw incompleteDeps(incs) + } + def failures[A](results: Seq[Result[A]]): Seq[Incomplete] = results.collect { + case Result.Inc(i) => i } - def failures[A](results: Seq[Result[A]]): Seq[Incomplete] = results.collect { case Inc(i) => i } def incompleteDeps(incs: Seq[Incomplete]): Incomplete = Incomplete(None, causes = incs) def select[A, B](fab: Task[Either[A, B]], f: Task[A => B]): Task[B] = - Task(newInfo(fab.info), new Selected[A, B](fab, f)) + Task(newInfo(fab.info), Action.Selected[A, B](fab, f)) // The "taskDefinitionKey" is used, at least, by the ".previous" functionality. // But apparently it *cannot* survive a task map/flatMap/etc. See actions/depends-on. diff --git a/tasks-standard/src/main/scala/sbt/std/Transform.scala b/tasks-standard/src/main/scala/sbt/std/Transform.scala index d79445f4c..77dfd2f9b 100644 --- a/tasks-standard/src/main/scala/sbt/std/Transform.scala +++ b/tasks-standard/src/main/scala/sbt/std/Transform.scala @@ -8,24 +8,28 @@ package sbt package std +import sbt.internal.Action import sbt.internal.util.Types._ import sbt.internal.util.{ ~>, AList, DelegatingPMap, RMap } import TaskExtra.{ all, existToAny } +import sbt.internal.util.Types.* + +object Transform: + def fromDummy[A](original: Task[A])(action: => A): Task[A] = + Task(original.info, Action.Pure(() => action, false)) -object Transform { - def fromDummy[T](original: Task[T])(action: => T): Task[T] = - Task(original.info, Pure(action _, false)) def fromDummyStrict[T](original: Task[T], value: T): Task[T] = fromDummy(original)(value) - implicit def to_~>|[K[_], V[_]](map: RMap[K, V]): K ~>| V = new (K ~>| V) { - def apply[T](k: K[T]): Option[V[T]] = map.get(k) - } + implicit def to_~>|[K[_], V[_]](map: RMap[K, V]): ~>|[K, V] = + [A] => (k: K[A]) => map.get(k) final case class DummyTaskMap(mappings: List[TaskAndValue[_]]) { def ::[T](tav: (Task[T], T)): DummyTaskMap = DummyTaskMap(new TaskAndValue(tav._1, tav._2) :: mappings) } + final class TaskAndValue[T](val task: Task[T], val value: T) + def dummyMap(dummyMap: DummyTaskMap): Task ~>| Task = { val pmap = new DelegatingPMap[Task, Task](new collection.mutable.ListMap) def add[T](dummy: TaskAndValue[T]): Unit = { @@ -36,35 +40,43 @@ object Transform { } /** Applies `map`, returning the result if defined or returning the input unchanged otherwise. */ - implicit def getOrId(map: Task ~>| Task): Task ~> Task = - λ[Task ~> Task](in => map(in).getOrElse(in)) + implicit def getOrId(map: Task ~>| Task): [A] => Task[A] => Task[A] = + [A] => (in: Task[A]) => map(in).getOrElse(in) def apply(dummies: DummyTaskMap) = taskToNode(getOrId(dummyMap(dummies))) - def taskToNode(pre: Task ~> Task): NodeView[Task] = new NodeView[Task] { - def apply[T](t: Task[T]): Node[Task, T] = pre(t).work match { - case Pure(eval, _) => uniform(Nil)(_ => Right(eval())) - case m: Mapped[t, k] => toNode[t, k](m.in)(right ∙ m.f)(m.alist) - case m: FlatMapped[t, k] => toNode[t, k](m.in)(left ∙ m.f)(m.alist) - case s: Selected[_, t] => val m = s.asFlatMapped; toNode(m.in)(left ∙ m.f)(m.alist) - case DependsOn(in, deps) => uniform(existToAny(deps))(const(Left(in)) compose all) - case Join(in, f) => uniform(in)(f) - } - def inline[T](t: Task[T]): Option[() => T] = t.work match { - case Pure(eval, true) => Some(eval) - case _ => None - } - } + def taskToNode(pre: [A] => Task[A] => Task[A]): NodeView[Task] = + new NodeView[Task]: + import Action.* + def apply[T](t: Task[T]): Node[Task, T] = pre(t).work match + case Pure(eval, _) => uniform(Nil)(_ => Right(eval())) + case m: Mapped[a, k] => toNode[a, k](m.in)(right[a] compose m.f)(m.alist) + case m: FlatMapped[a, k] => + toNode[a, k](m.in)(left[Task[a]] compose m.f)(m.alist) // (m.alist) + case s: Selected[a1, a2] => + val m = Action.asFlatMapped[a1, a2](s) + toNode[a2, [F[_]] =>> Tuple1[F[Either[a1, a2]]]](m.in)(left[Task[a2]] compose m.f)( + m.alist + ) + case DependsOn(in, deps) => uniform(existToAny(deps))(const(Left(in)) compose all) + case Join(in, f) => uniform(in)(f) - def uniform[T, D](tasks: Seq[Task[D]])(f: Seq[Result[D]] => Either[Task[T], T]): Node[Task, T] = - toNode[T, λ[L[x] => List[L[D]]]](tasks.toList)(f)(AList.seq[D]) + def inline1[T](t: Task[T]): Option[() => T] = t.work match + case Action.Pure(eval, true) => Some(eval) + case _ => None - def toNode[T, k[L[x]]]( - inputs: k[Task] - )(f: k[Result] => Either[Task[T], T])(implicit a: AList[k]): Node[Task, T] = new Node[Task, T] { - type K[L[x]] = k[L] - val in = inputs - val alist = a - def work(results: K[Result]) = f(results) - } -} + def uniform[A1, D](tasks: Seq[Task[D]])( + f: Seq[Result[D]] => Either[Task[A1], A1] + ): Node[Task, A1] = + toNode[A1, [F[_]] =>> List[F[D]]](tasks.toList)(f)(AList.list[D]) + + def toNode[A1, K1[F[_]]: AList]( + inputs: K1[Task] + )(f: K1[Result] => Either[Task[A1], A1]): Node[Task, A1] = + new Node[Task, A1]: + type K[F[_]] = K1[F] + val in = inputs + lazy val alist: AList[K] = AList[K] + def work(results: K[Result]) = f(results) + +end Transform diff --git a/tasks-standard/src/test/scala/TaskGen.scala b/tasks-standard/src/test/scala/TaskGen.scala index 92d8b29d8..14406eee3 100644 --- a/tasks-standard/src/test/scala/TaskGen.scala +++ b/tasks-standard/src/test/scala/TaskGen.scala @@ -30,14 +30,14 @@ object TaskGen extends std.TaskExtra { ExecuteProgress.empty[Task] )(std.Transform(dummies)) try { - x.run(root)(service) + x.run(root)(service.asInstanceOf) } finally { shutdown() } } def tryRun[T](root: Task[T], checkCycles: Boolean, maxWorkers: Int): T = run(root, checkCycles, maxWorkers) match { - case Value(v) => v - case Inc(i) => throw i + case Result.Value(v) => v + case Result.Inc(i) => throw i } } diff --git a/tasks-standard/src/test/scala/TaskSerial.scala b/tasks-standard/src/test/scala/TaskSerial.scala index a7bf8cbff..051f0cac6 100644 --- a/tasks-standard/src/test/scala/TaskSerial.scala +++ b/tasks-standard/src/test/scala/TaskSerial.scala @@ -90,7 +90,7 @@ object TaskTest { ExecuteProgress.empty[Task] )(taskToNode(idK[Task])) try { - x.run(root)(service) + x.run(root)(service.asInstanceOf) } finally { shutdown() } @@ -101,7 +101,7 @@ object TaskTest { restrictions: ConcurrentRestrictions[Task[_]] ): T = run(root, checkCycles, restrictions) match { - case Value(v) => v - case Inc(i) => throw i + case Result.Value(v) => v + case Result.Inc(i) => throw i } } diff --git a/tasks-standard/src/test/scala/Test.scala b/tasks-standard/src/test/scala/Test.scala index 0a369b6e4..8c7d63263 100644 --- a/tasks-standard/src/test/scala/Test.scala +++ b/tasks-standard/src/test/scala/Test.scala @@ -9,11 +9,13 @@ package sbt import sbt.internal.util.AList -object Test extends std.TaskExtra { - def t2[A, B](a: Task[A], b: Task[B]) = - multInputTask[λ[L[x] => (L[A], L[B])]]((a, b))(AList.tuple2) - def t3[A, B, C](a: Task[A], b: Task[B], c: Task[C]) = - multInputTask[λ[L[x] => (L[A], L[B], L[C])]]((a, b, c))(AList.tuple3) +object Test extends std.TaskExtra: + def t2[A1, A2](a1: Task[A1], a2: Task[A2]) = + given AList[[F[_]] =>> Tuple.Map[(A1, A2), F]] = AList.tuple[(A1, A2)] + multInputTask[[F[_]] =>> Tuple.Map[(A1, A2), F]]((a1, a2)) + def t3[A1, A2, A3](a1: Task[A1], a2: Task[A2], a3: Task[A3]) = + given AList[[F[_]] =>> Tuple.Map[(A1, A2, A3), F]] = AList.tuple[(A1, A2, A3)] + multInputTask[[F[_]] =>> Tuple.Map[(A1, A2, A3), F]]((a1, a2, a3)) val a = task(3) val b = task[Boolean](sys.error("test")) @@ -26,22 +28,22 @@ object Test extends std.TaskExtra { type Values = (Result[Int], Result[Boolean], Result[String]) val f: Values => Any = { - case (Value(aa), Value(bb), Value(cc)) => s"$aa $bb $cc" + case (Result.Value(aa), Result.Value(bb), Result.Value(cc)) => s"$aa $bb $cc" case x => - val cs = x.productIterator.toList.collect { case Inc(x) => + val cs = x.productIterator.toList.collect { case Result.Inc(x) => x } // workaround for double definition bug throw Incomplete(None, causes = cs) } val d2 = t3(a, b2, c) mapR f val f2: Values => Task[Any] = { - case (Value(aa), Value(bb), Value(cc)) => task(s"$aa $bb $cc") - case _ => d3 + case (Result.Value(aa), Result.Value(bb), Result.Value(cc)) => task(s"$aa $bb $cc") + case _ => d3 } lazy val d = t3(a, b, c) flatMapR f2 val f3: Values => Task[Any] = { - case (Value(aa), Value(bb), Value(cc)) => task(s"$aa $bb $cc") - case _ => d2 + case (Result.Value(aa), Result.Value(bb), Result.Value(cc)) => task(s"$aa $bb $cc") + case _ => d2 } lazy val d3 = t3(a, b, c) flatMapR f3 @@ -63,4 +65,4 @@ object Test extends std.TaskExtra { run(h1) run(h2) } -} +end Test diff --git a/tasks/src/main/scala/sbt/CompletionService.scala b/tasks/src/main/scala/sbt/CompletionService.scala index d42d042e0..aefd4cfe4 100644 --- a/tasks/src/main/scala/sbt/CompletionService.scala +++ b/tasks/src/main/scala/sbt/CompletionService.scala @@ -44,17 +44,21 @@ object CompletionService { ) (apply[A, T](pool), () => { pool.shutdownNow(); () }) } + def apply[A, T](x: Executor): CompletionService[A, T] = apply(new ExecutorCompletionService[T](x)) + def apply[A, T](completion: JCompletionService[T]): CompletionService[A, T] = new CompletionService[A, T] { def submit(node: A, work: () => T) = { CompletionService.submit(work, completion); () } def take() = completion.take().get() } + def submit[T](work: () => T, completion: JCompletionService[T]): () => T = { val future = submitFuture[T](work, completion) () => future.get } + private[sbt] def submitFuture[A](work: () => A, completion: JCompletionService[A]): JFuture[A] = { val future = try completion.submit { diff --git a/tasks/src/main/scala/sbt/Execute.scala b/tasks/src/main/scala/sbt/Execute.scala index ec3650e17..24f6b9f3f 100644 --- a/tasks/src/main/scala/sbt/Execute.scala +++ b/tasks/src/main/scala/sbt/Execute.scala @@ -39,13 +39,16 @@ private[sbt] object Execute { final val checkPreAndPostConditions = sys.props.get("sbt.execute.extrachecks").exists(java.lang.Boolean.parseBoolean) } + sealed trait Completed { def process(): Unit } + private[sbt] trait NodeView[F[_]] { def apply[A](a: F[A]): Node[F, A] def inline1[A](a: F[A]): Option[() => A] } + final class Triggers[F[_]]( val runBefore: collection.Map[F[Any], Seq[F[Any]]], val injectFor: collection.Map[F[Any], Seq[F[Any]]], @@ -278,7 +281,7 @@ private[sbt] final class Execute[F[_] <: AnyRef]( /** Send the work for this node to the provided Strategy. */ def submit[A](node: F[A])(implicit strategy: Strategy): Unit = { val v = viewCache(node) - val rs = AList.tuple.transform[F, Result, v.Tup](v.in)(getResult) + val rs = v.alist.transform[F, Result](v.in)(getResult) // v.alist.transform(v.in)(getResult) strategy.submit(node.asInstanceOf, () => work(node, v.work(rs))) } @@ -321,8 +324,7 @@ private[sbt] final class Execute[F[_] <: AnyRef]( def dependencies[A](node: F[A]): Iterable[F[Any]] = dependencies(viewCache(node.asInstanceOf)) def dependencies[A](v: Node[F, A]): Iterable[F[Any]] = - AList.tuple.toList[F, v.Tup](v.in).filter(dep => view.inline1(dep).isEmpty) - // v.alist.toList(v.in).filter(dep => view.inline(dep).isEmpty) + v.alist.toList[F](v.in).filter(dep => view.inline1(dep).isEmpty) def runBefore[A](node: F[A]): Seq[F[A]] = getSeq[A](triggers.runBefore, node) diff --git a/tasks/src/main/scala/sbt/Node.scala b/tasks/src/main/scala/sbt/Node.scala index bc5176631..09e17de70 100644 --- a/tasks/src/main/scala/sbt/Node.scala +++ b/tasks/src/main/scala/sbt/Node.scala @@ -7,6 +7,8 @@ package sbt +import sbt.internal.util.AList + /** * Represents a task node in a format understood by the task evaluation engine Execute. * @@ -15,10 +17,11 @@ package sbt * @tparam A * the type computed by this node */ -trait Node[Effect[_], A] { - type Tup <: Tuple - def in: Tuple.Map[Tup, Effect] +private[sbt] trait Node[Effect[_], A]: + type K[L[x]] + def in: K[Effect] + def alist: AList[K] /** Computes the result of this task given the results from the inputs. */ - def work(inputs: Tuple.Map[Tup, Result]): Either[Effect[A], A] -} + def work(inputs: K[Result]): Either[Effect[A], A] +end Node diff --git a/util-collection/src/main/scala/sbt/internal/util/AList.scala b/util-collection/src/main/scala/sbt/internal/util/AList.scala index 6b1a7ff04..73160b6ae 100644 --- a/util-collection/src/main/scala/sbt/internal/util/AList.scala +++ b/util-collection/src/main/scala/sbt/internal/util/AList.scala @@ -12,7 +12,7 @@ import sbt.util.Applicative import Types._ /** - * Arity-generic List. An abstraction over structured Tuple type constructor `X1[f[a]]`. + * Arity-generic List. An abstraction over structured Tuple/List type constructor `X1[f[a]]`. */ trait AList[K[F[_]]]: import AList.idPoly @@ -42,6 +42,8 @@ trait AList[K[F[_]]]: end AList object AList: + inline def apply[K[F[_]]: AList]: AList[K] = summon[AList[K]] + type Tail[X <: Tuple] <: Tuple = X match case _ *: xs => xs diff --git a/util-collection/src/main/scala/sbt/internal/util/TypeFunctions.scala b/util-collection/src/main/scala/sbt/internal/util/TypeFunctions.scala index aee296e51..d7d100dd8 100644 --- a/util-collection/src/main/scala/sbt/internal/util/TypeFunctions.scala +++ b/util-collection/src/main/scala/sbt/internal/util/TypeFunctions.scala @@ -19,11 +19,11 @@ trait TypeFunctions: sealed trait Compose[A[_], B[_]] { type Apply[T] = A[B[T]] } sealed trait ∙[A[_], B[_]] { type l[T] = A[B[T]] } - private type AnyLeft[T] = Left[T, Nothing] - private type AnyRight[T] = Right[Nothing, T] - final val left: [A] => A => Left[A, Nothing] = [A] => (a: A) => Left(a) + private type AnyLeft[A] = Left[A, Nothing] + private type AnyRight[A] = Right[Nothing, A] + final val left: [A] => A => AnyLeft[A] = [A] => (a: A) => Left(a) - final val right: [A] => A => Right[Nothing, A] = [A] => (a: A) => Right(a) + final val right: [A] => A => AnyRight[A] = [A] => (a: A) => Right(a) final val some: [A] => A => Some[A] = [A] => (a: A) => Some(a) // Id ~> Left[*, Nothing] = @@ -34,9 +34,10 @@ trait TypeFunctions: final def idFun[A]: A => A = ((a: A) => a) // .setToString("TypeFunctions.id") final def const[A, B](b: B): A => B = ((_: A) => b) // .setToString(s"TypeFunctions.const($b)") - /* - final def idK[M[_]]: M ~> M = λ[M ~> M](m => m).setToString("TypeFunctions.idK") + final def idK[F[_]]: [a] => F[a] => F[a] = [a] => + (fa: F[a]) => fa // .setToString("TypeFunctions.idK") + /* def nestCon[M[_], N[_], G[_]](f: M ~> N): (M ∙ G)#l ~> (N ∙ G)#l = f.asInstanceOf[(M ∙ G)#l ~> (N ∙ G)#l] // implemented with a cast to avoid extra object+method call. // castless version: @@ -47,6 +48,10 @@ trait TypeFunctions: */ type ~>|[F1[_], F2[_]] = [A] => F1[A] => Option[F2[A]] + extension (f: [a] => a => AnyRight[a]) + def compose[F3[_]](g: [a] => F3[a] => a): [a] => F3[a] => AnyRight[a] = + [a] => (f3: F3[a]) => f(g(f3)) + end TypeFunctions /*