第五十六章. 多项目构建

Chapter 56. Multi-project Builds

对多项目构建的强大支持是 Gradle 的独特优点之一。而本主题也是最具智力挑战的主题。
The powerful support for multi-project builds is one of Gradle's unique selling points. This topic is also the most intellectually challenging.

56.1. 跨项目配置

56.1. Cross project configuration

让我们从一个非常简单的多项目构建开始。毕竟 Gradle 核心上是一个通用的构建工具,因此所构建的项目不一定得是 Java 项目。我们的第一个例子是关于海洋生物的。
Let's start with a very simple multi-project build. After all Gradle is a general purpose build tool at its core, so the projects don't have to be java projects. Our first examples are about marine life.

56.1.1. 配置和执行

56.1.1. Configuration and execution

第55.1节,构建阶段》描述了每个 Gradle 构建的阶段。让我们放大多项目构建的配置和执行阶段。在执行任何任务之前,会进行所有项目的配置。这意味着,当请求一个项目里的某个任务时,首先会配置多项目构建的所有项目。所有项目需要配置的原因是为了支持灵活地访问及更改 Gradle 项目模型的任何部分。
Section 55.1, “Build phases” describes the phases of every Gradle build. Let's zoom into configuration and execution phases of a multi-project build. The configuration of all projects happens before any task is executed. This means that when a single task, from a single project is requested, all projects of multi-project build are configured first. The reason every project needs to be configured is to support the flexibility of accessing and changing any part of Gradle project model.

56.1.1.1. 按需配置

56.1.1.1. Configuration on demand

由于每个项目都在执行阶段开始之前配置,所以可以做到配置注入功能以及对完整项目模型的访问。然而,在一个非常大的多项目构建中,这种方法可能不是最有效的。有一些 Gradle 构建,它们在层次结构上会有上百个的子项目。对于大型的多项目,构建的配置时间可能会变得很明显。可扩展性是 Gradle 的一项重要需求,因此从 V1.4 开始,新引入了一个试验性的“按需配置”模式。
Configuration injection feature and access to the complete project model are possible because every project is configured before the execution phase. Yet, this approach may not be the most efficient in a very large multi-project builds. There are Gradle builds with a hierarchy of hundreds of subprojects. Configuration time of huge multi-project builds may become noticeable. Scalability is an important requirement for Gradle. Hence, starting from version 1.4 new incubating 'configuration on demand' mode is introduced.

按需配置模式尝试只配置与所请求的任务相关的项目,这样就大大地改善了大型多项目构建的配置时间。长远来看,这种模式将成为默认模式,可能会是 Gradle 构建执行的唯一模式。按需配置功能还处于试验性阶段,因此无法保证每个构建都能正确。对于解耦的多项目构建(《第56.9节,解耦项目》),这个功能部件应该非常有效。在按需配置的模式中,项目配置如下:
Configuration on demand mode attempts to configure only projects that are relevant for requested tasks. This way, the configuration time of a large multi-project build is greatly improved. In the long term, this mode will become the default mode, possibly the only mode for Gradle build execution. The configuration on demand feature is incubating so not every build is guaranteed to work correctly. The feature should work very well for multi-project builds that have decoupled projects (Section 56.9, “Decoupled Projects”). In configuration on demand mode projects are configured as follows:

  • 根项目始终会被配置。这种方式支持典型的公共配置(allprojects 或subprojects 脚本块)。
    Root project is always configured. This way the typical common configuration is supported (allprojects or subprojects script blocks).
  • 执行构建的所在目录的项目也会被配置,但是只在没有任何任务的情况下执行 Gradle 时。这种方式当项目按需配置时,默认任务会正常运行。
    Project in the directory where the build is executed is also configured, but only when Gradle is executed without any tasks. This way the default tasks behave correctly when projects are configured on demand.
  • 标准项目的依赖被支持,并且使相关的项目也被配置。如果项目 A 对项目 B 有编译依赖,那么构建 A 的时候也会导致这两个项目的配置。
    The standard project dependencies are supported and makes relevant projects configured. If project A has a compile dependency on project B then building A causes configuration of both projects: A and B.
  • 通过任务路径定义的任务依赖也被支持,并且会导致相关的项目被配置。示例:someTask.dependsOn(":someOtherProject:someOtherTask")
    The task dependencies declared via task path are supported and cause relevant projects configured. Example: someTask.dependsOn(":someOtherProject:someOtherTask")
  • 通过从命令行(或 Tooling API)中的任务路径所请求的任务将导致相关的项目被配置。构建 'projectA:projectB:someTask' 会使项目 B 被配置。
    Task requested via task path from the command line (or Tooling API) causes the relevant project configured. Building 'projectA:projectB:someTask' causes configuration of projectB.

想试试这个新功能吗?要在每次构建时都按需配置,请参阅《第20.1节,通过 gradle.properties 配置构建环境》。 只对给定的构建按需配置,请参阅《附录D,Gradle 命令行》。
Eager to try out this new feature? To configure on demand with every build run see Section 20.1, “Configuring the build environment via gradle.properties”. To configure on demand just for given build please see Appendix D, Gradle Command Line.

56.1.2. 定义共同行为

56.1.2. Defining common behavior

我们有以下项目树。 它是一个多项目构建,有一个根项目 water 和子项目 bluewhale
We have the following project tree. This is a multi-project build with a root project water and a subproject bluewhale.

示例 56.1. 多项目树——water & bluewhale 项目 - Example 56.1. Multi-project tree - water & bluewhale projects

构建布局
Build layout

water/
  build.gradle
  settings.gradle
  bluewhale/

注︰ 此示例的代码可以在 Gradle 的二进制及源码分发包的samples/userguide/multiproject/firstExample/water 中找到。
Note: The code for this example can be found at samples/userguide/multiproject/firstExample/water which is in both the binary and source distributions of Gradle.

settings.gradle

include 'bluewhale'

那么,bluewhale 项目的构建脚本在哪里呢?在 Gradle 中,构建脚本是可选的。显然,对于单项目构建而言,没有构建脚本的项目没有多大意义。但对于多项目构建,情况就不同了。让我们看看 water 项目的构建脚本并执行它:
And where is the build script for the bluewhale project? In Gradle build scripts are optional. Obviously for a single project build, a project without a build script doesn't make much sense. For multiproject builds the situation is different. Let's look at the build script for the water project and execute it:

示例 56.2. water(父)项目的构建脚本 - Example 56.2. Build script of water (parent) project

build.gradle

Closure cl = { task -> println "I'm $task.project.name" }
task hello << cl
project(':bluewhale') {
    task hello << cl
}

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

> gradle -q hello
I'm water
I'm bluewhale

Gradle 允许你从任何构建脚本中访问多项目构建的任何项目。Project API 提供了一个叫做 project() 的方法,它将路径作为参数,返回此路径的 Project 对象。这种从任何构建脚本中配置项目构建的功能,我们称之为跨项目配置。Gradle 通过配置注入来实现它。
Gradle allows you to access any project of the multi-project build from any build script. The Project API provides a method called project(), which takes a path as an argument and returns the Project object for this path. The capability to configure a project build from any build script we call cross project configuration. Gradle implements this via configuration injection.

