您可以在支持 Gradle 的 IDE 中打开此示例。

为了让您的软件项目做好增长准备,您可以将 Gradle 项目组织成多个子项目,以模块化您正在构建的软件。在本指南中,您将通过 Kotlin 应用示例了解如何构建此类项目。不过,通用概念适用于您使用 Gradle 构建的任何软件。您可以按照指南逐步从零开始创建一个新项目,或使用上面的链接下载完整的示例项目。

您将构建什么

您将构建一个 Kotlin 应用,它包含一个 application 项目和多个 library 项目。

您需要什么

创建项目文件夹

Gradle 内置了一个名为 init 的任务,它可以在一个空文件夹中初始化一个新的 Gradle 项目。init 任务使用(同样内置的)wrapper 任务来创建一个 Gradle wrapper 脚本 gradlew

第一步是为新项目创建一个文件夹并进入该目录。

$ mkdir demo
$ cd demo

运行 init 任务

在新项目目录中,在终端中运行以下命令来运行 init 任务:gradle init。当出现提示时,选择项目类型 1: application,实现语言选择 2: Kotlin。然后选择项目类型 2: Application and library project。接下来您可以选择用于编写构建脚本的 DSL - 1 : Kotlin2: Groovy。对于其他问题,按 Enter 键使用默认值。

输出将如下所示

$ gradle init

Select type of build to generate:
  1: Application
  2: Library
  3: Gradle plugin
  4: Basic (build structure only)
Enter selection (default: Application) [1..4] 1

Select implementation language:
  1: Java
  2: Kotlin
  3: Groovy
  4: Scala
  5: C++
  6: Swift
Enter selection (default: Java) [1..6]  2

Project name (default: demo):

Enter target Java version (min: 7, default: 21):

Select application structure:
  1: Single application project
  2: Application and library project
Enter selection (default: Single application project) [1..2] 2

Select build script DSL:
  1: Kotlin
  2: Groovy
Enter selection (default: Kotlin) [1..2]

Select test framework:
  1: JUnit 4
  2: TestNG
  3: Spock
  4: JUnit Jupiter
Enter selection (default: JUnit Jupiter) [1..4]

Generate build using new APIs and behavior (some features may change in the next minor release)? (default: no) [yes, no]

BUILD SUCCESSFUL
1 actionable task: 1 executed

init 任务生成的新项目具有以下结构

├── gradle (1)
│   ├── libs.versions.toml (2)
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew (3)
├── gradlew.bat (3)
├── settings.gradle.kts (4)
├── buildSrc
│   ├── build.gradle.kts (5)
│   ├── settings.gradle.kts (5)
│   └── src
│       └── main
│           └── kotlin (6)
│               ├── buildlogic.kotlin-application-conventions.gradle.kts
│               ├── buildlogic.kotlin-common-conventions.gradle.kts
│               └── buildlogic.kotlin-library-conventions.gradle.kts
├── app
│   ├── build.gradle.kts (7)
│   └── src
│       ├── main (8)
│       │   └── java
│       │       └── demo
│       │           └── app
│       │               ├── App.java
│       │               └── MessageUtils.kt
│       └── test (9)
│           └── java
│               └── demo
│                   └── app
│                       └── MessageUtilsTest.kt
├── list
│   ├── build.gradle.kts (7)
│   └── src
│       ├── main (8)
│       │   └── java
│       │       └── demo
│       │           └── list
│       │               └── LinkedList.kt
│       └── test (9)
│           └── java
│               └── demo
│                   └── list
│                       └── LinkedListTest.kt
└── utilities
    ├── build.gradle.kts (7)
    └── src
        └── main (8)
            └── java
                └── demo
                    └── utilities
                        ├── JoinUtils.kt
                        ├── SplitUtils.kt
                        └── StringUtils.kt
