Skip to content

第 4 章 构建脚本 DSL:Groovy vs Kotlin

学习目标:搞清楚 build.gradle(Groovy)和 build.gradle.kts(Kotlin)的区别;掌握两种 DSL 的 50+ 种常见写法对照;能把任何 Groovy 脚本无损翻译成 Kotlin 脚本;理解 settings 文件、properties 文件、Version Catalog 各自的职责。


4.1 两种 DSL 的"基因"

4.1.1 它们是什么?

   ┌─────────────────────────────────────────────────────────┐
   │  Groovy DSL                  Kotlin DSL                  │
   │  ────────────                ──────────                  │
   │  文件后缀: .gradle            文件后缀: .gradle.kts        │
   │  语言:    Groovy             语言:    Kotlin              │
   │  类型:    动态类型             类型:    静态类型             │
   │  IDE 提示: 弱                IDE 提示: 完整                │
   │  执行:    Groovy AOT编译       执行:    Kotlin 编译为字节码   │
   │  起步速度: ★★★★              起步速度: ★★★              │
   │  语法简洁: ★★★★★             语法简洁: ★★★★             │
   │  错误检测: 运行时              错误检测: 编译时              │
   │  推荐场景: 老项目维护           推荐场景: 新项目首选            │
   └─────────────────────────────────────────────────────────┘

4.1.2 同一段代码两种写法

groovy
// build.gradle (Groovy)
plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.0'
}

group = 'com.example'
version = '1.0.0'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

test {
    useJUnitPlatform()
}
kotlin
// build.gradle.kts (Kotlin)
plugins {
    java
    id("org.springframework.boot") version "3.2.0"
}

group = "com.example"
version = "1.0.0"

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

tasks.test {
    useJUnitPlatform()
}

📌 核心观察:差别不大。Kotlin 多了一些括号和引号;Groovy 可以省略很多语法糖。但 Kotlin 在 IDE 里每个属性都能跳转,Groovy 大部分是"哑的"。


4.2 Groovy DSL 速查(看老项目用)

4.2.1 Groovy 的"省略大法"

Groovy DSL 之所以看起来短,靠的是大量语法省略

groovy
// 1. 调方法可以省括号
println('hi')
println 'hi'

// 2. 字符串单引号 / 双引号都行(双引号支持 ${} 插值)
def name = 'Alice'
println "Hello, ${name}"

// 3. 闭包是"最后一个参数"时可以放在括号外
list.each({ println it })
list.each { println it }

// 4. 整段配置 = 调方法 + 传闭包
dependencies {
    implementation 'guava'    // 等价于 dependencies({ implementation('guava') })
}

4.2.2 Groovy 经常踩的坑

groovy
// ❌ 漏 def,结果意外创建了"全局变量"
project.ext {
    foo = 'a'      // 实际定义了 ext.foo
    bar = 'b'      // 实际定义了 ext.bar
}
def baz = 'c'     // 这才是真正的局部变量

// ❌ 字符串拼接拼错了引号类型
def url = 'https://repo.example.com/${project.name}'  // ← 单引号!${} 不会被替换
def url = "https://repo.example.com/${project.name}"  // ← 双引号才行

// ❌ 误用 = 给 task 属性赋值
task foo {
    description = 'this is foo'  // 这是配置 task
    enabled = false              // 这也是配置 task
    // 但下面这行:
    something                    // ← 找不到属性名 something,但 Groovy 不会立刻报错
}

📌 建议:Groovy DSL 维护老项目时多用 IDEA 的"Convert to Kotlin DSL"功能(IDEA 2023.x+ 已经支持),然后在 Kotlin 上编辑,更安全。


4.3 Kotlin DSL 速查(新项目首选)

4.3.1 Kotlin DSL 的核心特性

kotlin
// 1. 调方法必须括号、字符串必须双引号
println("hi")

// 2. 闭包语法 = lambda
list.forEach { println(it) }

// 3. dependencies 块 → 调用 Project.dependencies(Action<DependencyHandlerScope>)
dependencies {
    implementation("guava")
}

// 4. 类型安全访问器(Type-safe accessors)—— 由插件生成
plugins {
    java         // ← java 不是字符串,是一个 PluginAccessor
    `maven-publish`  // 中划线插件名要用反引号
}