我们对这个 water 项目的构建脚本不是很满意,因为要为每个项目显式地添加任务很不方便。我们可以进行改善。让我们首先将另一个名为 krill 的项目添加到我们的多项目构建中。
We are not that happy with the build script of the water project. It is inconvenient to add the task explicitly for every project. We can do better. Let's first add another project called krill to our multi-project build.

示例 56.3. 多项目树——water & bluewhale & krill 项目 - Example 56.3. Multi-project tree - water, bluewhale & krill projects

构建布局
Build layout

water/
  build.gradle
  settings.gradle
  bluewhale/
  krill/

注︰ 此示例的代码可以在 Gradle 的二进制及源码分发包的 samples/userguide/multiproject/firstExample/water 中找到。
Note: The code for this example can be found at samples/userguide/multiproject/addKrill/water which is in both the binary and source distributions of Gradle.

settings.gradle

include 'bluewhale', 'krill'

现在我们改写 water 的构建脚本,并把它归结成为一行。
Now we rewrite the water build script and boil it down to a single line.

示例 56.4. Water 项目构建脚本 - Example 56.4. Water project build script

build.gradle

allprojects {
    task hello << { task -> println "I'm $task.project.name" }
}

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

> gradle -q hello
I'm water
I'm bluewhale
I'm krill

是刚才那个酷还是现在这个酷?它又是怎么实现的呢?Project API 提供了一个 allprojects 属性,它返回一个包含当前项目及其下面的所有子项目的列表。如果你使用一个闭包调用 allprojects ,那么闭包的语句将委派给与 allprojects 相关联的项目。你也可以通过 allprojects.each 来执行迭代,但这样写会比较冗长。
Is this cool or is this cool? And how does this work? The Project API provides a property allprojects which returns a list with the current project and all its subprojects underneath it. If you call allprojects with a closure, the statements of the closure are delegated to the projects associated with allprojects. You could also do an iteration via allprojects.each, but that would be more verbose.

其他构建系统使用继承作为定义公共行为的主要方法。稍后你会看到,我们也提供了项目继承。但 Gradle 使用配置注入作为定义共同行为的常用方法。我们认为它提供了一种非常强大且灵活的方式来配置多项目构建。
Other build systems use inheritance as the primary means for defining common behavior. We also offer inheritance for projects as you will see later. But Gradle uses configuration injection as the usual way of defining common behavior. We think it provides a very powerful and flexible way of configuring multiproject builds.

56.2. 子项目配置

56.2. Subproject configuration

Project API 还提供了一个属性只用于访问子项目。
The Project API also provides a property for accessing the subprojects only.

56.1.2. 定义共同行为

56.2.1. Defining common behavior

示例 56.5. 定义所有项目以及子项目的共同行为 - Example 56.5. Defining common behaviour of all projects and subprojects

build.gradle

allprojects {
    task hello << {task -> println "I'm $task.project.name" }
}
subprojects {
    hello << {println "- I depend on water"}
}

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

> gradle -q hello
I'm water
I'm bluewhale
- I depend on water
I'm krill
- I depend on water

56.2.2. 添加特定行为

56.2.2. Adding specific behavior

你可以在共同行为的上面再添加特定行为。通常我们会把项目的特定行为放在要应用该行为的项目的构建脚本里。但正如我们已经看到的,我们可以不这样做。我们可以这样为 bluewhale 项目添加项目的特定行为:
You can add specific behavior on top of the common behavior. Usually we put the project specific behavior in the build script of the project where we want to apply this specific behavior. But as we have already seen, we don't have to do it this way. We could add project specific behavior for the bluewhale project like this:

示例 56.6. 定义特定项目的特定行为 - Example 56.6. Defining specific behaviour for particular project

build.gradle

allprojects {
    task hello << {task -> println "I'm $task.project.name" }
}
subprojects {
    hello << {println "- I depend on water"}
}
project(':bluewhale').hello << {
    println "- I'm the largest animal that has ever lived on this planet."
}

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

> gradle -q hello
I'm water
I'm bluewhale
- I depend on water
- I'm the largest animal that has ever lived on this planet.
I'm krill
- I depend on water

正如前面所说,我们通常倾向于将特定于项目的行为放入该项目的构建脚本中。让我们重构并将一些项目特定行为添加到 krill 项目中。
As we have said, we usually prefer to put project specific behavior into the build script of this project. Let's refactor and also add some project specific behavior to the krill project.

示例 56.7. 为 krill 项目定义特定行为 - Example 56.7. Defining specific behaviour for project krill

构建布局
Build layout

water/
  build.gradle
  settings.gradle
  bluewhale/
    build.gradle
  krill/
    build.gradle

注︰ 此示例的代码可以在 Gradle 的二进制及源码分发包的samples/userguide/multiproject/spreadSpecifics/water 中找到。
Note: The code for this example can be found at samples/userguide/multiproject/spreadSpecifics/water which is in both the binary and source distributions of Gradle.

settings.gradle

include 'bluewhale', 'krill'

bluewhale/build.gradle

hello.doLast { println "- I'm the largest animal that has ever lived on this planet." }

krill/build.gradle

hello.doLast {
    println "- The weight of my species in summer is twice as heavy as all human beings."
}

build.gradle

allprojects {
    task hello << {task -> println "I'm $task.project.name" }
}
subprojects {
    hello << {println "- I depend on water"}
}

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

> gradle -q hello
I'm water
I'm bluewhale
- I depend on water
- I'm the largest animal that has ever lived on this planet.
I'm krill
- I depend on water
- The weight of my species in summer is twice as heavy as all human beings.

56.2.3. 项目过滤

56.2.3. Project filtering

要体现配置注入的更强大之处,让我们添加另一个项目 tropicalFish,并通过 water 项目的构建脚本向这个构建添加更多的行为。
To show more of the power of configuration injection, let's add another project called tropicalFish and add more behavior to the build via the build script of the water project.

56.2.3.1. 按名称过滤

56.2.3.1. Filtering by name

示例 56.8. 将自定义行为添加到某些项目中(按项目名称过滤) - Example 56.8. Adding custom behaviour to some projects (filtered by project name)

构建布局
Build layout

water/
  build.gradle
  settings.gradle
  bluewhale/
    build.gradle
  krill/
    build.gradle
  tropicalFish/

注︰ 此示例的代码可以在 Gradle 的二进制及源码分发包的 samples/userguide/multiproject/addTropical/water 中找到。
Note: The code for this example can be found at samples/userguide/multiproject/addTropical/water which is in both the binary and source distributions of Gradle.

settings.gradle

include 'bluewhale', 'krill', 'tropicalFish'

build.gradle

