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
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?
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.
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.
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
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.
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?
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.
I cannot follow this completely. Is this somewhere documented or is there an example available?
Thanks for your patience.
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.
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?
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.
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