java {           // ← 这里的 java 来自 java 插件提供的 extension
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

4.3.2 Kotlin DSL 的"4 大写法"

kotlin
// 写法 1:直接配置 extension(最常见)
java {
    sourceCompatibility = JavaVersion.VERSION_17
}

// 写法 2:tasks.named —— 配置已存在的 task(懒求值)
tasks.named<Test>("test") {
    useJUnitPlatform()
    maxParallelForks = 4
}

// 写法 3:tasks.register —— 注册新 task(懒求值)
tasks.register<Copy>("copyDocs") {
    from("docs")
    into(layout.buildDirectory.dir("docs"))
}

// 写法 4:tasks.withType —— 配置某种类型的所有 task
tasks.withType<Test>().configureEach {
    useJUnitPlatform()
}

4.3.3 Kotlin DSL 的"加分写法"

kotlin
// 1. configureEach(强烈推荐):lazy + 适用于所有当前和未来的 task
tasks.withType<JavaCompile>().configureEach {
    options.encoding = "UTF-8"
}

// 2. extension lazy access —— extensions.getByName<XxxExtension>("xxx")
val springBoot = extensions.getByName<SpringBootExtension>("springBoot")

// 3. provider / property API(lazy + Configuration Cache 友好)
val outputFile = layout.buildDirectory.file("output.txt")  // 返回 Provider
tasks.register("write") {
    outputs.file(outputFile)
    doLast {
        outputFile.get().asFile.writeText("Hello")
    }
}

4.4 Groovy ⇄ Kotlin 完整对照表

4.4.1 基础语法

场景Groovy DSLKotlin DSL
字符串'hello'"hello""hello"(必须双引号)
字符串插值"Hello $name""Hello $name"
调方法println 'hi'println("hi")
列表def list = [1, 2, 3]val list = listOf(1, 2, 3)
Map[a: 1, b: 2]mapOf("a" to 1, "b" to 2)
闭包{ x -> x * 2 }{ x -> x * 2 }

4.4.2 项目元数据

场景GroovyKotlin
项目名rootProject.name = 'foo'rootProject.name = "foo"
groupgroup 'com.example'group = "com.example"
versionversion '1.0.0'version = "1.0.0"

4.4.3 插件

场景GroovyKotlin
内置插件id 'java'java
中划线插件id 'java-library'\java-library``
第三方插件id 'org.foo.bar' version '1.0'id("org.foo.bar") version "1.0"
Kotlin 插件id 'org.jetbrains.kotlin.jvm' version '...'kotlin("jvm") version "..."
apply(旧)apply plugin: 'java'apply(plugin = "java")

4.4.4 仓库

场景GroovyKotlin
Maven CentralmavenCentral()mavenCentral()
Googlegoogle()google()
自定义 mavenmaven { url 'https://x.y/z' }maven("https://x.y/z")
带凭证maven { url '...'; credentials { username 'u' } }maven { url = uri("..."); credentials { username = "u" } }

4.4.5 依赖

场景GroovyKotlin
implementationimplementation 'g:a:v'implementation("g:a:v")
testImplementationtestImplementation 'g:a:v'testImplementation("g:a:v")
模块依赖implementation project(':lib')implementation(project(":lib"))
文件依赖implementation files('libs/x.jar')implementation(files("libs/x.jar"))
排除传递implementation('g:a:v') { exclude group: 'x' }implementation("g:a:v") { exclude(group = "x") }
强制版本implementation('g:a:v!!')implementation("g:a:v!!")

4.4.6 任务

场景GroovyKotlin
注册 tasktask hello { doLast { println 'hi' } }tasks.register("hello") { doLast { println("hi") } }
注册 typedtask copyDocs(type: Copy) { ... }tasks.register<Copy>("copyDocs") { ... }
配置已有test { useJUnitPlatform() }tasks.test { useJUnitPlatform() }tasks.named<Test>("test") { ... }
配置一类tasks.withType(Test) { ... }tasks.withType<Test>().configureEach { ... }
dependsOntask foo { dependsOn 'bar' }tasks.register("foo") { dependsOn("bar") }

4.4.7 文件 / 路径

场景GroovyKotlin
项目目录project.projectDirproject.projectDir
build 目录project.buildDir (旧)layout.buildDirectory.get().asFile(推荐)
文件file('foo/bar.txt')file("foo/bar.txt")
文件树fileTree('src')fileTree("src")
文件集合files('a.txt', 'b.txt')files("a.txt", "b.txt")

4.4.8 扩展属性

场景GroovyKotlin
定义 extext.myProp = 'foo'extra["myProp"] = "foo"
用 extprintln myPropprintln(extra["myProp"])

4.5 settings.gradle.kts 详解

4.5.1 它能做什么?

kotlin
// settings.gradle.kts —— 完整版示例

// 1. 项目名
rootProject.name = "my-multi-project"

// 2. 包含子模块
include("app", "lib-core", "lib-data", "feature-billing")

// 3. 改子模块路径(如果不在标准位置)
project(":feature-billing").projectDir = file("features/billing")

// 4. 插件管理:插件从哪些仓库找
pluginManagement {
    repositories {
        gradlePluginPortal()
        google()
        mavenCentral()
    }
    // 也可以在这里管理插件版本
    plugins {
        kotlin("jvm") version "1.9.20"
    }
}

// 5. 依赖仓库管理:所有项目共享同一组仓库
dependencyResolutionManagement {
    repositoriesMode = RepositoriesMode.FAIL_ON_PROJECT_REPOS
    repositories {
        mavenCentral()
        google()
    }

    // 6. 引入 Version Catalog(强烈推荐)
    versionCatalogs {
        create("libs") {
            from(files("gradle/libs.versions.toml"))
        }
    }
}

// 7. Composite Build(包含其他独立项目作为依赖源)
includeBuild("../my-shared-library")

// 8. 插件版本仓库 + 镜像
// pluginManagement.repositories.maven("https://maven.aliyun.com/repository/gradle-plugin")

4.5.2 RepositoriesMode 三种模式

模式行为
PREFER_PROJECT项目里定义的仓库优先(默认,但容易让多模块仓库散乱
PREFER_SETTINGS优先用 settings 里的,子项目可以再加
FAIL_ON_PROJECT_REPOS强烈推荐 —— 子项目不允许定义 repositories,必须统一在 settings

💡 用 FAIL_ON_PROJECT_REPOS 的好处:避免某个子模块偷偷加个公司私服,导致依赖供应链混乱。


4.6 gradle.properties 详解

4.6.1 它能做什么?

gradle.properties 存"全局属性",构建脚本可以读到:

properties
# 项目级属性(可在 build.gradle.kts 里以 project property 访问)
projectVersion=1.0.0
springBootVersion=3.2.0

# JVM 配置
org.gradle.jvmargs=-Xmx2g -XX:+UseG1GC -XX:MaxMetaspaceSize=512m

# 性能开关
org.gradle.parallel=true              # 并行构建多模块
org.gradle.caching=true               # 开启 Build Cache
org.gradle.configuration-cache=true   # 开启 Configuration Cache(Gradle 7+)
org.gradle.daemon=true                # 启用 Daemon(默认 true)

# Kotlin 配置
kotlin.code.style=official
kotlin.incremental=true

# Android 相关
android.useAndroidX=true
android.nonTransitiveRClass=true

4.6.2 在脚本里读

kotlin
// build.gradle.kts
val springBootVersion: String by project   // 读 gradle.properties 里的同名属性

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web:$springBootVersion")
}

4.6.3 三种 properties 文件优先级

   ① 命令行 -P 参数(最高)  →  ./gradlew build -PprojectVersion=2.0
   ② 项目根 gradle.properties
   ③ ~/.gradle/gradle.properties(用户级,最低)

💡 典型用法:把 token、密码~/.gradle/gradle.properties(不会进 Git),把 版本号 放项目根(要进 Git)。


4.7 Version Catalog(版本目录)—— 现代依赖管理推荐

4.7.1 痛点:版本号散落各处

老项目里:

kotlin
// app/build.gradle.kts
implementation("org.springframework.boot:spring-boot-starter-web:3.2.0")

// lib/build.gradle.kts
implementation("org.springframework.boot:spring-boot-starter-web:3.1.5")  // ← 不一致!

// feature/build.gradle.kts
implementation("org.springframework.boot:spring-boot-starter-web:3.2.1")  // ← 又一个版本!

升级 Spring Boot 时要找遍所有 build 文件。版本散乱是大型项目的噩梦

4.7.2 解决:libs.versions.toml

gradle/libs.versions.toml 集中管理:

toml
[versions]
spring-boot = "3.2.0"
kotlin = "1.9.20"
junit = "5.10.0"
guava = "32.1.3-jre"

[libraries]
spring-boot-starter-web = { module = "org.springframework.boot:spring-boot-starter-web", version.ref = "spring-boot" }
spring-boot-starter-test = { module = "org.springframework.boot:spring-boot-starter-test", version.ref = "spring-boot" }
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" }
guava = { module = "com.google.guava:guava", version.ref = "guava" }

[plugins]
spring-boot = { id = "org.springframework.boot", version.ref = "spring-boot" }
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }

