Gradle 1.12用户指南翻译——第五十六章. 多项目构建

其他章节的翻译请参见:
http://blog.csdn.net/column/details/gradle-translation.html
翻译项目请关注Github上的地址:
https://github.com/msdx/gradledoc
本文翻译所在分支:
https://github.com/msdx/gradledoc/tree/1.12。
直接浏览双语版的文档请访问:
http://gradledoc.qiniudn.com/1.12/userguide/userguide.html。
另外,Android 手机用户可通过我写的一个程序浏览文档,带缓存功能的,目前0.6开发中版本兼容 Android 2.3以上系统,项目地址如下:
https://github.com/msdx/gradle-doc-apk
翻译不易,转载请注明本文在CSDN博客上的出处:
http://blog.csdn.net/maosidiaoxian/article/details/70217302

关于我对Gradle的翻译,以Github上的项目及http://gradledoc.qiniudn.com 上的文档为准。如发现翻译有误的地方,将首先在以上两个地方更新。因时间精力问题,博客中发表的译文基本不会同步修改。

另外,目前Gradle1.12版本的文档已经翻译完并进入校稿阶段,校稿的方式为到该项目https://github.com/msdx/gradledoc 提交issue或是pull request。校稿的结果不只是在此版本更新,也会用于改善Gradle下一版本(2.0)文档的翻译。


第五十六章. 多项目构建

对多项目构建的强大支持是 Gradle 的独特优点之一。本主题也是最具智力挑战性的。

56.1. 跨项目配置

让我们从一个非常简单的多项目构建开始。在全部 Gradle 的核心都是作为一个通用构建工具后,我们的项目并不一定得是个 java 项目。我们的第一个例子是关于海洋生活的。

56.1.1. 配置和执行

第 55.1 节,“构建阶段”描述了每个 Gradle 构建阶段。让我们放大一个多项目构建的配置和执行阶段。所有项目的配置都发生在任何任务执行之前。这意味着,当从一个项目请求一个任务时,首先会配置多项目构建的所有项目。每个项目需要配置的原因是为了支持灵活地访问及更改 Gradle 项目模型的任何部分。

56.1.1.1. 按需配置

由于每个项目都在执行阶段之前配置,所以也能做到配置注入功能以及访问整个项目模型。然而,在一个非常大的多项目构建里,这种方法可能不是最有效的。存在着一些项目,它们在层次结构上有上百个的子项目。对于巨大的项目,这个配置时间就会变得非常可观。可伸缩性是 Gradle 的一项重要要求。因此,从 1.4 版本起,新引入了一个孵化功能“按需配置”。

按需配置模式尝试只配置所请求的任务相关的项目。这种方式,大大提高了大型多项目构建的配置时间。长远来看,这种模式将成为默认模式,可能会是 Gradle 构建执行的唯一模式。配置需求功能正在孵化中,所以还无法保证每次的构建都能正确。对于解耦的多项目构建,这个功能应该会运行得很好(第 56.9 节,“解耦项目”)。在按需配置的模式下项目会如下配置︰

  • 根项目始终会被配置。这种方式的典型常见配置已经支持(allprojects 或subprojects 脚本块)。
  • 执行构建的所在目录的项目也会被配置,但是仅在Gradle 被执行但没有任务时。这种方式当项目按需配置时默认任务的行为正确。
  • 标准项目的依赖也被支持,并且使相关的项目也被配置。如果项目 A 对项目 B 有编译依赖,那么构建A的时候也会导致 A和B两个项目的配置。
  • 通过任务路径定义的任务依赖也被支持,并且会导致相关的项目被配置。示例:someTask.dependsOn(":someOtherProject:someOtherTask")
  • 通过从命令行 (或Tooling API)任务路径所请求的任务会导致相关的项目被配置。构建 “projectA:projectB:someTask”使项目B被配置。

