Gradle创建多项目构建

本文介绍了如何使用Gradle创建和管理多项目构建,包括构建模块化软件项目、子项目间依赖管理、跨语言项目支持、运行相似命名任务、重构通用构建脚本代码等关键技能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

多项目构建有助于模块化。它使一个人可以专注于较大项目中的一个工作领域,而Gradle负责处理项目其他部分的依赖性。

你会建立什么

您将构建一个greeting应用程序,其中还包括文档。在此过程中,您将创建一个基于Groovy的库项目,一个基于Asciidoctor的文档项目以及一个Java可分发的命令行应用程序。您将看到如何将这些项目连接在一起以创建最终产品。

你需要什么

  • 约25分钟
  • 文字编辑器
  • 命令提示符
  • Java Development Kit(JDK)版本1.8或更高版本
  • 一个Gradle,版本5.0或更高版本

创建一个根项目

第一步是为新项目创建一个文件夹,并将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任务,然后生成gradlewgradlew.bat包装脚本。

打开设置脚本。您会删除许多自动生成的评论,仅留下:

settings.gradle
rootProject.name = 'creating-multi-project-builds'

很好!现在您可以开始开发了。(记住要保存文件。)

从上配置

在多项目中,您可以使用顶级构建脚本(也称为根项目)来配置尽可能多的通用性,而子项目只能自定义该子项目所需的内容。

使用init不带参数的任务时,Gradle会在注释块中生成具有基本Java布局的构建脚本文件。打开它,并将其内容替换为:

build.gradle
allprojects {
    repositories {
        jcenter() 
    }
}

将JCenter存储库添加到所有项目。 

allprojects块用于添加将应用于所有子项目以及根项目的配置项目。以类似的方式,该subprojects块只能用于为所有子项目添加配置项。您可以在根项目中随意使用这两个块。

现在,通过subproject顶级构建脚本中的块,为要添加的每个模块设置版本,如下所示

build.gradle
subprojects {
    version = '1.0'
}

添加一个Groovy库子项目

为您的库子项目创建目录。

mkdir greeting-library

greeting-library中创建一个构建脚本,并添加基本的Groovy库项目内容,如下所示。

(不要担心,如果您以前从未构建过Groovy库。此处将提供完整的内容。如果您对详细信息感兴趣,则可能需要查看《用户手册》中的“ 构建Groovy库入门指南”)。

Greeting-library / build.gradle
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库项目成为多项目构建的一部分。

settings.gradle
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类。

greeting-library / src / main / groovy / greeter / GreetingFormatter.groovy
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测试。

greeting-library / src / test / groovy / greeter / GreetingFormatterSpec.groovy
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' 
}

应用程序的插件可以让你所有的应用程序JAR文件,以及他们所有的传递依赖的捆绑成一个ZIP或TAR文件。还将在存档中添加两个启动脚本(一个用于UNIX操作系统的脚本,一个用于Windows的脚本),以使用户可以轻松地运行应用程序。

再次,更新设置脚本以添加新项目

settings.gradle
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类。

greeter/ build.gradle
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的构建脚本,然后添加:

greeter/ build.gradle
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单词,如图所示。

greeter/ build.gradle
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目录中的子项目中(如果该目录不存在,则必须创建该目录)。

greeter / src / test / groovy / greeter / GreeterSpec.groovy
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块中。

build.gradle
plugins {
    id 'org.asciidoctor.convert' version '1.5.6' apply false 
}

使用apply false会将该插件添加到整个项目,但不会将其添加到根项目。 

现在为文档创建另一个名为docs的子项目文件夹。

$ mkdir docs

docs具有以下内容的文件夹中创建一个构建脚本文件:

docs / build.gradle
plugins {
    id 'org.asciidoctor.convert' 
}

asciidoctor {
    sources {
        include 'greeter.adoc'   
    }
}

build.dependsOn 'asciidoctor'  
  • 将Asciidoctor插件应用于此子项目。此技术使您可以在定义根项目中的所有插件的同时,将插件有选择地应用于子项目。
  • 告诉插件greeter.adoc在默认源文件夹中查找称为的文档src/docs/asciidoc
  •  asciidoctor任务添加到构建生命周期中,以便如果build对顶级项目执行任务,那么还将构建文档。

将此子项目添加到中settings.gradle

settings.gradle
include 'docs'

在Asciidoctor源文件夹中,添加一个名为greeter.adoc文档,在docs子项目的src/docs/asciidoc。如果该目录不存在,则需要生成该目录。

docs / src / docs / asciidoc / greeter.adoc
= 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子项目的构建脚本中的任务相关性,将此生成的文档添加到您的分发中。

greeter/ build.gradle
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

请注意,大多数任务仍是最新的,但是distZipdistTar任务都已重新运行以包含文档。如果你解开一个档案(greeter-1.0.zipgreeter-1.0.targreeter/build/distributions目录中),您将看到包括在文档html5文件夹中。

Gradle与其他构建工具不同的一个强大功能是它处理增量构建的能力。不必在每次构建前都进行./gradlew clean操作。

重构通用的构建脚本代码

此时,您可能已经注意到,在子项目构建脚本greeting-librarygreeter子项目构建脚本中都有通用的脚本代码。Gradle的一个关键功能是能够将这样的通用构建脚本代码放置在根项目中。

编辑根项目构建脚本并添加以下代码

build.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子项目的构建脚本中删除以下几行:

greeting-library / build.gradle(已删除代码)
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

摘要

通过执行以下步骤,您已经了解了如何

  • 通过组合多个子项目来创建模块化软件项目。

  • 让一个子项目消耗来自另一个子项目的工件。

  • 轻松使用多语种项目。

  • 在所有子项目中运行类似的命名任务。

  • 在特定子项目中运行任务,而无需更改到该子项目的文件夹。

  • 将通用子项目设置重构到根项目中。

  • 从根项目中选择性地配置子项目

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值