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

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
}

Related

Can I get more information about "implicit dependencies" in Gradle 7.0?

[ Updated 16 April 2021, see reproducible test case below.]
I started converting a small Gradle build to Gradle 7.0. It says:
> Task :junit_xsd
Execution optimizations have been disabled for task ':junit_xsd' to ensure correctness due to the following reasons:
- Gradle detected a problem with the following location: '/Users/ndw/Projects/xproc/test-suite/build'. Reason: Task ':git_log_shorter' uses this output of task ':junit_xsd' without declaring an explicit or implicit dependency. This can lead to incorrect results being produced, depending on what order the tasks are executed. Please refer to https://docs.gradle.org/7.0/userguide/validation_problems.html#implicit_dependency for more details about this problem.
I've looked at the documentation link and it wasn't very informative (to me).
The two tasks in question are, AFAICT, unrelated. junit_xsd is a simple copy task:
task junit_xsd(type: Copy) {
from "src/main/schema/"
into "build/"
include "*.xsd"
}
The git_log_shorter task is a bit harder to describe. It's a custom task that runs a process I wrote.
task git_log_shorter(dependsOn: ["git_log_summary"], type: XMLCalabashTask) {
inputs.file "tools/xsl/shorter-log.xsl"
inputs.file "build/git-log-summary.xml"
outputs.file "build/git-log-shorter.xml"
input("source", "build/git-log-summary.xml")
output("result", "build/git-log-shorter.xml")
pipeline "tools/xpl/run-xslt.xpl"
option("stylesheet", "../xsl/shorter-log.xsl")
}
But it doesn't read any xsd files, so I can't work out why Gradle thinks there's an implicit dependency on junit_xsd.
I mean, I suppose it reads out of the build directory, but I have lots of tasks that create and use temporary files from the build directory. I'll be a little shocked if any task that reads from the build directory is now implicitly dependent on every task that writes to it.
If that is the case, what's the proposed solution for temporary files?
The git_log_summary task, for example, is the task that writes build/git-log-summary.xml and I have an explicit dependency for that task.
Am I overlooking something obvious?
Here's a test case that doesn't involve any extension steps. Running the doall task produces the warning:
task doall(dependsOn: ["copyA", "pointless_gzip"]) {
// nop
}
task copyA(type: Copy) {
from "src/main/data/"
into "${buildDir}"
include "*.xsd"
}
task pointless_tar(type: Exec) {
outputs.file "${buildDir}/pointless.tar"
commandLine 'tar', '-cf', "${buildDir}/pointless.tar", 'src'
doFirst {
mkdir("${buildDir}")
}
}
task pointless_gzip(dependsOn: ["pointless_tar"], type: Exec) {
inputs.file "${buildDir}/pointless.tar"
outputs.file "${buildDir}/pointless.tar.gz"
commandLine 'gzip', '-k', "${buildDir}/pointless.tar"
}

Create task for dependency lock in Gradle >5.0

I want to create a task which to execute
dependencies --update-locks ':'
I had a configuration:
dependencyLocking {
lockAllConfigurations()
}
I try with
task lockDependencies {
dependsOn = ['dependencies','--update-locks *:*']
}
But have:
What went wrong: Could not determine the dependencies of task ':lockDependencies'.
Task with path '--update-locks :' not found in root project
You cannot pass Gradle command line parameters as a task dependency, that's what your error above is about.
The state of writing locks, either with --write-locks or --update-locks, is something that happens really early in the build lifecycle.
You can somewhat control it from a task with the following:
* Create a placeholder task in your build script
* In the settings.gradle(.kts) query the requested tasks from the command line, and if it is there, mutate the start parameters:
if (startParameter.taskNames.contains('placeHolder')) {
startParameter.setWriteDependencyLocks(true)
}
Note that this is not an option if you are trying to lock the classpath of the build itself, which is one of the motivations behind using a command line flag.
Note also that this just allows replacing a flag, like --update-locks *:* with a task invocation like updateLocks but will not work if that task is wired as a dependency of other tasks, as it needs to be requested explicitly. And doing the start parameter mutation after the task graph is computed is too late in the lifecycle.
The best way to do this in my opinion is to add inside the build.gradle file the following code:
dependencyLocking {
lockAllConfigurations()
}
task commitLockDependencies {
'git add /gradle/dependency-locks '.execute()
}
init {
dependsOn('commitLockDependencies')
}
And then inside the settings.gradle the following line:
startParameter.setWriteDependencyLocks(true)
Working with gradle 7.1.

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)

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