[bundles]
# 把多个常一起用的依赖打包
spring-web = ["spring-boot-starter-web", "spring-boot-starter-actuator"]

在 build.gradle.kts 里用:

kotlin
plugins {
    alias(libs.plugins.kotlin.jvm)
    alias(libs.plugins.spring.boot)
}

dependencies {
    implementation(libs.spring.boot.starter.web)        // ← 类型安全!
    implementation(libs.guava)

    testImplementation(libs.junit.jupiter)
    // 或者用 bundle:
    // implementation(libs.bundles.spring.web)
}

4.7.3 命名规范(重要)

libs.versions.toml 里:       Kotlin DSL 访问:
spring-boot                   libs.versions.spring.boot
spring-boot-starter-web       libs.spring.boot.starter.web   ← 中划线变成点
junit-jupiter                 libs.junit.jupiter

规则:TOML key 里的 - 在 Kotlin DSL 里变成 .,下划线 _ 不变。所以推荐统一用中划线

4.7.4 Version Catalog 的好处

好处说明
一处定义所有模块共享同一份版本号
类型安全IDE 能补全:libs.spring.boot.starter.web
重构友好改版本号不用改各 build 文件
依赖锁定配合 dependency-locking 可精确锁版本
可发布Catalog 本身可以发布给其他项目复用

