为 Gradle 个子项目配置 Kotlin 扩展
Configure Kotlin extension for Gradle subprojects
我正在为 JVM 创建一个基于 Kotlin 的多模块 Gradle 项目。由于根项目不包含任何代码,因此Kotlin插件应该只应用于子项目。
build.gradle.kts
(根项目)
plugins {
kotlin("jvm") version "1.6.20" apply false
}
subprojects {
apply(plugin = "kotlin")
group = "com.example"
repositories {
mavenCentral()
}
dependencies {}
kotlin {
jvmToolchain {
check(this is JavaToolchainSpec)
languageVersion.set(JavaLanguageVersion.of(11))
}
}
}
尝试设置工具链导致构建在 kotlin {...}
扩展失败:
Unresolved reference. None of the following candidates is applicable because of receiver type mismatch:
public fun DependencyHandler.kotlin(module: String, version: String? = ...): Any defined in org.gradle.kotlin.dsl
public fun PluginDependenciesSpec.kotlin(module: String): PluginDependencySpec defined in org.gradle.kotlin.dsl
如果我将扩展定义复制到每个子项目构建脚本,它工作正常,但为什么它在主脚本中不可用?
这是我最喜欢在 Gradle 中修复的问题之一,它真正展示了可能的灵活性(同时也展示了为什么 Gradle 会很复杂!)
首先,我将提供一些关于 subprojects {}
DSL 的背景信息,然后我将展示如何修复您的脚本,最后我将展示与 buildSrc 共享构建逻辑的最佳方式会议插件。 (虽然是最后一个,但我真的很推荐使用buildSrc!)
组合与继承
使用allprojects {}
和subprojects {}
真的很常见,我看到了很多。它更类似于 Maven 的工作方式,其中所有配置都在 'parent' 构建文件中定义。但是 Gradle.
不推荐
[A], discouraged, way to share build logic between subproject is cross project configuration via the subprojects {}
and allprojects {}
DSL constructs.
(这可能很常见,因为它很容易理解 - 它使 Gradle 更像 Maven,因此每个项目都继承自一个 parent。但是 Gradle 是为组合而设计的。延伸阅读:Composition over inheritance: Gradle vs Maven)
快速修复:'Unresolved reference'
您看到的错误基本上是因为您没有应用 Kotlin 插件。
plugins {
kotlin("jvm") version "1.6.20" apply false // <- Kotlin DSL won't be loaded
}
kotlin { }
配置块是一个非常有用的扩展函数,在应用Kotlin插件时加载。这是它的样子:
/**
* Configures the [kotlin][org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension] extension.
*/
fun org.gradle.api.Project.`kotlin`(configure: Action<org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension>): Unit =
(this as org.gradle.api.plugins.ExtensionAware).extensions.configure("kotlin", configure)
// (note: this is generated code)
所以如果我们没有扩展功能,我们可以直接调用configure
,从而配置Kotlin扩展。
subprojects {
// this is the traditional Gradle way of configuring extensions,
// and what the `kotlin { }` helper function will call.
configure<org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension> {
jvmToolchain {
check(this is JavaToolchainSpec)
languageVersion.set(JavaLanguageVersion.of(11))
}
}
// without the Kotlin Gradle plugin, this helper function isn't available
// kotlin {
// jvmToolchain {
// check(this is JavaToolchainSpec)
// languageVersion.set(JavaLanguageVersion.of(11))
// }
// }
}
然而,即使这有效,使用 subprojects {}
也有问题。有更好的方法...
buildSrc 和约定插件
buildSrc
基本上是一个独立的 Gradle 项目,我们可以在主项目的构建脚本中使用它的输出。所以我们可以编写自己的自定义 Gradle 插件,定义约定,我们可以有选择地将其应用于 'main' 构建中的任何子项目。
(这是Gradle和Maven的主要区别。在Gradle中,一个子项目可以被任意数量的插件配置。在Maven中,只有一个parent。组合与继承!)
Gradle文档中有a full guide on setting up convention plugins,所以我在这里简单总结一下解决方法。
1。设置 ./buildSrc
在您的项目根目录中创建一个名为 buildSrc
的目录。
因为 buildSrc
是一个独立的项目,所以创建一个 ./buildSrc/build.gradle.kts
和 ./buildSrc/settings.gradle.kts
文件,就像通常的项目一样。
在./buildSrc/build.gradle.kts
,
- 应用
kotlin-dsl
插件
- 添加对 Gradle 插件的依赖,以便在项目的任何地方使用
// ./buildSrc/build.gradle.kts
plugins {
`kotlin-dsl` // this will produce our Gradle convention plugins
kotlin("jvm") version "1.5.31"
// yes, use 1.5.31. It's a long story, but Gradle 7.4 uses an embedded version of Kotlin.
// It's annoying but not important. The Kotlin plugin version below, in dependencies { },
// will be used for our 'main' project.
// https://github.com/gradle/gradle/issues/16345
}
val kotlinVersion = "1.6.20"
dependencies {
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
}
请注意,我使用了 Kotlin Gradle 插件的 Maven 存储库 坐标,而不是插件 ID!
如果愿意,您还可以将其他依赖项添加到 ./buildSrc/build.gradle.ts
中。如果你想在构建脚本中解析 JSON,那么添加对 JSON 解析器的依赖,比如 kotlinx-serialization
.
2。创建约定插件
创建您可以应用于任何 Kotlin JVM 子项目的 Kotlin JVM 约定。
// ./buildSrc/src/main/kotlin/my/project/convention/kotlin-jvm.gradle.kts
package my.project.convention
plugins {
kotlin("jvm") // don't include a version - that's provided by ./buildSrc/build.gradle.kts
}
dependencies {
// you can define default dependencies, if desired
// testImplementation(kotlin("test"))
}
kotlin {
jvmToolchain {
check(this is JavaToolchainSpec)
languageVersion.set(JavaLanguageVersion.of(11))
}
}
}
不要忘记添加 package
声明!我忘记了几次,它会导致难以弄清楚的错误。
3。应用约定插件
就像 Gradle 插件有 ID 一样,我们的约定插件也有 ID。就是包名+.gradle.kts
前的那个位。所以在我们的例子中,ID 是 my.project.convention.kotlin-jvm
我们可以像常规 Gradle 插件一样应用它...
// ./subprojects/my-project/build.gradle.kts
plugins {
id("my.project.convention.kotlin-jvm")
}
(约定插件也可以导入其他约定插件,使用id("...")
)
此外,由于我们使用的是 Kotlin,因此还有一种更好的方法。您知道如何包含 Gradle 插件,例如 java
和 java-library
。我们可以用同样的方式导入我们的约定插件!
// ./subprojects/my-project/build.gradle.kts
plugins {
// id("my.project.convention.kotlin-jvm")
my.project.convention.`kotlin-jvm` // this works just like id("...") does
}
请注意插件 ID 周围的反引号 - 因为连字符,所以需要它们。
(警告:这种非 id("...")
方式在 buildSrc
中不起作用,仅在主项目中有效)
结果
现在根目录./build.gradle.kts
可以保持得非常干净整洁了——它只需要定义项目的组和版本。
因为我们使用的是约定插件而不是一揽子subprojects
,所以每个子项目都可以专门化,只导入它需要的约定插件,而不重复。
站点说明:在 buildSrc
和主项目
之间共享存储库
通常您想在 buildSrc
和主项目之间共享存储库。因为 Gradle 插件不是专门针对项目的,所以我们可以为任何东西写一个插件,包括 settings.gradle.kts
!
我所做的是创建一个包含我要使用的所有存储库的文件...
// ./buildSrc/repositories.settings.gradle.kts
@Suppress("UnstableApiUsage") // centralised repository definitions are incubating
dependencyResolutionManagement {
repositories {
mavenCentral()
jitpack()
gradlePluginPortal()
}
pluginManagement {
repositories {
jitpack()
gradlePluginPortal()
mavenCentral()
}
}
}
fun RepositoryHandler.jitpack() {
maven("https://jitpack.io")
}
(名称 repositories.settings.gradle.kts
并不重要 - 但将其命名为 *.settings.gradle.kts
应该意味着 IntelliJ 会提供建议,但目前存在错误。)
然后我可以将其作为插件导入到其他 settings.gradle.kts
文件中,就像您将 Kotlin JVM 插件应用于子项目一样。
// ./buildSrc/settings.gradle.kts
apply(from = "./repositories.settings.gradle.kts")
// ./settings.gradle.kts
apply(from = "./buildSrc/repositories.settings.gradle.kts")
我正在为 JVM 创建一个基于 Kotlin 的多模块 Gradle 项目。由于根项目不包含任何代码,因此Kotlin插件应该只应用于子项目。
build.gradle.kts
(根项目)
plugins {
kotlin("jvm") version "1.6.20" apply false
}
subprojects {
apply(plugin = "kotlin")
group = "com.example"
repositories {
mavenCentral()
}
dependencies {}
kotlin {
jvmToolchain {
check(this is JavaToolchainSpec)
languageVersion.set(JavaLanguageVersion.of(11))
}
}
}
尝试设置工具链导致构建在 kotlin {...}
扩展失败:
Unresolved reference. None of the following candidates is applicable because of receiver type mismatch:
public fun DependencyHandler.kotlin(module: String, version: String? = ...): Any defined in org.gradle.kotlin.dsl
public fun PluginDependenciesSpec.kotlin(module: String): PluginDependencySpec defined in org.gradle.kotlin.dsl
如果我将扩展定义复制到每个子项目构建脚本,它工作正常,但为什么它在主脚本中不可用?
这是我最喜欢在 Gradle 中修复的问题之一,它真正展示了可能的灵活性(同时也展示了为什么 Gradle 会很复杂!)
首先,我将提供一些关于 subprojects {}
DSL 的背景信息,然后我将展示如何修复您的脚本,最后我将展示与 buildSrc 共享构建逻辑的最佳方式会议插件。 (虽然是最后一个,但我真的很推荐使用buildSrc!)
组合与继承
使用allprojects {}
和subprojects {}
真的很常见,我看到了很多。它更类似于 Maven 的工作方式,其中所有配置都在 'parent' 构建文件中定义。但是 Gradle.
[A], discouraged, way to share build logic between subproject is cross project configuration via the
subprojects {}
andallprojects {}
DSL constructs.
(这可能很常见,因为它很容易理解 - 它使 Gradle 更像 Maven,因此每个项目都继承自一个 parent。但是 Gradle 是为组合而设计的。延伸阅读:Composition over inheritance: Gradle vs Maven)
快速修复:'Unresolved reference'
您看到的错误基本上是因为您没有应用 Kotlin 插件。
plugins {
kotlin("jvm") version "1.6.20" apply false // <- Kotlin DSL won't be loaded
}
kotlin { }
配置块是一个非常有用的扩展函数,在应用Kotlin插件时加载。这是它的样子:
/**
* Configures the [kotlin][org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension] extension.
*/
fun org.gradle.api.Project.`kotlin`(configure: Action<org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension>): Unit =
(this as org.gradle.api.plugins.ExtensionAware).extensions.configure("kotlin", configure)
// (note: this is generated code)
所以如果我们没有扩展功能,我们可以直接调用configure
,从而配置Kotlin扩展。
subprojects {
// this is the traditional Gradle way of configuring extensions,
// and what the `kotlin { }` helper function will call.
configure<org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension> {
jvmToolchain {
check(this is JavaToolchainSpec)
languageVersion.set(JavaLanguageVersion.of(11))
}
}
// without the Kotlin Gradle plugin, this helper function isn't available
// kotlin {
// jvmToolchain {
// check(this is JavaToolchainSpec)
// languageVersion.set(JavaLanguageVersion.of(11))
// }
// }
}
然而,即使这有效,使用 subprojects {}
也有问题。有更好的方法...
buildSrc 和约定插件
buildSrc
基本上是一个独立的 Gradle 项目,我们可以在主项目的构建脚本中使用它的输出。所以我们可以编写自己的自定义 Gradle 插件,定义约定,我们可以有选择地将其应用于 'main' 构建中的任何子项目。
(这是Gradle和Maven的主要区别。在Gradle中,一个子项目可以被任意数量的插件配置。在Maven中,只有一个parent。组合与继承!)
Gradle文档中有a full guide on setting up convention plugins,所以我在这里简单总结一下解决方法。
1。设置 ./buildSrc
在您的项目根目录中创建一个名为 buildSrc
的目录。
因为 buildSrc
是一个独立的项目,所以创建一个 ./buildSrc/build.gradle.kts
和 ./buildSrc/settings.gradle.kts
文件,就像通常的项目一样。
在./buildSrc/build.gradle.kts
,
- 应用
kotlin-dsl
插件 - 添加对 Gradle 插件的依赖,以便在项目的任何地方使用
// ./buildSrc/build.gradle.kts
plugins {
`kotlin-dsl` // this will produce our Gradle convention plugins
kotlin("jvm") version "1.5.31"
// yes, use 1.5.31. It's a long story, but Gradle 7.4 uses an embedded version of Kotlin.
// It's annoying but not important. The Kotlin plugin version below, in dependencies { },
// will be used for our 'main' project.
// https://github.com/gradle/gradle/issues/16345
}
val kotlinVersion = "1.6.20"
dependencies {
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
}
请注意,我使用了 Kotlin Gradle 插件的 Maven 存储库 坐标,而不是插件 ID!
如果愿意,您还可以将其他依赖项添加到 ./buildSrc/build.gradle.ts
中。如果你想在构建脚本中解析 JSON,那么添加对 JSON 解析器的依赖,比如 kotlinx-serialization
.
2。创建约定插件
创建您可以应用于任何 Kotlin JVM 子项目的 Kotlin JVM 约定。
// ./buildSrc/src/main/kotlin/my/project/convention/kotlin-jvm.gradle.kts
package my.project.convention
plugins {
kotlin("jvm") // don't include a version - that's provided by ./buildSrc/build.gradle.kts
}
dependencies {
// you can define default dependencies, if desired
// testImplementation(kotlin("test"))
}
kotlin {
jvmToolchain {
check(this is JavaToolchainSpec)
languageVersion.set(JavaLanguageVersion.of(11))
}
}
}
不要忘记添加 package
声明!我忘记了几次,它会导致难以弄清楚的错误。
3。应用约定插件
就像 Gradle 插件有 ID 一样,我们的约定插件也有 ID。就是包名+.gradle.kts
前的那个位。所以在我们的例子中,ID 是 my.project.convention.kotlin-jvm
我们可以像常规 Gradle 插件一样应用它...
// ./subprojects/my-project/build.gradle.kts
plugins {
id("my.project.convention.kotlin-jvm")
}
(约定插件也可以导入其他约定插件,使用id("...")
)
此外,由于我们使用的是 Kotlin,因此还有一种更好的方法。您知道如何包含 Gradle 插件,例如 java
和 java-library
。我们可以用同样的方式导入我们的约定插件!
// ./subprojects/my-project/build.gradle.kts
plugins {
// id("my.project.convention.kotlin-jvm")
my.project.convention.`kotlin-jvm` // this works just like id("...") does
}
请注意插件 ID 周围的反引号 - 因为连字符,所以需要它们。
(警告:这种非 id("...")
方式在 buildSrc
中不起作用,仅在主项目中有效)
结果
现在根目录./build.gradle.kts
可以保持得非常干净整洁了——它只需要定义项目的组和版本。
因为我们使用的是约定插件而不是一揽子subprojects
,所以每个子项目都可以专门化,只导入它需要的约定插件,而不重复。
站点说明:在 buildSrc
和主项目
之间共享存储库
通常您想在 buildSrc
和主项目之间共享存储库。因为 Gradle 插件不是专门针对项目的,所以我们可以为任何东西写一个插件,包括 settings.gradle.kts
!
我所做的是创建一个包含我要使用的所有存储库的文件...
// ./buildSrc/repositories.settings.gradle.kts
@Suppress("UnstableApiUsage") // centralised repository definitions are incubating
dependencyResolutionManagement {
repositories {
mavenCentral()
jitpack()
gradlePluginPortal()
}
pluginManagement {
repositories {
jitpack()
gradlePluginPortal()
mavenCentral()
}
}
}
fun RepositoryHandler.jitpack() {
maven("https://jitpack.io")
}
(名称 repositories.settings.gradle.kts
并不重要 - 但将其命名为 *.settings.gradle.kts
应该意味着 IntelliJ 会提供建议,但目前存在错误。)
然后我可以将其作为插件导入到其他 settings.gradle.kts
文件中,就像您将 Kotlin JVM 插件应用于子项目一样。
// ./buildSrc/settings.gradle.kts
apply(from = "./repositories.settings.gradle.kts")
// ./settings.gradle.kts
apply(from = "./buildSrc/repositories.settings.gradle.kts")