第五十九章. 组织构建逻辑

Chapter 59. Organizing Build Logic

Gradle 提供了多种方式来组织你的构建逻辑。首先,你可以把你的构建逻辑直接放到一个任务的action闭包里。如果两个任务有相同的逻辑,你可以把这段逻辑抽取为一个方法。如果一个多项目构建中的多个项目有相同的逻辑,你可以把这个方法定义在父项目里。如果构建逻辑通过方法来正确建模比较复杂的话,你就要使用面向对象的模型了。[25] 这对Gradle来说很简单。只需要把你的类放在一个确定的目录,Gradle会自动地对它们编译,并且把它们加入到你的构建脚本的classpath中。
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 you want have an OO Model. [25] 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:

59.1. 继承的属性和方法

59.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.

示例59.1. 使用继承的属性和方法 - Example 59.1. Using inherited properties and methods

build.gradle

srcDirName = 'src/java'

def getSrcDir(project) {
    return project.file(srcDirName)
}

child/build.gradle

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

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

gradle -q show 的输出结果
Output of gradle -q show

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

59.2. 注入配置

59.2. Injected configuration

你可以使用在 第56.1节,“跨项目配置”第56.2节,“子项目配置” 所论述的配置注入技术,来向不同的项目注入属性和方法。通常来说,它是比继承更好的选择,有好几个原因:注入是显示地存在于构建脚本中的,你可以向不同的项目注入不同的逻辑,并且你可以注入任意一种配置,例如仓库,插件,任务等等。下面是展示注入的例子。
You can use the configuration injection technique discussed in Section 56.1, “Cross project configuration” and Section 56.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.

示例59.2. 使用注入的属性和方法 - Example 59.2. Using injected properties and methods

build.gradle

