Organizing Build Logic - Gradle User Guide Version 3.5

# Chapter 43. Organizing Build Logic

Gradle offers a variety of ways to organize your build logic. First of all you can put your build logic directly in the action closure of a task. If a couple of tasks share the same logic you can extract this logic into a method. If multiple projects of a multi-project build share some logic you can define this method in the parent project. If the build logic gets too complex for being properly modeled by methods then you likely should implement your logic with classes to encapsulate your logic. [20] Gradle makes this very easy. Just drop your classes in a certain directory and Gradle automatically compiles them and puts them in the classpath of your build script.

Here is a summary of the ways you can organise your build logic:

- POGOs. You can declare and use plain old Groovy objects (POGOs) directly in your build script. The build script is written in Groovy, after all, and Groovy provides you with lots of excellent ways to organize code.

- Inherited properties and methods. In a multi-project build, sub-projects inherit the properties and methods of their parent project.

- Configuration injection. In a multi-project build, a project (usually the root project) can inject properties and methods into another project.

- `buildSrc` project. Drop the source for your build classes into a certain directory and Gradle automatically compiles them and includes them in the classpath of your build script.

- Shared scripts. Define common configuration in an external build, and apply the script to multiple projects, possibly across different builds.

- Custom tasks. Put your build logic into a custom task, and reuse that task in multiple places.

- Custom plugins. Put your build logic into a custom plugin, and apply that plugin to multiple projects. The plugin must be in the classpath of your build script. You can achieve this either by using `build sources` or by adding an external library that contains the plugin.

- Execute an external build. Execute another Gradle build from the current build.

- External libraries. Use external libraries directly in your build file.

## 43.1. Inherited properties and methods

Any method or property defined in a project build script is also visible to all the sub-projects. You can use this to define common configurations, and to extract build logic into methods which can be reused by the sub-projects.

Example 43.1. Using inherited properties and methods

`build.gradle`

    // Define an extra property
    ext.srcDirName = 'src/java'

    // Define a method
    def getSrcDir(project) {
        return project.file(srcDirName)
    }

`child/build.gradle`

    task show {
        doLast {
            // Use inherited property
            println 'srcDirName: ' + srcDirName

            // Use inherited method
            File srcDir = getSrcDir(project)
            println 'srcDir: ' + rootProject.relativePath(srcDir)
        }
    }

Output of `gradle -q show`

    > gradle -q show
    srcDirName: src/java
    srcDir: child/src/java

## 43.2. Injected configuration

You can use the configuration injection technique discussed in Section 26.1, “Cross project configuration” and Section 26.2, “Subproject configuration” to inject properties and methods into various projects. This is generally a better option than inheritance, for a number of reasons: The injection is explicit in the build script, You can inject different logic into different projects, And you can inject any kind of configuration such as repositories, plug-ins, tasks, and so on. The following sample shows how this works.

Example 43.2. Using injected properties and methods

`build.gradle`

    subprojects {
        // Define a new property
        ext.srcDirName = 'src/java'

        // Define a method using a closure as the method body
        ext.srcDir = { file(srcDirName) }

        // Define a task
        task show {
            doLast {
                println 'project: ' + project.path
                println 'srcDirName: ' + srcDirName
                File srcDir = srcDir()
                println 'srcDir: ' + rootProject.relativePath(srcDir)
            }
        }
    }

    // Inject special case configuration into a particular project
    project(':child2') {
        ext.srcDirName = "$srcDirName/legacy"
    }

`child1/build.gradle`

    // Use injected property and method. Here, we override the injected value
    srcDirName = 'java'
    def dir = srcDir()

Output of `gradle -q show`

    > gradle -q show
    project: :child1
    srcDirName: java
    srcDir: child1/java
    project: :child2
    srcDirName: src/java/legacy
    srcDir: child2/src/java/legacy

## 43.3. Configuring the project using an external build script

You can configure the current project using an external build script. All of the Gradle build language is available in the external script. You can even apply other scripts from the external script.

Example 43.3. Configuring the project using an external build script

`build.gradle`

    apply from: 'other.gradle'

`other.gradle`

    println "configuring $project"
    task hello {
        doLast {
            println 'hello from other script'
        }
    }

Output of `gradle -q hello`

    > gradle -q hello
    configuring root project 'configureProjectUsingScript'
    hello from other script

## 43.4. Build sources in the `buildSrc` project

When you run Gradle, it checks for the existence of a directory called `buildSrc`. Gradle then automatically compiles and tests this code and puts it in the classpath of your build script. You don't need to provide any further instruction. This can be a good place to add your custom tasks and plugins.

For multi-project builds there can be only one `buildSrc` directory, which has to be in the root project directory.

Listed below is the default build script that Gradle applies to the `buildSrc` project:

Figure 43.1. Default buildSrc build script

    apply plugin: 'groovy'
    dependencies {
        compile gradleApi()
        compile localGroovy()
    }

This means that you can just put your build source code in this directory and stick to the layout convention for a Java/Groovy project (see Table 47.4, “Java plugin - default project layout”).

