Confused about Gradle delete task - gradle

I'm currently learning Gradle, so this is probably a simple question but I can't seem to understand.
I need to create a task in my gradle build that deletes a set of intermediate files. So after a bunch of Google'ing, I tried the following:
task deleteTest (type: Delete) {
doLast {
delete fileTree ('src/main/gen') {
include '**/*'
}
}
}
This has no effect, since when I run the task all of the files in the 'src/main/gen' directory still exist. From reading various websites, it seemed like this was the correct approach, but it just doesn't work.
Just for grins, I tried:
task deleteTest (type: Delete) {
delete fileTree ('src/main/gen') {
include '**/*'
}
}
This seems to work, all of the files get removed from the directory(although it leaves empty sub-directories, which I also don't understand). But from what I read, this is not the correct way to go, since it executes during configuration, not during execution.
Can someone please explain this to me? There's apparently something I'm just not grokking with respect to Gradle in general and this problem in particular.

The short answer:
If you just want to delete the folder src/main/gen and everything inside, use something like this:
task deleteTest(type: Delete) {
delete 'src/main/gen'
}
Your second example is fine, too. It preserves directories because a fileTree is used, which only collects files.
The long answer:
Your first example mixes the two ways to delete files in Gradle. The first one is to use a task of type Delete, the second one is to invoke the method delete of the type Project. But how to they differ and why are they mixed in your example?
Gradle is based on its task system that allows to define and configure tasks which are only run if necessary. Whether a task is required for the build will be determined from task dependencies (dependsOn). This is the reason why Gradle distinguishes between the configuration phase and the execution phase. During configuration phase, the whole build script gets executed except the actual task actions (not visible in the build script) and code wrapped in doFirst / doLast closures. During execution phase, each required task gets run by Gradle. This involved executing the doFirst closures of the task, then the actual task actions and in the end the doLast closures of the task. Now for a Delete task like the one above this means, that the code in the configuration closure delete 'src/main/gen' gets executed during configuration phase, but the actual deletion of the files (the task action) happens later on, during execution phase.
The problem with this approach arises when its required to delete files directly or all the time (e.g. in a plugin or another scenario). It would be too complicated to create a task, setup the dependencies and so on. Here comes the method delete of the type Project to the rescue. It provides the same interface for configuration as the task type Delete, but executes directly. It can be called via the project instance (e.g. project.delete 'src/main/gen') everywhere in your script and runs instantly, but because the project instance is used as scope of the whole script, just using delete is sufficient, too. Well, it is not always sufficient. If the current scope provides a method called delete (with the same signature), this method will be used instead. This is the case inside a task of type Delete and this is the reason why your first script does not work:
Your task of type Delete gets configured in the doLast closure, which runs after the actual deletion should have taken place. If you remove the type: Delete, the method delete will no longer configure the task, but instead delete the files instantly because it is no longer the method delete of the task Delete, but the method delete of the type Project. This works fine, but using a real task should be preferred.
If you remove the type: Delete from your second example, the same thing will happen. Instead of configuring the task, the files will be deleted instantly (now during configuration phase). You do not want this behavior, because the task will be obsolete, since the files will be deleted every time Gradle is invoked. This is what you mentioned as a possible problem.

Related

Prevent gradle from skipping a task

I have a bunch of tasks that are run in sequence and some are skipped based on values in a properties file.
Without changing the properties or the original task definition, how can I tell Gradle to not skip a task?
I've tried adding
myTask.onlyIf { true }
but this doesn't seem to override the existing onlyIf call.
Simply use the assignment operator:
myTask.onlyIf = { true }
This will invoke the method setOnlyIf(Closure) instead of onlyIf(Closure), which will replace all existing checks instead of addding a new one.
Gradle often provides both a method to add a new parameter and a setter to overwrite previously set parameters.
Please note, that this might not work for execution of up-to-date tasks. This will only affect tasks that will be skipped due to previously set onlyIf closures.

How Gradle tasks inline configuration works under the hood ?

I'm trying to understand how gradle build script is interpreted under the hood.
I get that the entire build script delegates to the project object, so when we type
task taskName
we are executing a function defined in the project object with task as name and taking a string parameter.
What is weird for me is the inline configuration of the task :
taskName.description = "Description of the task"
taskName.group = "Group of the task"
How this really works in the level of project object ?
Take a look at https://docs.gradle.org/current/dsl/org.gradle.api.Project.html, the first "Properties" section.
Here are the useful bits with some of the details removed:
A project has 5 property 'scopes', which it searches for properties. The scopes are:
The Project object itself.
The extra properties of the project.
The extensions added to the project by the plugins.
The tasks of the project.
The extra properties and convention properties inherited from the project's parent, recursively up to the root project. The properties of this scope are read-only.
When reading a property, the project searches the above scopes in order, and returns the value from the first scope it finds the property in. If not found, an exception is thrown.
In your example, taskName.description = "..." means that Gradle looks up taskName as a method on Project, as an extra property, as an extension and then it finds a task with that name. Then you're just calling setDescription on a regular Task object.
The magic happens in Project.property() using a ExtensibleDynamicObject configured like so:
https://github.com/gradle/gradle/blob/0b9cb4429513297e03965b0578607d10a2a1fcdf/subprojects/core/src/main/java/org/gradle/api/internal/project/DefaultProject.java#L226-L230

How to set dynamic input dependency on gradle task

I'm working on a Gradle plugin that has a task that generates compilable Java source code. It takes as input for the code generator a "static" directory (property value) which should contain files in a special language (using a specific extent). In addition, if a particular configuration property is set to true, it will also search for files with the same extent in the entire classpath (or better, in a specific configuration).
I want to make sure that the task runs if any of its input dependencies are new.
It's easy enough to add #InputDirectory to the property definition for the "static" location, but I'm unsure how to handle the "dynamic" input dependency.
I have a property that defines the name of the configuration that would be used to search for additional files with that extent. We'll call that "searchConfiguration". This property is optional. If it's not set, it will use "compile". I also have the property that specifies whether we will search for additional files in the first place. We'll call that "inspectDependencies".
I think I could write a #Input-annotated method that returns essentially the "configurations.searchConfiguration.files" list. We'll call that "getDependencies". I think that is the basic idea. However, I don't understand what to do about "inspectDependencies". I could easily make "getDependencies" return an empty list if "inspectDependencies" is false, but is that truly the correct thing to do? It seems likely that if someone changed "inspectDependencies" from "true" to "false" after a build, the next build should run the task again.
Well, this is tentative, but I asked about this on the Gradle Forum and Mark Viera convinced me that it really should be this simple, although it requires #InputFiles instead of #Input. My particular method looks like this:
#InputFiles
def getOptionalYangClasspath() {
return inspectDependencies ? project.configurations[yangFilesConfiguration] : Collections.emptyList()
}

How to declare dependencies of a Gradle custom task?

If I've created a custom task:
class MyTask extends DefaultTask {
...
}
I can at another time create an instance and declare dependencies:
task(["type": MyTask, "dependsOn": importantThing], "MyTaskName")
However, it seems a bit weird to separate the task definition from the declaration of dependencies. That is, it seems like everything defining the task should be in one place, or else it would be easy to instantiate the task without the right dependencies. Is there some better way to do this?
Tasks should be generic and self-contained. They should only operate on their own input properties, and should not assume existence of other tasks. Declaring tasks and their dependencies is the responsibility of build scripts and/or plugins.

Copying files based on a pattern that is determined after the configuration phase has completed

I am currently assessing gradle as an alternative to Maven for a homegrown convention based ant+ivy build. The ant+ivy build is designed to provide a standard environment for a wide range of j2se apps & it supports the following conventional layout to app config
conf/
fooPROD.properties
fooUAT.properties
bar.properties
UK/
bazPROD.properties
bazUAT.properties
If I choose to do a build for UAT then I get
conf/
foo.properties
bar.properties
UK/
baz.properties
i.e. it copies the files that are suffixed with the target environment (UAT in this case) as well as anything that has no such pattern. There are a variety of other things that happen alongside this to make it rather more complicated but this is the core of my current problem.
I've been playing around with various gradle features while transcribing this as opposed to just getting it working. My current approach is to allow the targetenv to be provided on the fly like so
tasks.addRule("Pattern: make<ID>") { String taskName ->
task(taskName).dependsOn tasks['make']
}
The make task deals with the various copying/filtering/transforming of conf files from src into the build area. To do this, it has to work out what the targetenv is which I am currently doing after the DAG has been created
gradle.taskGraph.whenReady {taskGraph ->
def makeTasks = taskGraph.getAllTasks().findAll{
it.name.startsWith('make') && it.name != 'make'
}
if (makeTasks.size() == 1) {
project.targetEnv = makeTasks[0].name - 'make'
} else {
// TODO work out how to support building n configs at once
}
}
(it feels like there must be a quicker/more idiomatic way to do this but I digress)
I can then run it like gradle makeUAT
My problem is that setting targetEnv in this way means the targetEnv is not set at configuration time. Therefore if I have a copy task like
task prepareEnvSpecificDist(type: Copy) {
from 'src/main/conf'
into "$buildDir/conf"
include "**/*$project.targetEnv.*"
rename "(.*)$project.targetEnv.(.*)", '$1.$2'
}
it doesn't do what I want because $project.targetEnv hasn't been set yet. Naively, I changed this to
task prepareEnvSpecificDist(type: Copy) << {
from 'src/main/conf'
into "$buildDir/conf"
include "**/*$project.targetEnv.*"
rename "(.*)$project.targetEnv.(.*)", '$1.$2'
}
once I understood what was going on. This then fails like
Skipping task ':prepareEnvSpecificDist' as it has no source files.
because I haven't configured the copy task to tell it what the inputs and outputs are.
The Q is how does one deal with the problem of task configuration based on properties that become concrete after configuration has completed?
NB: I realise I could pass a system property in and do something like gradle -Dtarget.env=UAT make but that's relatively verbose and I want to work out what is going on anyway.
Cheers
Matt
Building for a particular target environment is a cross-cutting concern and does not really fit the nature of a task. Using a system property (-D) or project property (-P) is a natural way of dealing with this.
If you absolutely want to save a few characters, you can query and manipulate gradle.startParameter.taskNames to implement an environment switch that looks like a task name. However, this is a non-standard solution.
How does one deal with the problem of task configuration based on properties that become concrete after configuration has completed?
This is a special case of the more general problem that a configuration value gets written after it has been read. Typical solutions are:
Avoid it if you can.
Some task/model properties accept a closure that will then get evaluated lazily. This needs to be looked up in the corresponding task/plugin documentation.
Perform the configuration in a global hook like gradle.projectsEvaluated or gradle.taskGraph.whenReady (depending on the exact needs).
Perform the configuration in a task action (at execution time). As you have already experienced, this does not work in all cases, and is generally discouraged (but sometimes tolerable).
Plugins use convention mapping to lazily bind model values to task properties. This is an advanced technique that should not be used in build scripts, but is necessary for writing plugins that extend the build language.
As a side note, keep in mind that Gradle allows you to introduce your own abstractions. For example, you could add a method that lets you write:
environment("uat") {
// special configuration for UAT environment
}

Resources