1

I have a project with a bunch of subprojects. Some of the subprojects are utility projects, and some of the projects are actual applications.

The "application" subprojects should all produce a jar file with a given main class etc. To solve this I've put the following in the parent build.gradle:

def configureApplication(project, mainClass) {
    project.jar { ... }
}

and from each subproject's build.gradle, I call configureApplication(project, "my.main.Class")

Now I want to add a deploy task to all application subproject's so that I can deploy all applications using ./gradlew deploy. I've tried to mimic the above approach by putting this in the parent build.gradle:

def configureDeploy(project) {
    project {
        task deploy {
            println "Deploying!"
        }
    }
}

But I'm running in to:

Could not find method call() for arguments [build_51pn93...closure3@334ebcaa] on project ':subproj'.

I've tried about a hundred other variations for the past hour without success.

Question: How do add tasks to some of the subprojects without repeating myself in each subproject's build.gradle? (Also, is using methods in the parent build.gradle a reasonable way of achieving this, or is there a more gradle idiomatic way of doing it?)

aioobe
  • 413,195
  • 112
  • 811
  • 826

3 Answers3

3

I read the previous answers and their comments, especially the following statement:

I would very much like to add the deploy task to each relevant subproject through the subprojects build.gradle (while keeping code duplication to a minimum).

This sounds to me like the perfect use case for plugins. Gradle allows you to define plugins in the buildSrc subproject of your root project. Just place your plugin source code in <rootProjectDir>/buildSrc/src/main/groovy. You can find additional details in the docs.

An example plugin for your use case could look like this:

class MyDeployPlugin implements Plugin<Project> {
    void apply(Project project) {
        // you can access the project instance this plugin is applied to
        project.task('deploy') {
            // configure your task
        }
    }
}

Now you can apply the plugin in your subproject build.gradle files:

apply plugin: MyDeployPlugin

deploy {
     // configure your task project-specific
}

One last advice: People who work a lot with both Maven and Gradle, often state, that Gradle's flexibility through scripting is both the biggest advantage and the biggest disadvantage. In Maven, if you want to add or alter functionality, you often have to write a plugin. In Gradle, you can simply script the functionality, but if this happens to often, it will produce clusterfuck and unreusable code. So, whenever you need to add the same functionality to multiple (sub-)projects, use plugins. You can even publish plugins to Maven or your own repository.