想尝试这一新功能?要在每次构建时都按需配置,请参阅第 20.1 节,“通过 gradle.properties配置构建环境”。只对指定的构建按需配置,请参见附录 D, Gradle 命令行

56.1.2. 定义共同行为

我们有以下的项目树。这是一个多项目构建,它有一个根项目water,和一个子项目bluewhale

示例 56.1. 多项目树- water & bluewhale 项目

构建布局

water/
  build.gradle
  settings.gradle
  bluewhale/

注︰ 此示例的代码可以在Gradle 的二进制及源码分发包的samples/userguide/multiproject/firstExample/water中找到。

settings.gradle

include 'bluewhale'

那么,bluewhale项目的构建脚本在哪儿?在 Gradle 中,构建脚本是可选的。显然对于一个单项目构建,如果一个项目没有构建脚本是没道理的。对于 multiproject 构建,这咱情况则不同。让我们看看water项目的构建脚本并执行它︰

示例 56.2. water(父)项目的构建脚本

build.gradle

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

gradle -q hello的输出结果

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

Gradle 允许你从任何构建脚本访问多项目构建的任意一个project。Project API 提供了一个叫做project()的方法,它将路径作为参数,返回此路径的Project 对象。而这种从其他构建脚本配置一个项目构建的能力,我们叫做 交叉项目配置。Gradle 通过configuration injection来实现它。

我们对这个 water 项目的构建脚本不是很满意。因为要为每一个project 添加任务显示很不方便。我们可以进行改善。让我们先添加另一个叫做krill的项目到我们的多项目构建中。

示例 56.3. 多项目树- water & bluewhale & krill 项目

构建布局

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

注︰ 此示例的代码可以在Gradle 的二进制及源码分发包的samples/userguide/multiproject/addKrill/water中找到。

settings.gradle

include 'bluewhale', 'krill'

现在我们改写water的构建脚本,并把它归结成为一行。

示例 56.4. Water项目构建脚本

build.gradle

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

gradle -q hello的输出结果

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

是刚才的那个酷还是现在的这个酷?它是怎么实现的?Project API 提供了一个allprojects 属性,它返回一个当前项目的list,该项目的所有子项目都在这个list中。如果你使用一个闭包调用allprojects,这个闭包的声明会被委托给与allprojects关联的项目。你也可以通过allprojects.each来进行迭代,不过这样写会比较冗长。

其他构建系统使用继承作为主要手段,用于定义共同的行为。稍后您将看到,我们也提供了项目继承。但 Gradle 使用配置注入来作为定义共同行为的常用方式。我们认为它提供了配置 multiproject 的构建非常强大和灵活的方式。

56.2. 子项目配置

Project API 还提供了一个属性只用于访问子项目。

56.2.1. 定义共同行为

示例 56.5. 定义所有项目以及子项目的共同行为

build.gradle

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

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. 添加特定的行为

你可以在共同行为上面再添加特定的行为。通常我们会把项目的特定行为放到我们想添加的这个项目的构建脚本里。但正如我们已经看到的,我们可以不这样做。我们可以这样为bluewhale项目添加特定行为︰

示例 56.6. 为特定项目定义特定行为

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

> 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项目添加一些项目特定的行为。

示例 56.7. 为krill项目定义特定的行为

构建布局

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

注︰ 此示例的代码可以在Gradle 的二进制及源码分发包的samples/userguide/multiproject/spreadSpecifics/water中找到。

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

> 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. 项目筛选

若要体现配置注入的更强大之处,让我们添加另一个项目tropicalFish,并通过water项目的构建脚本向这个构建添加更多的行为。

56.2.3.1. 按名称过滤

示例 56.8. 将自定义行为添加到某些项目中(按项目名称筛选)

构建布局

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

注︰ 此示例的代码可以在Gradle 的二进制及源码分发包的samples/userguide/multiproject/addTropical/water中找到。

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

> 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里的项目。

56.2.3.2. 通过属性筛选

