Gradle Scripts Syntax - gradle

I am learning Gradle these days and trying to understand how the syntax in gradle maps to the Gradle docs provided.
Can someone help me understand how the jarTaskName in the below snippet is derived? I can't see any attribute/method with the name jarTaskName in sourceSets class.
https://docs.gradle.org/current/dsl/org.gradle.api.tasks.SourceSet.html
def integrationTest = sourceSets.create('integrationTest')
configurations[integrationTest.implementationConfigurationName].extendsFrom(configurations.testImplementation)
configurations[integrationTest.runtimeOnlyConfigurationName].extendsFrom(configurations.testRuntimeOnly)
def integrationTestJarTask = tasks.register(sourceSets.integrationTest.**jarTaskName**, Jar) {
archiveClassifier = 'integration-tests'
from integrationTest.output
}
Any documentation or quick pointers to understand above mapping will be helpful?

jarTaskName is documented in the API documentation to SourceSet. The documentation you linked is the DSL documentation which covers Gradle's build language.

Related

Custom Configuration dependency declaration

I am trying to convert build.gradle to kotlin dsl. Using gradle 7.4.1.What the right way to declare custom configuration. For custom configuration like
configurations { grafana }
sourceSets { grafana }
and within dependencies block
grafanaImplementation "org.slf4j:slf4j-simple:1.7.36"
grafanaImplementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
grafanaRuntimeOnly "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
While I am in kotlin-dsl I am doing
val grafana by configurations.creating
val grafanaSourceSet = sourceSets.create("grafana")
and within dependency block
grafana("org.slf4j:slf4j-simple:1.7.36")
grafana("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
When I try to put grafanaImplementation/ grafanaRuntimeOnly within kotlin dsl, it fails.
What is the equivalent of grafanaImplementation/ grafanaRuntimeOnly within kotlin dsl
Quick fix
When you do
val grafanaSourceSet = sourceSets.create("grafana")
behind the scenes Gradle will create the required configurations, grafanaImplementation, grafanaRuntimeOnly, etc, so you can use them without error like this:
val grafanaSourceSet = sourceSets.create("grafana")
dependencies {
"grafanaImplementation"("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
"grafanaRuntimeOnly"("org.slf4j:slf4j-simple:1.7.36")
}
This approach is more like how Groovy works - it basically disables type-checking and the strings will be evaluated during Gradle execution.
Generated DSL accessors
However, string-typing is not why we like Kotlin! We want type-safety and auto completion hints. That's exactly what we see with the implementation() and runtimeOnly(). So how do we get them for grafanaImplementation() and grafanaRuntimeOnly()?
Basically, Gradle will scan the registered config and when it sees that a plugin creates an implementation configuration, it generates Kotlin DSL accessors. However, it can't generate accessors for the build.gradle.kts that contains the definition for the accessors... that's too late. So we need to define the config earlier. We can do that with a buildSrc plugin.
buildSrc Grafana convention plugin
Set up a buildSrc project (this is covered more in the Gradle docs or other StackOverflow answers)
Create a pre-compiled script plugin for Grafana config
// $projectRoot/buildSrc/src/main/kotlin/grafana.convention.gradle.kts
plugins {
// using 'sourceSets' requires the Java plugin, so we must apply it
java
}
val grafanaSourceSet = sourceSets.create("grafana")
Note that this convention plugin is quite opinionated as it applies the Java plugin. In more complex setups you might want to instead react to the Java plugin, rather than always applying it.
Now apply the convention plugin, and Gradle will generate the Kotlin DSL accessors!
// $projectRoot/build.gradle.kts
plugins {
id("grafana.convention")
}
dependencies {
// no string-typing needed!
grafanaImplementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
grafanaRuntimeOnly("org.slf4j:slf4j-simple:1.7.36")
}

Aggregate Report for Gradle Multi-Project build

I need to create an aggregate code coverage report for my multiple-module Gradle project using the JaCoCo plugin for Gradle, similar to the one generated by the jacoco:aggregate-report of Maven's jaoco-maven-plugin.
I have been googling for a solution the last couple of days but nothing has worked so far. Most of the proposed solutions involve defining a task of type JacocoReport in the root project, that aggregates the execution data and produces the html code coverage report. However everything I tried so far either fails with an error or does not generate any report.
For example, this code snippet that I have tried:
def publishedProjects = subprojects.findAll()
task jacocoRootReport(type: JacocoReport) {
dependsOn(publishedProjects.test)
additionalSourceDirs = files(publishedProjects.sourceSets.main.allSource.srcDirs)
sourceDirectories = files(publishedProjects.sourceSets.main.allSource.srcDirs)
classDirectories = files(publishedProjects.sourceSets.main.output)
executionData = files(publishedProjects.jacocoTestReport.executionData)
doFirst {
executionData = files(executionData.findAll { it.exists() })
}
reports {
html.enabled = true
xml.enabled = false
}
}
gives the error groovy.lang.GroovyRuntimeException: Cannot set the value of read-only property 'additionalSourceDirs' for task ':jacocoRootReport' of type org.gradle.testing.jacoco.tasks.JacocoReport
The version of Gradle that I am using is 7.3. Unfortunately I'm very new to Gradle and I still cannot fiddle with the code snippets that I've found to make them work for my case.
Any help will be much appreciated. Thank you very much in advance.
There is an official example in the Gradle docs for this use case:
https://docs.gradle.org/7.3.3/samples/sample_jvm_multi_project_with_code_coverage.html
It is a somewhat complex and requires an additional project that will serve as the aggregate. The way you have above is generally discouraged by Gradle for reasons outside of this question/answer.
Edit 2022-09-11
As of Gradle 7.4, Gradle now offers a plugin to aggregate reports for multi-module projects.
See the documentation for the JaCoCo Report Aggregation Plugin for more details.

Where is documentation for Gradle's `test` block from Java plugin located?

As can be seen from the top of documentation of class org.gradle.api.tasks.testing.Test, test tasks can be configured using the following piece of code:
test {
// configuration here. For example:
useJUnitPlatform()
}
From usage of method useJUnitPlatform we can assume that method test is called with a Closure which has an instance of aforementioned class Test as delegate.
In Gradle, there are other similar methods which take a Closure. For example, afterEvaluate. Documentation of method afterEvaluate is readily available in documentation of class Project. This is also mentioned in the user guide:
This example uses method Project.afterEvaluate() to add a closure which is executed after the project is evaluated.
Where is the documentation of method test? I could not find it. Or maybe this isn't a method in a class, but inserted via reflection into class Project at runtime or some similar DSL magic?
test in this context is not a method per se, but rather a task named test. To figure out what exactly is going on here requires diving into the internals of Gradle itself which is not part of any public documentation because well, it's not part of the public API.
The only way to figure exactly out what is going on is to debug Gradle during its execution. The easiest way to do that is to generate a plugin project via gradle init. Write a simple Gradle build file such as (build.gradle; I am assuming you are using the Groovy DSL):
plugins {
id("java")
}
test {
useJUnitPlatform()
}
Then write a basic functional test and start debugging. I was curious myself what is going and did just that.
In the following screenshot, you can see the stack trace in the bottom left corner. As you can see, there is a lot of methods called.
There is a mixture of Groovy specific methods and Gradle specific methods. Digging further in, you will come to:
You can see here bottom right that the list of objects is:
Project (root project)
Extra properties
Extensions
Tasks
This aligns with what I mentioned earlier: Gradle will go out of its way to match to what is being asked for. This is also explained in the "A Groovy Build Script Primer" in official documentation (starting from "If the name is unqualified [...]").
Eventually, you will land in some of the public API methods:
getByName is part of NamedDomainObjectContainer which is documented. However, what actually implements that interface is not as you can see from the Javadoc here. The implementation, from debugging, is DefaultTaskContainer.
The rest I will leave to you as an exercise. Hopefully this gives you an idea as to what is going on behind the scenes.
Indeed, test { ... } in this case is not calling a method with name test. This block is a feature of the Gradle API called "Groovy dynamic task configuration block". Per Gradle documentation version 6.1:
// Configure task using Groovy dynamic task configuration block
myCopy {
from 'resources'
into 'target'
}
myCopy.include('**/*.txt', '**/*.xml', '**/*.properties')
This works for any task. Task access is just a shortcut for the tasks.named() (Kotlin) or tasks.getByName() (Groovy) method. It is important to note that blocks used here are for configuring the task and are not evaluated when the task executes.
As such, per this shortcut convention, test { ... } block is used for configuring a task registered in the project – task with name test in this case.
Although nowadays I'd recommend using Gradle's Configuration Avoidance API to configure a task lazily instead of eagerly:
project.tasks.named('test', Test).configure {
it.useJunitPlatform()
}
See getByName replacement in the table "Existing vs New API overview".

Gradle Kotlin DSL: get sourceSet of another project

Currently, we're trying to migrate our existing build.gradle scripts to the new Kotlin DSL. Right now, we are struggling with the jar task configuration.
Our project is a simple multi-project. Let's say we've Core and Plugin and Plugin uses classes from Core. Now, when building Plugin, the target jar should include any used classes from Core.
This is how it looked like before:
jar {
from sourceSets.main.output
from project(':Core').sourceSets.main.output
}
And this is the current solution we've with Kotlin DSL:
val jar: Jar by tasks
jar.apply {
from(java.sourceSets["main"].allSource)
from(project(":Core").the<SourceSetContainer>()["main"].allSource)
}
However, the above example just gives me an Extension of type 'SourceSetContainer' does not exist. Currently registered extension types: [ExtraPropertiesExtension] error. I've also tried other code snippets I've found, but none of them have been working so far.
I have also tried this (like suggested in the first answer):
val jar: Jar by tasks
jar.apply {
from(java.sourceSets["main"].allSource)
from(project(":Core").sourceSets.getByName("main").allSource)
}
But then the IDE (and also the jar task) argues that sourceSets is not available: Unresolved reference. None of the following candidates is applicable because of receiver type mismatch: public val KotlinJvmProjectExtension.sourceSets: NamedDomainObjectContainer<DefaultKotlinSourceSet> defined in org.gradle.kotlin.dsl.
I hope that someone can help us, because it is very frustrating to spend hours in configuration instead of writing any usefull code.
Thank you very much in advance.
You can access the SourceSetContainer by
project(":Core").extensions.getByType(SourceSetContainer::class)
it seems <T : Any> Project.the(extensionType: KClass<T>): T looks in the convention of the project, while val Project.sourceSets: SourceSetContainer get() looks in the extensions ExtensionContaier instead. This is somewhat odd, as the documentation for the says "Returns the plugin convention or extension of the specified type."
Note that you may need to do your sourceSet manipulation in gradle.projectsEvaluated, because otherwise the sourceSet in question may not be configured yet if the corresponding project is not yet evaluated.
If you get access to the project, then everything should looks like your actual groovy gradle script:
project(":Core").sourceSets.getByName("main").allSource
So regarding your actual code:
val jar: Jar by tasks
jar.apply {
from(java.sourceSets["main"].allSource)
from(project(":Core").sourceSets.getByName("main").allSource)
}

How to export asciidoc to ODT or ODF using gradle?

I was trying to export asciidoc document to ODT. I found unofficial backend implementation that does the work: https://github.com/dagwieers/asciidoc-odf. My question is: how can install and use this backend using gradle? Is it even possible? I have no idea how to start.
Here is my build.gradle that does it for PDF
buildscript {
repositories {
mavenCentral()
jcenter()
}
dependencies {
classpath 'org.asciidoctor:asciidoctor-gradle-plugin:1.5.3'
classpath 'org.asciidoctor:asciidoctorj-pdf:1.5.0-alpha.11'
classpath 'org.asciidoctor:asciidoctorj-diagram:1.5.0'
}
}
plugins {
id 'org.asciidoctor.convert' version '1.5.3'
}
apply plugin: 'java'
apply plugin: 'org.asciidoctor.convert'
asciidoctorj {
version = '1.5.4'
}
asciidoctor {
backends 'pdf'
requires 'asciidoctor-diagram'
sourceDir = file('dok/')
outputDir = file('build/')
attributes \
'buildDir': buildDir,
'imagesdir': "$buildDir/images"
}
Maybe not something you want to hear. I think it is no possible to generate odf files with the asciidoctor-gradle-plugin. If you look at https://github.com/asciidoctor/asciidoctor-gradle-examples which demonstrates examples for using gradle-plugin, I do not see an example for odf. :-(
It would be cool, if you could make a feature request at https://github.com/asciidoctor/asciidoctor-gradle-plugin/issues and hopefully, someone will start.
There's a thing to clarify first: the converter you mention (https://github.com/dagwieers/asciidoc-odf) is for the original Python implementation of the AsciiDoc language. The gradle plugin uses Asciidoctor, which is an alternative implementation done in Ruby, and if you ask me, the current reference standard.
Asciidoctor does not support ODF directly, but does support plugable converters and there's already an attempt (https://github.com/kubamarchwicki/asciidoctor-fodt), however, not stable it seems.
In those cases where no direct converter is available you can try some intermediate step like converting to doocbook and then from doocbook to the final format. Doing some googling I found a plugin from spring and apparently support to ODF from doocbook, but you'll need to test it and some features may be limited.
PS: I wanted to provide the links to the references but the ***** rep system prevents me from doing so.

Resources