How to call task from other task with parameters


(Marcel Overdijk) #1

I have the following task in my plugin which basically picks up the resolved dependencies and unpacks them into a /xlib/ folder. This works all great.

project.task("unpackDeps") << {
 project.configurations.compile.resolvedConfiguration.resolvedArtifacts.each { artifact ->
  logger.lifecycle("Unpack ${artifact.name} ${artifact.moduleVersion.id.version}")
  project.copy {
   from project.zipTree(artifact.file)
   into "${pluginConvention.xlibDir}/${artifact.name}"
  }
 }
}

To speed up performance I would like to implement “Skipping tasks that are up-to-date”.

I tried using the inputs and outputs like:

project.task("unpackDeps") << {
 project.configurations.compile.resolvedConfiguration.resolvedArtifacts.each { artifact ->
  logger.lifecycle("Unpack ${artifact.name} ${artifact.moduleVersion.id.version}")
  project.copy {
   inputs.file artifact.file
   outputs.dir "${pluginConvention.xlibDir}/${artifact.name}"
   from project.zipTree(artifact.file)
   into "${pluginConvention.xlibDir}/${artifact.name}"
  }
 }
}

but this does not seem to work.

Next thing I tried to factor out the copy task to independent task like:

project.task("unpackDeps") << {
 project.configurations.compile.resolvedConfiguration.resolvedArtifacts.each { artifact ->
  logger.lifecycle("Unpack ${artifact.name} ${artifact.moduleVersion.id.version}")
  // TODO call unpackDep
 }
}
  project.task("unpackDep", type: Copy) << {
 inputs.file
 outputs.dir
 // TODO
}

but I wonder how to call the unpackDep task from unpackDeps, and additionally how to specifiy the needed parameters.

Cheers, Marcel


(Marcel Overdijk) #2

My example got a little bit more complicated, but still looking for the answer.

I found out that we can call project.taskName.execute() to execute other tasks.

In my use case I have now two task classes:

class InstallTask extends DefaultTask {
   File lockFile
 File xlibDir
   @TaskAction
 void install() {
  println "lockFile = ${lockFile}"
  println "xlibDir = ${xlibDir}"
  // note that lockFile and xlibDir are provided using conventionMapping
      // unpacks depedencies to xlib folder
  // creates a .lock file which forces specfic version using
  //
   configurations.all { resolutionStrategy.force([..]) }
 }
}
  class UpdateTask extends DefaultTask {
   @TaskAction
 void update() {
     // removes specific forced modules from the configuration (based on provided -P project properties)
      project.install.execute()
 }
}

And everything works fine including calling the install task from update task. Except when calling the install task the properties normally set by the conventioMapping are not set.

What would be best way to implement something like this?


(Marcel Overdijk) #3

For now I just created an Abstract classes which contains the copy/unpack/lock logic. And I have 2 subclasses. This works fine.

My original problem is still related to the Copy task.

I want to call a copy task for all the files I need to copy/unpack. Important is to find a way to specify the inputs and outputs so Gradle will automatically will check if’s up to date already.


(Peter Niederwieser) #4

I think it has been said before, but a task cannot call another task - it can only depend on it. If you try hacks like calling ‘task.execute()’, bad things will happen.


(Marcel Overdijk) #5

Yes this is indeed what I read in the forums. But is there a way to solve implement this then?

Basically I have this task:

class InstallTask extends DefaultTask {
   @TaskAction
 void install() {
    // do other stuff
     project.configurations.all { configuration ->
   configuration.resolvedConfiguration.resolvedArtifacts.each { artifact ->
    ModuleVersionIdentifier module = artifact.moduleVersion.id
    logger.lifecycle "Installing ${module.name} (${module.version})"
          // now unpack all dependencies
    // below works for unpacking but does not take in account up-to-status when re-running the task
    project.copy {
     from project.zipTree(artifact.file)
     into "somedir/${module.name}"
    }
   }
  }
 }
}

How can the copy/unzip part use the up-to-date functionality to avoid unnecessary processing?

Thanks for your help, Marcel


(Peter Niederwieser) #6

It should be good enough to declare the inputs and outputs of ‘InstallTask’.

PS: Just to be clear, your task is calling the ‘Project.copy’ method, not the ‘Copy’ task.


(Marcel Overdijk) #7

