Custom Gradle Plugin Exec task with extension does not use input properly - gradle

I am following the Writing Custom Plugins section of the Gradle documentation, specifically the part about Getting input from the build. The following example provided by the documentation works exactly as expected:
apply plugin: GreetingPlugin
greeting.message = 'Hi from Gradle'
class GreetingPlugin implements Plugin<Project> {
void apply(Project project) {
// Add the 'greeting' extension object
project.extensions.create("greeting", GreetingPluginExtension)
// Add a task that uses the configuration
project.task('hello') << {
println project.greeting.message
}
}
}
class GreetingPluginExtension {
def String message = 'Hello from GreetingPlugin'
}
Output:
> gradle -q hello
Hi from Gradle
I'd like to have the custom plugin execute an external command (using the Exec task), but when changing the task to a type (including types other than Exec such as Copy), the input to the build stops working properly:
// previous and following sections omitted for brevity
project.task('hello', type: Exec) {
println project.greeting.message
}
Output:
> gradle -q hello
Hello from GreetingPlugin
Does anyone know what the issue could be?

It is not related to the type of the task, it's a typical << misunderstanding.
When you write
project.task('hello') << {
println project.greeting.message
}
and execute gradle hello, the following happens:
configuration phase
apply custom plugin
create task hello
set greeting.message = 'Hi from Gradle'
executon phase
run task with empty body
execute << closure { println project.greeting.message }
in this scenario output is Hi from Gradle
When you write
project.task('hello', type: Exec) {
println project.greeting.message
}
and execute gradle hello, the following happens
configuration phase
apply custom plugin
create exec task hello
execute task init closure println project.greeting.message
set greeting.message = 'Hi from Gradle' (too late, it was printed in step 3)
the rest of workflow does not matter.
So, small details matter. Here's the explanation of the same topic.
Solution:
void apply(Project project) {
project.afterEvaluate {
project.task('hello', type: Exec) {
println project.greeting.message
}
}
}

Related

Gradle task lines always getting executed

I have the following Gradle task:
task deploy(type: Exec) {
doFirst {
println 'Performing deployment'
}
final Map properties = project.getProperties()
if (!properties['tag']) {
throw new StopExecutionException("Need to pass a tag parameter")
}
commandLine "command", tag
}
If I run another task I get an error because properties['tag'] is undefined, I guess because Gradle is executing everything in the task except commandLine. Is there a better way to do this or a way to stop Gradle from executing parts of the task even when I'm running another task?
Using Gradle 6.6.1
I use this pattern:
// Groovy DSL
tasks.register("exec1", Exec) {
def tag = project.findProperty("tag") ?: ""
commandLine "echo", tag
doFirst {
if (!tag) {
throw new GradleException("Need to pass a tag parameter")
}
}
}
It adds the tag property if it exists.
If it does not exist, it adds an empty string but checks for this before it actually runs.
It would be great if the Exec task accepted providers as arguments so you could just give it providers.gradleProperty("tag"), but unfortunately it doesn't.

Can I create gradle task in plugin with dependsOn parameter?

I'm creating a gradle plugin as below
class MyPlugin implements Plugin<Project> {
void apply(Project project) {
project.extensions.create('testCoverageVerificationTask', TestCoverageVerificationPluginExtension)
project.tasks.create('testCoverageVerification', TestCoverageVerificationTask)
}
}
And to use it, I need to add these to my build.gradle
apply plugin: my.package.MyPlugin
testCoverageVerificationTask {
myreport = "testing report"
}
testCoverageVerification.dependsOn "myDependentTask"
However, I'm thinking it would be better to have the dependsOn as another parameter within the testCoverageVerificationTask so that it doesn't need to be defined separately. Is that feasible?
note: normal Task Definition could do this
task myTask(dependsOn: 'compile') {
doLast {
println 'I am not affected'
}
}
But I can't do
testCoverageVerificationTask(dependsOn: "myDependentTask") {
myreport = "testing report"
}
Use:
Task task = project.tasks.create('testCoverageVerification', TestCoverageVerificationTask)
task.dependsOn("compile")

Can gradle script property extensions be shared between different scripts

If there is a build.gradle file as follows:
...
apply from: 'Other.gradle'
task hello {
project.ext.hello = "hello"
}
And Other.gradle has:
task getHello {
println project.ext.hello
}
I get an error saying:
Cannot get property 'hello' on extra properties extension as it does not exist
Is there a way to share property extensions between the scripts?
Try setting ext.hello then have the tasks update it
== build.gradle
ext {
hello = null
}
apply from: 'Other.gradle'
task hello {
doLast {
hello = "hello"
}
}
== Other.gradle
task getHello {
doLast {
println hello
}
}
If you really want to be able to set info on a task, you can also use the ext on a task and scope it to a task. If you were implementing a larger plugin you could create an extension and set it on the task.

Execute gradle task on sub projects