4.8 章末小结

                    ★ 第 4 章核心知识图谱 ★

        ┌─────────────────────┼─────────────────────┐
        │                     │                     │
   ┌────▼────┐          ┌────▼────┐          ┌────▼─────┐
   │ Groovy  │          │ Kotlin  │          │ 配套文件 │
   │  DSL    │          │   DSL   │          ├──────────┤
   ├─────────┤          ├─────────┤          │ settings │
   │ .gradle │          │ .gradle.│          │ properties│
   │ 老项目  │          │   kts   │          │ Version  │
   │ 动态    │          │ 新项目   │          │ Catalog  │
   │ 弱提示  │          │ 静态    │          │          │
   │         │          │ 强提示  │          │          │
   └─────────┘          └─────────┘          └──────────┘

                                       ★ 多模块统一版本必备 ★

🎤 4.9 章末面试题(10 道高频题)

Q1. Groovy DSL 和 Kotlin DSL 的本质区别是什么?

:3 个核心区别:

  1. 语言:前者是 Groovy(动态类型),后者是 Kotlin(静态类型);
  2. 检错时机:Groovy 写错属性名要运行时才发现;Kotlin 编译期就报错(红线);
  3. IDE 体验:Kotlin DSL 智能提示完整、可重构;Groovy DSL 大部分地方"哑",靠记忆 + 文档。

第二个体现在新项目应该首选 Kotlin DSL,老项目维护可以继续 Groovy。


Q2. 为什么 .gradle.kts 文件第一次启动比 .gradle 慢?

:因为 Kotlin DSL 是真正的 Kotlin 代码,要先编译成字节码才能执行。Groovy DSL 由 Gradle 直接解释执行(虽然 Groovy 也是 JVM 语言,但执行速度更快)。

不过 Gradle 会缓存编译结果.gradle/ 目录,第二次起就跟 Groovy 差不多。如果脚本经常改,差异会显著。


Q3. settings.gradle.kts 里 pluginManagementdependencyResolutionManagement 分别管什么?

  • pluginManagement:管 plugin 的来源 —— plugins { id("...") version "..." } 里的插件去哪找、用哪个版本。
  • dependencyResolutionManagement:管依赖(dependencies {})的仓库 —— 所有子项目共享一套 repositories。

为啥要分开?因为插件本身就是 jar,需要先解析才能让脚本跑起来;它跟普通依赖的解析时机不同。


Q4. Version Catalog 是什么?为什么强烈推荐用?