我们可以选择使用project的名称来筛选,或者是使用额外的项目属性。(有关额外属性的更多信息,请参见第 13.4.2 节,“额外属性”。)

示例 56.9. 将自定义行为添加到某些项目中(按项目属性筛选)

构建布局

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

注︰ 此示例的代码可以在Gradle 的二进制及源码分发包的samples/userguide/multiproject/tropicalWithProperties/water中找到。

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

> 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 节, “依赖——哪些依赖”,你可以看到更多关于这个主题的内容。

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

当我们从根项目的目录执行了hello任务时,事情会以一种直观的方式来表现。不同项目的所有hello任务被都会被执行。让我们切换到bluewhale目录,看看如果我们从那里执行 Gradle 会发生什么。

示例 56.10. 从子项目的运行构建

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

示例 56.11. 项目的评估和执行

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

> gradle -q distanceToIceberg
20 nautical miles
5 nautical miles

这里是不使用-q选项的输出结果︰

示例 56.12. 项目的评估和执行

gradle distanceToIceberg的输出结果

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

BUILD SUCCESSFUL

Total time: 1 secs

这个构建从water项目执行。watertropicalFish项目都没有一个叫distanceToIceberg的任务。Gradle 不关心这个问题。上面已经提到的简单的规则是︰ 执行在该层次结构下具体该名字的所有任务。如果没有这样的任务,那只能complain。

56.4. 按绝对路径运行任务

正如我们所看到的,你可以通过进入任何子项目的目录,运行多项目构建并在那里执行构建。从当前目录开始的该项目层级里,所有名称匹配的任务会被执行。但 Gradle 也提供了按绝对路径执行任务(见第56.5节,“项目和任务路径”)︰

示例56.13. 按绝对路径运行任务

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项目执行的。我们执行了waterkrilltropicalFish项目的hello任务。前两个任务由它们的绝对路径指定,最后一项任务通过上面所述的匹配机制执行。

56.5. 项目和任务路径

一个项目路径有以下模式︰它总是始于一个冒号,表示根项目。根项目是唯一的在一个不由其名称指定的路径中的项目。在上面的示例中,该路径:bluewhale对应于文件系统路径water/bluewhale

一项任务的路径只是它的项目路径加上其任务名称。例如 :bluewhale:hello。在一个项目内,你可以通过任务的名称得到同一个项目的任务。这被解释为相对路径。

原来 Gradle 使用/字符作为自然路径分隔符。在引入目录任务(见第 14.1节,“创建目录”)后将不能再这样做,因为目录任务的名称包含/字符。

56.6. 依赖-哪些依赖?

上一节的例子很特别,因为项目里没有执行依赖。它们只有配置依赖。下面是一个展示不同之处的例子:

56.6.1. 执行依赖

56.6.1.1. 依赖和执行顺序

示例 56.14. 依赖和执行顺序

构建布局

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

注︰ 此示例的代码可以在 Gradle 二进制和源码分发包的samples/userguide/multiproject/dependencies/firstMessages/messages中找到。

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

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

它并没有成功。如果没有别的定义,Gradle 会按字母数字顺序执行该任务。因此:consumer:action会在:producer:action之前执行。让我们试着用一个技巧解决这个问题,并将生产者项目命名为aProducer

示例 56.15. 依赖和执行顺序

构建布局

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

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

现在我们来看这个技巧。我们简单地切换到consumer目录并执行构建。

示例 56.16. 依赖和执行顺序

gradle -q action的输出结果

> gradle -q action
Consuming message: null

对于 Gradle 而言,这两个action任务不相关。如果你从 messages 项目执行构建,Gradle 会执行它们两个,因为他们具有相同的名称,并且它们在该层次结构下。在上一个例子中只有一个action是在这个层次结构下,因此它是唯一一个被执行的任务。我们需要比这种技巧更好的实现。

56.6.1.2. 声明依赖

示例 56.17. 声明依赖

构建布局

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

