passing a closure in a plugin extension in gradle - gradle

I'd like to pass a closure as configuration for a plugin. Here is a minimal version of it:
package org.samuel.gradle.plugins
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
class TestPlugin implements Plugin<Project> {
void apply(Project project) {
project.extensions.create("testConfig", TestConfig)
Task test = project.task("testTask") {
doFirst {
println "The message is already " + project.extensions.testConfig.message
println "Trying to run closure " + project.extensions.testConfig.closure
project.extensions.testConfig.closure()
println "did it run?"
}
}
}
}
class TestConfig {
String message = "Testing ..."
Closure closure = {
println("running closure")
}
}
This doesn't work, the closure is never evaluated (nor at configuration nor when I intend it to:
$ ./gradlew test
:buildSrc:compileJava UP-TO-DATE
:buildSrc:compileGroovy
:buildSrc:processResources UP-TO-DATE
:buildSrc:classes
:buildSrc:jar
:buildSrc:assemble
:buildSrc:compileTestJava UP-TO-DATE
:buildSrc:compileTestGroovy UP-TO-DATE
:buildSrc:processTestResources UP-TO-DATE
:buildSrc:testClasses UP-TO-DATE
:buildSrc:test UP-TO-DATE
:buildSrc:check UP-TO-DATE
:buildSrc:build
:testTask
The message is already Testing ...
Trying to run closure org.samuel.gradle.plugins.TestConfig$_closure1#5600ea3b
did it run?
BUILD SUCCESSFUL
Total time: 1.569 secs
I think I am missing something about how gradle evaluates the contents of the extensions. Is it possible to somehow pass something via an extension and evaluate it in a task in the plugin?

The solution is surprisingly unimpressive. Change the line for calling the closure to:
project.extensions.testConfig.closure.call()
Notice the use of .call() vs just calling ()
Also noticed that this calls the closure:
println "Trying to run closure ${project.extensions.testConfig.closure}"
Why?

Related

Using both lombok and protobuf results in gradle tasks without dependencies resulting in builds failing sometimes

I use lombok and protobuf simultaneously in gradle. This generates the tasks generateEffectiveLombokConfig and generateProto which are independent. Yet the lombok task should depend on the protobuf task, otherwise the code generated by lombok is referring to Java code not yet generated by protoc.
syntax = "proto3";
package my.example.v1;
message Task {
string id = 1;
repeated string names_to_print = 2;
}
package org.example;
import lombok.experimental.UtilityClass;
import my.example.v1.*;
#UtilityClass
public class Worker {
public void work(TaskOuterClass.Task task) {
// do something
}
}
plugins {
id 'java'
id 'io.freefair.lombok' version '6.4.3'
id 'com.google.protobuf' version '0.8.18'
}
group 'org.example'
repositories {
mavenCentral()
}
dependencies {
implementation "com.google.protobuf:protobuf-java:3.20.1"
implementation "com.google.protobuf:protobuf-java-util:3.20.1"
}
I tried to add the output of protobuf as sourceSet to make sure the protobuf task is performed first, yet I get a warning:
sourceSets {
main {
java {
srcDir "${projectDir}/build/generated/source/proto/main/java"
}
}
}
warning:
Execution optimizations have been disabled for task ':generateProto' to ensure correctness due to the following reasons:
- Gradle detected a problem with the following location: '/build/generated/source/proto/main'. Reason: Task ':generateEffectiveLombokConfig' uses this output of task ':generateProto' 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.4.2/userguide/validation_problems.html#implicit_dependency for more details about this problem.
How can I make sure that all protobuf tasks are done before the lombok tasks?
you put task dependency as below
afterEvaluate {
generateEffectiveLombokConfig.mustRunAfter generateProto
}
Executing 'build -m'...
:extractIncludeProto SKIPPED
:extractProto SKIPPED
:generateProto SKIPPED
:generateEffectiveLombokConfig SKIPPED
:compileJava SKIPPED
:processResources SKIPPED
:classes SKIPPED
:jar SKIPPED
:assemble SKIPPED
:generateTestEffectiveLombokConfig SKIPPED
:extractIncludeTestProto SKIPPED
:extractTestProto SKIPPED
:generateTestProto SKIPPED
:compileTestJava SKIPPED
:processTestResources SKIPPED
:testClasses SKIPPED
:test SKIPPED
:check SKIPPED
:build SKIPPED
BUILD SUCCESSFUL in 299ms

How can I get Gradle to build dependencies before running my task?