:Version Catalog 是 Gradle 7+ 内置的集中式版本/依赖管理机制,把所有依赖的 group:artifact:version 统一定义到 gradle/libs.versions.toml,子模块通过 libs.xxx.yyy 类型安全引用。

好处

  1. 一处定义,处处复用:升级版本只改 1 个地方;
  2. 类型安全:IDE 全程智能提示;
  3. 避免散落:杜绝同一个库出现 3 个版本号;
  4. 可发布:把 catalog 自身发到 Maven 仓库,多项目共享。

老项目用 ext { springBootVersion = "3.2.0" } 管版本是过时做法,现代项目应该一律用 Version Catalog。


Q5. 为什么 Kotlin DSL 里 tasks.testtasks.named<Test>("test") 更简洁?

:因为 Kotlin DSL 有 type-safe accessors(类型安全访问器) —— 当 java 插件被应用时,Gradle 会自动生成 tasks.testtasks.compileJava 这些扩展属性,给你直接用。

tasks.named<Test>("test") 是手写访问 —— 适合:

  • 插件没生成 accessor 的 task;
  • 自定义 task(如 tasks.named<Copy>("myCopy"));
  • buildSrc 里写 convention plugin(accessor 不一定可用)。

Q6. 把一个 Groovy DSL 项目迁移到 Kotlin DSL,要注意什么?

:5 个常见坑:

  1. 字符串只能用双引号:单引号在 Kotlin 是 Char。
  2. 方法调用必须带括号println "x"println("x")
  3. 赋值必须显式 =:Groovy 的 version '1.0' → Kotlin 的 version = "1.0"
  4. 闭包参数加 it 时类型可能不匹配:Groovy 全动态,Kotlin 要看 lambda 的接收者类型。
  5. task 配置要用 tasks.namedtasks.<name>:不能再像 Groovy 那样 test { ... } 顶层调用。

💡 工具:IDEA 2023.x+ 提供 "Convert build.gradle to Kotlin DSL",能自动转大部分语法。


Q7. gradle.properties 里设置 org.gradle.parallel=true 有什么作用?

:开启 多 Project 并行配置 + 并行执行

  • 假如你有 5 个子模块,没开并行:依次执行(A→B→C→D→E);
  • 开了并行:Gradle 检测无依赖关系的模块并发跑(A、B、C 同时跑,D、E 同时跑);
  • 大型项目(10+ 模块)能把构建时间压缩 30-70%。

⚠️ 要求:每个 task 要正确声明 input/output,否则并行执行可能产生冲突。


Q8. 为什么有人推荐 repositoriesMode = RepositoriesMode.FAIL_ON_PROJECT_REPOS

:这是 settings.gradle.kts 里的一个开关,开启后子模块不允许定义 repositories {},必须统一在 settings 里配。

好处

  1. 统一仓库源 —— 避免某个子模块偷偷加个公司私服或镜像;
  2. 依赖供应链清晰 —— 所有依赖从可控的几个仓库下;
  3. 审计友好 —— 安全合规要求时容易回答"我们用了哪些仓库"。

代价:迁移老项目时,要把所有子模块的 repositories 块挪到 settings。


Q9. Version Catalog 里 bundles 是干啥的?

bundles = "依赖打包",把多个常一起用的依赖捆成一个引用。比如:

toml
[bundles]
spring-web-stack = ["spring-boot-starter-web", "spring-boot-starter-actuator", "spring-boot-starter-validation"]
kotlin
dependencies {
    implementation(libs.bundles.spring.web.stack)   // 一行 = 拉 3 个依赖
}

适合"配套使用的依赖组合",避免 build 文件里堆 5-6 行类似的依赖。


Q10. Kotlin DSL 和 Groovy DSL 性能差距有多大?

  • 首次冷启动:Kotlin DSL 比 Groovy DSL 慢 30-100% 不等(脚本越大越明显),因为要先编译 Kotlin。
  • 热启动:基本一致(都被 Daemon 缓存)。
  • 执行 task:完全没差别(都跑成字节码)。

结论:除非你的项目有 200+ 个 build 脚本且 CI 环境冷启动频繁,否则性能不是选择 DSL 的主要因素,可读性和类型安全更重要。


下一章 → 第 5 章 · Task 全攻略 →

🎬 可视化演示

演示加载缓慢或样式异常?点此在新标签页打开 ↗

