Gradle: where are task dependencies defined for distZip task - gradle

I am using the gradle application plugin.
I noticed that:
distZip task get's run before the assemble task.
And I can see
from the docs (link above), and confirmed during my gradle run, that
the distZip tasks depends on: jar, startScripts tasks
So execution looks something like this:
> Task :my-app:processResources
> Task :my-app:compileJava
> Task :my-app:classes
> Task :my-app:jar
> Task :my-app:startScripts
> Task :my-app:distTar
> Task :my-app:distZip
> Task :my-app:assemble
Looking through the code in ApplicationPlugin.java and/or DistributionPlugin.java, I expected to see the task dependencies defined.
e.g. something like this:
distZipTask.dependsOn(jarTask)
distZipTask.dependsOn(createScriptsTask)
assembleTask.dependsOn(distZipTask)
...but I couldn't find anything like this in the java code.
Would very much appreciate it if someone could point me in the direction of where to look to find out how these tasks are 'linked'.

Gradle can determine implicit task dependencies from task inputs. If (the output of) a task is used as input of another task, Gradle can derive that there must be a dependency on the task. This functionality is called incremental build support, as it also supports checking for changes in the inputs or outputs to skip tasks if the inputs did not change.
Regarding your example, the task dependencies are defined implicitly in line 197 and line 208 of the file ApplicationPlugin.java:
libChildSpec.from(jar);
...
binChildSpec.from(startScripts);
The arguments in these lines are the respective tasks (to be exact: providers of the tasks). The from method can often be used to define sources of a file operation (e.g. copying, zipping ...). So we define the results (outputs) of the tasks jar and startScripts as a source of some operation (CopySpec). The resulting CopySpec objects are then passed to both tasks distZip and distTar. When a task tries to resolve the defined sources it finds the tasks jar and startScripts and defines the dependencies on them on its own.
Please note that the mentioned task assemble is a so-called lifecycle task. It does not run any action but can be used to organize tasks into build phases (just like build and check).

Related

How do I know if a gradle task has inputs and outputs defined?

I cannot find any hints in the docs what the inputs and outputs of a task are. I would like to know for example if the test-task can be skipped and when it will be skipped.
I don't see a way to do this using the built-in features of the gradlew command, but it's possible using the Gradle runtime API if you're happy to add a bit of code to your build.gradle file. You could add the logic for grabbing a task and printing its inputs and outputs into a gradle.projectsEvaluated block, or in a task that you run specifically for this purpose.
For instance, this code (in a Groovy build.gradle file) defines a task printInputsAndOutputs that will print the inputs and outputs of the compileJava task:
tasks.register("printInputsAndOutputs") {
doLast {
Task task = tasks.getByPath(":app:compileJava")
TaskInputs inputs = task.inputs
println("compileJava has inputs? ${inputs.hasInputs}")
println("compileJava input files: ${inputs.files.asPath}")
println("compileJava hasSourceFiles? ${inputs.hasSourceFiles}")
println("compileJava input source files: ${inputs.sourceFiles.asPath}")
println()
TaskOutputs outputs = task.outputs
println("compileJava has output? ${outputs.hasOutput}")
println("compileJava output files: ${outputs.files.asPath}")
}
}
Here's the output I see when I run this locally in my newly gradle inited project:
$ ./gradlew printInputs
> Task :app:printInputsAndOutputs
compileJava has inputs? true
compileJava input files: /home/mark/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-text/1.9/ba6ac8c2807490944a0a27f6f8e68fb5ed2e80e2/commons-text-1.9.jar:/home/mark/demo/utilities/build/classes/java/main:/home/mark/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-lang3/3.11/68e9a6adf7cf8eb7e9d31bbc554c7c75eeaac568/commons-lang3-3.11.jar:/home/mark/demo/list/build/classes/java/main:/home/mark/demo/app/src/main/java/demo/app/MessageUtils.java:/home/mark/demo/app/src/main/java/demo/app/App.java
compileJava hasSourceFiles? true
compileJava input source files: /home/mark/demo/app/src/main/java/demo/app/MessageUtils.java:/home/mark/demo/app/src/main/java/demo/app/App.java
compileJava has output? true
compileJava output files: /home/mark/demo/app/build/classes/java/main:/home/mark/demo/app/build/generated/sources/annotationProcessor/java/main:/home/mark/demo/app/build/generated/sources/headers/java/main:/home/mark/demo/app/build/tmp/compileJava/previous-compilation-data.bin
BUILD SUCCESSFUL in 351ms
10 actionable tasks: 1 executed, 9 up-to-date
Naturally, tweak as you see fit to get a different task, slightly different information, or perhaps to take a task name via a -P parameter if you want a permanent, reusable task for doing this kind of introspection.
For example, your compile task input is the source code. If the source code hasn't changed since the last compile, then it will check the output to make sure you haven't blown away your class files generated by the compiler. If the input and output is unchanged, it considers the task UP-TO-DATE and doesn't execute that task.
If you want to know the specific input and output for each task, you'd need to look at the docs. However, there really is not much practical value in it. As the base input to series of tasks run by gradle test there is only the main source code and the test source code. And all other inputs are derived from it.
So if you've changed 0 code and do a rerun every subtask of the test task will be UP-TO-DATE. If you change only the test code, then all tasks will be UP-TO-DATE except for compileTestJava. If you change both the test and main code all tasks will be rerun.