allprojects {
    task hello << {task -> println "I'm $task.project.name" }
}
subprojects {
    hello << {println "- I depend on water"}
}
configure(subprojects.findAll {it.name != 'tropicalFish'}) {
    hello << {println '- I love to spend time in the arctic waters.'}
}

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

> gradle -q hello
I'm water
I'm bluewhale
- I depend on water
- I love to spend time in the arctic waters.
- I'm the largest animal that has ever lived on this planet.
I'm krill
- I depend on water
- I love to spend time in the arctic waters.
- The weight of my species in summer is twice as heavy as all human beings.
I'm tropicalFish
- I depend on water

configure()方法接受一个 list 作为参数,并将配置应用到这个 list 里的项目。
The configure() method takes a list as an argument and applies the configuration to the projects in this list.

56.2.3.2. 通过属性过滤

56.2.3.2. Filtering by properties

我们可以选择使用项目的名称,或者是额外的项目属性来进行过滤。(请参阅《第13.4.2节,额外属性 >,以获取有关额外属性的更多信息。)
Using the project name for filtering is one option. Using extra project properties is another. (See Section 13.4.2, “Extra properties” for more information on extra properties.)

示例 56.9. 向某些项目添加自定义行为(按项目属性过滤) - Example 56.9. Adding custom behaviour to some projects (filtered by project properties)

构建布局
Build layout

water/
  build.gradle
  settings.gradle
  bluewhale/
    build.gradle
  krill/
    build.gradle
  tropicalFish/
    build.gradle

注︰ 此示例的代码可以在 Gradle 的二进制及源码分发包的 samples/userguide/multiproject/tropicalWithProperties/water 中找到。
Note: The code for this example can be found at samples/userguide/multiproject/tropicalWithProperties/water which is in both the binary and source distributions of Gradle.

settings.gradle

include 'bluewhale', 'krill', 'tropicalFish'

bluewhale/build.gradle

ext.arctic = true
hello.doLast { println "- I'm the largest animal that has ever lived on this planet." }

krill/build.gradle

ext.arctic = true
hello.doLast {
    println "- The weight of my species in summer is twice as heavy as all human beings."
}

tropicalFish/build.gradle

ext.arctic = false

build.gradle

allprojects {
    task hello << {task -> println "I'm $task.project.name" }
}
subprojects {
    hello {
        doLast {println "- I depend on water"}
        afterEvaluate { Project project ->
            if (project.arctic) { doLast {
                println '- I love to spend time in the arctic waters.' }
            }
        }
    }
}

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

> gradle -q hello
I'm water
I'm bluewhale
- I depend on water
- I'm the largest animal that has ever lived on this planet.
- I love to spend time in the arctic waters.
I'm krill
- I depend on water
- The weight of my species in summer is twice as heavy as all human beings.
- I love to spend time in the arctic waters.
I'm tropicalFish
- I depend on water

water 项目的构建文件中,我们使用了一个 afterEvaluate 通知。这意味着,我们所传的闭包将在子项目的构建脚本评估之后才进行评估。由于属性 arctic 设置在这些构建脚本中,所以我们必须这样做。在《第56.6节,依赖——哪些依赖?》中,你可以看到更多有关这个主题的信息。
In the build file of the water project we use an afterEvaluate notification. This means that the closure we are passing gets evaluated after the build scripts of the subproject are evaluated. As the property arctic is set in those build scripts, we have to do it this way. You will find more on this topic in Section 56.6, “Dependencies - Which dependencies?”

56.3. 多项目构建的执行规则

56.3. Execution rules for multi-project builds

当我们从根项目目录执行了 hello 任务时,会以一种直观的方式表现出来。不同项目中的所有 hello 任务都被执行了。让我们切换到 bluewhale 目录,看看如果我们在那里执行 Gradle,会发生什么。
When we have executed the hello task from the root project dir things behaved in an intuitive way. All the hello tasks of the different projects were executed. Let's switch to the bluewhale dir and see what happens if we execute Gradle from there.

示例 56.10. 从子项目运行构建 - Example 56.10. Running build from subproject

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

> gradle -q hello
I'm bluewhale
- I depend on water
- I'm the largest animal that has ever lived on this planet.
- I love to spend time in the arctic waters.

Gradle 的行为背后的基本规则很简单。Gradle 从当前目录开始,按层次结构往下查找名为 hello 的任务并执行它们。有一件事很重要。Gradle 始终评估多项目构建的每一个项目,并创建所有存在的任务对象。然后,根据任务名称参数和当前目录, Gradle 过滤出应该执行的任务。由于 Gradle 的跨项目配置,每个项目都必须在执行任何任务之前先评估。我们将在下一节仔细研究这一点。现在让我们看看最后一个 marine 的例子。 我们先将任务添加到 bluewhalekrill
The basic rule behind Gradle's behavior is simple. Gradle looks down the hierarchy, starting with the current dir, for tasks with the name hello an executes them. One thing is very important to note. Gradle always evaluates every project of the multi-project build and creates all existing task objects. Then, according to the task name arguments and the current dir, Gradle filters the tasks which should be executed. Because of Gradle's cross project configuration every project has to be evaluated before any task gets executed. We will have a closer look at this in the next section. Let's now have our last marine example. Let's add a task to bluewhale and krill.

示例 56.11. 项目的评估和执行 - Example 56.11. Evaluation and execution of projects

bluewhale/build.gradle

ext.arctic = true
hello << { println "- I'm the largest animal that has ever lived on this planet." }

task distanceToIceberg << {
    println '20 nautical miles'
}

krill/build.gradle

ext.arctic = true
hello << { println "- The weight of my species in summer is twice as heavy as all human beings." }

task distanceToIceberg << {
    println '5 nautical miles'
}

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

> gradle -q distanceToIceberg
20 nautical miles
5 nautical miles

不使用 -q 选项的输出结果︰
Here the output without the -q option:

示例 56.12. 项目的评估和执行 - Example 56.12. Evaluation and execution of projects

gradle distanceToIceberg 的输出结果
Output of gradle distanceToIceberg

> gradle distanceToIceberg
:bluewhale:distanceToIceberg
20 nautical miles
:krill:distanceToIceberg
5 nautical miles

BUILD SUCCESSFUL

Total time: 1 secs

构建从 water 项目执行。watertropicalFish 都没有一个叫 distanceToIceberg的任务,但 Gradle 不关心这个问题。上面提到的简单规则是:执行这个层次结构下叫这个名字的所有任务。如果没有这样的任务,只能报错!
The build is executed from the water project. Neither water nor tropicalFish have a task with the name distanceToIceberg. Gradle does not care. The simple rule mentioned already above is: Execute all tasks down the hierarchy which have this name. Only complain if there is no such task!

56.4. 按绝对路径运行任务

56.4. Running tasks by their absolute path

如我们所见,你可以通过进入任意的子项目目录并从该处执行构建来运行多项目构建。从当前目录开始的项目层次结构中,所有名字匹配的任务都会被执行。不过 Gradle 也提供了按绝对路径来执行任务的方式(另请参阅《第56.5节,项目和任务路径》):
As we have seen, you can run a multi-project build by entering any subproject dir and execute the build from there. All matching task names of the project hierarchy starting with the current dir are executed. But Gradle also offers to execute tasks by their absolute path (see also Section 56.5, “Project and task paths”):

