我目前正在努力改进我们的项目共享其配置的方式.我们有很多不同的多模块Gradle项目,适用于我们所有的图书馆和微服务(即许多git repos).
我的主要目标是:
- To not have my Nexus repository config duplicated in every project (also, I can safely assume that the URL won't change)
- 使我的自定义Gradle插件(发布到Nexus)对每个项目都可用,最大限度地减少样板/重复(它们应该对每个项目都可用,而项目唯一关心的就是它正在使用的版本)
- 没有魔法——开发人员应该清楚地知道所有东西是如何配置的
我目前的解决方案是一个定制的gradle发行版,带有一个init脚本:
- adds
mavenLocal()
and our Nexus repository to the project repos (very similar to the Gradle init script documentation example, except it adds repos as well as validating them) - 配置一个扩展,允许我们的gradle插件添加到buildscript类路径(使用this workaround).它还将我们的Nexus repo添加为buildscript repo,因为这是插件托管的地方.我们有很多插件(基于Netflix的优秀nebula plugins构建)用于各种样板:标准项目设置(kotlin设置、测试设置等)、发布、发布、文档等,这意味着我们的project
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
}
}
}
}
}
然后,我通过向根构建添加以下内容来配置每个新项目.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'
}
}
While this approach works, allows reproducible builds (unlike our previous setup which applied a build script from a URL - which at the time wasn't cacheable), and allows working offline, it does make it a little magical and I was wondering if I could do things better.
This was all triggered by reading a comment on Github by Gradle dev Stefan Oehme stating that a build should work without relying on an init script, i.e. init scripts should just be decorative and do things like the documented example - preventing unauthorised repos, etc.
我的 idea 是编写一些扩展函数,允许我将Nexus repo和插件添加到一个构建中,就像它们被内置到gradle中一样(类似于gradle Kotlin DSL提供的扩展函数gradleScriptKotlin()
和kotlin-dsl()
).
So I created my extension functions in a kotlin gradle project:
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
块中使用时无法看到我的函数(无法编译脚本).不过,在正常的项目回购/依赖中使用它们效果很好(它们是可见的,并且按照预期工作).
如果这能奏效,我希望将jarBundle 到我的定制发行版中,这意味着我的init脚本可以只进行简单的验证,而不是隐藏神奇的插件和repo配置.扩展功能不需要更改,因此当插件更改时,不需要发布新的Gradle发行版.
我try 的是:
- adding my jar to the test project's buildscript classpath (i.e.
buildscript.dependencies
) - doesn't work (maybe this doesn't work by design as it doesn't seem right to be adding a dependency tobuildscript
that's referred to in the same block) - putting the functions in
buildSrc
(which works for normal project deps/repos but notbuildscript
, but is not a real solution as it just moves the boilerplate) - 将jar放入分发版的
lib
文件夹中
所以我的问题可以归结为:
- 我试图实现的目标可能吗(是否可以使自定义类/函数对
buildScript
000个挡路可见)? - Is there a better approach to configuring a corporate Nexus repo and making custom plugins (published to Nexus) available across lots of separate projects (i.e. totally different codebases) with minimal boilerplate configuration?