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

为了让您的软件项目更易于扩展,您可以将 Gradle 项目组织成多个子项目,从而将您正在构建的软件模块化。在本指南中,您将以 Scala 应用程序为例,了解如何构建这样的项目。不过,通用概念适用于使用 Gradle 构建的任何软件。您可以按照本指南的步骤从头创建一个新项目,或者使用上面的链接下载完整的示例项目。

您将构建什么

您将构建一个 Scala 应用程序,该应用程序由一个应用程序项目和多个项目组成。

您需要什么

创建项目文件夹

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

第一步是为新项目创建一个文件夹,并切换到该文件夹中。

$ mkdir demo
$ cd demo

运行 init 任务

在新的项目目录中,在终端中使用以下命令运行 init 任务:gradle init。当出现提示时,选择项目类型 1: application,选择实现语言 4: Scala。之后,选择 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]  4

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]

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.scala-application-conventions.gradle.kts
│               ├── buildlogic.scala-common-conventions.gradle.kts
│               └── buildlogic.scala-library-conventions.gradle.kts
├── app
│   ├── build.gradle.kts (7)
│   └── src
│       ├── main (8)
│       │   └── java
│       │       └── demo
│       │           └── app
│       │               ├── App.java
│       │               └── MessageUtils.scala
│       └── test (9)
│           └── java
│               └── demo
│                   └── app
│                       └── MessageUtilsTest.scala
├── list
│   ├── build.gradle.kts (7)
│   └── src
│       ├── main (8)
│       │   └── java
│       │       └── demo
│       │           └── list
│       │               └── LinkedList.scala
│       └── test (9)
│           └── java
│               └── demo
│                   └── list
│                       └── LinkedListTest.scala
└── utilities
    ├── build.gradle.kts (7)
    └── src
        └── main (8)
            └── java
                └── demo
                    └── utilities
                        ├── JoinUtils.scala
                        ├── SplitUtils.scala
                        └── StringUtils.scala
├── 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.scala-application-conventions.gradle
│               ├── buildlogic.scala-common-conventions.gradle
│               └── buildlogic.scala-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 用 Groovy 或 Kotlin DSL 编写的约定插件的源文件夹
7 三个子项目(applistutilities)的构建脚本
8 每个子项目中的 Scala 源文件夹
9 子项目中的 Scala 测试源文件夹

现在,您已设置好项目,可以构建模块化为多个子项目的 Scala 应用程序。

查看项目文件

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 文件夹中的所谓约定插件buildSrc 中的约定插件是利用 Gradle 插件系统编写可重用构建配置片段的简便方法。

在此示例中,我们可以找到三个基于彼此的约定插件

buildSrc/src/main/kotlin/buildlogic.scala-common-conventions.gradle.kts
plugins {
    scala (1)
}

repositories {
    mavenCentral() (2)
}

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

        implementation("org.scala-lang:scala-library:2.13.16")
    }

    implementation("org.scala-lang:scala-library") (4)

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

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

tasks.named<Test>("test") {
    useJUnitPlatform() (6)
}
buildSrc/src/main/groovy/buildlogic.scala-common-conventions.gradle
plugins {
    id 'scala' (1)
}

repositories {
    mavenCentral() (2)
}

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

        implementation 'org.scala-lang:scala-library:2.13.16'
    }

    implementation 'org.scala-lang:scala-library' (4)

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

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

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

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

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

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

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

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

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

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

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

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

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

dependencies {
    api project(':list')
}

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

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

  • 其次,如果项目有依赖项,应该添加一个 dependencies {} 块。依赖项可以是外部的,例如我们在 scala-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)
}
buildSrc/build.gradle
plugins {
    id 'groovy-gradle-plugin' (1)
}

repositories {
    gradlePluginPortal() (2)
}

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

除了 Gradle 构建文件外,您还可以在相应的文件夹中找到 Scala 示例源代码和测试源代码。您可以随意修改这些生成的源文件和测试,以探索 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
7 actionable tasks: 7 executed

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

发布构建扫描

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

$ ./gradlew build --scan

BUILD SUCCESSFUL in 0s
7 actionable tasks: 7 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 配置和构建了一个 Scala 应用程序项目。您已学会如何:

  • 初始化一个生成 Scala 应用程序的项目

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

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

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

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

  • 构建、打包并运行应用程序

下一步

随着项目增长,您可能对如何配置 JVM 项目、结构化多项目构建和依赖管理的更多细节感兴趣