subprojects {
    // Inject a property and method
    srcDirName = 'src/java'
    srcDir = { file(srcDirName) }

    // Inject a task
    task show << {
        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') {
    srcDirName = "$srcDirName/legacy"
}

child1/build.gradle

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

gradle -q show 的输出结果
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

59.3. 在buildSrc 项目中的构建源代码

59.3. Build sources in the buildSrc project

当你运行Gradle时,它会检查buildSrc目录是否存在。然后Gradle会自动的编译和测试它的代码,并且把它们加入到你的构建脚本的classpath中。你不需要再提供任何进一步的指示。这可以是用于添加你的自定义任务和插件的好地方。
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.

对于多项目构建,它们可能只有一个buildSrc目录,在根项目的目录中。
For multi-project builds there can be only one buildSrc directory, which has to be in the root project directory.

下面列出的是Gradle应用到 buildSrc 项目的默认构建脚本。
Listed below is the default build script that Gradle applies to the buildSrc project:

图 59.1. 默认的buildSrc构建脚本 - Figure 59.1. Default buildSrc build script

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

这意味着你可以只把你的构建源代码放到这个目录当中,并且保留一个 Java/Groovy 项目的约定布局(见表 23.4,“Java 插件 - 默认项目布局”)。
This means that you can just put you build source code in this directory and stick to the layout convention for a Java/Groovy project (see Table 23.4, “Java plugin - default project layout”).

如果你需要更多的灵活性,你可以提供你自己的build.gradle。Gradle配置了一个默认的构建脚本,无论是否有脚本被指定。这意味着,你只需声明你所需要的额外的东西。下面是一个例子。请注意,此示例不需要声明对 Gradle API的依赖,因为这由默认的构建脚本完成:
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:

示例 59.3. 自定义buildSrc构建脚本 - Example 59.3. Custom buildSrc build script

buildSrc/build.gradle

repositories {
    mavenCentral()
}

dependencies {
    testCompile 'junit:junit:4.11'
}

The buildSrc project can be a multi-project build. 这就像其他常规的Gradle多项目构建。但是,你需要使你想要的所有项目在buildSrc的根项目的实际构建runtime依赖的classpath上。 你可以通过把它添加到每一个你想导出的项目的配置上来完成:
The buildSrc project can be a multi-project build. This works like any other regular Gradle multi-project build. However, you need to make all of the projects that you wish be on the classpath of the actual build 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:

示例 59.4. 将子项目添加到根 buildSrc 项目 - Example 59.4. Adding subprojects to the root buildSrc project

buildSrc/build.gradle

rootProject.dependencies {
  runtime project(path)
}

注意: 此例子的代码可以在Gradle的二进制文件或源码中的 samples/multiProjectBuildSrc 里看到。
Note: The code for this example can be found at samples/multiProjectBuildSrc which is in both the binary and source distributions of Gradle.


59.4. 从一个构建中运行另一个Gradle构建

59.4. Running another Gradle build from a build

你可以使用GradleBuild 任务。你可以通过使用 dirbuildFile 属性来指定执行哪一个构建,以及使用 tasks 属性来指定执行哪一个任务。
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.

示例 59.5. 从一个构建运行另一个构建 - Example 59.5. Running another build from a build

build.gradle

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

other.gradle

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

gradle -q build的输出结果
Output of gradle -q build

> gradle -q build
hello from the other build.

59.5. 构建脚本的外部依赖

59.5. External dependencies for the build script

如果你的构建脚本需要使用一些外部库,你可以在这个构建脚本的把它们添加到该脚本的classpath中。你可以通过使用buildscript()方法,传入一个定义构建脚本classpath的闭包。
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.

示例 59.6. 声明构建脚本的外部依赖 - Example 59.6. Declaring external dependencies for the build script

build.gradle

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

这个传给buildscript() 方法的闭包配置了一个ScriptHandler 实例。你可以通过添加依赖到classpath 配置来定义构建脚本的classpath。这和你定义Java编译classpath是同样的。你可以使用在 第50.4节,“如何定义你的依赖”描述的除了项目依赖之外的任何依赖类型。
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 50.4, “How to declare your dependencies”, except project dependencies.

声明了构建脚本的classpath之后,你可以使用构建脚本中的类,就像classpath上的任何其他类一样。下面的示例将添加到前面的示例中,并使用构建脚本classpath中的类。
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.

示例 59.7. 具有外部依赖的构建脚本 - Example 59.7. 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 << {
    def byte[] encodedString = new Base64().encode('hello world\n'.getBytes())
    println new String(encodedString)
}

gradle -q encode的输出结果
Output of gradle -q encode

> gradle -q encode
aGVsbG8gd29ybGQK

对于多项目构建,定义在一个项目的构建脚本中的依赖,是可以在所有子项目的构建脚本中用的。
For multi-project builds, the dependencies declared in the a project's build script, are available to the build scripts of all sub-projects.

59.6. Ant 可选依赖

59.6. Ant optional dependencies

For reasons we don't fully understand yet, external dependencies are not picked up by Ant's optional tasks. 但是你可以很容易地通过另一种方法做到。[26]
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. [26]

示例 59.8. Ant 的可选依赖 - Example 59.8. Ant optional dependencies

build.gradle

configurations {
    ftpAntTask
}

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

task ftp << {
    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")
        }
    }
}

这对于客户端模块的用法也是一个很好的例子。在这个用例中,针对 ant-commons-net 任务的Maven中央仓的POM没有提供正确的信息。
This is also nice 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.

59.7. 总结

59.7. Summary

Gradle 提供了多种组织你的构建逻辑的方式。你可以选择最适合你的领域的方式,并在不必要的间界引用之间找到平衡,避免冗余和难以维护的代码库。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为你节省了这些不必要的开销和间接引用。
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.



[25] 这范围可能是从一个类到一些非常复杂的东西。
[25] Which might range from a single class to something very complex.

[26] 事实上,我们认为这是更好的解决方案。仅当你的构建脚本和Ant的可选任务需要相同的 library时,你才需要定义它两次。在这种情况下,如果Ant 的可选任务会自动地获取在 gradesettings中定义的classpath,就很好。
[26] In fact, we think this is anyway the nicer solution. Only if your buildscript and Ant's optional task need the same library you would have to define it two times. In such a case it would be nice, if Ant's optional task would automatically pickup the classpath defined in the gradesettings.