Configure a Gradle Task at Runtime

Is It possible to configure inputs of a gradle task at runtime after other tasks have run?
For example I am calculating a SHA of a zip in one step, and then uploading the zip with a path consisting of the SHA from a previous step. But when I got get get the value of the SHA which is contained in a file via: def sha = shaFile.text I get an error: (No such file or directory).
I had always assumed tasks were closures which were run at runtime but I guess that is just the doFirst & doLast, but the inputs need to be configured already before that.
Is It possible to configure inputs of a gradle task at runtime after other tasks have run?
Think of it this way:
In order for task B to run, task A must run first, that is to say, task B has a dependency on task A.
Refer to Adding dependencies to a task for more details on task dependencies.
Ok so now we're at the point where we need the output of task A (SHA value) as an input for task B. Since we have a dependency on task A, Gradle well make sure that task A is executed prior to B's execution.
Here's quick dirty example in Kotlin DSL (should be easily translated to Groovy):
tasks {
val taskA = register("taskA") {
val shaText = File("sha.txt")
if (shaText.exists()) {
shaText.delete()
}
File("sha.txt").writeText("abc");
}
register("taskB") {
dependsOn(taskA)
println(File("sha.txt").readText())
}
}
Ideally, you should create a custom task type specifying the input file and also specifying the output file so that Gradle can cache tasks inputs/outputs. Refer to Incremental tasks
for more details.

Conditionally skip subproject in multi-module Gradle build

Consider the following Gradle project structure:
- root
|- build.gradle
|- barProject
|- build.gradle
|- fooProject
|- build.gradle
where the root build.gradle configures its subprojects like so:
subprojects {
apply plugin: 'java'
//bunch of other stuff
}
Now when I call gradlew build on the root project it automatically configures both subprojects and then builds them - all well and good.
I also know that I can skip a specific task with various means (onlyIf(), [taskName].enabled = false, etc.) but when I utilize any of those on build Gradle still runs all the dependent tasks (compileJava, processResources, classes, etc.) until finally hitting build which it then skips.
My question is, is there any way to have Gradle stop and go to the next subproject right after the configuration phase?
EDIT:
To clarify; each subproject has a property called skip that is evaluated in the configuration phase.
Now when I call gradlew build on the root project I want Gradle to, ideally, check that property and if it's true then to completely skip the corresponding project.
Executing external task 'build'...
//check project.skip -> IF true -> skip project, go to :foo ELSE continue
:bar:compileJava
:bar:processResources UP-TO-DATE
:bar:classes
:bar:jar
:bar:startScripts
:bar:distTar
:bar:distZip
:bar:assemble
:bar:compileTestJava UP-TO-DATE
:bar:processTestResources UP-TO-DATE
:bar:testClasses UP-TO-DATE
:bar:test UP-TO-DATE
:bar:check UP-TO-DATE
:bar:build
//check project.skip -> IF true -> skip project, go to end ELSE continue
:foo:compileJava
:foo:processResources UP-TO-DATE
:foo:classes
:foo:jar
:foo:startScripts
:foo:distTar
:foo:distZip
:foo:assemble
:foo:compileTestJava UP-TO-DATE
:foo:processTestResources UP-TO-DATE
:foo:testClasses UP-TO-DATE
:foo:test UP-TO-DATE
:foo:check UP-TO-DATE
:foo:build
BUILD SUCCESSFUL
I hope this makes more sense
Well, first of all: An easy solution would be calling exactly the task(s) you need. In simple Gradle builds, task names are unique. However, in multi-project builds, each project can have a task with a specific name. This is the reason why Gradle introduced task paths. Task paths combine the unique project paths with the intra-project unique task names: :projectX:taskY.
Using project paths, you can easily specify the project-specific task you want to execute: :build for the build task in the root project and :<subproject>:build for the build task in a subproject. If a task name, e.g. build, is provided for a multi-project build, Gradle will search through any project (root and subs) for a task with the specified name and execute them all. This explains the current behaviour.
The task names for execution are managed by a StartParameter object of the Gradle Settings. These Settings can be modified in your settings.gradle file, where you also include subprojects:
include ':foo', ':bar'
startParameter.excludedTaskNames += ':foo:build'
This line excludes the build task of the foo subproject from the execution. You could add the subproject task path (even independent from the task name) to the excluded task names, if a specific condition is met. But, I did not find a way to access the Settings in your build file, so this solution can not be used during configuration phase. However, if your condition is solely based on project / system properties, they can be accessed from settings.gradle.
Another solution for the configuration phase came to my mind, but it's basically what you already mentioned, because it simply skips the tasks:
if (project.skip) {
project.tasks.all { task -> task.enabled = false }
}
This way, all tasks from the project (not only the build task) will be skipped.
Please consider, that tasks can also be executed, because they create a dependency for another project. Even gradle :bar:build will execute the task :foo:jar and its task dependencies, if foo is a project dependency of bar. You can disable this behaviour with the Gradle command line options -a or --no-rebuild or the following entry in your settings.gradle:
startParameter.buildProjectDependencies = false