If you need more flexibility, you can provide your own `build.gradle`. Gradle applies the default build script regardless of whether there is one specified. This means you only need to declare the extra things you need. Below is an example. Notice that this example does not need to declare a dependency on the Gradle API, as this is done by the default build script:

Example 43.4. Custom buildSrc build script

`buildSrc/build.gradle`

    repositories {
        mavenCentral()
    }

    dependencies {
        testCompile 'junit:junit:4.12'
    }

The `buildSrc` project can be a multi-project build, just like any other regular multi-project build. However, all of the projects that should be on the classpath of the actual build must be `runtime` dependencies of the root project in `buildSrc`. You can do this by adding this to the configuration of each project you wish to export:

Example 43.5. Adding subprojects to the root buildSrc project

`buildSrc/build.gradle`

    rootProject.dependencies {
      runtime project(path)
    }

Note: The code for this example can be found at `samples/multiProjectBuildSrc` in the ‘-all’ distribution of Gradle.

## 43.5. Running another Gradle build from a build

You can use the `GradleBuild` task. You can use either of the `dir` or `buildFile` properties to specify which build to execute, and the `tasks` property to specify which tasks to execute.

Example 43.6. Running another build from a build

`build.gradle`

    task build(type: GradleBuild) {
        buildFile = 'other.gradle'
        tasks = ['hello']
    }

`other.gradle`

    task hello {
        doLast {
            println "hello from the other build."
        }
    }

Output of `gradle -q build`

    > gradle -q build
    hello from the other build.

## 43.6. External dependencies for the build script

If your build script needs to use external libraries, you can add them to the script's classpath in the build script itself. You do this using the `buildscript()` method, passing in a closure which declares the build script classpath.

Example 43.7. Declaring external dependencies for the build script

`build.gradle`

    buildscript {
        repositories {
            mavenCentral()
        }
        dependencies {
            classpath group: 'commons-codec', name: 'commons-codec', version: '1.2'
        }
    }

The closure passed to the `buildscript()` method configures a `ScriptHandler` instance. You declare the build script classpath by adding dependencies to the `classpath` configuration. This is the same way you declare, for example, the Java compilation classpath. You can use any of the dependency types described in Section 25.4, “How to declare your dependencies”, except project dependencies.

Having declared the build script classpath, you can use the classes in your build script as you would any other classes on the classpath. The following example adds to the previous example, and uses classes from the build script classpath.

Example 43.8. A build script with external dependencies

`build.gradle`

    import org.apache.commons.codec.binary.Base64

    buildscript {
        repositories {
            mavenCentral()
        }
        dependencies {
            classpath group: 'commons-codec', name: 'commons-codec', version: '1.2'
        }
    }

    task encode {
        doLast {
            def byte[] encodedString = new Base64().encode('hello world\n'.getBytes())
            println new String(encodedString)
        }
    }

Output of `gradle -q encode`

    > gradle -q encode
    aGVsbG8gd29ybGQK

For multi-project builds, the dependencies declared with a project's `buildscript()` method are available to the build scripts of all its sub-projects.

Build script dependencies may be Gradle plugins. Please consult Chapter 27, Gradle Plugins for more information on Gradle plugins.

Every project automatically has a `buildEnvironment` task of type `BuildEnvironmentReportTask` that can be invoked to report on the resolution of the build script dependencies.

## 43.7. Ant optional dependencies

For reasons we don't fully understand yet, external dependencies are not picked up by Ant's optional tasks. But you can easily do it in another way. [21]

Example 43.9. Ant optional dependencies

`build.gradle`

    configurations {
        ftpAntTask
    }

    dependencies {
        ftpAntTask("org.apache.ant:ant-commons-net:1.9.6") {
            module("commons-net:commons-net:1.4.1") {
                dependencies "oro:oro:2.0.8:jar"
            }
        }
    }

    task ftp {
        doLast {
            ant {
                taskdef(name: 'ftp',
                        classname: 'org.apache.tools.ant.taskdefs.optional.net.FTP',
                        classpath: configurations.ftpAntTask.asPath)
                ftp(server: "ftp.apache.org", userid: "anonymous", password: "me@myorg.com") {
                    fileset(dir: "htdocs/manual")
                }
            }
        }
    }

This is also a good example for the usage of client modules. The POM file in Maven Central for the ant-commons-net task does not provide the right information for this use case.

## 43.8. Summary

Gradle offers you a variety of ways of organizing your build logic. You can choose what is right for your domain and find the right balance between unnecessary indirections, and avoiding redundancy and a hard to maintain code base. It is our experience that even very complex custom build logic is rarely shared between different builds. Other build tools enforce a separation of this build logic into a separate project. Gradle spares you this unnecessary overhead and indirection.

[20] Which might range from a single class to something very complex.

[21] In fact, we think this is a better solution. Only if your buildscript and Ant's optional task need the same library would you have to define it twice. In such a case it would be nice if Ant's optional task would automatically pick up the classpath defined in the “`gradle.settings`” file.