【问题标题】:Configure Kotlin extension for Gradle subprojects为 Gradle 子项目配置 Kotlin 扩展
【发布时间】:2022-12-24 22:01:08
【问题描述】:

我正在为 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

如果我将扩展定义复制到每个子项目构建脚本,它工作正常,但为什么它在主脚本中不可用?

【问题讨论】:

    标签: kotlin gradle gradle-kotlin-dsl gradle-multi-project-build kotlin-gradle-plugin


    【解决方案1】:

    这是我最喜欢在 Gradle 中修复的东西之一,并且真正展示了可能的灵活性(同时也展示了为什么 Gradle 会很复杂!)

    首先,我将提供有关 subprojects {} DSL 的一些背景信息,然后我将展示如何修复您的脚本,最后我将展示与 buildSrc 约定插件共享构建逻辑的最佳方式。 (尽管它是最后一个,但我真的推荐使用 buildSrc!)

    组合与继承

    使用allprojects {}subprojects {}真的很常见,我看到了很多。它更类似于 Maven 的工作方式,其中所有配置都在“父”构建文件中定义。但是 Gradle 不推荐它。

    [A],不鼓励,在子项目之间共享构建逻辑的方法是通过 subprojects {}allprojects {} DSL 构造进行跨项目配置。

    Gradle Docs: Sharing Build Logic between Subprojects

    (这可能很常见,因为它很容易理解——它让 Gradle 工作起来更像 Maven,所以每个项目都继承自一个父项目。但是 Gradle 是为组合而设计的。进一步阅读:Composition over inheritance: Gradle vs Maven

    快速修复:“未解决的参考”

    您看到的错误基本上是因为您没有应用 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 和 Convention 插件

    buildSrc 基本上是一个独立的 Gradle 项目,我们可以在主项目的构建脚本中使用它的输出。所以我们可以编写自己的自定义 Gradle 插件,定义约定,我们可以有选择地将其应用于“主”构建中的任何子项目。

    (这是 Gradle 和 Maven 之间的主要区别。在 Gradle 中,一个子项目可以由任意数量的插件配置。在 Maven 中,只有一个父项目。组合 vs 继承!)

    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

    1. 应用kotlin-dsl插件
    2. 添加对要在项目中任何地方使用的 Gradle 插件的依赖项
      // ./buildSrc/build.gradle.kts
      plugins {
        `kotlin-dsl` // this will create our Gradle convention plugins
      
        // don't add the Kotlin JVM plugin
        // kotlin("jvm") version embeddedKotlinVersion 
        // Why? It's a long story, but Gradle uses an embedded version of Kotlin,
        // (which is provided by the `kotlin-dsl` plugin)
        // which means importing an external version _might_ cause issues
        // It's annoying but not important. The Kotlin plugin version below, 
        // in dependencies { }, will be used for building our 'main' project.
        // https://github.com/gradle/gradle/issues/16345
      }
      
      val kotlinVersion = "1.6.20"
      
      dependencies {
        implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
      }
      

      请注意,我使用了Maven 仓库Kotlin Gradle 插件的坐标,而不是插件 ID!

      如果愿意,您还可以将其他依赖项添加到 ./buildSrc/build.gradle.kts 中。如果你想在构建脚本中解析 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 一样,我们的约定插件也是如此。它是包名 + .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 插件,例如 javajava-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")
      

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-28
    • 1970-01-01
    • 1970-01-01
    • 2022-10-05
    • 2021-02-04
    • 1970-01-01
    相关资源
    最近更新 更多