【发布时间】:2017-09-25 06:00:24
【问题描述】:
我目前正在尝试改进我们的项目共享其配置的方式。我们所有的库和微服务(即许多 git repos)都有许多不同的多模块 gradle 项目。
我的主要目标是:
- 不要在每个项目中重复我的 Nexus 存储库配置(另外,我可以放心地假设 URL 不会更改)
- 让我的自定义 Gradle 插件(发布到 Nexus)以最少的样板/重复用于每个项目(它们应该可用于每个项目,并且项目唯一关心的是它使用的版本)
- 没有魔法 - 开发人员应该很清楚一切是如何配置的
我当前的解决方案是一个自定义的 gradle 发行版,它带有一个 init 脚本:
- 将
mavenLocal()和我们的 Nexus 存储库添加到项目存储库(非常类似于 Gradle init script documentation example,除了它添加存储库并验证它们) - 配置一个扩展,允许将我们的 gradle 插件添加到 buildscript 类路径中(使用this workaround)。它还将我们的 Nexus 存储库添加为 buildscript 存储库,因为这是托管插件的地方。我们有很多插件(基于 Netflix 优秀的 nebula plugins 构建)用于各种样板:标准项目设置(kotlin 设置、测试设置等)、发布、发布、文档等,这意味着我们的项目
build.gradle文件很漂亮仅用于依赖项。
这是初始化脚本(已清理):
/**
* Gradle extension applied to all projects to allow automatic configuration of Corporate plugins.
*/
class CorporatePlugins {
public static final String NEXUS_URL = "https://example.com/repository/maven-public"
public static final String CORPORATE_PLUGINS = "com.example:corporate-gradle-plugins"
def buildscript
CorporatePlugins(buildscript) {
this.buildscript = buildscript
}
void version(String corporatePluginsVersion) {
buildscript.repositories {
maven {
url NEXUS_URL
}
}
buildscript.dependencies {
classpath "$CORPORATE_PLUGINS:$corporatePluginsVersion"
}
}
}
allprojects {
extensions.create('corporatePlugins', CorporatePlugins, buildscript)
}
apply plugin: CorporateInitPlugin
class CorporateInitPlugin implements Plugin<Gradle> {
void apply(Gradle gradle) {
gradle.allprojects { project ->
project.repositories {
all { ArtifactRepository repo ->
if (!(repo instanceof MavenArtifactRepository)) {
project.logger.warn "Non-maven repository ${repo.name} detected in project ${project.name}. What are you doing???"
} else if(repo.url.toString() == CorporatePlugins.NEXUS_URL || repo.name == "MavenLocal") {
// Nexus and local maven are good!
} else if (repo.name.startsWith("MavenLocal") && repo.url.toString().startsWith("file:")){
// Duplicate local maven - remove it!
project.logger.warn("Duplicate mavenLocal() repo detected in project ${project.name} - the corporate gradle distribution has already configured it, so you should remove this!")
remove repo
} else {
project.logger.warn "External repository ${repo.url} detected in project ${project.name}. You should only be using Nexus!"
}
}
mavenLocal()
// define Nexus repo for downloads
maven {
name "CorporateNexus"
url CorporatePlugins.NEXUS_URL
}
}
}
}
}
然后我通过将以下内容添加到根 build.gradle 文件来配置每个新项目:
buildscript {
// makes our plugins (and any others in Nexus) available to all build scripts in the project
allprojects {
corporatePlugins.version "1.2.3"
}
}
allprojects {
// apply plugins relevant to all projects (other plugins are applied where required)
apply plugin: 'corporate.project'
group = 'com.example'
// allows quickly updating the wrapper for our custom distribution
task wrapper(type: Wrapper) {
distributionUrl = 'https://com.example/repository/maven-public/com/example/corporate-gradle/3.5/corporate-gradle-3.5.zip'
}
}
虽然这种方法有效,但允许可重现的构建(与我们之前的设置不同,它从 URL 应用构建脚本 - 当时不可缓存),并且允许离线工作,它确实让它有点神奇,我是想知道我是否可以做得更好。
这一切都是通过阅读 Gradle 开发人员 Stefan Oehme 的 a comment on Github 触发的,他指出构建应该在不依赖 init 脚本的情况下工作,即 init 脚本应该只是装饰性的,并且可以执行文档示例之类的操作 - 防止未经授权的 repos 等.
我的想法是编写一些扩展函数,让我可以将我们的 Nexus 存储库和插件添加到构建中,就像它们内置在 gradle 中一样(类似于提供的扩展函数 gradleScriptKotlin() 和 kotlin-dsl()由 Gradle Kotlin DSL 提供。
所以我在一个 kotlin gradle 项目中创建了我的扩展函数:
package com.example
import org.gradle.api.artifacts.dsl.DependencyHandler
import org.gradle.api.artifacts.dsl.RepositoryHandler
import org.gradle.api.artifacts.repositories.MavenArtifactRepository
fun RepositoryHandler.corporateNexus(): MavenArtifactRepository {
return maven {
with(it) {
name = "Nexus"
setUrl("https://example.com/repository/maven-public")
}
}
}
fun DependencyHandler.corporatePlugins(version: String) : Any {
return "com.example:corporate-gradle-plugins:$version"
}
计划在我的项目build.gradle.kts中使用它们如下:
import com.example.corporateNexus
import com.example.corporatePlugins
buildscript {
repositories {
corporateNexus()
}
dependencies {
classpath(corporatePlugins(version = "1.2.3"))
}
}
但是,在 buildscript 块中使用时,Gradle 无法看到我的函数(无法编译脚本)。不过,在正常的项目存储库/依赖项中使用它们效果很好(它们是可见的并且按预期工作)。
如果这可行,我希望将 jar 捆绑到我的自定义发行版中,这意味着我的 init 脚本可以进行简单的验证,而不是隐藏神奇的插件和 repo 配置。扩展功能无需更改,因此无需在插件更改时发布新的 Gradle 发行版。
我尝试了什么:
- 将我的 jar 添加到测试项目的 buildscript 类路径(即
buildscript.dependencies) - 不起作用(也许这在设计上不起作用,因为向buildscript添加依赖项似乎不正确到同一块) - 将函数放在
buildSrc中(适用于普通项目deps/repos 但不适用于buildscript,但不是真正的解决方案,因为它只是移动样板) - 将 jar 放到分发版的
lib文件夹中
所以我的问题归结为:
- 我想要实现的目标是否可行(是否可以使自定义类/函数对
buildScript块可见)? - 是否有更好的方法来配置企业 Nexus 存储库并使自定义插件(发布到 Nexus)在许多单独的项目(即完全不同的代码库)中可用,且样板配置最少?
【问题讨论】:
-
我想总结一下你想要做什么,你想给
buildscript块添加扩展?您使用的 Gradle 版本是否有下限? -
@mkobit 不,我正在升级到 4.1。我想我真的在尝试改进我们的构建配置 nexus 的方式,并使我们的插件可用于项目。当前的解决方案(如本问题开头所述)有效,但插件配置特别像黑客!
-
感谢您的澄清。可能有几种方法可能/可能不起作用/改进您已经完成的工作。一种是写一个init script plugin,您可以在
settings.gradle中申请。在当前的4.2-rc中,还支持 脚本插件被缓存,并且仅在必要时下载,而不是在每次构建时下载。 这可能会改善您遇到的一些问题。另一个想法可能是提供一个自定义插件门户,例如 (github.com/linkedin/custom-gradle-plugin-portal)。 -
我在 KotlinConf 与 Rodrigo(又名 Bamboo)就此事进行了交谈。他的建议是将引导插件发布到 gradle 插件门户。我们还讨论了远程脚本插件在缓存后可能不再那么糟糕了。如果有人想出一个非常干净和令人信服的方法,那就太棒了——我还是有点犹豫不决!
-
嗨@mkobit 我刚刚添加了一个答案,其中包含我最终使用的解决方案。我确实尝试从远程脚本应用插件管理配置,但即使它被缓存,它仍然尝试对脚本执行 HEAD 请求,当我没有连接到我们的 Nexus 存储库(脚本托管的地方)时失败)。所以我最终在每个
settings.gradle.kts文件中都有一些样板,但这还不错!
标签: gradle kotlin gradle-kotlin-dsl