注︰ 此示例的代码可以在 Gradle 二进制和源码分发包的samples/userguide/multiproject/dependencies/messagesWithDependencies/messages中找到。

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

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

consumer目录运行如下:

示例 56.18. 声明依赖

gradle -q action的输出结果

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

我们现在声明了consumer项目的action任务对producer项目上的action任务有执行依赖

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

当然,在不同项目之间的任务依赖并不限于具有相同名称的任务。让我们改变我们的任务的命名,执行构建。

56.19. 跨项目任务依赖

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

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

56.6.2. 配置时依赖

让我们在进入Java领域之前,有更多的生产者-消费者构建的例子。我们添加一个属性到生产者项目,并且在生产者上创建一个来自消费者的配置时依赖。

示例 56.20. 配置时依赖

consumer/build.gradle

message = rootProject.producerMessage

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

producer/build.gradle

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

gradle -q consume的输出结果

> gradle -q consume
Consuming message: null

默认的项目评估顺序是按字母数字(同一嵌套级别)。因此consumer项目评估会在producer项目之前,并且producerkey值在它被consumer项目读取设置。Gradle 为此提供了一个解决方案。

示例 56.21. 配置时依赖——evaluationDependsOn

consumer/build.gradle

evaluationDependsOn(':producer')

message = rootProject.producerMessage

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

gradle -q consume的输出结果

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

命令evaluationDependsOn触发了producerconsumer之前评估。该示例也有点有意展示这个机制。在这种情况下,通过在执行时读key属性是一种更简单的解决方案。

示例 56.22. 配置时依赖

consumer/build.gradle

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

gradle -q consume的输出结果

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

配置依赖与执行依赖有很大上的不同。配置依赖是项目之间的,而执行依赖总是被解析为任务依赖。另一个区别是,所有的项目都总是被配置,即使你是从子项目开始构建的。默认的配置顺序是通常所需要的从顶至下的顺序。

如果要把默认配置的顺序改为从下到上,这意味着项目配置将会依赖于它的子项目的配置,可以使用evaluationDependsOnChildren()方法。

如果是相同的嵌套级别,则配置顺序取决于字母数字的位置。最常见的用例是,要分享共同的生命周期(例如所有项目都使用 Java 插件) 的多项目构建。如果你通过dependsOn声明了不同的项目之间的执行依赖,该方法的默认行为也会创建这两个项目之间的配置依赖。因此,很可能你不需要显式地定义配置依赖。

56.6.3. 实际生活中的例子

Gradle 的多项目特性是由现实生活中的用例所驱动的。第一个例子用于描述这种使用情况,包括两个 webapplication 项目,以及创建了它们的分布情况的父项目。[21] 对于这个例子,我们仅使用了一个构建脚本,并且进行了跨项目的配置

示例 56.23. 依赖——真实生活中的例子——跨项目配置

构建布局

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中找到。

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
            }
        }
    }
}

我们有一套有意思的依赖。很明显,date 和 hello 都有一个 配置 依赖于 webDist,作为webapp项目的所有构建逻辑都被webDist注入了。执行依赖则是另一个方面,因为webDist依赖于date 和 hello所构建的构件。这里甚至有第三个依赖。webDist有一个配置 依赖于 date 和 hello,因为它需要知道 archivePath。但它在执行时才需要这些信息。因此,我们没有循环依赖。

在多项目构建中,这样或其他的依赖模式是很常见的。如果一个构建系统不支持这种模式,你就不能解决你的问题,或者你需要用一些恶心的 hack方法,这样就会很难维护,并且非常影响你的生产力。

56.7. 项目库依赖

可能会出现,一个项目在它的编译路径中需要由另一个项目所输出的jar包?并且,不止是这个jar包,还包括了它的传递依赖?显示,对于Java 多项目构建当中,这是很常见的情况。第50.4.3节,“项目依赖”中已经提到,Gradle 为此提供了项目库依赖。

56.24. 项目库依赖

构建布局

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中找到。

