From 538f6872086443e750876c97279af08ae2f2e5eb Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Sun, 2 Dec 2012 03:17:19 -0500 Subject: [PATCH] Use and methods instead of mapR,mapFailure,flatFailure,flatMapR --- main/Tags.scala | 18 +++ main/settings/Structure.scala | 23 ++- main/src/test/scala/TagsTest.scala | 28 ++++ src/sphinx/Detailed-Topics/Tasks.rst | 216 +++++++++++---------------- tasks/standard/TaskExtra.scala | 22 ++- 5 files changed, 170 insertions(+), 137 deletions(-) create mode 100644 main/src/test/scala/TagsTest.scala diff --git a/main/Tags.scala b/main/Tags.scala index fab4ac4ca..d40e4eb6a 100644 --- a/main/Tags.scala +++ b/main/Tags.scala @@ -78,4 +78,22 @@ object Tags // If there is only one task, allow it to execute. tags.getOrElse(Tags.All, 0) == 1 } + + /** Ensure that a task with the given tag only executes with tasks also tagged with the given tag.*/ + def exclusiveGroup(exclusiveTag: Tag): Rule = customLimit { (tags: Map[Tag,Int]) => + val exclusiveCount = tags.getOrElse(exclusiveTag, 0) + val allCount = tags.getOrElse(Tags.All, 0) + // If there are no exclusive tasks in this group, this rule adds no restrictions. + exclusiveCount == 0 || + // If all tasks have this tag, allow them to execute. + exclusiveCount == allCount || + // Always allow a group containing only one task to execute (fallthrough case). + allCount == 1 + } + + /** A task tagged with one of `exclusiveTags` will not execute with another task with any of the other tags in `exclusiveTags`.*/ + def exclusiveGroups(exclusiveTags: Tag*): Rule = customLimit { (tags: Map[Tag,Int]) => + val groups = exclusiveTags.count(tag => tags.getOrElse(tag, 0) > 0) + groups <= 1 + } } diff --git a/main/settings/Structure.scala b/main/settings/Structure.scala index acdc16081..7e8b88f47 100644 --- a/main/settings/Structure.scala +++ b/main/settings/Structure.scala @@ -28,7 +28,7 @@ sealed trait ScopedTaskable[T] extends Scoped { * The scope is represented by a value of type Scope. * The name and the type are represented by a value of type AttributeKey[T]. * Instances are constructed using the companion object. */ -sealed trait SettingKey[T] extends ScopedTaskable[T] with KeyedInitialize[T] with Scoped.ScopingSetting[SettingKey[T]] with Scoped.DefinableSetting[T] +sealed abstract class SettingKey[T] extends ScopedTaskable[T] with KeyedInitialize[T] with Scoped.ScopingSetting[SettingKey[T]] with Scoped.DefinableSetting[T] { val key: AttributeKey[T] final def toTask: Initialize[Task[T]] = this apply inlineTask @@ -48,7 +48,7 @@ sealed trait SettingKey[T] extends ScopedTaskable[T] with KeyedInitialize[T] wit * The scope is represented by a value of type Scope. * The name and the type are represented by a value of type AttributeKey[Task[T]]. * Instances are constructed using the companion object. */ -sealed trait TaskKey[T] extends ScopedTaskable[T] with KeyedInitialize[Task[T]] with Scoped.ScopingSetting[TaskKey[T]] with Scoped.DefinableTask[T] +sealed abstract class TaskKey[T] extends ScopedTaskable[T] with KeyedInitialize[Task[T]] with Scoped.ScopingSetting[TaskKey[T]] with Scoped.DefinableTask[T] { val key: AttributeKey[Task[T]] def toTask: Initialize[Task[T]] = this @@ -144,6 +144,9 @@ object Scoped def dependsOn(tasks: AnyInitTask*): Initialize[Task[S]] = (i, Initialize.joinAny[Task](tasks)) { (thisTask, deps) => thisTask.dependsOn(deps : _*) } + def failure: Initialize[Task[Incomplete]] = i(_.failure) + def result: Initialize[Task[Result[S]]] = i(_.result) + def triggeredBy(tasks: AnyInitTask*): Initialize[Task[S]] = nonLocal(tasks, Def.triggeredBy) def runBefore(tasks: AnyInitTask*): Initialize[Task[S]] = nonLocal(tasks, Def.runBefore) private[this] def nonLocal(tasks: Seq[AnyInitTask], key: AttributeKey[Seq[Task[_]]]): Initialize[Task[S]] = @@ -159,12 +162,8 @@ object Scoped { protected def onTask[T](f: Task[S] => Task[T]): Initialize[R[T]] - def flatMapR[T](f: Result[S] => Task[T]): Initialize[R[T]] = onTask(_ flatMapR f) def flatMap[T](f: S => Task[T]): Initialize[R[T]] = flatMapR(f compose successM) def map[T](f: S => T): Initialize[R[T]] = mapR(f compose successM) - def mapR[T](f: Result[S] => T): Initialize[R[T]] = onTask(_ mapR f) - def flatFailure[T](f: Incomplete => Task[T]): Initialize[R[T]] = flatMapR(f compose failM) - def mapFailure[T](f: Incomplete => T): Initialize[R[T]] = mapR(f compose failM) def andFinally(fin: => Unit): Initialize[R[S]] = onTask(_ andFinally fin) def doFinally(t: Task[Unit]): Initialize[R[S]] = onTask(_ doFinally t) @@ -173,6 +172,18 @@ object Scoped def tag(tags: Tag*): Initialize[R[S]] = onTask(_.tag(tags: _*)) def tagw(tags: (Tag, Int)*): Initialize[R[S]] = onTask(_.tagw(tags : _*)) + + @deprecated("Use the `result` method to create a task that returns the full Result of this task. Then, call `flatMap` on the new task.", "0.13.0") + def flatMapR[T](f: Result[S] => Task[T]): Initialize[R[T]] = onTask(_ flatMapR f) + + @deprecated("Use the `result` method to create a task that returns the full Result of this task. Then, call `map` on the new task.", "0.13.0") + def mapR[T](f: Result[S] => T): Initialize[R[T]] = onTask(_ mapR f) + + @deprecated("Use the `failure` method to create a task that returns Incomplete when this task fails and then call `flatMap` on the new task.", "0.13.0") + def flatFailure[T](f: Incomplete => Task[T]): Initialize[R[T]] = flatMapR(f compose failM) + + @deprecated("Use the `failure` method to create a task that returns Incomplete when this task fails and then call `map` on the new task.", "0.13.0") + def mapFailure[T](f: Incomplete => T): Initialize[R[T]] = mapR(f compose failM) } type AnyInitTask = Initialize[Task[T]] forSome { type T } diff --git a/main/src/test/scala/TagsTest.scala b/main/src/test/scala/TagsTest.scala new file mode 100644 index 000000000..6f8c5da8e --- /dev/null +++ b/main/src/test/scala/TagsTest.scala @@ -0,0 +1,28 @@ +package sbt + +import org.scalacheck._ +import Gen.{genInt,listOf,genString} +import Prop.forAll +import Tags._ + +object TagsTest extends Properties("Tags") +{ + def tagMap: Gen[TagMap] = for(ts <- listOf(tagAndFrequency)) yield ts.toMap + def tagAndFrequency: Gen[(Tag, Int)] = for(t <- tag; count <- genInt) yield (t, count) + def tag: Gen[Tag] = for(s <- genString) yield Tag(s) + + property("exclusive allows all groups without the exclusive tag") = forAll { (tm: TagMap, tag: Tag) => + excl(tag)(tm - tag) + } + property("exclusive only allows a group with an excusive tag when the size is one") = forAll { (tm: TagMap, size: Int, etag: Tag) => + val tm: TagMap = tm.updated(etag, math.abs(size)) + excl(etag)(tm) == (size <= 1) + } + property("exclusive always allows a group of size one") = forAll { (etag: Tag, mapTag: Tag) => + val tm: TagMap = Map(mapTag -> 1) + excl(etag)(tm) + } + + private[this] def excl(tag: Tag): TagMap => Boolean = predicate(exclusive(tag) :: Nil) + +} \ No newline at end of file diff --git a/src/sphinx/Detailed-Topics/Tasks.rst b/src/sphinx/Detailed-Topics/Tasks.rst index 8c4665f85..be4fa3fde 100644 --- a/src/sphinx/Detailed-Topics/Tasks.rst +++ b/src/sphinx/Detailed-Topics/Tasks.rst @@ -383,9 +383,97 @@ do not affect already logged information. Handling Failure ---------------- -This section discusses the ``andFinally``, ``mapFailure``, and ``mapR`` +This section discusses the ``failure``, ``result``, and ``andFinally`` methods, which are used to handle failure of other tasks. +``failure`` +~~~~~~~~~~~ + +The ``failure`` method creates a new task that returns the ``Incomplete`` value +when the original task fails to complete normally. If the original task succeeds, +the new task fails. +`Incomplete `_ +is an exception with information about any tasks that caused the failure +and any underlying exceptions thrown during task execution. + +For example: + +:: + + intTask := error("Failed.") + + val intTask := { + println("Ignoring failure: " + intTask.failure.value) + 3 + } + +This overrides the ``intTask`` so that the original exception is printed and the constant ``3`` is returned. + +``failure`` does not prevent other tasks that depend on the target +from failing. Consider the following example: + +:: + + intTask := if(shouldSucceed) 5 else error("Failed.") + + // Return 3 if intTask fails. If intTask succeeds, this task will fail. + aTask := intTask.failure.value - 2 + + // A new task that increments the result of intTask. + bTask := intTask.value + 1 + + cTask := aTask.value + bTask.value + +The following table lists the results of each task depending on the initially invoked task: + +============== =============== ============= ============== ============== ============== +invoked task intTask result aTask result bTask result cTask result overall result +============== =============== ============= ============== ============== ============== +intTask failure not run not run not run failure +aTask failure success not run not run success +bTask failure not run failure not run failure +cTask failure success failure failure failure +intTask success not run not run not run success +aTask success failure not run not run failure +bTask success not run success not run success +cTask success failure success failure failure +============== =============== ============= ============== ============== ============== + +The overall result is always the same as the root task (the directly +invoked task). A ``failure`` turns a success into a failure, and a failure into an ``Incomplete``. +A normal task definition fails when any of its inputs fail and computes its value otherwise. + +``result`` +~~~~~~~~~~ + +The ``result`` method creates a new task that returns the full ``Result[T]`` value for the original task. +`Result `_ +has the same structure as ``Either[Incomplete, T]`` for a task result of +type ``T``. That is, it has two subtypes: + +- ``Inc``, which wraps ``Incomplete`` in case of failure +- ``Value``, which wraps a task's result in case of success. + +Thus, the task created by ``result`` executes whether or not the original task succeeds or fails. + +For example: + +:: + + intTask := error("Failed.") + + intTask := intTask.result.value match { + case Inc(inc: Incomplete) => + println("Ignoring failure: " + inc) + 3 + case Value(v) => + println("Using successful result: " + v) + v + } + +This overrides the original ``intTask`` definition so that if the original task fails, the exception is printed and the constant ``3`` is returned. If it succeeds, the value is printed and returned. + + andFinally ~~~~~~~~~~ @@ -434,129 +522,3 @@ execution. This case is similar to the following plain Scala code: It is obvious here that calling intTask() will never result in "finally" being printed. - -mapFailure -~~~~~~~~~~ - -``mapFailure`` accepts a function of type ``Incomplete => T``, where -``T`` is a type parameter. In the case of multiple inputs, the function -has type ``Seq[Incomplete] => T``. -`Incomplete `_ -is an exception with information about any tasks that caused the failure -and any underlying exceptions thrown during task execution. The -resulting task defined by ``mapFailure`` fails if its input succeeds and -evaluates the provided function if it fails. - -For example: - -:: - - intTask := error("Failed.") - - val intTaskImpl = intTask mapFailure { (inc: Incomplete) => - println("Ignoring failure: " + inc) - 3 - } - - intTask := intTaskImpl.value - -This overrides the ``intTask`` so that the original exception is printed and the constant ``3`` is returned. - -``mapFailure`` does not prevent other tasks that depend on the target -from failing. Consider the following example: - -:: - - intTask := if(shouldSucceed) 5 else error("Failed.") - - - - // return 3 if intTask fails. if it succeeds, this task will fail - val aTaskImpl = intTask mapFailure { (inc: Incomplete) => 3 } - - aTask := aTaskImpl.value - - // a new task that increments the result of intTask - bTask := intTask.value + 1 - - cTask := aTask.value + bTask.value - -The following table lists the results of each task depending on the initially invoked task: - -============== =============== ============= ============== ============== ============== -invoked task intTask result aTask result bTask result cTask result overall result -============== =============== ============= ============== ============== ============== -intTask failure not run not run not run failure -aTask failure success not run not run success -bTask failure not run failure not run failure -cTask failure success failure failure failure -intTask success not run not run not run success -aTask success failure not run not run failure -bTask success not run success not run success -cTask success failure success failure failure -============== =============== ============= ============== ============== ============== - -The overall result is always the same as the root task (the directly -invoked task). A ``mapFailure`` turns a success into a failure, and a -failure into whatever the result of evaluating the supplied function is. -A normal task definition fails when the input fails and applies the supplied function -to a successfully completed input. - -In the case of more than one input, ``mapFailure`` fails if all inputs -succeed. If at least one input fails, the supplied function is provided -with the list of ``Incomplete``\ s. For example: - -:: - - val cTaskImpl = (aTask, bTask) mapFailure { (incs: Seq[Incomplete]) => 3 } - - cTask := cTaskImpl.value - -The following table lists the results of invoking ``cTask``, depending -on the success of ``aTask`` and ``bTask``: - - -============= ============= ============= -aTask result bTask result cTask result -============= ============= ============= -failure failure success -failure success success -success failure success -success success failure -============= ============= ============= - -mapR -~~~~ - -``mapR`` accepts a function of type ``Result[S] => T``, where ``S`` is -the type of the task being mapped and ``T`` is a type parameter. In the -case of multiple inputs, the function has type -``(Result[A], Result[B], ...) => T``. -`Result `_ -has the same structure as ``Either[Incomplete, S]`` for a task result of -type ``S``. That is, it has two subtypes: - -- ``Inc``, which wraps ``Incomplete`` in case of failure -- ``Value``, which wraps a task's result in case of success. - -Thus, ``mapR`` is always invoked whether or not the original task -succeeds or fails. - -For example: - -:: - - intTask := error("Failed.") - - val intTaskImpl = intTask mapR { - case Inc(inc: Incomplete) => - println("Ignoring failure: " + inc) - 3 - case Value(v) => - println("Using successful result: " + v) - v - } - - intTask := intTaskImpl.value - -This overrides the original ``intTask`` definition so that if the original task fails, the exception is printed and the constant ``3`` is returned. If it succeeds, the value is printed and returned. diff --git a/tasks/standard/TaskExtra.scala b/tasks/standard/TaskExtra.scala index 9df354ad5..ef844d9de 100644 --- a/tasks/standard/TaskExtra.scala +++ b/tasks/standard/TaskExtra.scala @@ -20,18 +20,29 @@ sealed trait MultiInTask[K[L[x]]] sealed trait SingleInTask[S] { - def flatMapR[T](f: Result[S] => Task[T]): Task[T] def flatMap[T](f: S => Task[T]): Task[T] def map[T](f: S => T): Task[T] - def mapR[T](f: Result[S] => T): Task[T] - def flatFailure[T](f: Incomplete => Task[T]): Task[T] - def mapFailure[T](f: Incomplete => T): Task[T] def dependsOn(tasks: Task[_]*): Task[S] def andFinally(fin: => Unit): Task[S] def doFinally(t: Task[Unit]): Task[S] def || [T >: S](alt: Task[T]): Task[T] def && [T](alt: Task[T]): Task[T] + + def failure: Task[Incomplete] + def result: Task[Result[S]] + + @deprecated("Use the `result` method to create a task that returns the full Result of this task. Then, call `map` on the new task.", "0.13.0") + def mapR[T](f: Result[S] => T): Task[T] + + @deprecated("Use the `failure` method to create a task that returns Incomplete when this task fails and then call `flatMap` on the new task.", "0.13.0") + def flatFailure[T](f: Incomplete => Task[T]): Task[T] + + @deprecated("Use the `failure` method to create a task that returns Incomplete when this task fails and then call `mapFailure` on the new task.", "0.13.0") + def mapFailure[T](f: Incomplete => T): Task[T] + + @deprecated("Use the `result` method to create a task that returns the full Result of this task. Then, call `flatMap` on the new task.", "0.13.0") + def flatMapR[T](f: Result[S] => Task[T]): Task[T] } sealed trait TaskInfo[S] { @@ -114,6 +125,9 @@ trait TaskExtra 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 flatMapR[T](f: Result[S] => Task[T]): Task[T] = new FlatMapped[T, K](in, f, ml) def mapR[T](f: Result[S] => T): Task[T] = new Mapped[T, K](in, f, ml)