多项目构建有助于模块化。它使一个人可以专注于较大项目中的一个工作领域,而Gradle负责处理项目其他部分的依赖性。
你会建立什么
您将构建一个greeting应用程序,其中还包括文档。在此过程中,您将创建一个基于Groovy的库项目,一个基于Asciidoctor的文档项目以及一个Java可分发的命令行应用程序。您将看到如何将这些项目连接在一起以创建最终产品。
创建一个根项目
第一步是为新项目创建一个文件夹,并将Gradle Wrapper添加到项目中。如果您使用Build Init插件,那么还将添加必要的设置和构建脚本。
$ mkdir creating-multi-project-builds
$ cd creating-multi-project-builds
$ gradle init
> Task :wrapper
Select type of project to generate:
1: basic
2: application
3: library
4: Gradle plugin
Enter selection (default: basic) [1..4]
Select build script DSL:
1: Groovy
2: Kotlin
Enter selection (default: Groovy) [1..2]
Project name (default: creating-multi-project-builds):
> Task :init
Get more help with your project: https://guides.gradle.org/creating-new-gradle-builds
BUILD SUCCESSFUL
2 actionable tasks: 2 executed
如果您更喜欢Kotlin DSL,则可以选择kotlin
构建脚本DSL。
该init
任务首先运行wrapper
任务,然后生成gradlew
和gradlew.bat
包装脚本。
打开设置脚本。您会删除许多自动生成的评论,仅留下:
rootProject.name = 'creating-multi-project-builds'
很好!现在您可以开始开发了。(记住要保存文件。)
从上配置
在多项目中,您可以使用顶级构建脚本(也称为根项目)来配置尽可能多的通用性,而子项目只能自定义该子项目所需的内容。
使用init
不带参数的任务时,Gradle会在注释块中生成具有基本Java布局的构建脚本文件。打开它,并将其内容替换为:
allprojects {
repositories {
jcenter()
}
}
将JCenter存储库添加到所有项目。
该allprojects
块用于添加将应用于所有子项目以及根项目的配置项目。以类似的方式,该subprojects
块只能用于为所有子项目添加配置项。您可以在根项目中随意使用这两个块。
现在,通过subproject
顶级构建脚本中的块,为要添加的每个模块设置版本,如下所示
subprojects {
version = '1.0'
}
添加一个Groovy库子项目
为您的库子项目创建目录。
mkdir greeting-library
在greeting-library
中创建一个构建脚本,并添加基本的Groovy库项目内容,如下所示。
(不要担心,如果您以前从未构建过Groovy库。此处将提供完整的内容。如果您对详细信息感兴趣,则可能需要查看《用户手册》中的“ 构建Groovy库入门指南”)。
plugins {
id 'groovy'
}
dependencies {
compile 'org.codehaus.groovy:groovy:2.4.10'
testCompile 'org.spockframework:spock-core:1.0-groovy-2.4', {
exclude module: 'groovy-all'
}
}
现在,在顶级项目中编辑设置脚本,以使新的Groovy库项目成为多项目构建的一部分。
include 'greeting-library'
最后,在greeting-library
下创建src/main/groovy
文件夹,然后添加greeter
package文件夹。
$ cd greeting-library
$ mkdir -p src/main/groovy/greeter
$ mkdir -p src/test/groovy/greeter
向src/main/groovy
中的greeter
包添加一个GreetingFormatter
类。
package greeter
import groovy.transform.CompileStatic
@CompileStatic
class GreetingFormatter {
static String greeting(final String name) {
"Hello, ${name.capitalize()}"
}
}
在src/test/groovy
中的greeter
包中添加一个名为GreetingFormatterSpec
的Spock Framework测试。
package greeter
import spock.lang.Specification
class GreetingFormatterSpec extends Specification {
def 'Creating a greeting'() {
expect: 'The greeting to be correctly capitalized'
GreetingFormatter.greeting('gradlephant') == 'Hello, Gradlephant'
}
}
运行./gradlew build
从顶级项目目录。
$ cd ..
$ ./gradlew build
> Task :greeting-library:compileJava NO-SOURCE
> Task :greeting-library:compileGroovy
> Task :greeting-library:processResources NO-SOURCE
> Task :greeting-library:classes
> Task :greeting-library:jar
> Task :greeting-library:assemble
> Task :greeting-library:compileTestJava NO-SOURCE
> Task :greeting-library:compileTestGroovy
> Task :greeting-library:processTestResources NO-SOURCE
> Task :greeting-library:testClasses
> Task :greeting-library:test
> Task :greeting-library:check
> Task :greeting-library:build
Deprecated Gradle features were used in this build, making it incompatible with Gradle 7.0.
Use '--warning-mode all' to show the individual deprecation warnings.
See https://docs.gradle.org/6.0.1/userguide/command_line_interface.html#sec:command_line_warnings
BUILD SUCCESSFUL in 11s
4 actionable tasks: 4 executed
Gradle自动检测到其中有build
任务greeting-library
并执行了。这是Gradle多项目构建的强大功能之一。当子项目中的任务与顶级项目中的名称相同时,构建的维护将更加容易,并且Gradle可以通过在顶层指定通用任务名称来在每个项目中执行相同的任务。
但是,单个子项目并不能真正实现多项目构建。因此,下一步是添加将使用此库的子项目,
添加一个Java应用程序子项目
在根项目中为包含该应用程序的子项目创建一个文件夹。
$ mkdir greeter
greeter/ build.gradle
plugins {
id 'java'
id 'application'
}
- 这是一个Java项目,因此需要Java插件。
- 添加Application插件以使其成为Java应用程序。
该应用程序的插件可以让你所有的应用程序JAR文件,以及他们所有的传递依赖的捆绑成一个ZIP或TAR文件。还将在存档中添加两个启动脚本(一个用于UNIX操作系统的脚本,一个用于Windows的脚本),以使用户可以轻松地运行应用程序。
再次,更新设置脚本以添加新项目
plugins {
id 'java'
id 'application'
}
现在,创建一个具有主要功能的Java类文件,该Greeter
文件将使用子项目中的greeting-library
库。
$ mkdir -p greeter/src/main/java/greeter
greeter / src / main / java / greeter / Greeter.java
package greeter;
public class Greeter {
public static void main(String[] args) {
final String output = GreetingFormatter.greeting(args[0]);
System.out.println(output);
}
}
由于目标是Java应用程序,因此您还需要告诉Gradle类的名称,即入口点。编辑greeter
子项目的构建脚本,并将mainClassName
属性分配给包含该main
方法的Java类。
mainClassName = 'greeter.Greeter'
使用
mainClassName
设置的入口点。(分配的类必须具有标准main
方法)。
运行构建(这将失败,因为我们尚未解决所有依赖关系)。
$ ./gradlew build
...
.../Greeter.java:5: error: cannot find symbol
final String output = GreetingFormatter.greeting(args[0]);
^
symbol: variable GreetingFormatter
location: class Greeter
1 error
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':greeter:compileJava'.
> Compilation failed; see the compiler error output for details.
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.
* Get more help at https://help.gradle.org
BUILD FAILED in 292ms
这是因为greeter
项目不知道在哪里可以找到greeting-library
。创建子项目的集合不会自动使它们各自的工件自动供其他子项目使用-这会导致项目非常脆弱。Gradle具有特定的语法,可以将一个子项目的工件链接到另一个子项目的依赖项。再次编辑子项目greeter
的构建脚本,然后添加:
dependencies {
compile project(':greeting-library')
}
使用
project(NAME)
语法将一个子项目的工件添加到另一子项目的依赖项中。
再次运行构建,现在应该可以成功。
$ ./gradlew build
> Task :greeting-library:compileJava NO-SOURCE
> Task :greeting-library:compileGroovy UP-TO-DATE
> Task :greeting-library:processResources NO-SOURCE
> Task :greeting-library:classes UP-TO-DATE
> Task :greeting-library:jar UP-TO-DATE
> Task :greeter:compileJava
> Task :greeter:compileGroovy NO-SOURCE
> Task :greeter:processResources NO-SOURCE
> Task :greeter:classes
> Task :greeter:jar
> Task :greeter:startScripts
> Task :greeter:distTar
> Task :greeter:distZip
> Task :greeter:assemble
> Task :greeter:compileTestJava NO-SOURCE
> Task :greeter:compileTestGroovy
> Task :greeter:processTestResources NO-SOURCE
> Task :greeter:testClasses
> Task :greeter:test
> Task :greeter:check
> Task :greeter:build
> Task :greeting-library:assemble UP-TO-DATE
> Task :greeting-library:compileTestJava NO-SOURCE
> Task :greeting-library:compileTestGroovy UP-TO-DATE
> Task :greeting-library:processTestResources NO-SOURCE
> Task :greeting-library:testClasses UP-TO-DATE
> Task :greeting-library:test UP-TO-DATE
> Task :greeting-library:check UP-TO-DATE
> Task :greeting-library:build UP-TO-DATE
Deprecated Gradle features were used in this build, making it incompatible with Gradle 7.0.
Use '--warning-mode all' to show the individual deprecation warnings.
See https://docs.gradle.org/6.0.1/userguide/command_line_interface.html#sec:command_line_warnings
BUILD SUCCESSFUL in 8s
11 actionable tasks: 7 executed, 4 up-to-date
注意输出中每个子项目的前缀,以便您知道从哪个项目执行哪个任务。还要注意,Gradle在移至另一个子项目之前不会处理其所有任务。
添加一个测试,以确保您的代码在应用程序中正常运行。由于Spock Framework也是测试Java代码的一种流行方法,因此通过首先将Groovy插件添加到greeter
子项目的构建脚本中来创建测试。这需要groovy
插件,以及包括java
插件,这样你就可以java
替换groovy
单词,如图所示。
plugins {
id 'groovy'
}
// then, add the following testCompile dependency to the dependencies block:
dependencies {
testCompile 'org.spockframework:spock-core:1.0-groovy-2.4', {
exclude module: 'groovy-all'
}
}
拥有
groovy
插件会自动应用该java
插件,因此您实际上可以删除应用该java
插件的行。但是,对于语义而言,最好同时保留两者,因为这表明您主要是在构建Java项目。
然后,在greeter软件包中将名为GreeterSpec的测试添加到src / test / groovy / greeter目录中的子项目中(如果该目录不存在,则必须创建该目录)。
package greeter
import spock.lang.Specification
class GreeterSpec extends Specification {
def 'Calling the entry point'() {
setup: 'Re-route standard out'
def buf = new ByteArrayOutputStream(1024)
System.out = new PrintStream(buf)
when: 'The entrypoint is executed'
Greeter.main('gradlephant')
then: 'The correct greeting is output'
buf.toString() == "Hello, Gradlephant\n".denormalize()
}
}
无需再次运行完整的构建,只需在greeter
子项目中运行测试即可。Gradle包装器脚本gradlew
仅在顶层存在,因此请首先将目录更改为顶层。
$ ./gradlew :greeter:test
> Task :greeting-library:compileJava NO-SOURCE
> Task :greeting-library:compileGroovy UP-TO-DATE
> Task :greeting-library:processResources NO-SOURCE
> Task :greeting-library:classes UP-TO-DATE
> Task :greeting-library:jar UP-TO-DATE
> Task :greeter:compileJava UP-TO-DATE
> Task :greeter:compileGroovy NO-SOURCE
> Task :greeter:processResources NO-SOURCE
> Task :greeter:classes UP-TO-DATE
> Task :greeter:compileTestJava NO-SOURCE
> Task :greeter:compileTestGroovy UP-TO-DATE
> Task :greeter:processTestResources NO-SOURCE
> Task :greeter:testClasses UP-TO-DATE
> Task :greeter:test UP-TO-DATE
Deprecated Gradle features were used in this build, making it incompatible with Gradle 7.0.
Use '--warning-mode all' to show the individual deprecation warnings.
See https://docs.gradle.org/6.0.1/userguide/command_line_interface.html#sec:command_line_warnings
BUILD SUCCESSFUL in 347ms
5 actionable tasks: 5 up-to-date
通过使用格式:SUBPROJECT:TASK
作为任务名称,可以从顶部(或任何其他子项目)在任何子项目中运行任何任务。这样就无需执行以下替代方法(也可以使用):
$ cd greeter
$ ../gradlew test
$ cd ..
父目录,其中包含生成的Gradle包装器脚本
如果您要花费大量时间在一个子项目中,则转到该目录并从那里运行构建是正常的。但是,如果您需要在特定子项目中快速运行任务,那么灵活地按子项目路径指定任务非常有帮助。
您是否注意到执行的任务是
:greeter:test
,但是在运行它时,Gradle首次访问greeting-library
以确保依赖关系是最新的?这是另一个示例,说明Gradle中强大的任务图实现如何为您节省时间。
添加文档
创建软件项目的文档被认为是一种好习惯。尽管有多种创作方法可以实现此目标,但是您将使用非常流行的Asciidoctor工具。
首先将Asciidoctor插件添加到根项目构建脚本顶部的plugins块中。
plugins {
id 'org.asciidoctor.convert' version '1.5.6' apply false
}
使用
apply false
会将该插件添加到整个项目,但不会将其添加到根项目。
现在为文档创建另一个名为docs
的子项目文件夹。
$ mkdir docs
在docs
具有以下内容的文件夹中创建一个构建脚本文件:
plugins {
id 'org.asciidoctor.convert'
}
asciidoctor {
sources {
include 'greeter.adoc'
}
}
build.dependsOn 'asciidoctor'
- 将Asciidoctor插件应用于此子项目。此技术使您可以在定义根项目中的所有插件的同时,将插件有选择地应用于子项目。
- 告诉插件
greeter.adoc
在默认源文件夹中查找称为的文档src/docs/asciidoc
- 将
asciidoctor
任务添加到构建生命周期中,以便如果build
对顶级项目执行任务,那么还将构建文档。
将此子项目添加到中settings.gradle
。
include 'docs'
在Asciidoctor源文件夹中,添加一个名为greeter.adoc
文档,在docs
子项目的src/docs/asciidoc
。如果该目录不存在,则需要生成该目录。
= Greeter Command-line Application
A simple application demonstrating the flexibility of a Gradle multi-project.
== Installation
Unpack the ZIP or TAR file in a suitable location
== Usage
[listing]
----
$ cd greeter-1.0
$ ./bin/greeter gradlephant
Hello, Gradlephant
----
asciidoctor
从顶级项目运行任务。
如果运行命令./gradlew tasks,现在将在“文档任务”类别中看到一个名为asciidoctor的任务。
$ ./gradlew asciidoctor
> Task :docs:asciidoctor
Deprecated Gradle features were used in this build, making it incompatible with Gradle 7.0.
Use '--warning-mode all' to show the individual deprecation warnings.
See https://docs.gradle.org/6.0.1/userguide/command_line_interface.html#sec:command_line_warnings
BUILD SUCCESSFUL in 15s
1 actionable task: 1 executed
您是否注意到Gradle知道仅在docs子项目中运行此任务?这是因为当您从根项目级别运行任务并且该任务在根目录中不存在时,Gradle会在存在该名称任务的每个子项目中运行该任务。
文档工件将出现在中docs/build/asciidoc/html5
。随时打开greeter.html
文件并检查输出。
将文档包括在分发存档中
文档在发布时很有用,但是与应用程序一起分发的使用情况文档对于尝试您的应用程序的人来说非常有价值。通过更新greeter子项目的构建脚本中的任务相关性,将此生成的文档添加到您的分发中。
distZip {
from project(':docs').asciidoctor, {
into "${project.name}-${version}"
}
}
distTar {
from project(':docs').asciidoctor, {
into "${project.name}-${version}"
}
}
使用
project(:NAME)
引用另一个项目及其任务。
再次从顶部开始构建,这次生成的存档将包含文档。
$ ./gradlew build
> Task :docs:asciidoctor UP-TO-DATE
> Task :docs:assemble UP-TO-DATE
> Task :docs:check UP-TO-DATE
> Task :docs:build UP-TO-DATE
> Task :greeting-library:compileJava NO-SOURCE
> Task :greeting-library:compileGroovy UP-TO-DATE
> Task :greeting-library:processResources NO-SOURCE
> Task :greeting-library:classes UP-TO-DATE
> Task :greeting-library:jar UP-TO-DATE
> Task :greeter:compileJava UP-TO-DATE
> Task :greeter:compileGroovy NO-SOURCE
> Task :greeter:processResources NO-SOURCE
> Task :greeter:classes UP-TO-DATE
> Task :greeter:jar UP-TO-DATE
> Task :greeter:startScripts UP-TO-DATE
> Task :greeter:distTar
> Task :greeter:distZip
> Task :greeter:assemble
> Task :greeter:compileTestJava NO-SOURCE
> Task :greeter:compileTestGroovy UP-TO-DATE
> Task :greeter:processTestResources NO-SOURCE
> Task :greeter:testClasses UP-TO-DATE
> Task :greeter:test UP-TO-DATE
> Task :greeter:check UP-TO-DATE
> Task :greeter:build
> Task :greeting-library:assemble UP-TO-DATE
> Task :greeting-library:compileTestJava NO-SOURCE
> Task :greeting-library:compileTestGroovy UP-TO-DATE
> Task :greeting-library:processTestResources NO-SOURCE
> Task :greeting-library:testClasses UP-TO-DATE
> Task :greeting-library:test UP-TO-DATE
> Task :greeting-library:check UP-TO-DATE
> Task :greeting-library:build UP-TO-DATE
Deprecated Gradle features were used in this build, making it incompatible with Gradle 7.0.
Use '--warning-mode all' to show the individual deprecation warnings.
See https://docs.gradle.org/6.0.1/userguide/command_line_interface.html#sec:command_line_warnings
BUILD SUCCESSFUL in 2s
12 actionable tasks: 2 executed, 10 up-to-date
请注意,大多数任务仍是最新的,但是distZip
和distTar
任务都已重新运行以包含文档。如果你解开一个档案(greeter-1.0.zip
或greeter-1.0.tar
在greeter/build/distributions
目录中),您将看到包括在文档html5
文件夹中。
Gradle与其他构建工具不同的一个强大功能是它处理增量构建的能力。不必在每次构建前都进行
./gradlew clean
操作。
重构通用的构建脚本代码
此时,您可能已经注意到,在子项目构建脚本greeting-library
和greeter
子项目构建脚本中都有通用的脚本代码。Gradle的一个关键功能是能够将这样的通用构建脚本代码放置在根项目中。
编辑根项目构建脚本并添加以下代码
configure(subprojects.findAll { it.name == 'greeter' || it.name == 'greeting-library' }) {
apply plugin: 'groovy'
dependencies {
testCompile 'org.spockframework:spock-core:1.0-groovy-2.4', {
exclude module: 'groovy-all'
}
}
}
使用configure和谓词闭包可以配置选择性的子项目。谓词闭包将传递给可以查询其名称的子项目。在这种情况下,将仅配置具有特定名称匹配的子项目。
同时,从greeting-library
子项目的构建脚本中删除以下几行:
plugins {
id 'groovy'
}
dependencies {
testCompile 'org.spockframework:spock-core:1.0-groovy-2.4', {
exclude module: 'groovy-all'
}
}
还从greeter
子项目的构建脚本中删除类似的行。换句话说,从plugins
块中删除groovy
插件,从dependencies
块中删除spock
依赖项,但保留dependencies
块本身。
重新运行顶层的所有内容,以确保所有内容仍然有效。
$ ./gradlew clean build
摘要
通过执行以下步骤,您已经了解了如何
-
通过组合多个子项目来创建模块化软件项目。
-
让一个子项目消耗来自另一个子项目的工件。
-
轻松使用多语种项目。
-
在所有子项目中运行类似的命名任务。
-
在特定子项目中运行任务,而无需更改到该子项目的文件夹。
-
将通用子项目设置重构到根项目中。
-
从根项目中选择性地配置子项目