💻 示例代码

txt
/*
 * 第 4 章 · DSL 演示 — Kotlin DSL 完整示例
 *
 * 这个脚本展示了 Kotlin DSL 几乎所有常见写法,配合注释对照学习。
 * 跑:
 *   ./gradlew tasks
 *   ./gradlew showDsl     —— 打印各 DSL 写法的运行时结果
 */

// ============= 1. 应用插件 =============
plugins {
    java                                                       // 内置插件直接写
    `java-library`                                             // 中划线插件用反引号
    application                                                // 内置
    // id("org.springframework.boot") version "3.2.0"          // 第三方插件
    // kotlin("jvm") version "1.9.20"                          // Kotlin 插件简写
}

group = "com.example.gradle.dsl"                              // 用 = 赋值
version = "1.0.0-SNAPSHOT"

// ============= 2. Java 配置 =============
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
    withSourcesJar()                                            // 顺手生成 sources jar
    withJavadocJar()                                            // 顺手生成 javadoc jar
}

application {
    mainClass = "com.example.dsl.Main"                          // Provider 赋值
}

// ============= 3. 仓库 =============
repositories {
    mavenCentral()
    google()
    // maven("https://maven.aliyun.com/repository/public")      // 简化写法
    maven {                                                     // 完整写法(带凭证)
        url = uri("https://example.com/repo")
        // credentials {
        //     username = "reader"
        //     password = "secret"
        // }
    }
}

// ============= 4. 依赖 =============
dependencies {
    // 主代码依赖
    implementation("com.google.guava:guava:32.1.3-jre")

    // 库的 API(要传递给消费者)
    api("org.slf4j:slf4j-api:2.0.9")

    // 编译时 only
    compileOnly("javax.servlet:javax.servlet-api:4.0.1")

    // 运行时 only
    runtimeOnly("org.postgresql:postgresql:42.7.0")

    // 测试依赖
    testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
    testRuntimeOnly("org.junit.platform:junit-platform-launcher")

    // 排除某个传递依赖
    implementation("org.springframework:spring-core:6.1.1") {
        exclude(group = "commons-logging", module = "commons-logging")
    }

    // 模块依赖(多模块)
    // implementation(project(":lib-core"))

    // 本地文件
    // implementation(files("libs/local.jar"))
}

// ============= 5. 配置已存在的 Task =============
tasks.test {
    useJUnitPlatform()
    maxParallelForks = 4
    testLogging {
        events("passed", "skipped", "failed")
    }
}

// ============= 6. 配置一类 Task(推荐写法) =============
tasks.withType<JavaCompile>().configureEach {
    options.encoding = "UTF-8"
    options.compilerArgs.add("-Xlint:unchecked")
}

tasks.withType<Test>().configureEach {
    systemProperty("file.encoding", "UTF-8")
}

// ============= 7. 注册新 Task =============
tasks.register<Copy>("copyDocs") {
    group = "ch04-demo"
    from("docs")
    into(layout.buildDirectory.dir("docs"))
    include("**/*.md")
}

tasks.register<Zip>("packageDocs") {
    group = "ch04-demo"
    dependsOn("copyDocs")
    archiveFileName = "docs-" + version + ".zip"
    destinationDirectory = layout.buildDirectory.dir("dist")
    from(layout.buildDirectory.dir("docs"))
}

// ============= 8. 扩展属性 =============
val mySecret by extra("from-build-script")
extra["anotherKey"] = "another-value"

// 读 gradle.properties 里的属性
val springBootVersion: String by project   // 必须在 gradle.properties 里定义同名属性,否则会报错

// ============= 9. 一个综合演示 task =============
tasks.register("showDsl") {
    group = "ch04-demo"
    description = "打印各 DSL 配置的实际运行时值"
    doLast {
        println("================ Kotlin DSL Demo ================")
        println("project.name        = " + project.name)
        println("project.group       = " + project.group)
        println("project.version     = " + project.version)
        println("java target         = " + java.toolchain.languageVersion.get())
        println("repositories        = " + repositories.map { it.name })
        println("plugins applied     = " + plugins.map { it::class.simpleName })
        println("custom extra        = " + extra["mySecret"])
        println("=================================================")
    }
}
txt
# ===== 第 4 章 · DSL 演示 — gradle.properties =====
# 项目级属性 + JVM / 性能 / 工具链配置都放这里