Task with path 'build' not found in root project

I have a multiproject and after the last subproject is built, I'd like to process all jars.
Therefore I created a task in the root-project:
task install(dependsOn: 'build', type: Copy) {
doLast {
println "exec install task"
}
}
Upon calling ./gradlew install in the root directory, I'm facing this error:
FAILURE: Build failed with an exception.
* What went wrong:
Could not determine the dependencies of task ':install'.
> Task with path 'build' not found in root project 'foo'.
However, calling ./gradlew tasks shows me these tasks:
:tasks
------------------------------------------------------------
All tasks runnable from root project
------------------------------------------------------------
Build tasks
-----------
assemble - Assembles the outputs of this project.
build - Assembles and tests this project.
...
How can I achieve the desired functionality?
I assume, that your root project organizes the build, but does not define build action taken by itself. The build task is often defined by language plugins (in most cases via apply plugin: 'java'), but if your root project does not use any of them, it won't have a build task.
The description of the help task tasks, which you used, says:
Displays the tasks runnable from root project 'projectReports' (some of the displayed tasks may belong to subprojects).
The help task followes the same logic as the task activation via the command line. You can provide a task name and any task with the name in any subproject will be executed (thats why gradle build works).
But if you define a dependsOn dependency, the given string is evaluated as task path for a single task. Since each task name can only be used once in a project, the name is unique for tasks in the root project, but many tasks could be found, if subprojects would be considered. Therefor, one can use the syntax :<projectName>:<taskName> to identify tasks in subprojects.
Now, let's face your specific problem: If the install task should depend on the build task of one single subproject, you could use dependsOn ':<mySubproject>:build'. But I assume you want the install task to depend on each subproject build task, so I'd like to propose this approach:
task install(type: Copy) {
dependsOn subprojects*.tasks*.findByName('build').minus(null)
doLast {
println "exec install task"
}
}
This way, for each registered subproject, findByName('build') is called and the result (the found task or null) is put into a list, which is then used as task dependency list. I added the .minus(null) part to remove null entries from the list, because I am not sure how Gradle handles such entries in a dependency collection. If you are sure, that each subproject provides a build task, you can use getByName('build'), too.
EDIT: OP found the optionally recursive getTasksByName method, which suits this case even better than iterating over all subprojects manually:
dependsOn getTasksByName('build', true)

gradle 'build' task confusion

Hi I have multi project gradle setup
-root_project
|-sub_project1
|-sub_project2
|-sub_project3
All works great but one thing drives me crazy. In my build script:
defaultTasks 'build' <- this works just fine
task buildroom (description: 'This task is invoked by build room script, invokes default task plus publishes artifacts') {
// dependsOn('build') <-- this doesn't work
// alternative
dependsOn(":sub_project1:build")
dependsOn(":sub_project2:build")
when i call from command line 'gradlew' <- default task gets executed
when i call from command line 'gradlew tasks' <- task under 'all task runnable from root project' i see 'build'
but when i try to add dependsOn('build'), dependsOn(':build') or dependsOn(':root:build') it tells me
What went wrong: Execution failed for task ':tasks'.
Could not determine the dependencies of task ':buildroom'.
'base' plugin adds 'assemble', and 'clean' task but not build...
any tips?
The build task is declared by the java-base plugin. It's likely that your root project doesn't (directly or indirectly) apply java-base and therefore doesn't have a build task. This is why dependsOn("build"), which adds a task dependency on a task named build in the same project, eventually causes an error. defaultTasks is different in that:
It only accepts task names (whereas dependsOn also accepts task paths and Task objects).
Its task names get resolved to tasks as if the task names had been entered on the command line. In other words, all projects are searched for a task with the given name, and the set of matching tasks is returned.

Resources