├── gradle (1)
│   ├── libs.versions.toml (2)
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew (3)
├── gradlew.bat (3)
├── settings.gradle (4)
├── buildSrc
│   ├── build.gradle (5)
│   ├── settings.gradle (5)
│   └── src
│       └── main
│           └── groovy (6)
│               ├── buildlogic.kotlin-application-conventions.gradle
│               ├── buildlogic.kotlin-common-conventions.gradle
│               └── buildlogic.kotlin-library-conventions.gradle
├── app
│   ├── build.gradle (7)
│   └── src
│       ├── main (8)
│       │   └── java
│       │       └── demo
│       │           └── app
│       │               ├── App.java
│       │               └── MessageUtils.java
│       └── test (9)
│           └── java
│               └── demo
│                   └── app
│                       └── MessageUtilsTest.java
├── list
│   ├── build.gradle (7)
│   └── src
│       ├── main (8)
│       │   └── java
│       │       └── demo
│       │           └── list
│       │               └── LinkedList.java
│       └── test (9)
│           └── java
│               └── demo
│                   └── list
│                       └── LinkedListTest.java
└── utilities
    ├── build.gradle (7)
    └── src
        └── main (8)
            └── java
                └── demo
                    └── utilities
                        ├── JoinUtils.java
                        ├── SplitUtils.java
                        └── StringUtils.java
1 生成的 wrapper 文件目录
2 生成的版本目录
3 Gradle wrapper 启动脚本
4 用于定义构建名称和子项目的 Settings 文件
5 buildSrc 的构建脚本,用于配置构建逻辑的依赖项
6 惯例插件 (convention plugins) 的源文件夹,使用 Groovy 或 Kotlin DSL 编写
7 三个子项目 applistutilities 的构建脚本
8 每个子项目中的 Kotlin 源文件夹
9 子项目中的 Kotlin 测试源文件夹

您现在已经设置好了项目,可以构建一个模块化为多个子项目的 Kotlin 应用。

查看项目文件

settings.gradle(.kts) 文件中有两行值得关注

settings.gradle.kts
rootProject.name = "demo"
include("app", "list", "utilities")
settings.gradle
rootProject.name = 'demo'
include('app', 'list', 'utilities')
  • rootProject.name 为构建指定一个名称,这会覆盖默认的按目录名称命名构建的行为。建议设置一个固定名称,因为如果项目被共享(例如作为 Git 仓库的根目录),文件夹可能会改变。

  • include("app", "list", "utilities") 定义了构建由相应文件夹中的三个子项目组成。可以通过扩展列表或添加更多 include(…​) 语句来添加更多子项目。

由于我们的构建包含多个子项目,我们希望在它们之间共享构建逻辑和配置。为此,我们使用了位于 buildSrc 文件夹中的所谓 惯例插件 (convention plugins)buildSrc 中的惯例插件是利用 Gradle 插件系统编写可重用构建配置片段的简单方法。

在此示例中,我们可以找到三个相互基于的惯例插件

buildSrc/src/main/kotlin/buildlogic.kotlin-common-conventions.gradle.kts
plugins {
    id("org.jetbrains.kotlin.jvm") (1)
}

repositories {
    mavenCentral() (2)
}