# ===== 自定义属性(脚本里通过 by project 读取)=====
springBootVersion=3.2.0
kotlinVersion=1.9.20

# ===== JVM 配置 =====
# 给 Gradle 进程更多内存,否则大型项目容易 OOM
org.gradle.jvmargs=-Xmx2g -XX:+UseG1GC -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8

# ===== 性能优化(强烈建议开启)=====
# 多模块并行构建
org.gradle.parallel=true

# 启用 Build Cache(本地缓存)
org.gradle.caching=true

# 启用 Configuration Cache(Gradle 7+)
org.gradle.configuration-cache=true

# Daemon(默认就 true,明确写出来更清楚)
org.gradle.daemon=true

# ===== Kotlin 配置 =====
kotlin.code.style=official
kotlin.incremental=true

# ===== Android 相关(如用 Android 插件)=====
# android.useAndroidX=true
# android.nonTransitiveRClass=true
toml
# ===== 第 4 章 · DSL 演示 — Version Catalog =====
# 这是 Gradle 7+ 推荐的"统一版本管理"方案
# 所有依赖的 group:artifact:version 在这里定义,子模块通过 libs.xxx 类型安全访问

[versions]
# 核心版本号集中放这里
spring-boot = "3.2.0"
kotlin = "1.9.20"
junit = "5.10.0"
guava = "32.1.3-jre"
slf4j = "2.0.9"

[libraries]
# 单个依赖
spring-boot-starter-web = { module = "org.springframework.boot:spring-boot-starter-web", version.ref = "spring-boot" }
spring-boot-starter-actuator = { module = "org.springframework.boot:spring-boot-starter-actuator", version.ref = "spring-boot" }
spring-boot-starter-test = { module = "org.springframework.boot:spring-boot-starter-test", version.ref = "spring-boot" }

# 简短依赖(直接写版本,不引用 versions)
guava = { module = "com.google.guava:guava", version.ref = "guava" }
slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }

junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" }
junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" }

[plugins]
# 把插件也声明进来,build.gradle.kts 用 alias(libs.plugins.xxx)
spring-boot = { id = "org.springframework.boot", version.ref = "spring-boot" }
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }

[bundles]
# bundle = "依赖打包",多个常一起用的依赖打成一个引用
spring-web-stack = ["spring-boot-starter-web", "spring-boot-starter-actuator"]
testing = ["junit-jupiter", "junit-jupiter-engine"]

# 在 build.gradle.kts 里这样用:
#   plugins { alias(libs.plugins.spring.boot) }
#   dependencies {
#     implementation(libs.spring.boot.starter.web)
#     implementation(libs.bundles.spring.web.stack)
#     testImplementation(libs.junit.jupiter)
#   }
txt
/*
 * 第 4 章 · DSL 演示 — settings.gradle.kts
 * 演示 settings 的所有常见配置:插件管理、依赖仓库、Version Catalog、Composite Build
 */

rootProject.name = "ch04-dsl-demo"

// ===== 1. 插件管理 =====
pluginManagement {
    repositories {
        gradlePluginPortal()
        google()
        mavenCentral()
        // maven("https://maven.aliyun.com/repository/gradle-plugin")
    }
    // 也可以集中声明插件版本,build.gradle.kts 里只需 plugins { id("...") }
    plugins {
        // kotlin("jvm") version "1.9.20"
    }
}

// ===== 2. 依赖仓库管理 =====
dependencyResolutionManagement {
    // 强制所有依赖只从 settings 这里的 repositories 拉
    // 子模块再写 repositories {} 会构建失败
    repositoriesMode = RepositoriesMode.FAIL_ON_PROJECT_REPOS

    repositories {
        mavenCentral()
        google()
        // maven("https://maven.aliyun.com/repository/public")
    }

    // ===== 3. 引入 Version Catalog =====
    versionCatalogs {
        create("libs") {
            from(files("gradle/libs.versions.toml"))
        }
    }
}

// ===== 4. 包含子模块(如有)=====
// include("app", "lib-core", "feature-billing")

// 改子模块路径
// project(":feature-billing").projectDir = file("features/billing")

// ===== 5. Composite Build =====
// includeBuild("../my-shared-library")

build.gradle.kts ↗ · gradle.properties ↗ · gradle/libs.versions.toml ↗ · settings.gradle.kts ↗