示例 56.13. 按绝对路径运行任务 - Example 56.13. Running tasks by their absolute path

gradle -q :hello :krill:hello hello 的输出结果
Output of gradle -q :hello :krill:hello hello

> gradle -q :hello :krill:hello hello
I'm water
I'm krill
- I depend on water
- The weight of my species in summer is twice as heavy as all human beings.
- I love to spend time in the arctic waters.
I'm tropicalFish
- I depend on water

构建从 tropicalFish 项目执行。我们执行了 waterhellotropicalFish 这三个项目的 hello 任务。 前两个任务由其绝对路径指定,最后一个任务通过上面所述的名称匹配机制执行。
The build is executed from the tropicalFish project. We execute the hello tasks of the water, the krill and the tropicalFish project. The first two tasks are specified by there absolute path, the last task is executed on the name matching mechanism described above.

56.5. 项目和任务路径

56.5. Project and task paths

项目路径具有以下模式:它始终以冒号开头,表示根项目。根项目是路径中唯一不由其名称指定的项目。路径 :bluewhale 对应于上面示例中的文件系统路径 water/bluewhale
A project path has the following pattern: It starts always with a colon, which denotes the root project. The root project is the only project in a path that is not specified by its name. The path :bluewhale corresponds to the file system path water/bluewhale in the case of the example above.

任务的路径只是它的项目路径加上任务名称。 例如 :bluewhale:hello。在项目中,您可以通过任务的名称来处理同一项目的任务,这被解释为相对路径。
The path of a task is simply its project path plus the task name. For example :bluewhale:hello. Within a project you can address a task of the same project just by its name. This is interpreted as a relative path.

最初 Gradle 使用 “/” 字符作为自然路径分隔符。在引入目录任务(见《第14.1节,目录创建)》就不能再这样做了,因为目录任务的名称包含 “/” 字符。
Originally Gradle has used the '/' character as a natural path separator. With the introduction of directory tasks (see Section 14.1, “Directory creation”) this was no longer possible, as the name of the directory task contains the '/' character.

56.6. 依赖——哪些依赖?

56.6. Dependencies - Which dependencies?

上一节的例子比较特殊,因为项目里没有执行依赖,只有配置依赖。下面是一个不同的例子:
The examples from the last section were special, as the projects had no Execution Dependencies. They had only Configuration Dependencies. Here is an example where this is different:

56.6.1. 执行依赖

56.6.1. Execution dependencies

56.6.1.1. 依赖和执行顺序

56.6.1.1. Dependencies and execution order

示例 56.14. 依赖和执行顺序 - Example 56.14. Dependencies and execution order

构建布局
Build layout

messages/
  settings.gradle
  consumer/
    build.gradle
  producer/
    build.gradle

注︰ 此示例的代码可以在 Gradle 二进制和源码分发包的 samples/userguide/multiproject/dependencies/firstMessages/messages 中找到。
Note: The code for this example can be found at samples/userguide/multiproject/dependencies/firstMessages/messages which is in both the binary and source distributions of Gradle.

settings.gradle

include 'consumer', 'producer'

consumer/build.gradle

task action << {
    println("Consuming message: ${rootProject.producerMessage}")
}

producer/build.gradle

task action << {
    println "Producing message:"
    rootProject.producerMessage = 'Watch the order of execution.'
}

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

> gradle -q action
Consuming message: null
Producing message:

它并没有成功。如果没有定义别的,那么 Gradle 将按字母数字的顺序执行任务。因此,:consumer:action 会在 :producter:action 之前执行 。 让我们尝试用一个技巧来解决这个问题,并将生产者项目重命名为 aProducer
This did not work out. If nothing else is defined, Gradle executes the task in alphanumeric order. Therefore :consumer:action is executed before :producer:action. Let's try to solve this with a hack and rename the producer project to aProducer.

示例 56.15. 依赖和执行顺序 - Example 56.15. Dependencies and execution order

构建布局
Build layout

messages/
  settings.gradle
  aProducer/
    build.gradle
  consumer/
    build.gradle

settings.gradle

include 'consumer', 'aProducer'

aProducer/build.gradle

task action << {
    println "Producing message:"
    rootProject.producerMessage = 'Watch the order of execution.'
}

consumer/build.gradle

task action << {
    println("Consuming message: ${rootProject.producerMessage}")
}

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

> gradle -q action
Producing message:
Consuming message: Watch the order of execution.

现在我们来看这个技巧。我们简单地切换到 consumer 目录并执行构建。
Now we take the air out of this hack. We simply switch to the consumer dir and execute the build.

示例 56.16. 依赖和执行顺序 - Example 56.16. Dependencies and execution order

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

> gradle -q action
Consuming message: null

对于 Gradle 而言,这两个 action 任务没有关联。如果你从 messages 项目执行构建,Gradle 将同时执行它们两个,因为它们有相同的名称并且都在层次结构下。在最后一个示例中,只有一个 action 是在这个层次结构下,因此它是唯一一个被执行的任务。我们需要比这种方式更好的实现。
For Gradle the two action tasks are just not related. If you execute the build from the messages project Gradle executes them both because they have the same name and they are down the hierarchy. In the last example only one action was down the hierarchy and therefore it was the only task that got executed. We need something better than this hack.

56.6.1.2. 声明依赖

56.6.1.2. Declaring dependencies

示例 56.17. 声明依赖 - Example 56.17. Declaring dependencies

构建布局
Build layout

messages/
  settings.gradle
  consumer/
    build.gradle
  producer/
    build.gradle

注︰ 此示例的代码可以在 Gradle 二进制和源码分发包的 samples/userguide/multiproject/dependencies/messagesWithDependencies/messages 中找到。
Note: The code for this example can be found at samples/userguide/multiproject/dependencies/messagesWithDependencies/messages which is in both the binary and source distributions of Gradle.

settings.gradle

include 'consumer', 'producer'

consumer/build.gradle

task action(dependsOn: ":producer:action") << {
    println("Consuming message: ${rootProject.producerMessage}")
}

producer/build.gradle

task action << {
    println "Producing message:"
    rootProject.producerMessage = 'Watch the order of execution.'
}

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

> gradle -q action
Producing message:
Consuming message: Watch the order of execution.

consumer目录运行将得到:
Running this from the consumer directory gives:

示例 56.18. 声明依赖 - Example 56.18. Declaring dependencies

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

> gradle -q action
Producing message:
Consuming message: Watch the order of execution.

我们现在声明了 consumer 项目的 action 任务对 producer 项目的 action 任务有执行依赖
We have now declared that the action task in the consumer project has an execution dependency on the action task on the producer project.

56.6.1.3. 跨项目任务依赖的本质

56.6.1.3. The nature of cross project task dependencies

