Conditional logic in gradle build to only execute certain code when a specific task is running - gradle

So, I am using a plugin in my gradle build (the plugin is org.flywaydb.flyway but that is not really relevant). I want to validate the caller has passed in a runtime parameter when tasks from this plugin are executing but not when other tasks are executing.
I pass options to the flyway plugin based on a supplied parameter. I want an error to be returned when a flywayTask is being executed and no parameter is supplied. When a non-flyway task is being run, I do not want to validate if the parameter is supplied.
gradle -PmyParam=myValue flywayMigration
=> should run code and there should be no error
gradle flywayMigration
=> should run code and should produce error (as no parameter supplied)
gradle jar
=> should not run code and no error should be produced
I have been reading about gradle configuration and execution which is fine but I still can't find a way to only run the code when the flyway plugin is bveing executed OR specific flyway tasks are being executed.
This is my current code:
if(gradle.taskGraph.hasTask("flywayMigrate")) {
flyway {
def dbCode, dbUser, dbPassword, dbUrl
if (!project.hasProperty("db_env")) {
throw new GradleException("Expected db_env property to be supplied for migration task. Can be passed" +
" at command line e.g. [gradle -Pdb_env=ex1 flywayMigrate]")
} else {
// do stuff
}
user = balh
password = blah
url = blah
driver = 'oracle.jdbc.OracleDriver'
cleanDisabled = true
baselineOnMigrate = true
baselineVersion = '1.0.0'
}
}
To be clear, I only want this code:
if (!project.hasProperty("db_env")
to run for flyway tasks.
The code above throws this error:
Task information is not available, as this task execution graph has not been populated.
I've tried a few things here, any advice would be appreciated.

It's not really clear to me, what exactly do you want to do in case if this property is provided, but I think, you can do it without accesing task graph, just try to use doFirst Closure of the flywayMigrate task. Just something like this:
flywayMigrate.doFirst {
if(!project.hasProperty("db_env")) {
throw ...
} else {
//Do something
}
}
And leave your plugin configuration free of any additional logic.
As for exception, have you tried to wait until graph is ready? It's usualy done as follows:
gradle.taskGraph.whenReady {taskGraph ->
if(gradle.taskGraph.hasTask("flywayMigrate")) {
...
}
}
Update: to answer the question from the comments
if I can attach doFirst to multiple tasks?
Yes, you can use somthing like:
//declare task names
def names = ["taskA", "taskB", "taskC"]
tasks.findAll {it ->
//filter tasks with names
if (it.name in names)
return it
}.each { it ->
//add some extra logic to it's doFirst closure
it.doFirst {
println 'hello'
}
}
Just check, that all the tasks are exists before this configuration.

Related

Set the properties in the configuration phase of a task in another task

I have spent the last few hours trying to find a solution for my requirement, without luck:
I have a task that has to run some logic in a certain path:
task run(type: MyPlugin) {
pathForPlugin = myPath //Defined as a property in another gradle file
}
I want to set the "pathForPlugin" property dynamically in another task because it has to be read from some configuration file.
task initPaths(type: PathFinder) {
configurationFile = 'C:\\myConfig.conf'
}
The myConfig.conf would look like this:
pathForPlugin = 'C:\\Correct\\Path'
The problem is that "initPaths" has to run before the configuration phase of "run".
I have tried several approaches for this (GradleBuild task, dependsOn, Using Properties in the Plugin for "Lazy Configuration") but every approach only takes effect in the Execution phase leading to the "pathForPlugin" always staying at its default value.
Is there some way i can realize this or should i look for another solution outside of the gradle build?
I found a solution for the problem:
Instead of defining a task "initPaths" i directly used the java class "Pathfinder" in the build script:
import mypackage.PathFinder;
new PathFinder(project).run()
You only have to make sure that this part is above the definition of the task where the properties are used.
I admit this is a bit of a "hacky" solution but it works fine for my requirement.
you can do like this:
ext {
myPath //use it as a global variable that you can set and get from different gradle tasks and files
}
task firstTask {
doLast {
ext.myPath = "your path"
}
}
task run(type: MyPlugin) {
doFirst { //executed on runtime not on task definition
pathForPlugin = ext.myPath //Defined as a property in another gradle file
}
}
//example 2 - create run task dynamic
task initPath {
doLast {
tasks.create(name: "run", type: MyPlugin) {
pathForPlugin = ext.myPath
}
}
}

Gradle Task . "(type: Copy)" and <doLast> can't both work

task simpleTask{
print("simpleTask is reach");
}
task copySomeFile(type: Copy){
print("copySomeFile is reach");
from baseProjectPath;
into toProjectPath;
appendXML();
}
def appendXML(){
//modify a.txt
}
//i just want to run "simpleTask" only, but when "gradle simpleTask", the task"copySomeFile" will be run also ! I know beacuse gradle initialization.
but if write like this
task copySomeFile(type: Copy)<<{
}
the "copySomeFile" will not work.
it seems like "(type: Copy)" can't work with the "<<" or "doLast{}"?
i just want "--gradle simpleTask" "--gradle copySomeFile" can run alone.
You have to read about Gradle build lifecycle.
There are 2 phases you should note - Configuration and Execution. All tasks are always been configured on every build, but only some of them are really executed as the Execution phase.
What you see is that copySomeFile task was configured during the configuration phase. It doesn't copy anything, but it has to be configured. And everything within a tasks closure is task's configuration, that is why you see results of the print("copySomeFile is reach"); in the output.
<< or doLast are used to run something at the Execution phase, but your task of type Copy will not be configured if you place all it's configuration into doLast section or add << to the task definition - that is the reason why copy doesn't work.
Yeh, i got it. How much I appreciate both of you. SHARE THE CODE:
task simpleTask {
print("\nsimpleTask is configured"); // executed during the configuration plase, always
doLast {
print("\nsimpleTask is executed"); // executed during the execution plase, only if the simpleTask is executed
}
}
task copySomeFile(type: Copy) {
print("\ncopySomeFile is configured"); // executed always,执行其他任务时,此代码也会执行
from "D:/a.txt";// not executed. 执行其他任务时,此代码不会执行
into "D:/b.txt";// not executed. 执行其他任务时,此代码不会执行
doLast {
appendXML(); //only this task executed, the appendXML executed. 只有此task执行时,才会执行.比如(gradle copySomeFile);
}
}
def appendXML(){
print("\nappendXML");
}

Configuring a Gradle task based on task to be executed

I'm seemingly in a bit of a chicken/egg problem. I have the test task that Gradle defines with the Java plugin. I set the JUnit categories to run using a property. My team has expressed interest in a task that will run tasks in a specific category instead of having to use -P to set a property on the command line.
I can't come up with a way to pull this off since the new task would need to configure the test task only if it's executed. Since the categories to run need to be a input parameter for the test task to make sure the UP-TO-DATE check functions correctly, they need to be set during the configuration phase and can't wait for the execution phase.
Does anyone know how to make a setup like this work? Maybe I'm approaching it from the wrong angle entirely.
Edit 1
Current build.gradle
apply plugin: 'java'
def defaultCategory = 'checkin'
test {
def category = (project.hasProperty('category') ? project['category'] : defaultCategory)
inputs.property('category', category)
useJUnit()
options {
includeCategories category
}
}
What I'd like, but doesn't work:
apply plugin: 'java'
def defaultCategory = 'checkin'
test {
def category = (project.hasProperty('category') ? project['category'] : defaultCategory)
inputs.property('category', category)
useJUnit()
options {
includeCategories category
}
}
task nightly(dependsOn: 'build') {
defaultCategory = 'nightly'
}
task weekly(dependsOn: 'build') {
defaultCategory = 'weekly'
}
Since both tasks are configured regardless of whether they will be run, they become useless. I can't defer setting the defaultCategory value until the execution phase because it's value is needed to configure the task inputs of the test task and because the value is required to be able to run the test task, which runs before the build task.
I don't know if I'd call this solution elegant, but it has proven to be effective:
task nightly(dependsOn: 'build')
test {
useJUnit()
gradle.taskGraph.whenReady {
if (gradle.taskGraph.hasTask(":${project.name}:nightly")) {
options {
includeCategories 'nightly'
}
}
}
}

Manage multiple database with the flyway migrations gradle plugin

We have two databases for which we'd like to manage their migrations using flyway's gradle plugin.
I'd like to have a single task that can migrate both databases. However, I can't seem to get the flywayMigrate task to be called twice from a single task.
Here's what I have...
task migrateFoo() {
doFirst {
flyway {
url = 'jdbc:mysql://localhost/foo'
user = 'root'
password = 'password'
locations = ['filesystem:dev/src/db/foo']
sqlMigrationPrefix = ""
initOnMigrate = true
outOfOrder = true
}
}
doLast {
tasks.flywayMigrate.execute()
}
}
task migrateBar() {
doFirst {
flyway {
url = 'jdbc:mysql://localhost/bar'
user = 'root'
password = 'password'
locations = ['filesystem:dev/src/db/bar']
sqlMigrationPrefix = ""
initOnMigrate = true
outOfOrder = true
}
}
doLast {
tasks.flywayMigrate.execute()
}
}
task migrate(dependsOn: ['migrateFoo','migrateBar']) {}
Explicitly calling either migrateFoo or migrateBar from the command line works fine, however, if I try to call the migrate task only database foo is updated.
Both the doFirst and doLast tasks of the migrateBar task are called, however, the tasks.flywayMigrate.execute() task isn't called the second time from migrateBar.
How can I get flyway to migrate both foo and bar from a single task?
First, you should never call execute() on a task (bad things will happen). Also, a task will be executed at most once per Gradle invocation.
To answer your question, apparently the flyway plugin doesn't support having multiple tasks of the same type. Looking at its implementation, I think you'll have to roll your own task. Something like:
import com.googlecode.flyway.core.Flyway
import org.katta.gradle.plugin.flyway.task.AbstractFlywayTask
class MigrateOtherDb extends AbstractFlywayTask {
#Override
void executeTask(Flyway flyway) {
// set any flyway properties here that differ from
// those common with other flyway tasks
println "Executing flyway migrate"
flyway.migrate()
}
task migrateOtherDb(type: MigrateOtherDb)
I recommend to file a feature request to support multiple tasks of the same type, with a convenient way to configure them.
I also had the same problem. I wanted to run flyway migrations for different databases and even for the same database with different configurations in ONE gradle build.
for each database i need to migrate normal data tables and static data tables, so i use two flyway version tables and also two locations for the scripts. E.g.
ENV: dev MIGRATION1: data (locations: db/scripts/data table: _flyway_version_data)
MIGRATION2: static (locations: db/scripts/static table: _flyway_version_static)
ENV: test MIGRATION1 ....
MIGRATION2 ....
As Peter states above, the flyway tasks are only executed ONCE no matter how often you call them.
The workaround i found does not seem to be nicest, but it works:
in build.gradle
task migrateFlywayDevData(type: GradleBuild) {
buildFile = 'build.gradle'
tasks = ['flywayMigrate']
startParameter.projectProperties = [env: "dev", type="data"]
}
task migrateFlywayDevStatic(type: GradleBuild) {
buildFile = 'build.gradle'
tasks = ['flywayMigrate']
startParameter.projectProperties = [env: "test", type="static"]
}
....(task defs for test env)
Basically i create a new gradle build for each of the configurations.
"buildFile = 'build.gradle'"
refers to itself, so all code is contained in one build.gradle file.
The gradle call is then:
gradle migrateFlywayDevData migrateFlywayDevStatic ...
This is the first version. so code might be easily improved.
However this solution lets you execute flyway tasks multiple times with one gradle call.
Feel free to comment (flyway plugin configuration is not shown here)

Custom conditional configuration for Gradle project

Having an extract from https://github.com/gradle/gradle/blob/master/build.gradle:
ext {
isDevBuild = {
gradle.taskGraph.hasTask(developerBuild)
}
}
task developerBuild {
description = 'Builds distributions and runs pre-checkin checks'
group = 'build'
dependsOn testedDists
}
When I used this approach to create custom configuration in my project I discovered that:
isDevBuild === true
i.e. it's always true because task 'developerBuild' is inside my build.gradle project, and hence in graph. They have a couple of "different" configs (isCIBuild, isCommitBuild, isFinalReleaseBuild, ...) so I suppose I got something wrong here.
Can someone explain how to make this configs conditional based on some external parameter?
taskGraph.hasTask() tells if a task is in the task execution graph, that is whether it will get executed. Because the task execution graph is only created after the configuration phase, this method has to be called from a whenReady callback (or in the execution phase):
gradle.taskGraph.whenReady { graph ->
if (graph.hasTask(developerBuild)) {
// do conditional configuration
}
}
To make this more readable, we can introduce a new method:
def onlyFor(task, config) {
gradle.taskGraph.whenReady { graph ->
if (graph.hasTask(task)) {
project.configure(project, config)
}
}
}
Now we can write:
onlyFor(developerBuild) { ... }
onlyFor(ciBuild) { ... }
Another, simpler way to solve this problem is to check whether a particular task name is contained in gradle.startParameter.taskNames. However, this has two limitations: First, it compares task names, which can make a difference in multi-project builds. Second, it will only find tasks that have been specified directly (e.g. on the command line), but not dependencies thereof.
PS.: In your code, isDevBuild always holds because a (non-null) closure is true according to Groovy truth. (In contrast to isDevBuild(), isDevBuild won't call the closure.)

Resources