8

I want to try new rule based configuration in Gradle to configure my plugin. Plugin's purspose is uploading data to cloud storages, like Google Drive or S3. I need a configuration for each storage the user want's to use. So, what I want to achieve is:

model {
    backup { // this is a container for all storages I want to support
        googleDrive {
            clientId = ''
            clientSecret = ''
            accessToken = ''
            refreshToken = ''
        }

        s3 {
           // S3 specific config (like API keys)
        }
    }
}

In my plugin I configure rule source for backup element:

class BackupPlugin implements Plugin<Project> {

    static class Rules extends RuleSource {
        @Model
        void backup(BackupPluginExtension backupModel) {}
    }
}

@Managed
interface BackupPluginExtension {
    GoogleDrive getGoogleDrive()

    void setGoogleDrive(GoogleDrive googleDrive)
}

@Managed
interface GoogleDrive {
    String getClientId()

    void setClientId(String clientId)

    String getClientSecret()

    void setClientSecret(String clientSecret)

    String getAccessToken()

    void setAccessToken(String accessToken)

    String getRefreshToken()

    void setRefreshToken(String refreshToken)
}

But, that doesn't work saying: Could not find method googleDrive() for arguments [build_8w85xu7hrz3atgeg839d33hzl$_run_closure1_closure2_closure3@1b06ac95] on root project 'test'.. Looks like it tries to call methods inside backup and not configure nested beans.

So, what is the correct syntax for that?

I know about named collections, but the question is how to configure custom bean hierarchies.

smac89
  • 39,374
  • 15
  • 132
  • 179
madhead
  • 31,729
  • 16
  • 153
  • 201
  • 1
    `build.gradle` from [here](https://github.com/Opalo/stackoverflow/tree/master/34359856) compiles well. Do you have an example of problematic file? – Opal Mar 29 '16 at 08:29
  • @Opal, no, it doesn't. `./gradlew build` may work because of lazy nature of Gradle, but `./gradlew model` forces to build model hierarchy and fails with: `No signature of method: BackupPluginExtension.googleDrive() is applicable for argument types: (build_dg6xcmwhd5yulrz1952ef2hf7$_run_closure1$_closure2$_closure3) values: [build_dg6xcmwhd5yulrz1952ef2hf7$_run_closure1$_closure2$_closure3@6b95c03e]` – madhead Mar 29 '16 at 09:49

2 Answers2

3

Here is a model example of bean hierarchy collection inspired from Gradle package samples/modelRules/ruleSourcePluginEach/.

BackupStorage contains common attributes for both GoogleDrive and S3 storages.

apply plugin: BackupPlugin

model {
    backup {
        storage1(GoogleDrive) {
            clientId = '1'
            clientSecret = ''
            accessToken = ''
            refreshToken = ''
        }
        storage2(S3) {
           clientId = '2'
        }
        storage3(GoogleDrive) {
            clientId = '3'
            clientSecret = ''
            accessToken = ''
            refreshToken = ''
        }
    }
}

class BackupPlugin implements Plugin<Project> {

    void apply(Project p) { }

    static class Rules extends RuleSource {
        @Model void backup(ModelMap<BackupStorage> bs) {
        }
    }
}

@Managed
interface BackupStorage {
    String getClientId()
    void setClientId(String clientId)
}

@Managed
interface GoogleDrive extends BackupStorage {
    String getClientSecret()
    void setClientSecret(String clientSecret)

    String getAccessToken()
    void setAccessToken(String accessToken)

    String getRefreshToken()
    void setRefreshToken(String refreshToken)
}

@Managed
interface S3 extends BackupStorage {
}
Yves Martin
  • 10,217
  • 2
  • 38
  • 77
3

After playing with it for a while, it seems that making googleDrive read-only (i.e. by removing setGoogleDrive() setter) fixes your problem (I get BUILD SUCCESSFUL when running gradle model).

And after few minor modifications (using @Mutate instead of Plugin.apply), it looks like this:

apply plugin: BackupPlugin

class BackupPlugin extends RuleSource {
    @Model
    void backup(BackupPluginExtension backupModel) {
    }

    @Mutate void createTasks(ModelMap<Task> tasks, BackupPluginExtension model) {
        tasks.create("backup") {
            doLast {
                // ... do stuff
                println "googleDrive.clientId = ${model.googleDrive.clientId}"
            }
        }
    }
}

@Managed
interface BackupPluginExtension {
    GoogleDrive getGoogleDrive()

//    void setGoogleDrive(GoogleDrive googleDrive)
}

@Managed
interface GoogleDrive {
    String getClientId()

    void setClientId(String clientId)

    String getClientSecret()

    void setClientSecret(String clientSecret)

    String getAccessToken()

    void setAccessToken(String accessToken)

    String getRefreshToken()

    void setRefreshToken(String refreshToken)
}

model {
    backup { // this is a container for all storages I want to support
        googleDrive  {
            clientId = 'someId'
            clientSecret = ''
            accessToken = ''
            refreshToken = ''
        }

    }
}

Running gradle backup on this prints googleDrive.clientId = someId.

Yoav Aharoni
  • 2,672
  • 13
  • 18
  • Awesome! Thank you! Just what I need. – madhead Mar 30 '16 at 09:10
  • Also, important note on this answer. It still doesn't work with Gradle 2.9 (I used that version when asked the question), but works in 2.12. So, the problem was not only the syntax, but they have changed something inside Gradle too :) – madhead Mar 31 '16 at 10:07