当然,不同项目之间的任务依赖并不局限于名字相同。让我们更改任务名并执行构建。
Of course, task dependencies across different projects are not limited to tasks with the same name. Let's change the naming of our tasks and execute the build.

示例 56.19. 跨项目任务依赖 - Example 56.19. Cross project task dependencies

consumer/build.gradle

task consume(dependsOn: ':producer:produce') << {
    println("Consuming message: ${rootProject.producerMessage}")
}

producer/build.gradle

task produce << {
    println "Producing message:"
    rootProject.producerMessage = 'Watch the order of execution.'
}

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

> gradle -q consume
Producing message:
Consuming message: Watch the order of execution.

56.6.2. 配置时依赖

56.6.2. Configuration time dependencies

在进入 Java 领域之前,让我们再使用生产者——消费者构建的例子。我们添加一个属性到生产者项目,并且现在创建一个消费者对生产者的配置时间依赖。
Let's have one more example with our producer-consumer build before we enter Java land. We add a property to the producer project and create now a configuration time dependency from consumer on producer.

示例 56.20. 配置时依赖 - Example 56.20. Configuration time dependencies

consumer/build.gradle

def message = rootProject.producerMessage

task consume << {
    println("Consuming message: " + message)
}

producer/build.gradle

rootProject.producerMessage = 'Watch the order of evaluation.'

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

> gradle -q consume
Consuming message: null

项目的默认评估顺序是字母数字(对于同一嵌套级别)。因此,consumer 项目评估会在 producer 项目之前,并且 producerkey 值在它被 consumer 项目读取之后设置。Gradle 为此提供了一个解决方案。
The default evaluation order of the projects is alphanumeric (for the same nesting level). Therefore the consumer project is evaluated before the producer project and the key value of the producer is set after it is read by the consumer project. Gradle offers a solution for this.

示例 56.21. 配置时依赖——evaluationDependsOn - Example 56.21. Configuration time dependencies - evaluationDependsOn

consumer/build.gradle

evaluationDependsOn(':producer')

def message = rootProject.producerMessage

task consume << {
    println("Consuming message: " + message)
}

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

> gradle -q consume
Consuming message: Watch the order of evaluation.

命令 evaluationDependsOn 触发 producerconsumer 之前 进行评估。这个例子是特意展示这个机制的。这种情况下,通过在执行时读取关键属性,会是一种更简单的解决方案。
The command evaluationDependsOn triggers the evaluation of producer before consumer is evaluated. The example is a bit contrived for the sake of showing the mechanism. In this case there would be an easier solution by reading the key property at execution time.

示例 56.22. 配置时依赖 - Example 56.22. Configuration time dependencies

consumer/build.gradle

task consume << {
    println("Consuming message: ${rootProject.producerMessage}")
}

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

> gradle -q consume
Consuming message: Watch the order of evaluation.

配置依赖与执行依赖有很大不同。配置依赖是项目之间的,而执行依赖则总是被解析为任务依赖。另一个区别是,所有项目总是都会被配置,即使你是从子项目启动构建的。默认的配置顺序是通常所需要的从顶至下的顺序。
Configuration dependencies are very different to execution dependencies. Configuration dependencies are between projects whereas execution dependencies are always resolved to task dependencies. Another difference is that always all projects are configured, even when you start the build from a subproject. The default configuration order is top down, which is usually what is needed.

如果要把默认配置的顺序改为从下到上,这意味着一个项目的配置将会依赖于它的子项目的配置,可以使用 evaluationDependsOnChildren() 方法。
To change the the default configuration order to be bottom up, That means that a project configuration depends on the configuration of its child projects, the evaluationDependsOnChildren() method can be used.

嵌套级别相同时,配置顺序取决于字母数字位置。最常见的用例是共享公共生命周期的多项目构建(例如,所有项目都使用 Java 插件)。如果通过 dependsOn 声明了不同项目之间的执行依赖,该方法的默认行为也会创建两个项目之间的配置依赖。因此,你可能不需要显式定义配置依赖。
On the same nesting level the configuration order depends on the alphanumeric position. The most common use case is to have multi-project builds that share a common lifecycle (e.g. all projects use the Java plugin). If you declare with dependsOn a execution dependency between different projects, the default behavior of this method is to create also a configuration dependency between the two projects. Therefore it is likely that you don't have to define configuration dependencies explicitly.

56.6.3. 真实生活示例

56.6.3. Real life examples

Gradle 的多项目功能是由真实生活用例所驱动的。描述这种用例的第一个示例包括了两个 webapplication 项目,以及一个创建分发的父项目。[21] 对于这个例子,我们只使用一个构建脚本,并执行跨项目配置
Gradle's multi-project features are driven by real life use cases. The first example for describing such a use case, consists of two webapplication projects and a parent project that creates a distribution out of them. [21] For the example we use only one build script and do cross project configuration.

示例 56.23. 依赖——真实生活示例——跨项目配置 - Example 56.23. Dependencies - real life example - crossproject configuration

构建布局
Build layout

webDist/
  settings.gradle
  build.gradle
  date/
    src/main/java/
      org/gradle/sample/
        DateServlet.java
  hello/
    src/main/java/
      org/gradle/sample/
        HelloServlet.java

注︰ 此示例的代码可以在 Gradle 二进制和源码分发包的 samples/userguide/multiproject/dependencies/webDist/messages 中找到。
Note: The code for this example can be found at samples/userguide/multiproject/dependencies/webDist which is in both the binary and source distributions of Gradle.

settings.gradle

include 'date', 'hello'

build.gradle

allprojects {
    apply plugin: 'java'
    group = 'org.gradle.sample'
    version = '1.0'
}

subprojects {
    apply plugin: 'war'
    repositories {
        mavenCentral()
}
    }
    dependencies {
        compile "javax.servlet:servlet-api:2.5"
    }
}

task explodedDist(dependsOn: assemble) << {
    File explodedDist = mkdir("$buildDir/explodedDist")
    subprojects.each {project ->
        project.tasks.withType(Jar).each {archiveTask ->
            copy {
                from archiveTask.archivePath
                into explodedDist
            }
        }
    }
}

我们有一套有意思的依赖。显然,datehello 项目都对 webDist配置依赖,因为 web 应用程序项目的所有构建逻辑都由 webDist 注入。执行依赖则反过来,因为 webDist 依赖于 datehello 的构建工件。这里甚至还有第三个依赖。webDistdatehello配置依赖,因为它需要知道 archivePath。但是,它会在执行时才要求提供该信息。因此,我们没有循环依赖。
We have an interesting set of dependencies. Obviously the date and hello projects have a configuration dependency on webDist, as all the build logic for the webapp projects is injected by webDist. The execution dependency is in the other direction, as webDist depends on the build artifacts of date and hello. There is even a third dependency. webDist has a configuration dependency on date and hello because it needs to know the archivePath. But it asks for this information at execution time. Therefore we have no circular dependency.

