Gradle: Common Zip task for every subproject - gradle

I have a multi-project gradle build, where all subprojects of root project have common zip task. They should zip some source, depending of the subproject's settings.
First, I configured it in parent build.gradle:
subprojects { subproject ->
apply plugin: 'java'
...
task submission(type: Zip) {
destinationDir = subproject.buildDir
archiveName = subproject.name
from subproject.ext.submissionFiles
}
}
But this does not work with Cannot get property 'submissionFiles' on extra properties extension as it does not exist. So i moved the task in each subproject's build.gradle, which breaks DRY principle.
I think the problem is that the configuration phase takes place before subprojects are configured. How can I "defer" configuration of this task, so that I only configure ext.submissionFiles in subprojects?

I think you're saying that submissionFiles is set in the subproject's build.gradle?
There are a few ways of doing it.
Option 1. You could defer the evaluation of subproject.ext.submissionFiles by wrapping it in a closure:
task submission(type: Zip) {
destinationDir = subproject.buildDir
archiveName = subproject.name
from ({ subproject.ext.submissionFiles })
}
Option 2. I think it could be better if you didn't use the intermediate variable (submissionFiles) and directly configured the submission task in the children:
// In root
task submission(type: Zip) {
destinationDir = subproject.buildDir
archiveName = subproject.name
}
// In child
submission {
from("path/to/files")
}
Using an intermediate variable like that to move configuration around can make your builds harder to understand connections between tasks.
Option 3. In some cases, it makes sense to use evaluationDependsOnChildren(), but I would generally not recommend it.

Related

Use build.finalizedBy on each subproject with Gradle kotlin

I want to run a specific task after EVERY build of my subprojects. I can go into each of my subprojects build.gradle.kts file and add the following
tasks.build {
finalizedBy("afterbuildtask")
}
However, this should be possible to do in my root project build.gradle.kts file right? I tried it by doing the following:
subprojects {
this.tasks.findByName("build")?.dependsOn("afterbuildtask")
}
But nothing happens. How can I achieve this?
You can't programatically execute tasks from other tasks in newer versions of Gradle.
Instead, you are supposed to declare task dependencies and Gradle will ensure they get executed in the correct order. But I think it's not what you want
Alternatively, you could move your logic into the doLast block in the build task. eg:
build {
doLast {
println("Copying...")
copy {
from 'source'
into 'target'
include '*.war'
}
println("completed!")
}
}
good coding! ¯_(ツ)_/¯

Gradle JAR files in a folder recursively

How can I JAR a folder and all of it's sub directories and files in a simple JAR?
I tried something like this and it does not work.
task jarVrCore(type: Jar, description: 'JARs core part of the project') {
doLast {
archiveName = "vasasdasasdasd"
from "${projectDir}"
println "${vrCoreSourceDir}"
destinationDir = file("${dirTmpLibsVr4}")
}
}
I always get an empty JAR with only the manifest.
The problem is that all of your task configurations are applied too late. You are using a doLast closure, which is executed after the actual task action (for a Jar task: the creation of the .jar file).
Normally, all task configuration is done directly inside the task configuration closure:
task jarVrCore(type: Jar, description: 'JARs core part of the project') {
archiveName = "vasasdasasdasd"
from "${projectDir}"
println "${vrCoreSourceDir}"
destinationDir = file("${dirTmpLibsVr4}")
}
If you need to apply configuration after other tasks have been executed, use a doFirst closure.
Please note, that using the example above, the println statement will be executed during configuration phase and therefore on each Gradle invocation, whether the task jarVrCore is executed or not.

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.

Generate CLASSPATH from all multiproject gradle build dependencies

We're using an external testing tool (Squish) which runs after our main gradle build. This requires visibility of classes under test. Currently the CLASSPATH environment variable is built from various bash scripts and other string manipulation. I'm aiming to get gradle to do this automagically.
Since the build is done on a very slow source control system (clearcase) it is preferable for the tests to be run against the class files/JARs as left by the build rather than extra copies / compression into a single JAR etc.
The classpath needs to contain
Generated JAR files, typically in build/libs/project-x.jar
Test classes typically build/classes/test
Test resources typically build/resources/test
Any 3rd party jars any of the child projects depend upon, log4j, spring etc.
It's a complex multiproject build, but I've simplified to following example with parent and two children.
Parent settings.gradle
include ':child1'
include ':child2'
Parent build.gradle
allprojects {
apply plugin: 'java'
repositories {
mavenCentral()
}
}
Child 1 build.gradle
dependencies {
compile 'org.springframework:spring-context:4.1.2.RELEASE'
compile 'org.springframework:spring-beans:4.1.2.RELEASE'
}
Child 2 build.gradle
dependencies {
project (":child1")
}
What I've got so far. Is this the right approach? Can it be simplified or completely rewritten in a better way?
task createSquishClasspath << {
def paths = new LinkedHashSet()
paths.addAll(subprojects.configurations.compile.resolvedConfiguration.resolvedArtifacts.file.flatten())
paths.addAll(subprojects.jar.outputs.files.asPath)
paths.addAll(subprojects.sourceSets.test.output.resourcesDir)
paths.addAll(subprojects.sourceSets.test.output.classesDir)
paths.each {
println "${it}"
}
println paths.join(File.pathSeparator)
}
Output
C:\so-question\Parent\local-repo\spring\spring-context-4.1.2.RELEASE.jar
C:\so-question\Parent\local-repo\spring\spring-beans-4.1.2.RELEASE.jar
C:\so-question\Parent\child1\build\libs\child1.jar
C:\so-question\Parent\child2\build\libs\child2.jar
C:\so-question\Parent\child1\build\resources\test
C:\so-question\Parent\child2\build\resources\test
C:\so-question\Parent\child1\build\classes\test
C:\so-question\Parent\child2\build\classes\test
In summary the best answer so far is to join all the paths from the projects dependencies and all the test.output.resourcesDir and test.output.classesDir
task createSquishClasspath << {
def paths = new LinkedHashSet()
paths.addAll(subprojects.configurations.compile.resolvedConfiguration.resolvedArtifacts.file.flatten())
paths.addAll(subprojects.jar.outputs.files.asPath)
paths.addAll(subprojects.sourceSets.test.output.resourcesDir)
paths.addAll(subprojects.sourceSets.test.output.classesDir)
paths.each {
println "${it}"
}
println paths.join(File.pathSeparator)
}

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