Lazy evaluation of properties

I have a chicken/egg problem.

I have a task createDatabase that need properties from a config file.
And another task createClient that creates the config file.

If I run task createClient gradle will evaluate task createDatabase and find that a property is missing and failing before the file is created.

I need ideas on how to solve this little problem.

File configFile = file("$rootDir/$client/conf/config.properties")
Properties props = new Properties()
if (configFile.exists()) {
    props.load(new FileInputStream(configFile))
    props.each { prop ->
        project.ext.set(prop.key, prop.value)
    }
}

task createDatabase(type: Exec) {
    args = [database]
    def command = ['CMD', '/C', 'scripts\\db\\CreateNewDB.cmd']
    commandLine = command + args
}

task createClient() {
    doLast {
        def customer = project.property('client')
        mkdir "$customer/conf"
	
        file("$customer/conf/config.properties").write("database=foo")
    }
}

You don’t need to configure the args of your createDatabase task at configuration time. You can create a task such as configureDatabase that reads the properties file created by createClient and sets the args value of createDatabase during the execution phase.

Alternatively, you could create a class that implements CommandLineArgumentProvider to load the property file and provide the appropriate argument. The execution of this logic should be deferred until the createDatabase task actually requires it.

I like the idea of postpone reading of args to execution phase.
But I’m not sure how.

I guess I could change the createDatabase to something like:

task createDatabase() {
    doLast {
        exec {
            ...
        }
    }
}

Is that you had in mind?

Using exec in your own task action should work as well. However, I was specifically mentioning that you can continue to use an Exec task, but defer adding the final argument until execution with a task that does nothing other than read the properties and add the arg to the createDatabase task. Something like this would work:

task createClient {
    doLast {
        def customer = project.property('client')
        mkdir "$customer/conf"
        file("$customer/conf/config.properties").write("database=foo")
    }
}

task createDatabase(type: Exec) {
    commandLine = ['CMD', '/C', 'scripts\\db\\CreateNewDB.cmd']
}

task configureCreateDatabase {
    createDatabase.dependsOn it
    dependsOn createClient
    doLast {
        File configFile = file("$rootDir/$client/conf/config.properties")
        Properties props = new Properties()
        if (configFile.exists()) {
            configFile.withInputStream { props.load(it) }
            createDatabase.args props.getProperty('database')
        }
    }
}

You also have the CommandLineArgumentProvider option, which will work as long as you don’t eagerly access the commandLine property:

class DatabaseArgumentProvider implements CommandLineArgumentProvider {
    Iterable<String> asArguments() {
        File configFile = file("$rootDir/$client/conf/config.properties")
        Properties props = new Properties()
        configFile.withInputStream { props.load(it) }
        return Arrays.asList(props.getProperty('database'))
    }
}

task createDatabase(type: Exec) {
    commandLine = ['CMD', '/C', 'scripts\\db\\CreateNewDB.cmd']
    argumentProviders.add(new DatabaseArgumentProvider())   
}