在多项目构建中,这样的或其他的依赖模式是很常见的。如果一个构建系统不支持这种模式,那你要么无法解决问题,要么需要用一些恶心的 hack 方法,这样就会很难维护,并且作为构建分支将严重影响你的生产力。
Such and other dependency patterns are daily bread in the problem space of multi-project builds. If a build system does not support such patterns, you either can't solve your problem or you need to do ugly hacks which are hard to maintain and massively afflict your productivity as a build master.

56.7. 项目库依赖

56.7. Project lib dependencies

如果一个项目在其编译路径上需要另一个项目生成的 jar 呢?并且不仅是 jar,还有它的传递依赖呢?显然,这是 Java 多项目构建中非常常见的情况。如《第50.4.3节,项目依赖》中所述,Gradle 为此提供了项目库依赖。
What if one projects needs the jar produced by another project in its compile path? And not just the jar but also the transitive dependencies of this jar? Obviously this is a very common use case for Java multi-project builds. As already mentioned in Section 50.4.3, “Project dependencies”, Gradle offers project lib dependencies for this.

示例 56.24. 项目库依赖 - Example 56.24. Project lib dependencies

构建布局
Build layout

java/
  settings.gradle
  build.gradle
  api/
    src/main/java/
      org/gradle/sample/
        api/
          Person.java
        apiImpl/
          PersonImpl.java
  services/personService/
    src/
      main/java/
        org/gradle/sample/services/
          PersonService.java
      test/java/
        org/gradle/sample/services/
          PersonServiceTest.java
  shared/
    src/main/java/
      org/gradle/sample/shared/
        Helper.java

注︰ 此示例的代码可以在 Gradle 二进制和源码分发包的 samples/userguide/multiproject/dependencies/java/messages 中找到。
Note: The code for this example can be found at samples/userguide/multiproject/dependencies/java which is in both the binary and source distributions of Gradle.


我们有这些项目:sharedapipersonServicepersonService 对其他两个项目有一个库依赖,apishared上有一个库依赖。[22]
We have the projects shared, api andpersonService. personService has a lib dependency on the other two projects. api has a lib dependency on shared. [22]

示例 56.25. 项目库依赖 - Example 56.25. Project lib dependencies

settings.gradle

include 'api', 'shared', 'services:personService'

build.gradle

subprojects {
    apply plugin: 'java'
    group = 'org.gradle.sample'
    version = '1.0'
    repositories {
        mavenCentral()
}
    }
    dependencies {
        testCompile "junit:junit:4.11"
    }
}

project(':api') {
    dependencies {
        compile project(':shared')
    }
}

project(':services:personService') {
    dependencies {
        compile project(':shared'), project(':api')
    }
}

所有构建逻辑都在根项目的 build.gradle 中。 [23]依赖是执行依赖的一种特殊形式。它会首先构建另一个项目,并把其他项目的 jar 文件和类一起添加到类路径中。它还将其他项目的依赖也添加到类路径。因此你可以进入 api 目录并触发 gradle compile。首先构建 shared,然后构建 api。项目依赖支持部分多项目构建。
All the build logic is in the build.gradle of the root project. [23] A lib dependency is a special form of an execution dependency. It causes the other project to be built first and adds the jar with the classes of the other project to the classpath. It also adds the dependencies of the other project to the classpath. So you can enter the api directory and trigger a gradle compile. First shared is built and then api is built. Project dependencies enable partial multi-project builds.

如果你是从 Maven 转过来的,你会对这一点很满意的。如果你是从 Ivy 转过来,你可能还想要一些更细粒度的控制。Gradle 向你提供了这种控制:
If you come from Maven land you might be perfectly happy with this. If you come from Ivy land, you might expect some more fine grained control. Gradle offers this to you:

示例 56.26. 对依赖的细粒度控制 - Example 56.26. Fine grained control over dependencies

build.gradle

subprojects {
    apply plugin: 'java'
    group = 'org.gradle.sample'
    version = '1.0'
}

project(':api') {
    configurations {
        spi
    }
    dependencies {
        compile project(':shared')
    }
    task spiJar(type: Jar) {
        baseName = 'api-spi'
        dependsOn classes
        from sourceSets.main.output
        include('org/gradle/sample/api/**')
    }
    artifacts {
        spi spiJar
    }
}

project(':services:personService') {
    dependencies {
        compile project(':shared')
        compile project(path: ':api', configuration: 'spi')
        testCompile "junit:junit:4.11", project(':api')
    }
}

Java 插件将每个默认的 jar 添加到包含所有类的项目库中。在这个例子中,我们创建了一个额外的库,它只包含 api 项目的接口。 我们把这个库指定给一个新的依赖配置。对于 person service,我们声明这个项目应该只针对 api 接口进行编译,但使用 api 中的所有类进行测试。
The Java plugin adds per default a jar to your project libraries which contains all the classes. In this example we create an additional library containing only the interfaces of the api project. We assign this library to a new dependency configuration. For the person service we declare that the project should be compiled only against the api interfaces but tested with all classes from api.

56.7.1. 禁用依赖项目的构建

56.7.1. Disabling the build of dependency projects

有时在进行局部构建的时候,你不希望依赖的项目也进行构建。如果想禁用依赖项目的构建,可以在运行 Gradle 的时候使用 -a 选项。
Sometimes you don't want depended on projects to be built when doing a partial build. To disable the build of the depended on projects you can run Gradle with the -a option.

56.8. 并行项目执行

56.8. Parallel project execution

随着在开发人员的台式机和 CI 服务器上,有越来越多的 CPU 内核可用,Gradle 能够充分利用这些处理资源就很重要。更具体地说,并行执行将尝试:
With more and more CPU cores available on developer desktops and CI servers, it is important that Gradle is able to fully utilise these processing resources. More specifically, the parallel execution attempts to:

  • 通过让执行进行 IO 绑定,或者是其他不消耗所有可用资源的方式,减少多项目构建的总构建时间。
    Reduce total build time for a multi-project build where execution is IO bound or otherwise does not consume all available CPU resources.
  • 更快地反馈小项目的执行,而不必等待其他项目的完成。
    Provide faster feedback for execution of small projects without awaiting completion of other projects.

虽然 Gradle 已通过 Test.setMaxParallelForks() 提供了并行测试执行,本节中描述的功能是项目级别的并行执行。并行执行是一个实验性的功能,请使用它,并让我们知道它是如何为你处理的。
Although Gradle already offers parallel test execution via Test.setMaxParallelForks() the feature described in this section is parallel execution at a project level. Parallel execution is an incubating feature. Please use it and let us know how it works for you.

并行项目执行允许在一个解耦的多项目构建中的一些单独项目并行执行(另请参阅《第56.9节,解耦项目》)。虽然并行执行并不严格要求在配置时进行解耦,但它的长期目标是提供一组强大的功能部件,用于完全解耦项目。这些功能包括:
Parallel project execution allows the separate projects in a decoupled multi-project build to be executed in parallel (see also: Section 56.9, “Decoupled Projects”). While parallel execution does not strictly require decoupling at configuration time, the long-term goal is to provide a powerful set of features that will be available for fully decoupled projects. Such features include:

