Gradle tasks definition - gradle

These three forms to define a task in build.gradle seem identical. All of them call org.gradle.api.internal.project.DefaultProject#task(java.lang.String, groovy.lang.Closure), but really I can't understand how the second and the third one can work.
def myAction = {t -> println "${t.name} [${t.class.name}]"}
task('myTaskA') {task ->
group = 'MyTasks'
description = name
doLast myAction
}
task myTaskB {task ->
group = 'MyTasks'
description = name
doLast myAction
}
task myTaskC() {task ->
group = 'MyTasks'
description = name
doLast myAction
}

Task Configuration Avoidance: As of Gradle 5.1, it is recommend that the configuration avoidance APIs are used whenever tasks are created by custom plugins. The Configuration Avoidance API will co-exists with the existing APIs that will be replaced with the usual deprecation process over several major releases. In a nutshell, the API allows builds to avoid the cost of creating and configuring tasks during Gradle’s configuration phase when those tasks will never be executed, which can have a significant impact on total configuration time.
Instead of: “task ... {…}”, “task ... << {…}”, “task('...') {…}”, etc. use “tasks.register('...') {…}” API.

Related

Classes in bundle 'app' do not match with execution data - Android

Although similar questions are already present on internet, but I am unable to find any solution to this problem with regards to android platform. My project has multiple product flavors, uses kotlin and hilt. I believe byte code transformation while compiling the project is the root cause of disrupture.
I first thought probably Hilt is injecting code inside classes, therefore I made sure to copy classes before Hilt tasks execution into a separate folder, then use those classes as source for jacoco. But it didn't work.
Error
[ant:jacocoReport] Classes in bundle 'app' do not match with execution data. For report generation the same class files must be used as at runtime.
[ant:jacocoReport] Execution data for class com/company/myapp/Data$callApi$1 does not match.
[ant:jacocoReport] Execution data for class com/company/myapp/factory/SomeFactory$SomeGenerator does not match.
and the list continues for whole bunch of classes in the app. Due to these errors, code coverage is always zero although there are bunch of unit test already written in the app.
gradle.projectsEvaluated {
def hiltTaskPattern = ~/\bhilt.*\w+FlavorADebug\b/
def tasksList = getSubprojects()
.collect { it.tasks }
.flatten()
def copyFilesTask = tasksList.find { it.name == "copyClassFilesForJacoco" }
if (copyFilesTask != null) {
tasksList.findAll { hiltTaskPattern.matcher(it.name).matches() }
.each { it.dependsOn copyFilesTask }
}
}
task copyClassFilesForJacoco(dependsOn: "compileDebugJavaWithJavac", type: Copy) {
def javaDebugTree = fileTree(dir: "${buildDir}/intermediates/javac/flavorADebug/classes", excludes: androidFilesExcluded)
def kotlinDebugTree = fileTree(dir: "${buildDir}/tmp/kotlin-classes/flavorADebug", excludes: androidFilesExcluded)
from javaDebugTree, kotlinDebugTree
into layout.buildDirectory.dir(classFilesPathForInstrumentation)
doLast {
println("Files copied to ${classFilesPathForInstrumentation}")
}
}
Then in the testCoverage task of type JacocoReport, classDirectories point out to copied files
Also, kotlinx-kover seemed interesting, but it's in pre-mature state and lacks support of multiple product flavors along with outdated documentation which makes its usage unfavourable.
This answer explains very well reason for the problem, and also provide potential solution. But it is old, and not applicable to android project since it uses java plugin and jacoco-ant agent which is not compatible with android.
Can someone guide towards potential solutions for the aforementioned problem? TIA

Cannot have different system properties values for different tasks

