为Gradle子项目配置Kotlin扩展

4sup72z8  于 2022-11-14  发布在  Kotlin
关注(0)|答案(1)|浏览(187)

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

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

mrphzbgm

mrphzbgm1#

这是我最喜欢在Gradle中修复的问题之一,并且真正展示了可能的灵活性(以及演示为什么Gradle会很复杂!)
首先,我将给予一些关于subprojects {} DSL的背景信息,然后我将展示如何修复您的脚本,最后我将展示与buildSrc约定插件共享构建逻辑的最佳方法。(尽管这是最后一部分,但我确实推荐使用buildSrc!)

组合与继承

使用allprojects {}subprojects {}确实很常见,我经常看到这种情况。它更类似于Maven的工作方式,所有配置都在“父”构建文件中定义。
[A]不鼓励在子项目之间共享构建逻辑的方法是通过subprojects {}allprojects {} DSL构造进行跨项目配置。

  • 评分文档:在子项目之间共享构建逻辑 *

(It的常见原因可能是它易于理解-它使Gradle的工作方式更像Maven,因此每个项目都继承自一个父项目。但Gradle是专为组合设计的。(第10页)

快速修复:'未解析引用'

你看到的错误基本上是因为你还没有应用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'构建中的任何子项目。
(This是Gradle和Maven之间的关键区别。在Gradle中,一个子项目可以由任意数量的插件配置。在Maven中,只有一个父项目。
Gradle文档提供了关于设置约定插件的完整指南,因此我在此仅简要总结一下解决方案。

1.设置./buildSrc

在项目根目录中创建一个名为buildSrc的目录。
因为buildSrc是独立项目,所以请像通常的项目一样,建立./buildSrc/build.gradle.kts./buildSrc/settings.gradle.kts档案。
./buildSrc/build.gradle.kts中,
1.应用kotlin-dsl插件
1.添加您希望在项目中的任何位置使用的Gradle插件的依赖项

// ./buildSrc/build.gradle.kts
plugins {
  `kotlin-dsl` // this will produce our Gradle convention plugins
  kotlin("jvm") version embeddedKotlinVersion 
  // What's embeddedKotlinVersion? 
  // It's a long story, but Gradle uses an embedded version of Kotlin,
  // which means using the the latest version can be tricky. 
  // embeddedKotlinVersion is a shortcut for the embedded version.
  // 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存储库KotlinGradle插件的坐标,而不是插件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一样,我们的约定插件也有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")
}

(the名称repositories.settings.gradle.kts并不重要-但将其命名为*.settings.gradle.kts应该意味着IntelliJ会提供建议,但目前这是错误的。)
然后,我可以将其作为插件导入到其他settings.gradle.kts文件中,就像您将KotlinJVM插件应用到子项目中一样。

// ./buildSrc/settings.gradle.kts
apply(from = "./repositories.settings.gradle.kts")
// ./settings.gradle.kts
apply(from = "./buildSrc/repositories.settings.gradle.kts")

相关问题