Sequential chaining operators for Initialize

240 views
Skip to first unread message

Johannes Rudolph

unread,
May 22, 2012, 10:34:47 AM5/22/12
to simple-b...@googlegroups.com
Is there a reason why those two combinators are missing from sbt?

RichInitializeTask:

def && [T](alt: Initialize[Task[T]]): Initialize[Task[T]] = (i, alt)(_ && _)

and

RichAnyTaskSeq:

def sequentially: Initialize[Task[Unit]] =  Initialize.joinAny(keys) { _.reduce(_ && _).asInstanceOf[Task[Unit]] }

(Is there a possibility to remove the cast btw?)

Johannes

Eugene Vigdorchik

unread,
May 22, 2012, 11:59:00 AM5/22/12
to simple-b...@googlegroups.com
On Tue, May 22, 2012 at 6:34 PM, Johannes Rudolph
<johannes...@gmail.com> wrote:
> Is there a reason why those two combinators are missing from sbt?
>
> RichInitializeTask:
>
> def && [T](alt: Initialize[Task[T]]): Initialize[Task[T]] = (i, alt)(_ && _)
>
> and
>
> RichAnyTaskSeq:
>
> def sequentially: Initialize[Task[Unit]] =  Initialize.joinAny(keys) {
> _.reduce(_ && _).asInstanceOf[Task[Unit]] }

As far as I understand, since the sequence is Seq[Initialize[Task[_]]]
and the result of the last task is returned, the correct definition
would be:

def sequentially: Initialize[Task[_]] = Initialize.joinAny(keys) {
_.reduce(_ && _) }

However I don't see when invoking a sequence of heterogenuos tasks is
needed. Do you have a usecase?

Eugene.
>
> (Is there a possibility to remove the cast btw?)
>
> Johannes
>
> --
> You received this message because you are subscribed to the Google Groups
> "simple-build-tool" group.
> To view this discussion on the web visit
> https://groups.google.com/d/msg/simple-build-tool/-/NCjwZx6IAIwJ.
> To post to this group, send email to simple-b...@googlegroups.com.
> To unsubscribe from this group, send email to
> simple-build-t...@googlegroups.com.
> For more options, visit this group at
> http://groups.google.com/group/simple-build-tool?hl=en.

Johannes Rudolph

unread,
May 22, 2012, 12:51:16 PM5/22/12
to simple-b...@googlegroups.com
On Tuesday, May 22, 2012 5:59:00 PM UTC+2, Eugene Vigdorchik wrote:
As far as I understand, since the sequence is Seq[Initialize[Task[_]]]
and the result of the last task is returned, the correct definition
would be:

def sequentially: Initialize[Task[_]] =  Initialize.joinAny(keys) {
_.reduce(_ && _) }

But you can't use that to define any TaskKey because of the existential.
 
However I don't see when invoking a sequence of heterogenuos tasks is
needed. Do you have a usecase?

Yes, I want to be able to define a task which consists of executing subtasks in a sequential fashion. I mean, I didn't define Task.&& that was already there for a reason I think. The new operators would just simplify using the operators which are defined for Task directly on TaskKeys.

Johannes

Josh Suereth

unread,
May 22, 2012, 1:46:26 PM5/22/12
to simple-b...@googlegroups.com
Why use a task, why not use a command for that?

When using tasks, you give up sequentially-ness for dependencies *and* parallelism on purpose.   If you want sequential execution, you can do that in a command.


- Josh


Johannes

--
You received this message because you are subscribed to the Google Groups "simple-build-tool" group.
To view this discussion on the web visit https://groups.google.com/d/msg/simple-build-tool/-/euuR71QY06EJ.

Lee Mighdoll

unread,
May 22, 2012, 2:57:17 PM5/22/12
to simple-b...@googlegroups.com
My use case is for integration tests.  Here's an example:

vanillaIntegration <<= (startServers, waitForServers, externalTests, stopServers) {(start,wait,tests,stop) =>
  start && wait && tests doFinally stop 
}