并行执行是怎么做的?首先,你需要告诉 Gradle 使用并行模式。你可以使用命令行参数(《附录D,Gradle 命令行》),或者是配置你的构建环境(《第20.1节,通过 gradle.properties 配置构建环境》)。除非你指定并行线程的个数,否则 Gradle 将根据可用的 CPU 内核来选择正确的线程数。当执行一个任务时,每个并行的工作线程都专用于一个给定的项目。这意味着同一项目中的两个任务永远不会并行执行。因此,只有多项目构建可以利用并行执行。并行执行完全支持任务依赖,并且并行线程将首先开始执行上游任务。记住,解耦的任务按字母顺序调度,且顺序执行,并不是真正的以并行模式执行。你需要确保正确声明了任务依赖,以避免排序问题。
How does the parallel execution work? First, you need to tell Gradle to use the parallel mode. You can use the command line argument (Appendix D, Gradle Command Line) or configure your build environment (Section 20.1, “Configuring the build environment via gradle.properties”). Unless you provide specific number of parallel threads Gradle attempts to choose the right number based on available CPU cores. Every parallel worker exclusively owns a given project while executing a task. This means that 2 tasks from the same project are never executed in parallel. Therefore only multi-project builds can take advantage of parallel execution. Task dependencies are fully supported and parallel workers will start executing upstream tasks first. Bear in mind that the alphabetical scheduling of decoupled tasks, known from the sequential execution, does not really work in parallel mode. You need to make sure the task dependencies are declared correctly to avoid ordering issues.

56.9. 解耦的项目

56.9. Decoupled Projects

Gradle 允许任何项目在配置和执行阶段期间访问其他项目。虽然这为构建作者提供了很大的控制性和灵活性,但也限制了 Gradle 在构建这些项目时本身所具有的灵活性。例如,项目的紧耦合会阻碍 Gradle 以并行方式构建多项目,或阻碍以预构建的工件替代项目依赖。
Gradle allows any project to access any other project during both the configuration and execution phases. While this provides a great deal of power and flexibility to the build author, it also limits the flexibility that Gradle has when building those projects. For instance, this tight coupling of projects effectively prevents Gradle from building multiple projects in parallel, or from substituting a pre-built artifact in place of a project dependency.

如果两个项目不直接访问彼此的项目模型,那么它们就被认为是解耦的。解耦的项目可能只在声明依赖方面会互相影响:项目依赖(《第50.4.3节,项目依赖》),或者是任务依赖(《第6.5节,任务依赖》)。任何其他形式的项目交互(即通过修改另一个项目对象或从另一个项目对象中读值)都会导致项目耦合。
Two projects are said to be decoupled if they do not directly access each other's project model. Decoupled projects may only interact in terms of declared dependencies: project dependencies (Section 50.4.3, “Project dependencies”) and/or task dependencies (Section 6.5, “Task dependencies”). Any other form of project interaction (i.e. by modifying another project object or by reading a value from another project object) causes the projects to be coupled.

通过使用配置注入(《第56.1节,跨项目配置》),是一种很常见的使项目耦合的方式。它可能不明显,但是使用关键的 Gradle 功能如 allprojectssubproject 关键字,会自动导致你的项目耦合。这是因为这些关键字在定义项目的 build.gradle 文件中使用。而通常这是一个“根项目”,它只定义公共配置而不执行其他操作,但对 Gradle 而言这个根项目仍然是一个完全成熟的项目,并且通过使用 allprojects,该项目实际上就和所有其他项目耦合了。
A very common way for projects to be coupled is by using configuration injection (Section 56.1, “Cross project configuration”). It may not be immediately apparent, but using key Gradle features like the allprojects and subprojects keywords automatically cause your projects to be coupled. This is because these keywords are used in a build.gradle file, which defines a project. Often this is a "root project" that does nothing more than define common configuration, but as far as Gradle is concerned this root project is still a fully-fledged project, and by using allprojects that project is effectively coupled to all other projects.

这意味着使用任何形式的共享构建脚本逻辑或配置注入(allprojectsubprojects等)都会导致你的项目耦合。当我们扩展项目解耦的概念,并提供利用去耦项目的功能时,我们也将引入新功能来帮助你解决常见的情况(如配置注入)而不会导致项目耦合。
This means that using any form of shared build script logic or configuration injection (allprojects, subprojects, etc.) will cause your projects to be coupled. As we extend the concept of project decoupling and provide features that take advantage of decoupled projects, we will also introduce new features to help you to solve common use cases (like configuration injection) without causing your projects to be coupled.

56.10. 多项目构建和测试

56.10. Multi-Project Building and Testing

Java 插件的 build 任务通常用于一个项目的编译、测试和执行代码风格检查(如果使用了 CodeQuality 插件的话)。在多项目构建中,你可能经常想在一系列项目中执行所有这些任务。buildNeededbuildDependents 任务能够帮助完成这一点。
The build task of the Java plugin is typically used to compile, test, and perform code style checks (if the CodeQuality plugin is used) of a single project. In multi-project builds you may often want to do all of these tasks across a range of projects. The buildNeeded and buildDependents tasks can help with this.

我们使用示例 56.25,项目库依赖中显示的项目结构。在此示例中,:services:personservice 依赖于 :api 和 :shared。:api 项目还依赖于:shared。
Let's use the project structure shown in Example 56.25, “Project lib dependencies”. In this example :services:personservice depends on both :api and :shared. The :api project also depends on :shared.

假设你正在处理 :api 这一个项目,你做了一些修改,但由于执行了 clean 以来还没有构建整个项目。你希望构建所有必需的支持 jar,但只对这个做了修改的项目执行代码质量和单元测试,这正是 build 任务执行的。
Assume you are working on a single project, the :api project. You have been making changes, but have not built the entire project since performing a clean. You want to build any necessary supporting jars, but only perform code quality and unit tests on the project you have changed. The build task does this.

示例 56.27. 构建和测试单个项目 - Example 56.27. Build and Test Single Project

gradle :api:build 的输出结果
Output of gradle :api:build

> gradle :api:build
:shared:compileJava
:shared:processResources
:shared:classes
:shared:jar
:api:compileJava
:api:processResources
:api:classes
:api:jar
:api:assemble
:api:compileTestJava
:api:processTestResources
:api:testClasses
:api:test
:api:check
:api:build

BUILD SUCCESSFUL

Total time: 1 secs

当你在典型的开发周期中反复构建并测试对 :api 项目所作的修改(要知道你只修改了这个项目中的一个文件)时,你可能不想查看 :shared 项目中所修改的内容时承担 :shared:compile 检查的成本。添加 -a 选项能够让 Gradle 使用缓存的 jar 来解析所有项目库依赖,而不会尝试重新构建所依赖的项目。
While you are working in a typical development cycle repeatedly building and testing changes to the :api project (knowing that you are only changing files in this one project), you may not want to even suffer the expense of :shared:compile checking to see what has changed in the :shared project. Adding the -a option will cause Gradle to use cached jars to resolve any project lib dependencies and not try to re-build the depended on projects.