dependencies {
    constraints {
        implementation("org.apache.commons:commons-text:1.13.0") (3)
    }

    testImplementation("org.junit.jupiter:junit-jupiter:5.12.1") (4)

    testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

tasks.named<Test>("test") {
    useJUnitPlatform() (5)
}
buildSrc/src/main/groovy/buildlogic.kotlin-common-conventions.gradle
plugins {
    id 'org.jetbrains.kotlin.jvm' (1)
}

repositories {
    mavenCentral() (2)
}

dependencies {
    constraints {
        implementation 'org.apache.commons:commons-text:1.13.0' (3)
    }

    testImplementation 'org.junit.jupiter:junit-jupiter:5.12.1' (4)

    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

tasks.named('test') {
    useJUnitPlatform() (5)
}

kotlin-common-conventions 定义了一些应由我们所有 Kotlin 项目共享的配置,无论它们代表库还是实际应用。首先,我们应用 Kotlin Gradle 插件 (1) 以便拥有构建 Kotlin 项目所需的所有功能。然后,我们声明一个仓库 mavenCentral() 作为外部依赖项的来源 (2),定义依赖约束 (3) 以及所有子项目共享的标准依赖项,并将 JUnit 5 设置为测试框架 (4…​)。其他共享设置,例如编译器标志或 JVM 版本兼容性,也可以在这里设置。

buildSrc/src/main/kotlin/buildlogic.kotlin-library-conventions.gradle.kts
plugins {
    id("buildlogic.kotlin-common-conventions") (1)
    `java-library` (2)
}
buildSrc/src/main/groovy/buildlogic.kotlin-library-conventions.gradle
plugins {
    id 'buildlogic.kotlin-common-conventions' (1)
    id 'java-library' (2)
}
buildSrc/src/main/kotlin/buildlogic.kotlin-application-conventions.gradle.kts
plugins {
    id("buildlogic.kotlin-common-conventions") (1)
    application (2)
}
buildSrc/src/main/groovy/buildlogic.kotlin-application-conventions.gradle
plugins {
    id 'buildlogic.kotlin-common-conventions' (1)
    id 'application' (2)
}

kotlin-library-conventionskotlin-application-conventions 都应用了 kotlin-common-conventions 插件 (1),以便在其中进行的配置可以被库项目和应用项目共享。接下来它们分别应用了 java-libraryapplication 插件 (2),从而将我们的通用配置逻辑与库或应用的特定配置结合起来。虽然此示例中没有更细粒度的配置,但库或应用项目特定的构建配置可以放在这些惯例插件脚本中的一个。

让我们看一下子项目中的 build.gradle(.kts) 文件。

app/build.gradle.kts
plugins {
    id("buildlogic.kotlin-application-conventions")
}

dependencies {
    implementation("org.apache.commons:commons-text")
    implementation(project(":utilities"))
}

application {
    mainClass = "demo.app.AppKt" (1)
}
app/build.gradle
plugins {
    id 'buildlogic.kotlin-application-conventions'
}

dependencies {
    implementation 'org.apache.commons:commons-text'
    implementation project(':utilities')
}

application {
    mainClass = 'demo.app.AppKt' (1)
}
list/build.gradle.kts
plugins {
    id("buildlogic.kotlin-library-conventions")
}
list/build.gradle
plugins {
    id 'buildlogic.kotlin-library-conventions'
}
utilities/build.gradle.kts
plugins {
    id("buildlogic.kotlin-library-conventions")
}

dependencies {
    api(project(":list"))
}
utilities/build.gradle
plugins {
    id 'buildlogic.kotlin-library-conventions'
}

dependencies {
    api project(':list')
}

查看构建脚本,我们可以看到它们最多包含三个块

  • 每个构建脚本都应该有一个 plugins {} 块来应用插件。在一个结构良好的构建中,它可能只应用一个 惯例插件 (convention plugin),如本示例所示。惯例插件随后会负责应用和配置核心 Gradle 插件(如 applicationjava-library)、其他惯例插件或来自插件门户的社区插件。

  • 其次,如果项目有依赖项,应添加一个 dependencies {} 块。依赖项可以是外部的,例如我们在 kotlin-common-conventions 中添加的 JUnit 依赖项,也可以指向其他本地子项目。为此,使用了 project(…​) 标记。在我们的示例中,utilities 库需要 list 库。而 app 使用了 utilities 库。如果本地项目相互依赖,Gradle 会在需要时(且仅在需要时)负责构建依赖的项目。要了解更多信息,请查阅关于Gradle 中的依赖管理的文档。

  • 第三,可能有一个或多个插件配置块。这些配置块只有在为特定项目配置特定内容时才应直接在构建脚本中使用。否则,此类配置也应放在惯例插件中。在此示例中,我们使用特定于 application 插件的 application {} 块,将 app 项目中的 mainClass 设置为 demo.app.App (1)

我们拥有的最后一个构建文件是 buildSrc 中的 build.gradle(.kts) 文件。

buildSrc/build.gradle.kts
plugins {
    `kotlin-dsl` (1)
}

repositories {
    gradlePluginPortal() (2)
}

dependencies {
    implementation(libs.kotlin.gradle.plugin)
}
buildSrc/build.gradle
plugins {
    id 'groovy-gradle-plugin' (1)
}

repositories {
    gradlePluginPortal() (2)
}

dependencies {
    implementation libs.kotlin.gradle.plugin
}

此文件为构建惯例插件本身设置了基础。通过应用插件开发插件之一 —— groovy-gradle-pluginkotlin-dsl (1),我们可以在 buildSrc 中以构建文件形式编写惯例插件,这些正是我们上面已查看过的惯例插件。此外,我们将 Gradle 的插件门户添加为仓库 (2),这使我们可以访问社区插件。要使用插件,需要在 dependencies {} 块中将其声明为依赖项。

除了 Gradle 构建文件之外,您还可以在相应的文件夹中找到示例 Kotlin 源代码和测试源代码。请随意修改这些生成的源代码和测试,以探索 Gradle 在按接下来描述的方式运行构建时如何对更改做出反应。

运行测试

您可以使用 ./gradlew check 来执行所有子项目中的所有测试。当您使用纯任务名称(如 check)调用 Gradle 时,该任务将在所有提供它的子项目中执行。要仅针对特定子项目,您可以使用任务的完整路径。例如,:app:check 将仅执行 app 项目的测试。但是,在此示例中,其他子项目仍将被编译,因为 app 声明了对它们的依赖项。

$ ./gradlew check

BUILD SUCCESSFUL
9 actionable tasks: 9 executed

如果所有测试都成功通过,Gradle 不会在控制台打印更多输出。您可以在 <subproject>/build/reports 文件夹中找到测试报告。请随意更改一些示例代码或测试并重新运行 check,看看如果测试失败会发生什么。

运行应用

借助 application 插件,您可以直接从命令行运行应用。run 任务告诉 Gradle 执行分配给 mainClass 属性的类中的 main 方法。

$ ./gradlew run

> Task :app:run
Hello world!

BUILD SUCCESSFUL
2 actionable tasks: 2 executed
第一次运行 wrapper 脚本 gradlew 时,可能会有一段延迟,因为 Gradle 的该版本正在下载并存储在您的本地 ~/.gradle/wrapper/dists 文件夹中。

打包应用

application 插件还会为您打包应用及其所有依赖项。归档文件还将包含一个脚本,用于通过单个命令启动应用。

$ ./gradlew build

BUILD SUCCESSFUL in 0s
8 actionable tasks: 8 executed

如果您运行如上所示的完整构建,Gradle 将会为您生成两种格式的归档文件:app/build/distributions/app.tarapp/build/distributions/app.zip

发布构建扫描

了解您的构建幕后正在做什么的最佳方式是发布构建扫描。为此,只需使用 --scan 标志运行 Gradle。

$ ./gradlew build --scan

BUILD SUCCESSFUL in 0s
8 actionable tasks: 8 executed

Publishing a build scan to scans.gradle.com requires accepting the Gradle Terms of Service defined at https://gradle.com/terms-of-service.
Do you accept these terms? [yes, no] yes

Gradle Terms of Service accepted.

Publishing build scan...
https://gradle.com/s/5u4w3gxeurtd2

点击链接并探索执行了哪些任务、下载了哪些依赖项以及更多详细信息!

总结

就这样!您现在已成功使用 Gradle 配置和构建了一个 Kotlin 应用项目。您已经学会了如何

  • 初始化一个生成 Kotlin 应用的项目

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

  • 使用 buildSrc 中的惯例插件在子项目之间共享构建配置逻辑

  • 在所有子项目中运行同名任务

  • 在特定子项目中运行任务

  • 构建、打包并运行应用

下一步

当您的项目增长时,您可能对如何配置 JVM 项目、构建多项目构建以及依赖管理的更多细节感兴趣