Skip to content

Commit

Permalink
ready for a final review
Browse files Browse the repository at this point in the history
  • Loading branch information
CXwudi committed May 14, 2024
1 parent 4e4a402 commit 612d784
Show file tree
Hide file tree
Showing 2 changed files with 174 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: "A beautiful workaround for accessing Gradle Version Catalogs from Precom
# slug: "" # if :slug is in the permalinks configuration, use this to resolve URL conflict with other posts
date: 2024-05-12T14:52:55Z # if year month day in the permalinks configuration and other posts have the same date, modify this to resolve URL conflict with other posts
lastmod: 2024-05-12T14:52:55Z # no longer needed if enableGitInfo = true
draft: true # remember to change it back to false before opening the PR for publishing
draft: false # remember to change it back to false before opening the PR for publishing
authors: [CXwudi] # no quotes
featuredImage: "img/featured image.webp"
description: "Using the gradle-buildconfig-plugin or the BuildKonfig plugin to access Gradle Version Catalogs from precompiled script plugins"
Expand Down Expand Up @@ -163,7 +163,7 @@ Several intelligent people have proposed great workarounds to this issue. The mo

Are there other workarounds, preferably without hacks?

Fortunately, there is one for the `plugins {}` block mentioned by [this comment](https://github.com/gradle/gradle/issues/15383#issuecomment-1855984127). Since applying external plugins to the precompiled script plugin requires adding the corresponding dependency of the external plugin to the `build.gradle.kts` file, you can apply the version catalog to that dependency. This method assumes that the `settings.gradle.kts` file in the build project [imports the same version catalog](https://docs.gradle.org/current/userguide/platforms.html#sec:importing-catalog-from-file) used in the main project. I also discovered that the method works for setting plugins, as I described in [this forum post](https://discuss.gradle.org/t/how-to-use-version-catalog-in-the-root-settings-gradle-kts-file/44603/5).
Fortunately, there is one for the `plugins {}` block mentioned by [this comment](https://github.com/gradle/gradle/issues/15383#issuecomment-1855984127). Since applying external plugins to the precompiled script plugin requires adding the corresponding dependency of the external plugin to the `build.gradle.kts` file, you can add that dependency to the version catalog. This method assumes that the `settings.gradle.kts` file in the build project [imports the same version catalog](https://docs.gradle.org/current/userguide/platforms.html#sec:importing-catalog-from-file) used in the main project. I also discovered that the method works for setting plugins, as I described in [this forum post](https://discuss.gradle.org/t/how-to-use-version-catalog-in-the-root-settings-gradle-kts-file/44603/5).

<!-- . If you have ever written a precompiled script plugin that applies other plugins, you know how painful is it to find the right coordinate of the dependency of the plugin from [Gradle Plugin Portal](https://plugins.gradle.org/), and add it into the `implementation()` inside the `build.gradle.kts` file. And that's right, your `build.gradle.kts` has direct access to the version catalog, as long as your `setting.gradle.kts` imported the `libs.versions.toml` that can be anywhere in your project. Therefore simply just add the plugin's dependency into the `[libraries]` section of the version catalog, and replace the string in `implementation()` with `libs.something`. -->

Expand Down Expand Up @@ -274,6 +274,6 @@ dependencies {

## Conclusion

The method that utilizes the gradle-buildconfig-plugin or the BuildKonfig plugin is a beautiful workaround for accessing Gradle Version Catalogs from precompiled script plugins. It is not too hacky, guaranteed to work in any Gradle project, and doesn't have the limitation where it is only accessible from the `plugins {}` block or the `dependencies {}` block. It is a great solution for centralizing version management in your Gradle project.
The method, that utilizes the gradle-buildconfig-plugin or the BuildKonfig plugin for accessing Gradle Version Catalogs from precompiled script plugins, is a beautiful workaround. It is not too hacky, guaranteed to work in any Gradle project, and doesn't have the limitation where it is only accessible from the `plugins {}` block or the `dependencies {}` block. It is a great solution for centralizing version management in your Gradle project.

I hope this workaround can help you in your Gradle project. If you have any questions or suggestions, feel free to leave a comment below. 😊
191 changes: 171 additions & 20 deletions content/posts/2024/version-catalog-plugin-wordaround/index.zh-cn.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,39 @@
---
title: "Gradle Version Catalog Issue"
slug: "gradle-version-catalog-issue" # if :slug is in the permalinks configuration, use this to resolve URL conflict with other posts
date: 2024-05-12T14:53:10Z # if year month day in the permalinks configuration and other posts have the same date, modify this to resolve URL conflict with other posts
lastmod: 2024-05-12T14:53:10Z # no longer needed if enableGitInfo = true
draft: true # remember to change it back to false before opening the PR for publishing
authors: [] # no quotes
featuredImage: ""
description: ""
title: "从预编译脚本插件访问Gradle版本目录的漂亮解决方案"
# slug: "" # 如果在永久链接配置中有:slug,使用这个来解决与其他帖子的URL冲突
date: 2024-05-12T14:52:55Z # 如果永久链接配置中有年月日,并且其他帖子有相同的日期,修改这个来解决URL冲突
lastmod: 2024-05-12T14:52:55Z # 如果启用了enableGitInfo,则不再需要
draft: false # 发布前记得将其改为false
authors: [CXwudi] # 没有引号
featuredImage: "img/featured image.webp"
description: "使用gradle-buildconfig-plugin或BuildKonfig插件从预编译脚本插件访问Gradle版本目录"
# license: '<a rel="license external nofollow noopener noreffer" href="https://creativecommons.org/licenses/by/4.0/" target="_blank">CC BY 4.0</a>'

# need quotes for all three
tags: []
categories: []
# 所有三个都需要引号
tags: [devops]
categories: [tech]
series: []
series_weight:

# you can copy any config from [params.page] to here to override global default
# 你可以从[params.page]复制任何配置到这里来覆盖全局默认

# outdatedArticleReminder: # uncomment to enable, default is false in config
# outdatedArticleReminder: # 取消注释以启用,默认在配置中为false
# enable: true
# reminder: 180
# warning: 365
# sponsor: # uncomment to disable, default is false in config
# enable: false
# table: # uncomment to disable, default is true
sponsor:
enable: true
bio: "写完这篇文章后,我感到非常疲惫 😫。我需要一杯咖啡 ☕ 来提神。如果你喜欢我的Gradle 🐘 解决方案,你介意请我喝一杯咖啡吗?感谢你的支持!🤗"
custom: "<div style='display: flex; justify-content: center;'><a href='https://ko-fi.com/X7X56IIAQ' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://storage.ko-fi.com/cdn/kofi2.png?v=3' border='0' alt='在ko-fi.com给我买咖啡' /></a></div>"
# table: # 取消注释以禁用,默认为true
# sort: false
# comment: # uncomment to disable comment system
# comment: # 取消注释以禁用评论系统
# enable = false
# lightgallery: true # uncomment if using the better image shortcode
lightgallery: true # 如果使用更好的图片shortcode则取消注释
code:
maxShownLines: 50
seo:
images: [] # same as featuredImage
images: ["img/featured image.webp"] # 与featuredImage相同
---

太长不看:在 `buildSrc/build.gradle.kts` 中应用 [gradle-buildconfig-plugin](https://github.com/gmazzo/gradle-buildconfig-plugin)[BuildKonfig](https://github.com/yshrsmz/BuildKonfig) 插件。
Expand All @@ -39,7 +43,7 @@ seo:
**来源**: [Medium](https://medium.com/@gopalsays108/android-gradle-version-catalog-by-gopal-cf459e90fb92)
{{< /admonition >}}
{{< admonition type=info title="注意" open=true >}}
此文章是从本站英文版原文通过[什么工具]翻译并适当修改而来,可能会出现语言不自然等Bug,请予以谅解。
此文章是从本站英文版原文通过GPT-4 Turbo翻译并适当修改而来,可能会出现语言不自然等Bug,请予以谅解。
{{< /admonition >}}

## 简介
Expand Down Expand Up @@ -129,3 +133,150 @@ Gradle官方文档推荐两种[构建多模块项目的结构](https://docs.grad
它看起来就像一个普通的Gradle项目,对吧?

所以,接下来就是precompiled script plugin的真相了。

### precompiled script plugin的真相

你是否曾经好奇为什么precompiled script plugin会被放在`src/main/kotlin`文件夹中?🤔 就像上面的`shared-build-conventions.gradle.kts`示例。它能否就留在构建项目的第一层,比如`build-logic/shared-build-conventions.gradle.kts`

这里,我想邀请你观看{{<person url="https://onepiece.software/#jendrik" name="Jendrik Johannes" nick="jjohannes" text="Gradle团队的前核心成员" picture="https://onepiece.software/img/jendrik.png" >}}的一个视频,标题为[Understanding Gradle #25 – Using Java to configure builds](https://youtu.be/XnVZdMROVG8?list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE&t=263)。基本上,每个Gradle脚本都是[`Plugin`](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html)接口的一个实现。构建脚本`build.gradle.kts`对应`Plugin<Project>`,设置脚本`settings.gradle.kts`对应`Plugin<Settings>`

{{<image src="https://docs.gradle.org/current/userguide/img/author-gradle-4.png" caption="构建脚本只是配置`Project`实例的一段代码,precompiled script plugin也是如此" >}}

因此,放在`src/main/<jvm language>`文件夹中的precompiled script plugin也是业务逻辑代码,但这里的业务逻辑是你主项目的构建逻辑。这种代码通常从你的`class MyPlugin implements Plugin<Project>`实现中的`apply(Project project)`方法开始。

### `src/main/kotlin`中使用version catalog?🤔

现在,让我们回顾一下[gradle/gradle#15383](https://github.com/gradle/gradle/issues/15383)中的声明:

> We want to make the version catalogs accessible to precompiled script plugins
翻译过来:

> 我们希望使version catalog对precompiled script plugin可见
利用我们上面回顾的知识来翻译这句话,它就变成了:

“一个名为`libs.versions.toml`的文件本意是用在`build.gradle.kts`文件中。现在我们想在`src/main/kotlin`文件夹中的业务代码中使用它。”

听起来很奇怪,不是吗?🤔

这就是为什么这个问题很难解决的原因,因为这是一个设计问题,违反了关注点分离原则。

## 现有的workaround

一些聪明的人提出了几个绝妙的workaround。最著名的一个来自{{<person url="https://github.com/Vampire" name="Björn Kautler" nick="Vampire" text="Gradle团队的核心成员" picture="https://avatars.githubusercontent.com/u/325196?v=4" >}}的[评论](https://github.com/gradle/gradle/issues/15383#issuecomment-779893192),其中你需要添加一个隐秘的Gradle内部文件到`dependencies {}`块中。这个方法虽然有效,但非常hacky,不能保证在任何Gradle项目中都有效(至少我没弄成功过 😕),[`plugins {}`块中不起作用](https://github.com/gradle/gradle/issues/15383#issuecomment-900569305),而且这个workaround依赖于一个可能随时变更的Gradle内部API。

还有其他不需要hack的workaround吗?

幸运的是,有一个针对`plugins {}`块的workaround,如[此评论](https://github.com/gradle/gradle/issues/15383#issuecomment-1855984127)中提到的。由于将外部插件应用到precompiled script plugin需要将外部插件的相应依赖添加到`build.gradle.kts`文件中,你可以将哪个依赖放进version catalog中。这种方法假设构建项目中的`settings.gradle.kts`文件[导入了与主项目中使用的相同的version catalog](https://docs.gradle.org/current/userguide/platforms.html#sec:importing-catalog-from-file)。我还发现这种方法适用于设置插件,正如我在[此论坛帖子](https://discuss.gradle.org/t/how-to-use-version-catalog-in-the-root-settings-gradle-kts-file/44603/5)中描述的那样。

{{<image src="img/Screenshot 2024-05-12 170857.png" caption="Jendrik Johannes (jjohannes)的[视频](https://www.youtube.com/watch?v=N95YI-szd78&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE&index=3)截图,介绍了编写你自己的precompiled script plugin的步骤之一,就是找到插件依赖的正确coordinate。幸运的是,这个coordinate可以进入你的version catalog。" >}}

我还找到了一个不需要hack的`dependencies {}`块的workaround,并在[此评论](https://github.com/gradle/gradle/issues/15383#issuecomment-1858465843)中进行了描述。这种workaround利用了[Gradle platform](https://docs.gradle.org/current/userguide/platforms.html#sub:using-platform-to-control-transitive-deps)[Gradle的构建阶段](https://docs.gradle.org/current/userguide/build_lifecycle.html#sec:build_phases)来实现目的。然而,这种方法非常麻烦,因为添加/移除依赖需要在你的项目中进行三处修改。

总的来说,非hacky的workaround可以覆盖`plugins {}`块和`dependencies {}`块。在大多数情况下,这已经足够。但是对于[extensions](https://docs.gradle.org/current/userguide/implementing_gradle_plugins_precompiled.html#sec:getting_input_from_the_build)怎么办呢?例如,如果precompiled script plugin应用了[Micronaut Gradle插件](https://micronaut-projects.github.io/micronaut-gradle-plugin/latest/index.html),如何使用version catalog在`micronaut {}`扩展中设置Micronaut框架的版本呢?🤷‍♂️

{{<admonition type=Note title="题外话:关于我对Micronaut Gradle插件的功能请求的有趣事件" open=false >}}

有一天,我在研究Micronaut框架时,意识到我无法应用{{<person name="Jendrik Johannes" nick="jjohannes" text="Gradle团队的前核心成员" picture="https://onepiece.software/img/jendrik.png" >}}在[Understanding Gradle #09 – Centralizing Dependency Versions](https://www.youtube.com/watch?v=8044F5gc1dE&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE&index=9)中提到的任何集中版本管理解决方案。

所以我向Micronaut团队提出了一个[功能请求](https://github.com/micronaut-projects/micronaut-gradle-plugin/issues/681)。我甚至深入研究了插件的源代码,并指出了阻止使用version catalog或Gradle platform的[代码](https://github.com/micronaut-projects/micronaut-gradle-plugin/blob/cc84332f5635e3da7c71e81460659f41fd36ae2b/minimal-plugin/src/main/java/io/micronaut/gradle/PluginsHelper.java#L48-L60)

创建了gradle/gradle#15383问题的那个人,{{< person name="Cédric Champeau" nick="melix" text="Micronaut团队和GraalVM团队成员,为多个Micronaut工具和GraalVM Native Build Tools做出了贡献" picture="https://avatars.githubusercontent.com/u/316357?v=4" >}}回应了我。😲

他很快提出了[一个PR](https://github.com/micronaut-projects/micronaut-gradle-plugin/pull/701),在Micronaut Gradle插件中增加了一个新选项`importMicronautPlatform`,可以设置为`false`以允许你使用Gradle platform实现集中版本管理。一旦我能够使用Gradle platform,我就能够使用我上面提到的workaround来使用version catalog。

今天,你可以在Micronaut Gradle插件的[文档](https://micronaut-projects.github.io/micronaut-gradle-plugin/latest/index.html#_micronaut_library_plugin)中看到这个选项。

{{< /admonition >}}

## 现在开始介绍全新漂亮的workaround 🤩

上面提到的所有workaround或多或少都在做一件事:“发送”原本应该用在Gradle构建脚本中的变量到`src/main/kotlin`文件夹中的业务代码里。

有一天,我无意中发现了两个Gradle插件:[gradle-buildconfig-plugin](https://github.com/gmazzo/gradle-buildconfig-plugin)[BuildKonfig](https://github.com/yshrsmz/BuildKonfig)插件。

这两个插件通常用于Kotlin跨平台项目(通常是跨平台Compose应用),用来生成一个包含你在`build.gradle.kts`脚本中定义的配置变量的Kotlin文件。

例如,如果你有:

```kotlin {title="build.gradle.kts"}
plugins {
// ...
id("com.github.gmazzo.buildconfig") version <current version>
}

buildConfig {
className("MyConfig") // 强制类名。默认为'BuildConfig'
packageName("com.foo") // 强制包名。默认为'${project.group}'

buildConfigField(String::class.java, 'APP_NAME', "my-project")
}
//...
```

你会得到:

```kotlin {title="com.foo.MyConfig.kt"}
package com.foo

object MyConfig {
const val APP_NAME: String = "my-project"
}
```

突然间,我意识到如果我将这个插件应用到构建项目中的`build.gradle.kts`会怎样?😲

```toml {title="gradle/libs.versions.toml"}
[versions]
java = "21"
slf4j = "2.0.13"

[libraries]
slf4j-api = {module = "org.slf4j:slf4j-api", version.ref = "slf4j"}
```

```kotlin {title="build-logic/conventions/build.gradle.kts"}
plugins {
// ...
id("com.github.gmazzo.buildconfig") version <current version>
}

buildConfig {
className("VersionCatalog") // 强制类名。默认为'BuildConfig'
packageName("my.util") // 强制包名。默认为'${project.group}'

buildConfigField(Int::class.java, "JAVA_VERSION", libs.versions.java.get().toInt())
buildConfigField(String::class.java, "SLF4J_API", libs.dep.slf4j.get().toString())
}
```

那么,我的precompiled script plugin能访问`JAVA_VERSION``SLF4J_API`变量吗?

你擦怎么着?它居然真的有效!😱

```kotlin {title="build-logic/conventions/src/main/kotlin/shared-build-conventions.gradle.kts"}
import my.util.VersionCatalog

// 其他配置

java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(VersionCatalog.JAVA_VERSION))
}
}

dependencies {
implementation(VersionCatalog.SLF4J_API)
}

// 其他配置
```

{{<figure src="img/ohhhhhhh.gif">}}

## 结论

利用gradle-buildconfig-plugin或BuildKonfig插件的方法是从precompiled script plugin访问Gradle的version catalog是一个漂亮的workaround。它不是太hacky,能确保在任何Gradle项目中都行得通,而且没有仅在`plugins {}`块或`dependencies {}`块可访问的限制。这是一个在你的Gradle项目中集中版本管理的绝佳解决方案。

我希望这个workaround能帮助到你的Gradle项目。如果你有任何问题或建议,欢迎在下面留言。😊

0 comments on commit 612d784

Please sign in to comment.