示例 56.28. 部分构建和测试单个项目 - Example 56.28. Partial Build and Test Single Project

gradle :api:build 的输出结果
Output of gradle -a :api:build

> gradle -a :api:build
:api:compileJava
:api:processResources
:api:classes
:api:jar
:api:assemble
:api:compileTestJava
:api:processTestResources
:api:testClasses
:api:test
:api:check
:api:build

BUILD SUCCESSFUL

Total time: 1 secs

如果你刚从版本控制系统中获取到最新的代码,其中包含了 :api 所依赖的其他项目的修改,那么你可能不只想构建所依赖的所有项目,还要对其进行测试。buildNeeded 任务也用于测试 testRuntime 配置中项目库依赖所配置的所有项目。
If you have just gotten the latest version of source from your version control system which included changes in other projects that :api depends on, you might want to not only build all the projects you depend on, but test them as well. The buildNeeded task also tests all the projects from the project lib dependencies of the testRuntime configuration.

示例 56.29. 构建和测试所依赖的项目 - Example 56.29. Build and Test Depended On Projects

gradle: api:buildNeeded 的输出结果
Output of gradle :api:buildNeeded

> gradle :api:buildNeeded
:shared:compileJava
:shared:processResources
:shared:classes
:shared:jar
:api:compileJava
:api:processResources
:api:classes
:api:jar
:api:assemble
:api:compileTestJava
:api:processTestResources
:api:testClasses
:api:test
:api:check
:api:build
:shared:assemble
:shared:compileTestJava
:shared:processTestResources
:shared:testClasses
:shared:test
:shared:check
:shared:build
:shared:buildNeeded
:api:buildNeeded

BUILD SUCCESSFUL

Total time: 1 secs

你还可能想要重构 :api 项目中被其他项目使用的部分内容。如果你做这一类的修改,那么只测试 :api 项目是不够的,你还需要测试依赖于 :api 项目的所有项目。buildDependents 任务也用于测试依赖于(在 testRuntime 配置中)指定项目上的所有项目。
You also might want to refactor some part of the :api project that is used in other projects. If you make these types of changes, it is not sufficient to test just the :api project, you also need to test all projects that depend on the :api project. The buildDependents task also tests all the projects that have a project lib dependency (in the testRuntime configuration) on the specified project.

示例 56.29. 构建和测试依赖它的项目 - Example 56.30. Build and Test Dependent Projects

gradle :api:buildDependents 的输出结果
Output of gradle :api:buildDependents

> gradle :api:buildDependents
:shared:compileJava
:shared:processResources
:shared:classes
:shared:jar
:api:compileJava
:api:processResources
:api:classes
:api:jar
:api:assemble
:api:compileTestJava
:api:processTestResources
:api:testClasses
:api:test
:api:check
:api:build
:services:personService:compileJava
:services:personService:processResources
:services:personService:classes
:services:personService:jar
:services:personService:assemble
:services:personService:compileTestJava
:services:personService:processTestResources
:services:personService:testClasses
:services:personService:test
:services:personService:check
:services:personService:build
:services:personService:buildDependents
:api:buildDependents

BUILD SUCCESSFUL

Total time: 1 secs

最后,你可能想要构建和测试所有项目中的所有内容。在根项目文件夹中运行的任何任务,都会导致所有子项目中的同名任务也被运行。因此,你可以只运行 gradle build 来构建和测试所有项目。
Finally, you may want to build and test everything in all projects. Any task you run in the root project folder will cause that same named task to be run on all the children. So you can just run gradle build to build and test all projects.

56.11. 属性和方法的继承

56.11. Property and method inheritance

项目中声明的属性和方法将继承到所有子项目。这是配置注入的替代方法,但我们认为,继承的模式并不能反映多项目构建的问题空间。在本用户指南的未来版本中,我们可能会写一下与之有关的更多内容。
Properties and methods declared in a project are inherited to all its subprojects. This is an alternative to configuration injection. But we think that the model of inheritance does not reflect the problem space of multi-project builds very well. In a future edition of this user guide we might write more about this.

方法继承可能值得使用,尽管 Gradle 的配置注入还不支持方法(但会在以后的版本中支持的)。
Method inheritance might be interesting to use as Gradle's Configuration Injection does not support methods yet (but will in a future release).

你可能想知道,为什么我们实现了一个我们显然不喜欢的功能。原因之一是其他工具也有这个功能,而我们希望在功能比较中有这一点:)。我们想为我们的用户提供一种选择。
You might be wondering why we have implemented a feature we obviously don't like that much. One reason is that it is offered by other tools and we want to have the check mark in a feature comparison :). And we like to offer our users a choice.

56.12. 总结

56.12. Summary

写这一章相当耗费精力,可能你读起来也有同样的感受。我们对这一章最后要说的是,使用 Gradle 的多项目构建通常难。你需要记住五个元素:allproject subprojectevaluationDependsOnevaluationDependsOnChildren 和项目库依赖。[24] 使用这些元素,并记住 Gradle 有不同的配置和执行阶段,你就已经可以很灵活地运用它。但当你进入一个陡峭的领域时,Gradle 不会成为你的障碍,并且通常会伴随你带你到峰顶。
Writing this chapter was pretty exhausting and reading it might have a similar effect. Our final message for this chapter is that multi-project builds with Gradle are usually not difficult. There are five elements you need to remember: allprojects, subprojects, evaluationDependsOn, evaluationDependsOnChildren and project lib dependencies. [24] With those elements, and keeping in mind that Gradle has a distinct configuration and execution phase, you have already a lot of flexibility. But when you enter steep territory Gradle does not become an obstacle and usually accompanies and carries you to the top of the mountain.



[21] 我们的实际用例是使用 http://lucene.apache.org/solr,在上面你访问的每个索引都需要一个单独的 war。这就是我们创建 webapps 分发包的原因之一。Resin servlet 容器允许我们让这样的分发点指向一个基本安装的 servlet 容器中。
[21] The real use case we had, was using http://lucene.apache.org/solr, where you need a separate war for each index you are accessing. That was one reason why we have created a distribution of webapps. The Resin servlet container allows us, to let such a distribution point to a base installation of the servlet container.

[22] services 也是一个项目,但我们只是把它用作一个容器。它没有构建脚本,并且其他构建脚本也没有向它注入内容。
[22] services is also a project, but we use it just as a container. It has no build script and gets nothing injected by another build script.

[23] 我们在这里实现,是因为它使布局更容易。我们通常将项目的具体内容放在各自项目的构建脚本里。
[23] We do this here, as it makes the layout a bit easier. We usually put the project specific stuff into the build script of the respective projects.

[24] 因此在 7 plus 2 Rule 的范围内我们做得很好:)
[24] So we are well in the range of the 7 plus 2 Rule :)