I am trying to create 2 tasks to execute the sonarcube task. I want to be able to specify different properties depending on the task
task sonarqubePullRequest(type: Test){
System.setProperty( "sonar.projectName", "sonarqubePullRequest")
System.setProperty("sonar.projectKey", "sonarqubePullRequest")
System.setProperty("sonar.projectVersion", serviceVersion)
System.setProperty("sonar.jacoco.reportPath",
"${project.buildDir}/jacoco/test.exec")
tasks.sonarqube.execute()
}
task sonarqubeFullScan(type: Test){
System.setProperty("sonar.projectName", "sonarqubeFullScan")
System.setProperty("sonar.projectKey", "sonarqubeFullScan")
System.setProperty("sonar.projectVersion", serviceVersion)
System.setProperty("sonar.jacoco.reportPath",
"${project.buildDir}/jacoco/test.exec")
tasks.sonarqube.execute()
}
The tasks work but there seems to be an issue with the properties I am setting
if I run the first task which is sonarqubePullRequest then everything is fine, but if run sonarqubeFullScan then if uses the values specified in the sonarqubePullRequest. so the project name is set sonarqubePullRequest
it is as if those properties are set at run time and cannot be updated. I feel like I am missing something obvious any suggestions greatly received.
First of all: NEVER use execute() on tasks. The method is not part of the public Gradle API and therefor, its behaviour can change or be undefined. Gradle will execute the tasks on its own, either because you specified them (command line or settings.gradle) or as task dependencies.
The reason, why your code does not work, is the difference between the configuration phase and the execution phase. In the configuration phase, all the (configuration) code in your task closures is executed, but not the tasks. So, you'll always overwrite the system properties. Only (internal) task actions, doFirst and doLast closures are executed in the execution phase. Please note, that every task is only executed ONCE in a build, so your approach to parametrize a task twice will never work.
Also, I do not understand why you are using system properties to configure your sonarqube task. You can simply configure the task directly via:
sonarqube {
properties {
property 'sonar.projectName', 'sonarqubePullRequest'
// ...
}
}
Now you can configure the sonarqube task. To distinguish between your two cases, you can add a condition for different property values. The next example makes use of a project property as condition:
sonarqube {
properties {
// Same value for both cases
property 'sonar.projectVersion', serviceVersion
// Value based on condition
if (project.findProperty('fullScan') {
property 'sonar.projectName', 'sonarqubeFullScan'
} else {
property 'sonar.projectName', 'sonarqubePullRequest'
}
}
}
Alternatively, you can add another task of the type SonarQubeTask. This way, you could parametrize both tasks differently and call them (via command line or dependency) whenever you need them:
sonarqube {
// Generated by the plugin, parametrize like described above
}
task sonarqubeFull(type: org.sonarqube.gradle.SonarQubeTask) {
// Generated by your build script, parametrize in the same way
}

Trying to loop through an array and pass each value into a gradle task

I'm trying to do a series of things which should be quite simple, but are causing me a lot of pain. At a high level, I want to loop through an array and pass each value into a gradle task which should return an array of its own. I then want to use this array to set some Jenkins config.
I have tried a number of ways of making this work, but here is my current set-up:
project.ext.currentItemEvaluated = "microservice-1"
task getSnapshotDependencies {
def item = currentItemEvaluated
def snapshotDependencies = []
//this does a load of stuff like looping through gradle dependencies,
//which means this really needs to be a gradle task rather than a
//function etc. It eventually populates the snapshotDependencies array.
return snapshotDependencies
}
jenkins {
jobs {
def items = getItems() //returns an array of projects to loop through
items.each { item ->
"${item}-build" {
project.ext.currentItemEvaluated = item
def dependencies = project.getSnapshotDependencies
dsl {
configure configureLog()
//set some config here using the returned dependencies array
}
}
}
}
I can't really change how the jenkins block is set-up as it's already well matured, so I need to work within that structure if possible.
I have tried numerous ways of trying to pass a variable into the task - here I am using a project variable. The problem seems to be that the task evaluates before the jenkins block, and I can't work out how to properly evaluate the task again with the newly set currentItemEvaluated variable.
Any ideas on what else I can try?
After some more research, I think the problem here is that there is no concept of 'calling a task' in Gradle. Gradle tasks are just a graph of tasks and their dependencies, so they will compile in an order that only adheres to those dependencies.
I eventually had to solve this problem without trying to call a Gradle task (I have a build task printing the relevant data to a file, and my jenkins block reads from the file)
See here

gradle: how do I list tasks introduced by a certain plugin

Probably a simple question but I can't find a way to list which tasks are introduced by the plugins that get applied in a build.gradle file.
So, say that your build.gradle is simply:
apply plugin: 'java'
is there a simple way to make gradle list all the tasks introduced by that plugin?
PS: that would come handy in case of messy and large build files with dozens of applied plugins
PS2: I'm not asking about the dependencies of the tasks. My question is different and quite clear. Each plugin that I apply introduces some tasks of its own (never mind what depends on what). The question is which are the newly introduced tasks in the first place?
I'm afraid it is not possible because of the nature how gradle plugins are applied.
If you take a look at Plugin interface, you will see it has a single apply(Project p) method. Plugin responsibility is to configure a project - it can add specific tasks / configurations / etc. For example, gradle JavaPlugin is stateless, so you can't get tasks from it.
The only solution that comes to mind is to get a difference of tasks after the plugin is applied:
build.gradle
def tasksBefore = [], tasksAfter = []
project.tasks.each { tasksBefore.add(it.name) } // get all tasks
apply(plugin: 'idea') // apply plugin
project.tasks.each { tasksAfter.add(it.name) } // get all tasks
tasksAfter.removeAll(tasksBefore); // get the difference
println 'idea tasks: ' + tasksAfter;
This will print tasks that were added by Idea plugin:
idea tasks: [cleanIdea, cleanIdeaModule, cleanIdeaProject,
cleanIdeaWorkspace, idea, ideaModule, ideaProject, ideaWorkspace]
You can play a bit with this code and build an acceptable solution.
In some cases origin from specific plugin can be restored by checking the task's group and name:
tasks.findAll { it.group == 'verification' && it.name.startsWith('jacoco') }.each { task ->
println(task.name)
}

Dynamically configuring a task in a parent build.gradle

I have a multi-project C++ Gradle build, which produces a number of libraries and executables. I'm trying to get the executables (but not the libraries) subprojects to get compiled in with a 'fingerprint' object. This works fine if I sprinkle smth like this in individual subprojects' build.gradle:
compileMain.doFirst {
// code to generate a 'BuildInfo.cpp' from from a template.
// embeds name of executable in so has to be generated anew for each exe
}
Following DRY principles, I'd much rather do this once and for all in a top level build.gradle. This is my attempt, to apply it to just the subprojects that use the cpp-exe plugin, following these instructions:
configure(subprojects.findAll { it.plugins.hasPlugin('cpp-exe') }) {
compileMain.doFirst {
// same code as above
}
}
Alas, this doesn't get triggered. However, if I put smth like this in a less restrictive configure, block, this demonstrates that the idea of querying the plugin should work:
configure(subprojects.findAll { true }) {
task mydebug << {
if ( project.plugins.hasPlugin( 'cpp-exe' ) ) {
println ">>> $project.name has it!"
}
}
}
Could it be that the plugins don't get applied to the subprojects at the time the configure closure is evaluated (in the top-level build.gradle)? There may well be a much simpler way of achieving this altogether?
You probably apply the cpp-exe plugin in the child projects' build scripts. By default, a parent build script gets evaluated before its children, which explains why it's not finding any projects that have cpp-exe applied.
There are several ways to solve this problem. One way is to move all configuration that's specific to a cpp-exe project (like applying the plugin and adding the action) to the same spot. Either you do all such configuration from the parent build script (for example by enumerating the cpp-exe subprojects and configuring them with a single configure(cppExeProjects) { ... }), or you move the cpp-exe specific configuration into its own build script (say gradle/cpp-exe.gradle) and apply it from selected subprojects like so: apply from: "$rootDir/gradle/cpp-exe.gradle".
Another solution is to change the evaluation order of build scripts. But I would only use this as a last resort, and it is certainly not necessary here.
Gradle 1.5 is recently out, I am not sure if this is a new feature but as it looks, you can solve the issue by using afterEvaluate.
Take a look at section 53.6.1 in http://www.gradle.org/docs/current/userguide/build_lifecycle.html
Something like:
subprojects {subProject ->
afterEvaluate {
if ( subProject.plugins.hasPlugin('cpp-exe')){
println "Project $subProject.name has plugin cpp-exe"
}
}
}
would give you a start.

Resources