I have a MultiModule gradle project that I am trying to configure.
Root
projA
projB
other
projC
projD
projE
...
What I want to be able to do is have a task in the root build.gradle which will execute the buildJar task in each of the projects in the other directory.
I know I can do
configure(subprojects.findAll {it.name != 'tropicalFish'}) {
task hello << { task -> println "$task.project.name"}
}
But this will also get projA and projB, I want to only run the task on c,d,e...
Please let me know the best way to achieve this.
Not entirely sure which of these you're after, but they should cover your bases.
1. Calling the tasks directly
You should just be able to call
gradle :other/projC:hello :other/projD:hello
I tested this with:
# Root/build.gradle
allprojects {
task hello << { task -> println "$task.project.name" }
}
and
# Root/settings.gradle
include 'projA'
include 'projB'
include 'other/projC'
include 'other/projD'
2. Only creating tasks in the sub projects
Or is it that you only want the task created on the other/* projects?
If the latter, then the following works:
# Root/build.gradle
allprojects {
if (project.name.startsWith("other/")) {
task hello << { task -> println "$task.project.name" }
}
}
and it can then be called with:
$ gradle hello
:other/projC:hello
other/projC
:other/projD:hello
other/projD
3. Creating a task that runs tasks in the subprojects only
This version matches my reading of your question meaning there's already a task on the subprojects (buildJar), and creating a task in root that will only call the subprojects other/*:buildJar
allprojects {
task buildJar << { task -> println "$task.project.name" }
if (project.name.startsWith("other/")) {
task runBuildJar(dependsOn: buildJar) {}
}
}
This creates a task "buildJar" on every project, and "runBuildJar" on the other/* projects only, so you can call:
$ gradle runBuildJar
:other/projC:buildJar
other/projC
:other/projC:runBuildJar
:other/projD:buildJar
other/projD
:other/projD:runBuildJar
Your question can be read many ways, hope this covers them all :)
All of the ways mentioned by Mark can be used but all of them have some cons. So I am adding one more option:
4. Switching the current project
gradle -p other hello
This switches the "current project" and then runs all tasks named hello under the current project.
Example 5. Defining common behavior of all projects and subprojects,
allprojects {
task hello {
doLast { task ->
println "I'm $task.project.name"
}
}
}
subprojects {
hello {
doLast {
println "- I depend on water"
}
}
}
From the Gradle documentation,
https://docs.gradle.org/current/userguide/multi_project_builds.html

Gradle task should not execute automatically

I'm defining a task in gradle:
task releaseCandidate(type: Exec) {
commandLine 'git', 'checkout', 'develop'
// Increment version code in Manifest
String manifest = new File('AndroidManifest.xml').getText('UTF-8')
Pattern pattern = Pattern.compile('android:versionCode="([0-9]+)"')
Matcher matcher = pattern.matcher(manifest)
matcher.find()
int newVersionCode = Integer.parseInt(matcher.group(1)) + 1
manifest = manifest.replaceAll(
"android:versionCode=\"([0-9]+)\"", "android:versionCode=\"$newVersionCode\""
)
new File('AndroidManifest.xml').write(manifest, 'UTF-8')
commandLine 'git', 'diff'
}
Which I want to execute only when I explicitly call it as gradle releaseCandidate. However, when I run any other task, such as gradle assembleDebug, it also runs task releaseCandidate. I don't want that behaviour to happen. There is no task depending on releaseCandidate or vice-versa.
My project is an Android app, so I am using android gradle plugin.
A common pitfall. Add an action to the task otherwise code will run at configuration phase. Sample task with action:
task sample << {
}
As I see You'd rather need to write a custom task than using Exec type. I suppose it's not valid to define commandLine twice.
EDIT
You can read this post to get the general idea how it all works.
You are mixing Task configuration and groovy code. Everything that is part of the main body of a task definition will be executed in the configuration phase. The task task1 << { code } is a shorthand for
task task1 {
doLast {
code
}
}
commandLine is part of the Exec Task but your other code is not and should be wrapped into a doLast this will execute the commandline first and then execute your additional code. If you need another exec commandLine then you'll need another task.
task releaseCandidate(type: Exec) {
commandLine 'git', 'checkout', 'develop'
doLast {
// Increment version code in Manifest
String manifest = new File('AndroidManifest.xml').getText('UTF-8')
Pattern pattern = Pattern.compile('android:versionCode="([0-9]+)"')
Matcher matcher = pattern.matcher(manifest)
matcher.find()
int newVersionCode = Integer.parseInt(matcher.group(1)) + 1
manifest = manifest.replaceAll(
"android:versionCode=\"([0-9]+)\"", "android:versionCode=\"$newVersionCode\""
)
new File('AndroidManifest.xml').write(manifest, 'UTF-8')
}
}
Just to complete #Opal answer for cases when Exec is really used (for example CommandLine reference) :
task task1 << {
exec {
List<String> arguments = new ArrayList<String>()
//..
commandLine arguments
}
}

Resources