我们有这些项目: shared, api 和personServicepersonService 对其他两个项目有一个库依赖。api 对 shared有一个库依赖。[22]

示例 56.25. 项目库依赖

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] 一个 lib 依赖是一种特殊形式的执行依赖。它使其他项目先构建,并把其他项目的生成的 jar 文件添加到类路径中。它也把其他项目的依赖添加到类路径中。所以你可以进入api目录并触发gradle compile。首先会先构建shared,然后构建api 。项目依赖项启用了部分的多项目构建。

如果你是从Maven中转过来的,那么你会对这一点很满意。如果你是从Ivy转过来的,你可能想要一些更细粒度的控制。Gradle 也向你提供了这种更细粒度的控制:

示例 56.26. 对依赖的细粒度的控制

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里的所有类进行测试。

56.7.1. 禁止依赖项目的构建

有时候,在做局部构建时,你不想依赖的项目也进行构建。如果要禁止依赖项目的构建,你可以使用-a 选项运行 Gradle。

56.8. 并行项目执行

随着在开发人员的台式机和CI服务器上,有越来越多的CPU内核可用,Gradle 能够充分利用这些处理资源就很重要。更具体地说,并行执行尝试︰

  • 通过让执行进行IO绑定,或者是其他不消耗所有可用资源的方式,减少多项目构建 的总构建时间
  • 对于一些小项目的执行,提供更快的反馈,而不必等待其他项目的完成。

虽然 Gradle 已经通过Test.setMaxParallelForks()提供了并行执行,本节所描述的这个功能是在项目级别上的并行执行。并行执行仍是一个孵化中的功能。请使用它,并且让我们知道它是帮助你的。

并行项目执行允许在一个解耦的多项目构建中的一些单独的项目被并行执行(参见︰第 56.9节,“解耦项目”)。而并行执行并不严格要求在配置时解耦,它的长期目标是提供一套强大的可用于全解耦的项目的功能。这些功能包括︰

  • 第 56.1.1.1 节,“按需配置”。
  • 并行的项目配置。
  • 对于不变的项目复用配置。
  • 项目级别的 up-to-date 检查。
  • 在构建依赖项目时使用预构建的构件。

并行执行是怎么做的?首先,你需要告诉 Gradle 使用并行模式。你可以使用命令行参数(附录 D, Gradle 命令行),或者是配置你的构建环境(第 20.1 节,“通过 gradle.properties 配置构建环境”)。除非你提供了特定数量的并行线程,否则Gradle 将基于可用的 CPU 内核尝试选择正确的线程数。当执行一个任务时,每一个并行的worker都仅拥有一个给定的项目。这意味着同一个项目的两个任务都永远不会被执行。因此并行执行只对多项目构建有用。完全支持任务依赖,并且平行的workers将会首先开始执行上游的任务。记住,解耦的任务按字母顺序调度,并且这个顺序执行,而不是真的在并行模式下执行。你需要确保正确地声明任务,以避免排序的问题。

56.9. 解耦的项目

Gradle 允许任何项目在配置和执行阶段访问其他项目。虽然这能为构建作者提供强大的功能和灵活性,但它也限制了在构建这些项目时Gradle 的灵活性。例如,紧耦合的项目实际上会阻碍 Gradle 在多项目上的并行构建,或是使用预构建的构件来代替某个项目依赖。

如果两个项目不能直接访问彼此的项目模型,那么可以说它们是解耦的。解耦的项目可能只在声明依赖方面会互相影响:项目依赖(第 50.4.3节, “项目依赖”), 或者是任务依赖(第 6.5节, “任务依赖”)。任何其他形式的项目交互(即通过修改另一个project对象或从另一个项目对象中读取值)将会导致项目的耦合。