Thanks Peter,

The problem is I don’t know the actual inputs and output on the InstallTask level. This is dependent on which dependencies are resolved.

When calling the install task it might resolve to let’s say dep1:1.0, dep2:1.1 and dep3.1.0.1. Now what I’m doing I’m unzipping these dependencies to: /xlib/dep1 /xlib/dep2 /xlib/dep3

The problem is I want to avoid unzipping everytime I call install as it will take some amount of time. But only do this when input (the dependency zip) or the contents of the /xlib/ has changed. Do you think I can arrange that with inputs and output on the InstallTask?

You mentioned I’m using Project.copy and not the Copy task. I thought it was the same… but as you mentioned it I assume there is a difference. Do you suggest to do this differently?


(Peter Niederwieser) #8

The task should have a property of type ‘Configuration’ annotated with ‘@InputFiles’, which is set to the configuration. It should have a property of type ‘File’ annotated with ‘@OutputDirectory’, which is set to the output directory.

‘Project.copy()’ doesn’t to up-to-date checking because it’s not a task. But if you declare inputs/outputs for your own task, you should be fine.


(Marcel Overdijk) #9

I cannot follow this completely. Is this somewhere documented or is there an example available?

Thanks for your patience.


(Peter Niederwieser) #10

Actually, if you want to iterate over all configurations, you’ll have to use the input API, rather than an input annotation. The input/output API is documented in the user guide. Also have a look at the source code of Gradle’s own plugins, e.g. the code quality plugins.


(Marcel Overdijk) #11

I tried various things like:

class InstallTask extends DefaultTask {
   @TaskAction
 void install() {
  unpack()
 }
     protected void unpack() {
  project.configurations.all { configuration ->
   configuration.resolvedConfiguration.resolvedArtifacts.each { artifact ->
    ModuleVersionIdentifier module = artifact.moduleVersion.id
    logger.lifecycle "Installing ${module.group}:${module.name}:${module.version}"
      // add input file (the dep zip) and output dir
    inputs.file(artifact.file)
    outputs.dir("${getXlibDir()}/${module.name}")
          // print for debugging
    inputs.getFiles().each { println it }
    outputs.getFiles().each { println it }
          project.copy {
     from project.zipTree(artifact.file)
     into "${getXlibDir()}/${module.name}"
    }
   }
  }
 }
}

But it keeps unzipping the files. Should I add the inputs/outputs at a different stage?


(Peter Niederwieser) #12

Inputs/outputs need to be declared at configuration time, not at execution time. I wonder if you really need to process all configurations. This complicates matters.


(Marcel Overdijk) #13

First of all Happy New Year!

I probably don’t need to process all configurations, but still wonder how to tackle this best then.

Here is some of my code:

abstract class AbstractInstallableTask extends DefaultTask {
   File lockFile
 File xlibDir
   protected void lock(boolean force = false) {
  // creates a lock file
 }
   protected void unpack() {
  // NOTE not necessary to process all configurations; compile configuration should be enough
  project.configurations.all { configuration ->
   configuration.resolvedConfiguration.resolvedArtifacts.each { artifact ->
    ModuleVersionIdentifier module = artifact.moduleVersion.id
    logger.lifecycle "Installing ${module.group}:${module.name}:${module.version}"
    project.copy {
     from project.zipTree(artifact.file)
     into "${getXlibDir()}/${module.name}"
    }
   }
  }
 }
}
  class InstallTask extends AbstractInstallableTask {
   @TaskAction
 void install() {
  lock()
  unpack()
  logger.lifecycle("Your dependencies are now installed.")
 }
}
  class UpdateTask extends AbstractInstallableTask {
   String dependency
   @TaskAction
 void update() {
  lock(true)
  unpack()
  logger.lifecycle("Your dependencies are now updated.")
 }
}
  class UpdateAllTask extends UpdateTask {
   UpdateAllTask() {
  dependency = "*"
 }
}

It contains an abstract AbstractInstallableTask with a unpack method which will be called by the Install/Update/UpdateAll tasks.

Which files need to be unzipped will be determined from the resolved dependencies. So I cannot ‘hardcode’ this using inputs/outputs I assume? That’s why I was trying to this at execution time/trying to call other tasks with. Do you have any other suggestion or am I hitting a limitation here?

Cheers, Marcel