How to declare dependencies of a Gradle custom task? - gradle

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.

Related

Declaring gradle dependencies with arguments

I have a multi-moduled Gradle project setup. I would like for a module to be able to accept an argument from a module that specifies it as a dependency. Can this be achieved?
Something like this in the parent module:
dependencies {
implementation project(':innerModule') {
arg "foo"
}
}
And the inner module would also have to declare the variable that gets assigned. def arg = ""? Really not sure on the correct syntax here.
EDIT:
What I am trying to do is create a build type variable, which I can then use for conditional logic in my project code. It is an Android project, but the one module it requires is non-Android. So I cannot use the buildTypes closure in that module, but I was hoping to feed a variable into the non-Android module once the parent Android module determines the build type. I feel like that should be possible
The answer to your specific question is no.
However, if you describe what you are trying to achieve, maybe Gradle will be able to help you.
What is this variable controlling?
What is the effect of the different values?

How to run filtering tests in gradle?

I have to run tests in a specific order using build.gradle file.
I have the build.gradle file looks like the following:
test {
include 'com.my-project.MyTestClass'
include 'com.my-project.MyTestClass1'
}
but when I running test task I have the following message:
Tests event were not received
How can I fix this problem?
That message just means that no tests were actually run. There could be a number of reasons for that, but the most likely given your example is that the include method takes an Ant style file pattern, but you have given it (fully qualified) class names. Also, 'my-project' is not a valid package name, but I assume this is just a error in your example here.
But more importantly, if your intent is to run tests in a specific order, you will not achieve that with a single test task. The specified includes just tell Gradle what tests are part of the suite, but doesn't affect the order.
I don't know what test framework you are using, but I also don't think it is possible with JUnit 4 and 5. The only way I can think of is to create multiple Test tasks in Gradle, where each task represent a single unit test (or group of tests that can be run in any order), and where you order each task through dependsOn. So something like this:
task myTest1(type: Test) {
include 'example/MyTestClass1.class'
}
task myTest2(type: Test) {
dependsOn myTest1
include 'example/MyTestClass2.class'
}
test {
exclude 'example/**'
dependsOn myTest2
}

Confused about Gradle delete task

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.

Referencing the outputs of a task in another project in Gradle

Consider the following setup
rootProject
|--projectA
|--projectB
There is a task taskB in projectB and I would like the reference the outputs of that task in a copy task taskA in projectA. For example, taskA might look something like:
task taskA(type: Copy) {
dependsOn ':projectB:taskB'
from ':projectB:taskB.outputs'
into 'someFolder'
}
Of course the above example doesn't actually work. While it's okay to reference the task as :projectB:taskB as a dependency, :projectB:taskB.outputs doesn't seem to mean anything to Gradle. I've tried reading through the Gradle docs but didn't find anything that referenced what I'm trying to do.
The accepted answer has been to only and recommended way to solve this problem. However building up this kind of project dependencies and reaching from one project into another is discouraged by the Gradle team now. Instead of this, projects should only interact with each others using publication variants. So the idiomatic (but sadly at the moment more verbose) way would be:
On the producing side (projectB) define a configuration that is not resolvable but consumable by other projects and creates a new variant (called taskB-variant)
configurations.create("taskElements") {
isCanBeResolved = false
isCanBeConsumed = true
attributes {
attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage::class, "taskB-variant"))
}
outgoing.artifact(taskB.outputs)
}
On the consuming side (projectA) define a configuration that is resolvable but not consumable for the same variant and define a dependency to projectB
val taskOutputs by configurations.creating {
isCanBeResolved = true
isCanBeConsumed = false
attributes {
attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage::class, "taskB-variant"))
}
}
dependencies {
taskOutputs(project(":projectB"))
}
tasks.register<Copy>("taskA") {
from taskOutputs
into 'someFolder'
}
This way you decouple how the outputs are produced and the publication variant (called "taskB-variant") becomes the interface between projectA and projectB. So whenever you change the way the output is created you only need to refactor projectB but not projectA as long as you make sure the outputs end up in the taskElements configuration.
At the moment this is still pretty verbose but hopefully Gradle will get more powerful APIs to describe this kind of project relationships in the future.
projectA build.gradle should be:
evaluationDependsOn(':projectB')
task taskA(type:Copy, dependsOn:':projectB:taskB'){
from tasks.getByPath(':projectB:taskB').outputs
into 'someFolder'
}

Custom gradle task classes: is there a "post-construction" hook?

Is there some sort of "post-construction hook" available on custom task classes, so I can call methods like inputs and outputs in class-specific logic?
Let's say I'm defining a custom Gradle task class like
class ExampleTask extends DefaultTask {
def exFile = null
}
Now, I'd like to instantiate it via
task('ex', type: ExampleTask) {
exFile = file("some-example.json")
}
... and I'd like to automatically run the equivalent of inputs(exFile) on the instance. Where does the logic go to handle this kindof configuration? I see that I could add an #InputFiles decorator on a method in my custom task class, like
#InputFiles
def getFiles(){
file(exFile)
}
... but this doesn't seem very general. I'd rather just use the existing inputs() functionality, rather than rewriting portions of it. But I can't figure out where to call it from.
If necessary, you can do these initializations in the zero-argument constructor of the task class. Default property values are often set by a plugin (especially if a default value depends on information from outside the task class). Input/output annotations should be preferred over the input/output API. (The latter exists for ad-hoc tasks that don't have their own task class.)
I need the exact same thing, and to my understanding, the answers are more or less - no, that is currently not possible.
See https://discuss.gradle.org/t/custom-task-with-extensions/12491

Resources