使项目之间耦合的一种很常的方式是使用配置注入(第56.1节, “跨项目配置”)。它可能不明显,但使用关键的 Gradle 特性,像allprojectssubprojets关键字会自动导致你的项目耦合。这是因为这些关键字在 build.gradle 文件中使用,而这个文件定义了一个project。通常这是一个“根项目”,除了定义常见的配置不执行任何操作,但对 Gradle 而言这个根项目仍然是一个成熟的项目,并且通过使用allprojects,该项目有效地与所有其他项目耦合。

这意味着,使用任何形式的共享构建脚本逻辑或配置注入(allprojectssubprojects等)会导致你的项目耦合。当我们扩大项目解耦的概念,并提供利用了解耦项目的功能,我们也将引入新的功能来帮助你解决常见的情况(如配置注入)而不会导致你的项目耦合。

56.10. 多项目构建和测试

Java 插件的build 通常用于一个项目的编译,测试和执行代码风格检查(如果使用了CodeQuality插件的话)。在多项目构建中,你可能经常想跨多项目范围执行这些任务。buildNeeded 和 buildDependents 任务能帮你做到这一点。

让我们使用示例 56.25,“项目库依赖”中显示的项目结构。在这个例子中 :services:personservice 同时依赖于 :api 和 :shared。:api 项目还依赖于 :shared.

假设你在 :api 这一个项目上。你做了一些修改,但从执行clean以来还没有构建整个项目。你想要构建出所有必需的支持jar包,但只对这个你修改了的项目进行代码质量和单元测试。build 任务能做到这一点。

示例 56.27. 构建和测试单个项目

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:compile 花费检查来看在:shared项目里修改了什么内容。添加-a选项会导致 Gradle 使用缓存的jar 来解决任何项目库依赖,并且不会尝试重新生构建所依赖的项目。

示例 56.28. 部分构建和测试单个项目

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依赖的其他项目的修改,你可能不只想构建所有你依赖的项目,还想对它们进行测试。buildNeeded 任务也用于测试这个项目在testRuntime配置中依赖的所有库项目。

示例 56.29. 构建和测试被依赖的项目

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配置)指定项目的所有项目。

示例 56.30. 构建和测试依赖它的项目

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 来构建和测试所有的项目。

56.11. 属性和方法的继承

定义在一个项目里的属性和方法都会被继承到它所有的子项目中。这是配置注入的另一种方法。但我们认为,继承模型并不能好地反映多项目构建的问题。在本用户指南的未来版本中,我们可能会写一下与之有关的更多内容。

方法继承可能值得使用,尽管 Gradle 的配置注射还未支持方法(但会在以后的版本中支持的)。

你可能想知道,为什么我们要实现一个我们显然不喜欢做的功能。原因之一是其他工具也有这个功能,而我们想要在功能比较中有这一点。我们想为我们的用户提供一种选择。

56.12. 总结

写这一章相当耗费精力,可能你想起来也有同样的感受。我们对这一章最后要说的是,使用 Gradle 的多项目构建通常不会很难。你需要记住的是五个元素:allprojects subprojectsevaluationDependsOn, evaluationDependsOnChildren 和项目库依赖。[24] 使用这些元素,并记住 Gradle 具有不同的配置和执行阶段,你就已经可以很灵活地使用它。当你进入一个陡峭的领域的时候,Gradle 不会成为你的障碍,并且通常会伴随着你并且带你到达峰顶。



[21] 我们真正的使用情况,正使用http://lucene.apache.org/solr,在上面你所访问的每一个索引你都需要一个单独的war。这是为什么我们创建一个 webapps 分发包的原因之一。Resin servlet 容器能允许我们让这样的一个分发指向一个基本安装的servlet容器中。

[22] services 也是一个项目,但我们只是把它用作一个容器。它没有构建脚本,并且其他项目也没有向它注入内容。

[23] 我们在这里实现,因为它可以使布局更容易。我们通常把项目的具体东西放到各自的项目的构建脚本里。

[24] 因为在 7 plus 2 Rule 的范围内我们做得很好:)


你可能感兴趣的:(Gradle 1.12用户指南翻译——第五十六章. 多项目构建)