我目前正在尝试改进我们的项目共享配置的方式。我们所有的库和微服务(即许多 git repos)都有许多不同的多模块 gradle 项目。
我的主要目标是:
- 不要在每个项目中重复我的 Nexus 存储库配置(另外,我可以放心地假设 URL 不会更改)
- 为了使我的自定义 Gradle 插件(发布到 Nexus)可用于每个项目,且样板/重复最少(它们应该可用于每个项目,并且项目唯一关心的是它使用的版本)
- 没有魔法 - 开发人员应该很清楚一切是如何配置的
我当前的解决方案是一个自定义的 gradle 发行版,它带有一个 init 脚本:
- 将
mavenLocal()
我们的 Nexus 存储库添加到项目存储库(非常类似于 Gradle初始化脚本文档示例,除了它添加存储库并验证它们) - 配置一个扩展,允许将我们的 gradle 插件添加到 buildscript 类路径中(使用此解决方法)。它还将我们的 Nexus 存储库添加为 buildscript 存储库,因为这是托管插件的地方。我们有很多插件(基于 Netflix 优秀的nebula 插件构建)用于各种样板:标准项目设置(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在 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"))
}
}
但是,Gradle 在buildscript
块中使用时无法看到我的函数(无法编译脚本)。不过,在正常的项目存储库/依赖项中使用它们效果很好(它们是可见的并且按预期工作)。
如果这可行,我希望将 jar 捆绑到我的自定义发行版中,这意味着我的 init 脚本可以只进行简单的验证,而不是隐藏神奇的插件和 repo 配置。扩展功能不需要更改,因此在插件更改时不需要发布新的 Gradle 发行版。
我尝试了什么:
- 将我的 jar 添加到测试项目的 buildscript 类路径(即
buildscript.dependencies
) - 不起作用(也许这在设计上不起作用,因为buildscript
在同一个块中添加依赖项似乎不正确) - 将功能放入
buildSrc
(这适用于正常的项目部门/回购但不是buildscript
,但不是真正的解决方案,因为它只是移动样板) - 将 jar 放到
lib
发行版的文件夹中
所以我的问题真的归结为:
- 我想要实现的目标是否可行(是否可以使自定义类/函数对
buildScript
块可见)? - 是否有更好的方法来配置企业 Nexus 存储库并使自定义插件(发布到 Nexus)在许多单独的项目(即完全不同的代码库)中可用,且样板配置最少?