gradle use variables in parent task that are defined in child - gradle

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).

Related

How to copy files between sub-projects in Gradle

How can I make a sub-project copy a file that is produced by a sibling sub-project? All this with proper dependency management, and without assuming that any language-specific plugins (like the JavaPlugin) are used.
I have looked at the updated Gradle 6 draft Sharing artifacts between projects but it does not really answer that question.
My multi-project structure is something like:
top/
build.gradle
settings.gradle
producer/
build.gradle
myFile_template.txt
consumer/
build.gradle
I want a Copy-task in producer/build.gradle to copy+transform myFile_template.txt into $buildDir/target/myFile.txt and another Copy-task in consumer/build.gradle should further copy+transform that myFile.txt to a finalFile.txt.
Presumably a proper solution would be able to use task outputs.files or some such so that consumer/build.gradle does not need to explicitly mention the location of $buildDir/target/myFile.txt.
(I'm completely new to Gradle).
Gradle gives you lots of freedom but I prefer that projects only "share" with each other by Configurations and/or Artifacts. I feel that one project should never concern itself with another project's tasks and feel that the tasks are private to each project.
With this principle in mind you could do something like
project(':producer') {
configurations {
transformed
}
task transformTemplate(type: Copy) {
from 'src/main/template'
into "$buildDir/transformed"
filter(...) // transformation goes here
}
dependencies {
// file collection derived from a task.
// Any task which uses this as a task input will depend on the transformTemplate task
transformed files(transformTemplate)
}
}
project(':consumer') {
configurations {
producerTransformed
}
dependencies {
producerTransformed project(path: ':producer', configuration: 'transformed')
}
task transformProducer(type:Copy) {
from configurations.producerTransformed // this will create a task dependency
into ...
filter ...
}
}

A code generator task in a multi-project gradle build

I have studied thousand similar questions on SO and I am still lost. I have a simple multiproject build:
rootProject.name = 'mwe'
include ":Generator"
include ":projectB"
include ":projectC"
with a top level build.gradle as follows (settings.gradle):
plugins { id "java" }
allprojects { repositories { jcenter() } }
and with two kinds of project build.gradle files. The first one (Generator) exposes a run command that runs the generator taking the command line argument:
plugins {
id "application"
id "scala"
}
dependencies { compile "org.scala-lang:scala-library:2.12.3" }
mainClassName = "Main"
ext { cmdlineargs = "" }
run { args cmdlineargs }
The code generator is to be called from projectB (and an analogous projectC, and many others). I am trying to do this as follows (projectB/build.gradle):
task TEST {
project (":Generator").ext.cmdlineargs = "Hurray!"
println ("Value set:" + project(":Generator").ext.cmdlineargs )
dependsOn (":Generator:run")
}
Whatever I try to do (a gradle newbie here) I am not getting what I need. I have two problems:
The property cmdlineargs is not set at the point that task :projectB:TEST is run. The println sees the right value but the argument passed to the executed main method is the one configured in Generator/build.gradle, not the one in projectB/build.gradle. As pointed out in responses this can be work around using lazy property evaluation, but this does not solve the second problem.
The generator is only run once, even if I build both projectB and projectC. I need to run Generator:run for each of projectB and projectC separately (to generate different sources for each dependent project).
How can I get this to work? I suppose a completely different strategy is needed. I don't have to use command line and run; I can also try to run the main class of the generator more directly and pass arguments to it, but I do find the run task quite convenient (the complex classpath is set up automatically, etc.). The generator is a Java/Scala project itself that is compiled within the same multi-project build.
Note: tasks aren't like methods in java. A task will execute either 0 or 1 times per gradle invocation. A task will never execute twice (or more) in a single Gradle invocation
I think you want two or more tasks. Eg:
task run1(type:xxx) {
args 'foo'
}
task run2(type:xxx) {
args 'bar'
}
Then you can depend on run1 or run2 in your other projects.

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.

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

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.

Resources