BuildService lifecycle usage question to bring in more tasks / task actions

Hi,

after Build lifecylce question / task ordering with org.gradle.workers.max > 1 - #19 by tkrah most of my test tasks are running like expected, happy with that.

But I’ve got one test scenario which I could need some suggestion how to fix that in a proper way.

At the moment it is (still) done with dependsOn and finalizers where this graph is built:

  • startComposeStack CS
  • run a NpmTask A from Gradle to prepare resources (which e.g. makes some rest calls to the compose stack)
  • run a Gradle Sync task B, to sync some of those resources to a required destination
  • run the actual test NpmTask C
  • run the finalizer to stop the composeStack CE

Now with the buildServices above my testTask would look like:

task<NpmTask>(“test”) {

   val buildService = extension.buildService

   doFirst {
      buildService.startUpComposeStack(..) 
   }

  ... actual task staff C ...

  usesServices(buildService)
  
  // shutdown will be done via TaskFinishedEvent

}

So running the compose stack and stopping would be done via the buildService, so task CS and CE are covered, the actual test task C is the body of the “test” task - but how can I integrate A (resource preparation) + B (Sync) in this scenario?

Is there a way to have something like:

task<NpmTask>(“test”) {

   val buildService = extension.buildService

   doFirst {
      buildService.startUpComposeStack(..) 
      tasks.named("A").actions.run()
      tasks.named("B").actions.run()
   }

  ... actual task staff C ...

  usesServices(buildService)

}

If not or is this is not how it should be done, how would such a thing be composed with Gradle?

No, you cannot manually run a task in Gradle.
You would probably either do what A and B does as part of test or as part of the service implementation,
or maybe start it in doFirst of A and shut it down after C is finished.
Not sure, never tried it with that pattern.

Hm it does not really belong to the service, so I guess having it as part of the test task seems better to me.

But how could I do what A + B do in my test task C?

E.g. task B is a Sync - Gradle DSL Version 9.5.1 task and A ist a NpmTask from gradle-node-plugin/docs/usage.md at main · node-gradle/gradle-node-plugin · GitHub - do I really have to copy all the code from those tasks to a new task of mine to make that happen or do I just miss something more obvious here?

But how could I do what A + B do in my test task C?

However you would do it in any other JVM program.
Don’t forget to also declare the additional inputs that A and B use if you do it that way.

E.g. task B is a Sync - Gradle DSL Version 9.5.1 task and

If you don’t care about configuration cache, just use sync { ... }.
If you do care about CC, acquire a FileSystemOperations service instance and use sync { ... } on that.
With a custom task you could just get it injected, when just adding an ad-hoc action to an existing task you need for example

interface FileSystemOperationsProvider {
    @get:Inject
    val fileSystemOperations: FileSystemOperations
}

tasks.test {
    val fileSystemOperations = objects.newInstance<FileSystemOperationsProvider>().fileSystemOperations
    doFirst {
        fileSystemOperations.sync { 
            ...
        }
    }
}

A ist a NpmTask from gradle-node-plugin/docs/usage.md at main · node-gradle/gradle-node-plugin · GitHub - do I really have to copy all the code from those tasks to a new task of mine to make that happen or do I just miss something more obvious here?

Maybe it provides some way / class you can you to do it programmatically, no idea.
Least plugins do, but it is certainly possible.

Yeah, thanks for the hint.

Oh many thanks for that sample - I did not know the the fs provider does expose the sync too, that should make things easier than expected. The NpmTask I can replace with a plain providers.exec(..) call and with the sync on the fileSystemOperations - it should be possible to remove those 2 tasks A + B and be back to the buildService solution which works already - I’ll try that.

Many thanks @Vampire , works like a charm.

I replaced those tasks and moved them to:

 doFirst {

   startUpComposeStack()

   injected.providerFactory.exec { /* this was task A */ }

   injected.fs.sync { /* this was task B */ }
   
 }

Works like a charm, tyvm.

You are aware that providerFactory.exec is lazy?
If you have it like shown, it will not be executed.

I know, was just pseudo code - I do have:

val exec =  providerFactory.exec {
              environment(env)
              commandLine("docker", "compose", ...)
              isIgnoreExitValue = true
            }
val execResult = exec.result.get()
logExecOutput(exec)
execResult.assertNormalExitValue()

As stdout and stderr text outputs are only available after the command has finished - is there an example of a ValueSource or a ready to use Gradle ?? provider or … to be able to stream those to the Gradle console output while it is running - I’ve searched the docs but did not yet find such a thing.

providerFactory.exec is more intended for cases where you then provide the output or something from the execution as Provider to some task input or similar which is then evaluated late, and the output is not available in a streamed way but only via the result provider.

If you want more an Exec / exec behavior, what you wanted to use was ExecOperations.exec.

Your’re right, for that usecase this one is much more what I need, switched to ExecOperations which does exactly what I was looking for.