我目前正在尝试改进项目共享配置的方式。我们为所有库和微服务(即许多git repos)提供了许多不同的多模块gradle项目。
我的主要目标是:
我当前的解决方案是一个带有init脚本的自定义gradle分发:
mavenLocal()
和我们的Nexus存储库添加到项目回购中(非常类似于Gradle init script documentation example,除了它添加了回购以及验证它们)build.gradle
文件几乎只用于依赖项。这是init脚本(已清理):
/**
* 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 dev Stefan Oehme阅读a comment on Github引发的,说明构建应该不依赖于init脚本就可以工作,即init脚本应该只是装饰性的并且做一些类似于记录的示例 - 防止未经授权的回购等等。
我的想法是编写一些扩展函数,这些函数允许我将Nexus repo和插件添加到构建中,其方式看起来像是构建在gradle中(类似于扩展函数gradleScriptKotlin()
和{{ 3}}由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
块(无法编译脚本)中使用时无法看到我的函数。在正常的项目repos / dependencies中使用它们工作正常(它们是可见的并按预期工作)。
如果这有效,我希望将jar捆绑到我的自定义发行版中,这意味着我的init脚本可以只进行简单的验证,而不是隐藏神奇的插件和repo配置。扩展功能不需要更改,因此在插件更改时不需要发布新的Gradle分发。
我尝试了什么:
buildscript.dependencies
) - 不起作用(也许这不是设计工作,因为它似乎没有向同一个区块中引用的buildscript
添加依赖项的权利buildSrc
中(适用于普通项目代表/ repos而不是buildscript
,但不是真正的解决方案,因为它只是移动样板文件)lib
文件夹所以我的问题归结为:
buildScript
块可以看到自定义类/函数? 答案 0 :(得分:11)
如果您想从所有Gradle Kotlin DSL中获益,您应该努力使用plugins {}
块应用所有插件。见https://github.com/gradle/kotlin-dsl/blob/master/doc/getting-started/Configuring-Plugins.md
您可以在设置文件中管理插件存储库和解决方案策略(例如,他们的版本)。从Gradle 4.4开始,可以使用Kotlin DSL编写此文件,即settings.gradle.kts
。请参阅https://docs.gradle.org/4.4-rc-1/release-notes.html。
考虑到这一点,您可以拥有一个集中的Settings
脚本插件,用于设置并在您的构建settings.gradle.kts
文件中应用它:
// corporate-settings.gradle.kts
pluginManagement {
repositories {
maven {
name = "Corporate Nexus"
url = uri("https://example.com/repository/maven-public")
}
gradlePluginPortal()
}
}
和
// settings.gradle.kts
apply(from = "https://url.to/corporate-settings.gradle.kts")
然后在项目构建脚本中,您只需从公司存储库中请求插件:
// build.gradle.kts
plugins {
id("my-corporate-plugin") version "1.2.3"
}
如果您希望项目在多项目构建中构建脚本以不重复插件版本,则可以通过在根项目中声明版本来Gradle 4.3执行此操作。请注意,如果所有版本都使用相同的插件版本,您还可以使用settings.gradle.kts
在pluginManagement.resolutionStrategy
中设置版本。
另请注意,要使所有这些工作正常,您的插件必须与plugin marker artifact一起发布。这可以通过使用java-gradle-plugin
插件轻松完成。
答案 1 :(得分:3)
我向@eskatos承诺,我会回来并就他的答案给出反馈 - 所以这就是它!
我的最终解决方案包括:
settings.gradle.kts
文件,用于将我们的maven repo和gradle插件门户镜像(均在Nexus中)配置为插件管理存储库。 settings.gradle.kts
文件包含以下内容:
pluginManagement {
repositories {
// local maven to facilitate easy testing of our plugins
mavenLocal()
// our plugins and their markers are now available via Nexus
maven {
name = "CorporateNexus"
url = uri("https://nexus.example.com/repository/maven-public")
}
// all external gradle plugins are now mirrored via Nexus
maven {
name = "Gradle Plugin Portal"
url = uri("https://nexus.example.com/repository/gradle-plugin-portal")
}
}
}
这意味着所有插件及其依赖项现在都通过Nexus代理,Gradle将通过id找到我们的插件,因为插件标记也会发布到Nexus。同时拥有mavenLocal
有助于在本地轻松测试我们的插件更改。
然后,每个项目的根build.gradle.kts
文件应用插件,如下所示:
plugins {
// plugin markers for our custom plugins allow us to apply our
// plugins by id as if they were hosted in gradle plugin portal
val corporatePluginsVersion = "1.2.3"
id("corporate-project") version corporatePluginsVersion
// 'apply false` means this plugin can be applied in a subproject
// without having to specify the version again
id("corporate-publishing") version corporatePluginsVersion apply false
// and so on...
}
并配置gradle包装器以使用我们的镜像分发,当与上述相结合时,所有内容(gradle,plugins,依赖项)都来自Nexus):
tasks {
"wrapper"(Wrapper::class) {
distributionUrl = "https://nexus.example.com/repository/gradle-distributions/gradle-4.7-bin.zip"
}
}
我希望使用@ eskatos建议在settings.gradle.kts
中应用来自远程URL的脚本来避免设置文件中的样板文件。即。
apply { from("https://nexus.example.com/repository/maven-public/com/example/gradle/corporate-settings/1.2.3/corporate-settings-1.2.3.kts" }
我甚至设法生成了一个模板化的脚本(与我们的插件一起发布):
然而,尽管它删除了样板,但这意味着我们的构建依赖于与Nexus repo的连接,因为即使从URL应用的脚本被缓存,Gradle仍会执行HEAD请求以检查变化。它还使得在本地测试插件更改变得很烦人,因为我必须手动将其指向我本地maven目录中的脚本。使用我当前的配置,我可以简单地将插件发布到maven local并更新我项目中的版本。
我对当前的设置非常满意 - 我认为现在开发人员如何应用这些插件会更加明显。它使得Gradle和我们的插件独立升级变得容易得多,因为两者之间没有依赖关系(并且不需要自定义gradle分发)。
答案 2 :(得分:2)
我在构建
中一直在做这样的事情buildscript {
project.apply {
from("${rootProject.projectDir}/sharedValues.gradle.kts")
}
val configureRepository: (Any) -> Unit by extra
configureRepository.invoke(repositories)
}
在我的sharedValues.gradle.kts
文件中,我的代码如下:
/**
* This method configures the repository handler to add all of the maven repos that your company relies upon.
* When trying to pull this method out of the [ExtraPropertiesExtension] use the following code:
*
* For Kotlin:
* ```kotlin
* val configureRepository : (Any) -> Unit by extra
* configureRepository.invoke(repositories)
* ```
* Any other casting will cause a compiler error.
*
* For Groovy:
* ```groovy
* def configureRepository = project.configureRepository
* configureRepository.invoke(repositories)
* ```
*
* @param repoHandler The RepositoryHandler to be configured with the company repositories.
*/
fun repositoryConfigurer(repoHandler : RepositoryHandler) {
repoHandler.apply {
// Do stuff here
}
}
var configureRepository : (RepositoryHandler) -> Unit by extra
configureRepository = this::repositoryConfigurer
我遵循类似的模式来配置插件的解析策略。
关于这种模式的好处是,您在sharedValues.gradle.kts
项目中配置的任何内容也可以在buildSrc
项目中使用,这意味着您可以重用存储库声明。
更新:
您可以从URL应用其他脚本,例如:
apply {
// This was actually a plugin that I used at one point.
from("http://dl.bintray.com/shemnon/javafx-gradle/8.1.1/javafx.plugin")
}
只需托管您希望所有构建在一些http服务器上共享的脚本(强烈建议使用HTTPS,这样您的构建就无法成为中间人攻击的目标)。
这样做的缺点是,我不认为从网址应用的脚本没有缓存,因此每次运行构建时都会重新下载这些脚本。 这可能已经修复了,我不确定。
答案 3 :(得分:1)
当我遇到类似问题时,Stefan Oehme向我提供的解决方案是提供我自己的Gradle定制发行版。据他说,这对大公司来说很常见。
只需创建一个gradle repo的自定义分支,使用此自定义版本的gradle将您公司的特殊酱添加到每个项目中。
答案 4 :(得分:0)
在每个项目中复制通用配置时,我遇到了类似的问题。通过具有初始化脚本中定义的常用设置的自定义gradle分发解决了该问题。
创建了gradle插件来准备这种自定义发行版-custom-gradle-dist。它非常适合我的项目,例如图书馆项目的 build.gradle 看起来像这样(这是一个完整的文件):
dependencies {
compile 'org.springframework.kafka:spring-kafka'
}