Background: I am trying to hook the compiler for my own domain-specific language into Gradle. The DSL is compiled to Java source code, so I have built a task that runs before the Java compiler. The compiler cannot currently handle multiple projects with dependencies, so I'm trying to add that.
My DSL has packages like Java that get mapped to identical Java packages. The same should be true for projects. In that case, for each project, the DSL sources get compiled to Java source code, as well as meta-data (a JSON file per compiled class, containing information from the DSL's type system that cannot be mapped to Java types). When project A depends on B, the DSL compilation process for A needs the meta-data files from B. That meta-data should be packaged as resources into the JAR file together with the generated and compiled Java code, as well as possibly hand-written and compiled Java code.
FoobarPlugin.groovy:
class FoobarPlugin implements Plugin<Project> {
#Override
void apply(Project project) {
// create the compileFoobar task
CompileFoobarTask task = project.getTasks().create('compileFoobar', CompileFoobarTask.class);
task.group = 'build';
task.setDescription('Compiles Foobar to Java code.');
task.sourceDirectory = new File(project.projectDir, "src/main/foobar");
task.outputDirectory = new File(project.getBuildDir(), "foobar-java");
// compileFoobar must run before compiling Java code
project.tasks.compileJava.dependsOn(task);
// add the task's output folders as Java source folders
project.sourceSets.main.java.srcDirs += task.outputDirectory;
project.sourceSets.main.resources.srcDirs += task.outputDirectory;
project.sourceSets.test.java.srcDirs += task.outputDirectory;
project.sourceSets.test.resources.srcDirs += task.outputDirectory;
// Turn project dependencies into task dependencies. We have to delay this until the end of the configuration
// phase because project dependencies are not fully known until then.
project.gradle.addBuildListener(new BuildAdapter() {
#Override
void projectsEvaluated(Gradle gradle) {
project.configurations.compile.each {
task.dependencyOutputs += it
}
}
});
}
}
CompileFoobarTask.groovy:
class CompileFoobarTask extends DefaultTask {
#InputDirectory
File sourceDirectory;
#InputFiles
List<File> dependencyOutputs = new ArrayList<>();
#OutputDirectory
File outputDirectory;
#TaskAction
void run() {
FileUtils.write(new File(outputDirectory, "timestamp"), "" + System.currentTimeMillis(), StandardCharsets.UTF_8);
}
}
build.gradle from project A:
apply plugin: 'java'
apply plugin: foobar.gradle.FoobarPlugin
repositories {
mavenCentral()
}
dependencies {
compile project(':b')
}
build.gradle from project B:
apply plugin: 'java'
apply plugin: foobar.gradle.FoobarPlugin
repositories {
mavenCentral()
}
dependencies {
compile 'org.apache.commons:commons-lang3:3.0'
}
Test runs and output:
martin#xyz:~/git-repos/gradle-test$ ./gradlew clean a:compileFoobar
adding dependency /home/martin/git-repos/gradle-test/b/build/libs/b.jar to task task ':a:compileFoobar'
> Task :a:compileFoobar
running task ':a:compileFoobar'
BUILD SUCCESSFUL in 1s
4 actionable tasks: 2 executed, 2 up-to-date
martin#xyz:~/git-repos/gradle-test$ ./gradlew clean b:compileFoobar
adding dependency /home/martin/git-repos/gradle-test/b/build/libs/b.jar to task task ':a:compileFoobar'
> Task :b:compileFoobar
running task ':b:compileFoobar'
BUILD SUCCESSFUL in 469ms
4 actionable tasks: 2 executed, 2 up-to-date
martin#xyz:~/git-repos/gradle-test$ ./gradlew clean a:compileJava
adding dependency /home/martin/git-repos/gradle-test/b/build/libs/b.jar to task task ':a:compileFoobar'
> Task :a:compileFoobar
running task ':a:compileFoobar'
> Task :b:compileFoobar
running task ':b:compileFoobar'
BUILD SUCCESSFUL in 487ms
7 actionable tasks: 5 executed, 2 up-to-date
martin#xyz:~/git-repos/gradle-test$ ./gradlew clean b:compileJava
adding dependency /home/martin/git-repos/gradle-test/b/build/libs/b.jar to task task ':a:compileFoobar'
> Task :b:compileFoobar
running task ':b:compileFoobar'
BUILD SUCCESSFUL in 471ms
4 actionable tasks: 3 executed, 1 up-to-date
As you can see, even though I add b.jar as a dependency to a:compileFoobar, Gradle won't build that JAR before running a:compileFoobar. The Java plugin seems to do something different because running a:compileJava WILL build b.jar first. What do I have to do to achieve the same for my task?
What you need to do is to explicitly create a Task dependency between consumer project's compileFoobar task and the producer project's jar task (in your example where project a depends on project b, you need to create task dependency a:compileFoobar -> b.jar)
You can achieve this in your custom plugin, by checking if the current project has dependencies of type ProjectDependency: if so you create the task dependency accordingly.
Code sample (in your plugin apply() method):
// Turn project dependencies into task dependencies. We have to delay this until the end of the configuration
// phase because project dependencies are not fully known until then.
project.gradle.addBuildListener(new BuildAdapter() {
#Override
void projectsEvaluated(Gradle gradle) {
project.configurations.each { config ->
config.dependencies.each { dep ->
if (dep instanceof ProjectDependency) {
def producerProject = ((ProjectDependency) dep).dependencyProject
def producerJarTask = producerProject.tasks.jar
println " **** Project $project.name depends on $producerProject.name"
println " => create dependency between $task to $producerJarTask"
task.dependsOn(producerJarTask)
}
}
}
}
})
Build execution:
$ ./gradlew clean a:compileFoobar
**** Project a depends on b
=> create dependency between task ':a:compileFoobar' to task ':b:jar'
> Task :a:clean
> Task :b:clean
> Task :b:compileFoobar
> Task :b:compileJava NO-SOURCE
> Task :b:processResources NO-SOURCE
> Task :b:classes UP-TO-DATE
> Task :b:jar
> Task :a:compileFoobar

Run Task in Gradle Plugin after check

I've written a Gradle Plugin in Groovy under buildSrc as:
package test
import org.gradle.api.Plugin
import org.gradle.api.Project
class SamplePlugin implements Plugin<Project> {
#Override
void apply(Project project) {
println "This line prints" //Just for Configuration. This prints
def sample = project.tasks.create("sample") {
doLast {
println "This line does not print"
}
}
project.configure(project) {
sample.mustRunAfter('check')
}
}
}
Here, I'm trying to run the sample task at the end of my build, so I have it run after check
I now try to call it in my projects build.gradle file that looks like:
buildscript {
repositories {
mavenLocal()
mavenCentral()
}
}
apply plugin: 'java'
apply plugin:'application'
apply plugin: test.SamplePlugin
repositories {
mavenLocal()
mavenCentral()
}
mainClassName = "test.Widget"
Unfortunately, I don't see that it runs i.e. the code in the doLast does not appear in the output, but the configuration code does:
:buildSrc:compileJava NO-SOURCE
:buildSrc:compileGroovy
:buildSrc:processResources UP-TO-DATE
:buildSrc:classes
:buildSrc:jar
:buildSrc:assemble
:buildSrc:compileTestJava NO-SOURCE
:buildSrc:compileTestGroovy NO-SOURCE
:buildSrc:processTestResources NO-SOURCE
:buildSrc:testClasses UP-TO-DATE
:buildSrc:test NO-SOURCE
:buildSrc:check UP-TO-DATE
:buildSrc:build
This line prints
:compileJava UP-TO-DATE
:processResources NO-SOURCE
:classes UP-TO-DATE
:jar UP-TO-DATE
:startScripts UP-TO-DATE
:distTar UP-TO-DATE
:distZip UP-TO-DATE
:assemble UP-TO-DATE
:compileTestJava NO-SOURCE
:processTestResources NO-SOURCE
:testClasses UP-TO-DATE
:test NO-SOURCE
:check UP-TO-DATE
:build UP-TO-DATE
BUILD SUCCESSFUL in 1s
5 actionable tasks: 5 up-to-date
I'd be grateful for any help or pointers
Edit: As M.Ricciuti commented below order matters, so I have moved the test.SamplePlugin after the plugin java. Otherwise, please follow lu.koerfers solution of using the pluginManager.
In your plugin you are creating a new task 'sample' and set a constraint "sample must run after check": but this does not include the sample task in the task graph . It just says: "if sample and check tasks are both executed , then check task must be executed first". So if you just execute 'gradle build', this will not trigger execution of task "sample".
Try to execute directly "gradle sample" : you will see it will trigger its execution, and make the execution of "check" task first in respect of the contraint you have defined in plugin.
If you want to make "sample" task execute each time you execute "build" task, then just set a "dependsOn" constraint between "build" and "sample" tasks, in your plugin:
class SamplePlugin implements Plugin<Project> {
#Override
void apply(Project project) {
println "This line prints" //Just for Configuration. This prints
def sample = project.tasks.create("sample") {
doLast {
println "This line does not print"
}
}
project.configure(project) {
sample.mustRunAfter('check')
project.getTasks().findByName('build').dependsOn(sample) // <== set this contraint
}
}
}
EDIT : to avoid having to rely on plugin apply order, the task dependency declaration could be wrapped in a "afterEvaluate" block:
void apply(Project project) {
// task 'sample' def ...
// ...
project.configure(project) {
project.afterEvaluate {
sample.mustRunAfter('check')
project.getTasks().findByName('build').dependsOn(sample)
}
}
The methods mustRunAfter and shouldRunAfter only define execution order, not causality. That means that they won't cause a task to be executed. But if both tasks are executed, the specified order will be taken into account.
To specify a task dependency, use dependsOn or finalizedBy:
project.pluginManager.withPlugin('java') {
project.tasks.getByName('check').finalizedBy('sample');
}
This would cause sample to run everytime check runs and it ensures that it runs after check.

Gradle: Executing task in subproject programmatically

My WAR file should contain Java source files from components.
In my root project build.gradle I am executing tasks in subprojects programmatically:
apply plugin: 'war'
jar.enabled = false
war {
// - Copy Java source files to the folder corresponding to the component;
into("/") { from { collectFilesFromCopyTask('copySourceFiles') } }
}
// Collects files from destinationDirs of copy tasks with matching taskName
def collectFilesFromCopyTask(taskName) {
FileCollection collectedFiles = files{}
// for each task in subprojects
subprojects.each {project ->
project.tasks.withType(Copy).matching { task -> task.name.equals( taskName ) }.each { copyFilesTask ->
println 'copyFilesTask.destinationDir=' + copyFilesTask.destinationDir
// execute task
copyFilesTask.execute()
// add destinationDir of the task to the collected files
collectedFiles += files(copyFilesTask.destinationDir)
}
}
return collectedFiles
}
In subproject I have task:
task copySourceFiles(type: Copy) {
destinationDir = file(project.buildDir.name + '/sourceFiles')
into('componentName') {
from(project.projectDir)
exclude('build')
exclude('bin')
exclude('src/main/webapp')
exclude('.gradle')
}
}
Console output:
[sts] -----------------------------------------------------
[sts] Starting Gradle build for the following tasks:
[sts] clean
[sts] build
[sts] -----------------------------------------------------
copyFilesTask.destinationDir=<...>application1\build\sourceFiles
:clean
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:war
copyFilesTask.destinationDir=<...>application1\build\sourceFiles
copyFilesTask.destinationDir=<...>application1\build\sourceFiles
copyFilesTask.destinationDir=<...>application1\build\sourceFiles
:assemble
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:test UP-TO-DATE
:check UP-TO-DATE
:build
, which means that collectFilesFromCopyTask() is executed 4 times.
It should be executed only once, from WAR task.
Never ever use the .execute() method of a task in Gradle.
Except of when ...... no, never ever do that.
It is not a supported thing to do and does probably not work as expected.
Always use task dependencies or task ordering dependencies to make sure dependent tasks are run or tasks are run in a specific order if they both run but otherwise do not depend on each other directly.
Make your war task depend on your copy tasks and make your war task use the outputs of those tasks (not a manual files(...) call).
EDIT:
war {
into("/") { from { subprojects.tasks*.findByName('copySourceFiles').findAll { it instanceof Copy } } }
}

Gradle dependency does not work as my expectation

I have two sub projects subproject1 and subproject2. I'd like to add some classes from subproject2 to subproject1 and get the subproject1.jar. Below is my gradle file:
task copyClasses (dependsOn: [ ':subproject1:clean', ':subproject1:classes']) {
println "copyClasses "
doLast {
Task study = tasks.getByPath(':subproject1:jar')
study.doFirst {
copy {
println "copy ... "
println sourceSets.main.output.classesDir
println project(':subproject1').sourceSets.main.output.classesDir
from sourceSets.main.output.classesDir
into project(':subproject1').sourceSets.main.output.classesDir
}
}
}
}
task jarUpdated (dependsOn: [ clean, classes, copyClasses, ':subproject1:jar']) {
doLast {
println "jarUpdated"
}
}
But I got the build sequence as below:
$ gradle jarUpdated
copyClasses
:subproject1:compileJava
:subproject1:processResources UP-TO-DATE
:subproject1:classes
:subproject1:jar
:subproject2:compileJava
:subproject2:processResources UP-TO-DATE
:subproject2:classes
:subproject2:clean
:subproject1:clean
:subproject2:copyClasses
Calling Task.doFirst(Closure) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':subproject1:jar'.
:subproject2:jarUpdated
jarUpdated
BUILD SUCCESSFUL
My expectation is:
$ gradle jarUpdated
:subproject2:clean
:subproject2:compileJava
:subproject2:processResources UP-TO-DATE
:subproject2:classes
:subproject1:clean
:subproject1:compileJava
:subproject1:processResources UP-TO-DATE
:subproject2:copyClasses
copyClasses
copy ...
:subproject1:jar
:subproject2:jarUpdated
jarUpdated
BUILD SUCCESSFUL
Would you please suggest or point out what I missed? Thanks a lot!
The "easiest" way to do exactly what you asked for is probably something like this in your subproject1 build file.
jar {
from tasks.getByPath(':subproject2:compileJava')
}
However this is a very simplistic approach with a LOT of caveats, for example
Subproject 1 can not compile against subproject 2 classes
Any dependencies of Subproject 2 will not be included
etc
I would actually advise declaring subproject2 as a dependency of subproject1 and using one of the plugins that Peter suggested.

Resources