Unable to overwrite a property in Gradle in Multi-Project build - gradle

I've setup a multi-module Gradle build, with several modules inside. What I'm trying to do is very simple - when I create a distribution, I'd always like to use a directory called "dist/lib", with the exception of one project, for which it should just be "dist".
The obvious solution - make a variable called "distLibDir", and overwrite it for the specific project in question, does not appear to be working.
Here's the code for the sub-module:
project.version = '1.1'
project.ext.distLibDir = 'dist'
dependencies {
....
}
And here's the code for the top-level project:
subprojects {
apply plugin: 'java'
apply plugin: 'eclipse'
if (! project.hasProperty('distLibDir')) {
project.ext.distLibDir = 'dist/lib'
}
task copyLib(type: Copy) {
into project.distLibDir
from configurations.runtime
}
task dist(type: Copy, dependsOn: [clean, jar, copyLib]) {
from 'build/libs'
into project.distLibDir
}
}
No matter what I try, the directory always comes out to be "dist/lib" and I can't overwrite it to be different for just that one module. Does anyone have insight into what is going wrong here?

By default, build scripts of parent projects get evaluated before build scripts of child projects. Hence the property doesn't exist at the time that you check for its existence.
There are several solutions. One is to set the property's value in the parent script's suprojects block, based on the name or path of the current project (e.g. if (project.path == ":foo") {...} else {...}). Another is to move the body of the subprojects block (or at least the part that makes use of the property) into a script plugin and apply that plugin from every build script (e.g. apply from: "$rootDir/gradle/foo.gradle"). This gives you a chance to set the property's value before applying the script plugin. Yet another solution (which I personally try to avoid) is to keep things as they are and call evaluationDependsOnChildren() before accessing the property from the parent build script.

Related

Execute Gradle task after subprojects are configured