Lukas Körfer
  • 13,515
  • 7
  • 46
  • 62
  • I agree that's what I did for some of my custom tasks when they grew too big. Great solution, I didn't think about it! – LazerBanana Jul 06 '17 at 10:54
  • Thanks! Do I have the same possibilities in terms of depending on other plugins such as the [Gradle SSH Plugin](https://gradle-ssh-plugin.github.io/docs/)? – aioobe Jul 06 '17 at 11:12
  • Depending in which way? Do you need to configure tasks or extensions of the other plugin or do you want to apply it yourself? You have access to the whole project object, so if you applied the Gradle SSH plugin before, you can work with it like you do in your build script. – Lukas Körfer Jul 06 '17 at 11:15
  • Ok cool! I will go with this approach then! Thanks. A final question (out of curiosity) is it *possible* to add a task to a subproject from a method defined in the parent build.gradle? – aioobe Jul 06 '17 at 11:18
  • Of course, it is the method I used in the example plugin (`project.task()`). You just need the correct project object, e.g. via `project(':projectA')`. Alternatively, get the [`TaskContainer`](https://docs.gradle.org/3.5/javadoc/org/gradle/api/tasks/TaskContainer.html) via `project.tasks` and use one of its `create()` methods. – Lukas Körfer Jul 06 '17 at 11:23
  • Just as addition to the question on interaction with other plugins. You can use `project.plugins.hasPlugin()` to check for an other plugin or `project.pluginManager.withPlugin()` to execute an action only if a specific plugin is applied. – Lukas Körfer Jul 06 '17 at 11:47
2

It is indeed common to add tasks for subprojects in the parent build.gradle file.

Edit: per comments, the goal is to apply this to all subproject, and not selective ones.

Consider the following settings.gradle:

include 'appA', 'appB', 'utilityC'

Here is the root build.gradle. It defines a custom DeployTask (doc here) for each application (using subprojects), but does not configure it:

class DeployTask extends DefaultTask {
    String appName
    String destDir

    @TaskAction
    def deploy() {
        if (appName != null && destDir != null) {
            println "deploying ${appName} to ${destDir}"
        }
    }
}

subprojects { 
    task deploy(type: DeployTask) {}
}

Here is build.gradle for appA:

deploy() {
    appName = "Application A"
    destDir = "~/server/appA"
}

Here is build.gradle for appB:

deploy() {
    appName = "Application B"
    destDir = "~/web/appB"
}

In this simple example, utilityC does not have a build.gradle

Example output:

$ gradle :appA:deploy 
deploying Application A to ~/server/appA

and

$ gradle :appB:deploy 
deploying Application B to ~/web/appB

and

$ gradle :utilityC:deploy 
[no output]
Michael Easter
  • 23,733
  • 7
  • 76
  • 107
  • Interesting. My only concern here is the `p.name ==~ /app.*/` thing. I don't have a mechanism like that. I would very much like to add the deploy task to each relevant subproject through the subprojects build.gradle (while keeping code duplication to a minimum). – aioobe Jul 06 '17 at 09:22
  • Is it acceptable that the utility subprojects have a 'deploy' task that is a no-op? – Michael Easter Jul 06 '17 at 09:26
  • Yes. Absolutely. This was my thought too actually. – aioobe Jul 06 '17 at 09:28
  • I have added an `enabled` boolean to the task, which allows subprojects to opt-in. Even this could potentially be simplified (by setting `enabled` when the other fields are updated), but IMHO this is sufficient. – Michael Easter Jul 06 '17 at 09:35
  • Tasks already provide an `enabled` property, which defines whether the task will be skipped. – Lukas Körfer Jul 06 '17 at 09:36
  • I have removed the `enabled` property and simply check `appName` and `destDir` as a predicate for the deploy task. – Michael Easter Jul 06 '17 at 09:43
  • I Agree, i have edited my answer, but probably going to delete it as your explained it well. Thank you. – LazerBanana Jul 06 '17 at 09:54
1

I would strongly suggest creating a custom groovy class in your buildSrc with configurable parameters. And then configure them in gradle.

class DeployTask extends DefaultTask {

    def deploymentName, targets, source, deploymentPlan

    @TaskAction
    def deploy() {
       // your stuff using above variables                
    }

}

You can use a subprojects{} in main build.gradle to specify everything there instead of repeating yourself.

subprojects { project ->

    if (project.name.contains('application1')) {

        task deploy (type: package.DeployTask) {
            source          = ""
            deploymentName  = ""
            targettype      = ""
        }

    }

    if (project.name.contains('application2')) {

        task deploy (type: package.DeployTask) {
            source          = ""
            deploymentName  = ""
            targettype      = ""
        }

    }

}

or simply add it to the projects itself.

This is going to iterate through all subprojects specified in settings.gradle and create a task deploy for each subproject that met the above condition.


I See @Michael Easter did actually explain it already in a separate answer, and I Agree I have my apps deployed to Weblogic in exactly same way.

LazerBanana
  • 6,865
  • 3
  • 28
  • 47
  • Right. This has struck me too. I would however like to put all subproject specific configuration in the subproject's bulid.gradle. (Like I've done with the `configureApplication` method.) Do you know a way of achieving that? – aioobe Jul 06 '17 at 08:45
  • @aioobe im confused, you said you don't want to repeat yourself and add to subprojects without repeating yourself and now you saying you want to `put all subproject specific configuration in the subproject's bulid.gradle` im lost here a bit. – LazerBanana Jul 06 '17 at 08:57
  • Ok. Sorry. You're right. I want to repeat as little as possible. I would like to "opt in" to the deploy task, by writing something like `configureDeploy(project)` in each deployable subproject's build.gradle. After that be able to do `./gradlew deploy` and all subprojects with a deploy task should be deployed. – aioobe Jul 06 '17 at 08:59
  • @aioobe one more thing, is the deploy task your task or it comes from a plugin? – LazerBanana Jul 06 '17 at 09:08
  • I plan to replace the `println "Deploying!"` line in my question with an ssh thing as described [here](https://stackoverflow.com/questions/13183433). – aioobe Jul 06 '17 at 09:13
  • @aioobe i see then you are not using any plugin to deploy the apps? where is the deploy task coming from then? normally it comes from `war` or `ear` plugins etc. unless you have defined the deploy task on your own, this is what I mean, where is the deploy task coming from - you want to define it on your own and configure it? – LazerBanana Jul 06 '17 at 09:15
  • Yes, precisely that. I'd like to create a deploy task (what I usually do with `task deploy { ... }` I would like this to be done for each subproject, by calling something like `configureDeploy(project)` from each subproject's build.gradle. – aioobe Jul 06 '17 at 09:18