I think the && on RichInitializeTask would win the following pithier version:  (well, assuming there's a way way to get andFinally to work with TaskKey...)

vanillaIntegration <<= startServers && waitForServers && externalTests andFinally stopServers



I had tried writing this with Command, but didn't find anything clean, e.g.:

 Command.command("vanilla-integration") {state =>
      val extracted = Project.extract(state)
      try {
        extracted.runTask(startServersTask, state)
        extracted.runTask(waitForServersTask, state)
        runExternalTests()
      } finally {
        extracted.runTask(stopServers, state)
      }
      state
    }

Suggestions welcome.  Hopefully the concrete example helps.

Mark Harrah

unread,
May 23, 2012, 10:05:38 PM5/23/12
to simple-b...@googlegroups.com
On Tue, 22 May 2012 11:57:17 -0700
Lee Mighdoll <l...@underneath.ca> wrote:

> My use case is for integration tests. Here's an example:
>
> vanillaIntegration <<= (startServers, waitForServers, externalTests,
> stopServers) {(start,wait,tests,stop) =>
> start && wait && tests doFinally stop
> }
>
>
> I think the && on RichInitializeTask would win the following pithier
> version: (well, assuming there's a way way to get andFinally to work with
> TaskKey...)
>
> vanillaIntegration <<= startServers && waitForServers && externalTests
> andFinally stopServers

The problem is that strict sequencing and tasks are not really compatible. A task defines the inputs it needs to run and doesn't enforce an order beyond that.

All 'b && c' does is delay introducing 'c' into the run until after 'a' completes. It doesn't force c to run after b. If 'b' depends on 'c' somehow (and I don't think this is an unreasonable situation), 'c' will get introduced. To prevent this, c would have to declare 'b' as a dependency. In fact, if you really wanted to stick with tasks, you'd define stopServers to depend on externalTests, externalTests to depend on waitForServers and define waitForServers to depend on startServers.

I haven't made && easy to use because I don't think its semantics are intuitive and users will expect to use them like sequencing as in this thread. If what you want is sequencing (and it looks like you do), use a command like you show below, or sequence within a task. Conceptually, you want a task that does:

externalTests = {
startServers()
waitForServers()
try runExternalTests()
finally stopServers()
}

all in one task within the task. (These are plain methods.) The difference here is that execution order is explicit and nothing else can interfere, such as by causing stopServers to run earlier than runExternalTests or whatever.

(I seem to remember someone else explaining this more succinctly/clearly on this mailing list, but I couldn't find it. Hopefully it gets the right idea across.)

>
>
> I had tried writing this with Command, but didn't find anything clean, e.g.:
>
> Command.command("vanilla-integration") {state =>
> val extracted = Project.extract(state)
> try {
> extracted.runTask(startServersTask, state)
> extracted.runTask(waitForServersTask, state)
> runExternalTests()
> } finally {
> extracted.runTask(stopServers, state)
> }
> state
> }

Is this what you want, what you have but don't like, or what?

> Suggestions welcome. Hopefully the concrete example helps.

Yes, thanks.

-Mark

Johannes Rudolph

unread,
May 24, 2012, 3:52:23 AM5/24/12
to simple-b...@googlegroups.com
On Thu, May 24, 2012 at 4:05 AM, Mark Harrah <dmha...@gmail.com> wrote:
> All 'b && c' does is delay introducing 'c' into the run until after 'a' completes.  It doesn't force c to run after b.  If 'b' depends on 'c' somehow (and I don't think this is an unreasonable situation), 'c' will get introduced.

Ok, that makes sense. When I tried it, I was already wondering if it
could work because it was on the wrong layer to properly put the
dependencies in place but it seemed to work in the simple cases. I
agree that it shouldn't be made easier to make that mistake.

--
Johannes

-----------------------------------------------
Johannes Rudolph
http://virtual-void.net
Reply all
Reply to author
Forward
0 new messages