首页 文章

Gradle with Gradle Kotlin DSL中的Boilerplate项目配置

提问于
浏览
27

我目前正在努力改进项目共享配置的方式 . 我们为所有库和微服务(即许多git repos)提供了许多不同的多模块gradle项目 .

我的主要目标是:

  • 没有在每个项目中重复我的Nexus存储库配置(我也可以放心地假设URL不会更改)

  • 使我的自定义Gradle插件(发布到Nexus)可用于每个项目,只需极少的样板/复制(它们应该可用于每个项目,项目唯一关心的是它使用的版本)

  • 没有魔力 - 开发人员应该明白如何配置所有内容

我当前的解决方案是使用init脚本的自定义gradle分发:

  • mavenLocal() 和我们的Nexus存储库添加到项目存储库中(非常类似于Gradle init script documentation example,除了它添加了repos以及验证它们)

  • 配置一个扩展,允许我们的gradle插件添加到buildscript类路径(使用this workaround) . 它还添加了我们的Nexus repo作为一个buildcript repo,因为它适用于各种样板:标准项目设置(kotlin设置,测试设置等),发布,发布,文档等等,这意味着我们的项目 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中一样(类似于Gradle Kotlin DSL提供的扩展函数gradleScriptKotlin()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"
}

计划在我的项目_979633中使用它们如下:

import com.example.corporateNexus
import com.example.corporatePlugins

buildscript {

    repositories {
        corporateNexus()
    }

    dependencies {
        classpath(corporatePlugins(version = "1.2.3"))
    }
}

但是,在 buildscript 块(无法编译脚本)中使用时,Gradle无法看到我的函数 . 在正常的项目repos / dependencies中使用它们工作正常(它们是可见的并且按预期工作) .

如果这有效,我希望将jar捆绑到我的自定义发行版中,这意味着我的init脚本可以只进行简单的验证,而不是隐藏神奇的插件和repo配置 . 扩展函数不需要更改,因此在插件更改时不需要释放新的Gradle分发 .

我尝试了什么:

  • 将我的jar添加到测试项目的buildscript类路径(即 buildscript.dependencies ) - 并没有按设计工作,因为向同一块中引用的 buildscript 添加依赖项似乎不正确

  • 将函数放在 buildSrc (适用于普通项目deps / repos但不适用于 buildscript ,但不是真正的解决方案,因为它只是移动样板文件)

  • 将jar放在发行版的 lib 文件夹中

所以我的问题归结为:

  • 我正在努力实现的目标(是否可以使 buildScript 块可以看到自定义类/函数)?

  • 是否有更好的方法来配置企业Nexus仓库并使用最少的样板配置在许多单独的项目(即完全不同的代码库)中提供自定义插件(发布到Nexus)?

5 回答

  • 3

    如果您想从所有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通过在根项目中声明版本来实现 . 请注意,如果所有构建版本都使用相同的插件版本,则还可以使用 pluginManagement.resolutionStrategysettings.gradle.kts 中设置版本 .

    另请注意,要使所有这些工作正常,您的插件必须与plugin marker artifact一起发布 . 这是使用 java-gradle-plugin 插件轻松完成 .

  • 0

    我向@eskatos承诺,我会回来并就他的答案给出反馈 - 所以在这里!

    我的最终解决方案包括:

    每个项目

    • Gradle 4.7包装器(指向Nexus中http://services.gradle.org/distributions设置的镜像作为原始代理存储库,即它是vanilla Gradle但通过Nexus下载)

    • 自定义Gradle插件发布到我们的Nexus仓库以及插件标记(由Java Gradle Plugin Development Plugin生成)

    • 镜像我们的Nexus仓库中的Gradle插件门户(即指向https://plugins.gradle.org/m2的代理仓库)

    • 每个项目的 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" }
    

    我甚至设法生成一个模板化的脚本(与我们的插件一起发布):

    • 配置了插件存储库(如上面的设置脚本中所示)

    • 使用解决策略来应用与脚本关联的插件版本,如果请求的插件ID是我们的插件之一并且未提供版本(因此您可以通过id应用它们)

    但是,即使它删除了样板,也意味着我们的构建依赖于与Nexus存储库的连接,因为即使从URL应用的脚本被缓存,Gradle仍会执行HEAD请求以检查更改 . 它也让在本地测试插件更改变得很烦人,因为我不得不手动指向我本地maven目录中的脚本 . 使用我当前的配置,我可以简单地将插件发布到maven local并更新我的项目中的版本 .

    我对当前的设置非常满意 - 我认为现在开发人员如何应用插件更为明显 . 而且,由于两者之间没有依赖关系(并且不需要自定义gradle分发),因此可以更轻松地单独升级Gradle和我们的插件 .

  • 10

    我在构建中一直在做这样的事情

    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,这样您的构建就不会被中间攻击中的人攻击) .

    这样做的缺点是我不认为从URL中应用的脚本不会被缓存,因此每次运行构建时都会重新下载这些脚本 . 这可能已经修复了,我不确定 .

  • 2

    当我遇到类似问题时,Stefan Oehme向我提供的解决方案是供应我自己的Gradle定制发行版 . 据他说,这对大公司来说是常见的事情 .

    只需创建gradle repo的自定义分支,使用此自定义版本的gradle为每个项目添加公司特殊酱 .

  • 1

    当在每个项目中复制通用配置时,我遇到了类似的问题 . 通过自定义gradle分发解决它,使用init脚本中定义的常用设置 .

    创建了一个用于准备此类自定义发行版的gradle插件 - custom-gradle-dist . 它适用于我的项目,例如库项目的build.gradle看起来像这样(这是一个完整的文件):

    dependencies {
        compile 'org.springframework.kafka:spring-kafka'
    }
    

相关问题