gradle 'build' task confusion - gradle

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.

Related

Gradle: where are task dependencies defined for distZip task

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).

How to diagnose / troubleshoot gradle build not running dependency task?

I'm adding gradle to a multi-project build and hitting the learning curve.
I need to invoke a custom task of type:Exec before compilation of a subproject. The task is not invoked. Why?
build.gradle of parent project
task precompiletask(type:Exec) {
println "Executing pre-compile task"
// ...
}
task(":cppproj:build").dependsOn precompiletask
// Also tried this, same output
//project(":cppproj").task(":build").dependsOn precompiletask
// Also tried this -> error "Cannot add task 'build' as a task with that name already exists"; why on Earth would this syntax *add* the task "build"?
//project(":cppproj").task("build").dependsOn precompiletask // I also tried this
build.gradle of subproject 'cppproj'
apply plugin: "cpp"
model {
components {
api(NativeLibrarySpec) {
sources {
// ...
}
}
}
}
-
Related questions
Why do I find it so hard to debug this? I am running gradle with the verbose-est output (gradle build --debug --warning-mode all). The only mentions of my custom task precompiletask are these, clustered towards the beginning of the output:
07:24:41.151 [DEBUG] [org.gradle.internal.operations.DefaultBuildOperationExecutor] Build operation 'Realize task :precompiletask' started
07:24:41.243 [DEBUG] [org.gradle.internal.operations.DefaultBuildOperationExecutor] Completing Build operation 'Realize task :precompiletask'
07:24:41.243 [DEBUG] [org.gradle.internal.operations.DefaultBuildOperationExecutor] Build operation 'Realize task :precompiletask' completed
07:24:41.249 [QUIET] [system.out] Executing pre-compile task
Why no errors related to failing to link the two tasks using dependsOn? Since dependsOn is clearly invoked in my code I'd expect an error that either the subproject or tasks are not found, or dependsOn itself fails somehow. Does this have to do with gradle's flexibility of being able to refer to stuff that doesn't exist yet?
What does it mean to 'Realize task' anyway? Could not find this documented. Thanks.
You are not using the proper way to access the :cppproj:build task in your root project build: you are using project.task() methods , which is actually creating a new task.
You have several ways available to 'locate' the task you want to configure (adding the dependsOn constraint), as explained here : locating tasks
In your case you could write:
tasks.getByPath(':cppproj:build').dependsOn precompiletask
Note 1:
When using the syntax task(":cppproj:build").dependsOn precompiletask: you create a new task named ':cppproj:build' on the root project and make it depend on precompiletask: that's why precompiletask is not executed if you execute build tasks from parent or subproject.
Note 2:
// Also tried this -> error "Cannot add task 'build' as a task with that name already exists"; why on Earth would this syntax add the task "build"?
//project(":cppproj").task("build").dependsOn precompiletask
=> because the project.task(String) method creates a task, so you are trying to add new task named build to the subproject which already have one build task.
Just adding an answer to my sequel question: why does this seemingly equivalent syntax fail to achieve the same thing: project("cppproj").tasks["build"].dependsOn.
I found that the "configuration" step of gradle build is done by default in a top-down manner, meaning when the root project build.gradle is executed, the subprojects don't contain their tasks yet. This can be changed by doing:
evaluationDependsOnChildren()
... and then one can write:
subprojects.each {
it.tasks["build"].dependsOn myPreCompileTask
}

Basic Gradle copy-task not working (until directly called)

I have a simple Gradle script:
task copyall(){
println 'starting...'
task copyA(type: Copy) {
mkdir 'web'
from 'src'
into 'web'
}
}
It behaves strangely: if I call "gradle copyall" from command line - it creates directory but doesn't copy files from 'src' to 'web' folders.
If I call directly task.copyA ("gradle copyA") from command line - it does both makes dir and copying files (with subfolders).
Why task.copyA is only partially executed as subtask of task.copyall?
Well, I understand why your Gradle script behaves the way you describe, but it behaves this way for various reasons.
First of all, there is no such thing as a subtask in Gradle. You can create tasks and you can define dependencies between tasks, that's it. So your task copyA is exactly the same thing if you just define it on its own (the recommended way):
task copyAll {
println 'starting...'
}
task copyA(type: Copy) {
mkdir 'web'
from 'src'
into 'web'
}
Now you have two tasks, but the task copyAll does nothing at all. Even your println statement is not, what the task does, but how it is configured:
Gradle has two major phases when executing build scripts: configuration phase and execution phase. The whole build script will be evaluated (executed) during configuration phase and only the task actions (e.g. copying for a Copy task), doFirst and doLast closures of selected tasks will be executed during execution phase. The selected tasks are determined from the command line parameters and task dependencies.
As a conclusion, the 'starting ...' output is printed when task copyAll is configured, not when it is started!
Last but not least, the mkdir command is causing confusion, because it is not part of the Copy task, but a method of the Project instance. It executed directly when called, so in your case directly during the configuration phase, completely independent from any task execution. It will be executed every time you execute Gradle.
But, to hear some good news, you simply don't need it. A Copy task will create all required target directories on its own.
Now, to summarize all points from above, we come up with the following build script:
task copyAll {
dependsOn 'copyA'
}
task copyA(type: Copy) {
from 'src'
into 'web'
}

How to call a Gradle subproject’s task from a Gradle test?

I have a project that includes a subproject like so:
Root Project
|----gradle.build
|----SubProject
|----|----gradle.build
The SubProject here contains a copy script that I need called when the root project’s test command is called.
So I have attempted to call the SubProject’s task in the Root project like this:
Task myTest(type: Test) {
Project(‘:SubProject’).tasks.myCopyTask.execut()
}
However, this results in an error, “Could not get unknown property ‘myCopyTask’ for task set.”
Do you know how this call should be done, and what the proper syntax should be?
There a multiple things not working in your example:
You should never call execute on tasks! NEVER! Tasks are called by Gradles task system automatically and calling execute may break this system.
The closure ({ }) you use when creating a task is for configuration. It is not executed when the task gets executed, but when it is created.
Subprojects in Gradle are created and evaluated after the root project is created and evaluated. So tasks from subprojects do not even exist when the root project gets evaluated.
You can solve all these problems by using the dependsOn method with absolute task paths:
task myTest (type: Test) {
dependsOn ':Subproject:myCopyTask'
}

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)

Resources