I have a multi-project Gradle build where subprojects are assigned version numbers independent of the root project. I'd like to inject this version number into a few resource files in each subproject. Normally, I'd do this by configuring the processResources task for each subproject in the root build. However, the problem is that Gradle appears to be executing the processResources task before loading the subprojects' build files and is injecting "unspecified" as the version.
Currently, my project looks like this:
/settings.gradle
include 'childA' // ... and many others
/build.gradle
subprojects {
apply plugin: 'java'
apply plugin: 'com.example.exampleplugin'
}
subprojects {
// This has to be configured before processResources
customPlugin {
baseDir = "../common"
}
processResources {
// PROBLEM: version is "unspecified" here
inputs.property "version", project.version
// Inject the version:
from(sourceSets.main.resources.srcDirs) {
include 'res1.txt', 'res2.txt', 'res3.txt'
expand 'version':project.version
}
// ...
}
}
/childA/build.gradle
version = "0.5.424"
I looked into adding evaluationDependsOnChildren() at the beginning of root's build.gradle, but that causes an error because childA/build.gradle runs before customPlugin { ... }. I've tried using dependsOn, mustRunAfter, and other techniques, but none seem have the desired effect. (Perhaps I don't fully understand the lifecycle, but it seems like the root project is configured and executed before the subprojects. Shouldn't it configure root, then configure subprojects, and then execute?)
How can I get inject the version of each subproject into the appropriate resource files without a lot of copy/paste or boilerplate?
You could try using this method, with a hook:
gradle.projectsEvaluated({
// your code
})
I got this figured out for myself. I'm using a init.gradle file to apply something to the rootProject, but I need data from a subproject.
First option was to evaluate each subproject before I modified it:
rootProject {
project.subprojects { sub ->
sub.evaluate()
//Put your code here
But I wasn't sure what side effects forcing the sub project to evaluate would have so I did the following:
allprojects {
afterEvaluate { project ->
//Put your code here
Try doing it like this:
subprojects { project ->
// your code
}
Otherwise project will refer to your root project where no version has been specified.

Gradle `tasks.withType()` strange behavior for tasks added afterwards

I am using Gradle 2.14.1 and the https://github.com/unbroken-dome/gradle-testsets-plugin to add an integration test task. I would like to configures the location of the HTML reports.
The default tasks uses:
<project>/build/reports/tests
The testsets plugin configures:
<project>/build/intTest
for the intTest task, and I want:
<project>/build/reports/test
<project>/build/reports/intTest
Here is my configuration:
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'org.unbroken-dome.gradle-plugins:gradle-testsets-plugin:1.2.0'
}
}
apply plugin: 'java'
apply plugin: 'org.unbroken-dome.test-sets'
defaultTasks = ['clean', 'build']
tasks.withType(Test) {
reports.html.destination = new File(project.reportsDir, name)
println(it.name + '\t' + reports.html.destination)
}
task wrapper(type: Wrapper) {
description = 'Defines the common gradle distribution for this project.'
gradleVersion = '2.14.1'
}
testSets {
intTest
}
intTest.dependsOn test
check.dependsOn intTest
repositories {
jcenter()
}
dependencies {
testCompile 'junit:junit:4.12'
intTestCompile 'junit:junit:4.12'
}
println('===== final config =====')
println(test.name + '\t' + test.reports.html.destination)
println(intTest.name + '\t' + intTest.reports.html.destination)
(Please ignore the println statements for the time being.)
After a full build, the intTest task's reports are in the wrong place (the default), and the configuration for the standard test task is applied:
$ ls build/
classes dependency-cache intTest intTest-results libs reports test-results tmp
$ ls build/reports/
test
I added some output to see what is going on, and it seems strange (the project's root is 'blob'):
test /home/wujek/blob/build/reports/test
intTest /home/wujek/blob/build/reports/intTest
===== final config =====
test /home/wujek/blob/build/reports/test
intTest /home/wujek/blob/build/intTest
So, in the tasks.withType() block the location is reported to be correct, but in the end, it is not.
Please note that moving the tasks.withType() block after the testSets block works for this project, but my real setup is mode complex: I have mutliple modules, and the root build.gradle uses the subprojects block with the tasks.withType() block to configure the report locations for all modules, and then one of the submodules adds a new test set and its test task's HTML report has the wrong location. To fix this, I have to repeat the configuration in the submodules that add test sets.
What is going on here? Why does the tasks.withType() block say the config works, but in reality it doesn't?
This is due to the pecularities of ordering configuration in Gradle. Let's walk through your code as Gradle would process it to see what happens (skipping over lines that aren't relevant):
apply plugin: 'org.unbroken-dome.test-sets'
This executes the apply method of the test-sets plugin, which includes the creation of a class which listens for test sets to be added. It adds a whenObjectAdded action to the testSets container. You haven't added any test sets yet, so lets move back to your build.gradle.
tasks.withType(Test) {
reports.html.destination = new File(project.reportsDir, name)
println(it.name + '\t' + reports.html.destination)
}
You've now added an action that will apply to all existing Test tasks, and to new ones as they are created. Now where it all unwinds:
testSets {
intTest
}
This creates a testSet called intTest. The whenObjectAdded action in the test-sets plugin now fires:
The intTest sets Test task is created.
Your withType action fires, because there's now a new Test task. This sets the report to go where you want.
The whenObjectAdded action now continues, getting to this line which also sets the html report location, overriding what you just set.
When you change this to declare the testSet first it goes:
whenObjectAdded - Create the Test task
whenObjectAdded - Set the test task's HTML report location
withType is registered by you
withType fires setting the HTML report location to your desired destination
There aren't hard and fast rules to avoid this, since plugins can and do take wildly different approaches to how/when they register their configuration actions. Generally, I try to break my build.gradle down in this order:
Buildscript block (if needed)
apply plugins
set project level properties (group, version, sourceCompatibility, etc)
Configure extensions (sourceSets, testSets, configurations, dependencies)
Configure tasks (either direct or withType)
Usually this helps allow plugins that fire config to register default values before my configuration comes in to change things.
FYI, Gradle's new, and still incubating, model space is intended to help solve this configuration ordering problem. It won't be perfect, but allows the order to be more explicit.

How can I make Gradle extensions lazily evaluate properties that are set dynamically by tasks?

I'm pretty new to working with Gradle and I'm trying to develop a plugin that helps manage version numbering. This plugin defines a task that sets the project.version property of the project it's applied to.
What I'm trying to do is make it so that this property is set at the start of every Gradle build. Using Peter's answer to another Gradle question, I've managed to get my task to execute before any other by adding gradle.startParameter.taskNames = [":setProjectVersionNumber"] + gradle.startParameter.taskNames within my plugin's apply method.
However, other plugins (notably 'Maven-publish') rely on the version being specified during the configuration phase:
publishing {
publications {
somePublication(MavenPublication) {
version = project.version
}
}
}
What I'd like to know is if there's a way that I can make the evaluation of properties like version within these extensions as lazy as possible - such that they're not evaluated until a task that depends upon them is called, which in this case might be :publishToMavenLocal.
Below is an SSCCE that demonstrates what I'm hoping to achieve:
// This would be included within the plugin
class SetProjectVersionNumber extends DefaultTask {
#TaskAction
void start() {
// This will set project.version during execution phase
project.version = "1.2.3"
logger.info "Set project version number: $project.version"
}
}
task setProjectVersionNumber(type: SetProjectVersionNumber)
// Imagine this block being replaced by a maven 'publishing' block (or something similar)
ext {
version = project.version
// This will print 'unspecified', as it's evaluated during configuration phase
println "In extension, setting version=$project.version"
}
If you can provide a way to make ext.version equal 1.2.3 in the example above, I believe you've resolved my issue.
If this is asking too much, it may be possible for me to make my plugin generate the version string at configuration-time rather than execution-time. It would be nice to know if I could do it this way, though.
EDIT
In an experimental branch, I tried moving all the version string assignment logic to the configuration-phase (by making it all happen during plugin application rather than during task execution), but I don't believe this will work as the plugin extension has not yet been processed and trying to refer to properties defined in it fail.
EDIT 2
Wrapping the version string assignment logic in a project.afterEvaluate closure seems to have worked:
#Override
public void apply(Project project) {
logger = project.logger
project.extensions.create(EXTENSION_NAME, SemVerPluginExtension)
project.afterEvaluate {
setVersionProjectNumber(project)
addTasks(project)
}
}
In a mock project, I implement build.gradle as follows:
apply plugin: 'semver'
apply plugin: 'maven-publish'
group = 'temp'
buildscript {
repositories {
mavenLocal()
jcenter()
}
dependencies {
classpath 'com.github.tagc:semver-plugin:0.2.2'
}
}
semver {
versionFilePath = 'version.properties'
}
publishing {
publications {
testPublication(MavenPublication) {
version = project.version
assert version
println "Set publication version to $version"
}
}
}
For some reason, this seems to work. Although the version string assignment logic is wrapped in an 'afterEvaluate' closure and the test publication version assignment isn't, the former still occurs before the latter:
Compiling build file '/Users/davidfallah/Documents/semver/TestSemver2/build.gradle' using StatementExtractingScriptTransformer.
Compiling build file '/Users/davidfallah/Documents/semver/TestSemver2/build.gradle' using BuildScriptTransformer.
VERSION FILE PATH=version.properties
Current Git branch: develop
Set project version to 0.2.1-SNAPSHOT
Set publication version to 0.2.1-SNAPSHOT
All projects evaluated.
I'm leaving this question open and unresolved since I'd still like to know if it's possible to do it the way I originally intended. Additionally, I'd appreciate any explanation about why the publication version is assigned after the project version is set, and whether I can depend on that always being the case or whether that's just happening now by accident.
You can use lazy instantiation of GStrings to evaluate properties at run time:
project.tasks.create("example_task", Exec.class, {
commandLine 'echo', "${-> project.someproperty}"
})
Note that you have to use quotation marks and not apostrophes - "${...}" works, but '${...}' does not.

Make The Sonar Runner Gradle Task Depend On One Of My Tasks

I am trying out the new Sonar Runner task recently released in gradle 1.5. What I would like to do is be able to make the sonar runner task dependent on another task so that I can set the Sonar properties correctly for this project (i.e. sonar.sources, sonar.binaries, sonar.libraries, sonar.java.source, sonar.java.target).
Specifically I am using an osgi build tool called bnd which will provide these values when an ant init task is executed (note that whilst I include the default bnd build.xml file, my complete build is really being done using gradle).
I thought I would be able to customize the sonar runner task by doing this (this is a multi-module build):
subprojects {
sonarRunner.dependsOn init
}
Eventually adding something like this (from what I understand of the bnd ant variables):
subprojects {
sonarRunner {
sonarProperties {
property "sonar.java.source", ant.property["project.sourcepath"]
property "sonar.java.target", ant.property["project.output"]
property "sonar.sources", ant.property["project.allsourcepath"]
property "sonar.libraries", ant.property["project.buildpath"]
}
}
sonarRunner.dependsOn init
}
Unfortunately when I try to add the dependsOn I get the error:
* What went wrong:
A problem occurred evaluating root project 'myproject'.
> Could not find property 'init' on project ':com.company.myproject.mymodule'.
If I try to make sonarRunner depend on a gradle task I get the following error:
* What went wrong:
A problem occurred evaluating root project 'myproject'.
> Could not find method dependsOn() for arguments [task ':gradletask'] on org.gradle.api.sonar.runner.SonarRunnerExtension_Decorated#c4d7c0c.
Am I missing something obvious here? If someone could point me in the right direction it would be a big help.
Your problem with not being able to call dependsOn() on sonarRunner task comes from the fact that the plugin defines both both sonarRunner extension and a sonarRunner task. It looks like extensions take precedence over tasks when objects are resolved by name in a gradle build file, hence your stacktrace points out that you are trying to call dependsOn() on an instance of org.gradle.api.sonar.runner.SonarRunnerExtension_Decorated instead of caling it on a SonarRunner task instance.
I think that if you retrieved the task from the task container explicitly you should be ok:
tasks.sonarRunner.dependsOn init
The root project gradle file is evaluated before the child project gradle files, that means init does not exist on the location you try to address it.
A workaround if you want to declare dependencies in the root project is to use afterEvaluate as described in http://www.gradle.org/docs/current/userguide/build_lifecycle.html, try:
subprojects {
afterEvaluate{
sonarRunner.dependsOn init
}
}
Another solution would be to add the dependency in the sub projects, directly or by applying another root gradle file.
apply from: '../sonardependency.gradle'
If anyone is interested, this is one way of getting the bnd information to be set correctly in Sonar for each subproject (I am sure there are better ways):
subprojects {
afterEvaluate {
sonarRunner {
sonarProperties {
ant.taskdef(resource:"aQute/bnd/ant/taskdef.properties",
classpath: "../cnf/plugins/biz.aQute.bnd/biz.aQute.bnd-2.0.0.jar");
def projectDir = project.rootDir.toString() + "/" + project.name;
ant.bndprepare(basedir:projectDir,print:"false",top:null);
def binaries = ant.properties['project.buildpath'].split(':') as ArrayList;
binaries.remove(0);
def binariesString = binaries.join(',');
properties["sonar.java.source"] = ant.properties['javac.source'];
properties["sonar.java.target"] = ant.properties['javac.target'];
properties["sonar.binaries"] = ant.properties['project.output'].replace(':',',');
properties["sonar.sources"] = ant.properties['project.sourcepath'].replace(':',',');
properties["sonar.libraries"] = binariesString;
}
}
}
}

gradle use variables in parent task that are defined in child

I have a multiproject gradle build where I declare a task in the parent build that uses a variable that gets declared in the child projects (the value can change depending on the subproject). However, I get an error during the configuration phase that the variable doesn't exist. My setup looks like
build.gradle (top level)
subprojects {
myTask {
prop = valueDefinedInChild
}
}
And then
build.gradle (subproject)
valueDefinedInChild = 'someValue'
Is there a way to do this correctly?
There is a way to do this (project.evaluationDependsOnChildren()), but I recommend to only use it as a last resort. Instead, I'd configure the commonalities at the top level, and the differences at the subproject level:
build.gradle (top level):
subprojects {
task myTask { // add task to all subprojects
// common configuration goes here
}
}
build.gradle (subproject):
myTask {
prop = 'someValue'
}
Another way to avoid repeating yourself is to factor out common code into a separate script, and have subprojects include it with apply from:. This is a good choice when the logic only applies to selected subprojects, or in cases where it's desirable to avoid coupling between parent project and subprojects (e.g. when using Gradle's new configuration on demand feature).

Resources