web-dev-qa-db-ja.com

Gradle Kotlin DSLを使用したGradleのボイラープレートプロジェクト構成

現在、プロジェクトが構成を共有する方法を改善しようとしています。すべてのライブラリとマイクロサービス(つまり、多くのgitリポジトリ)に対応する、さまざまなマルチモジュールGradleプロジェクトがたくさんあります。

私の主な目標は:

  • すべてのプロジェクトでNexusリポジトリの構成が複製されないようにする(また、URLが変更されないと想定しても問題ありません)
  • カスタムのGradleプラグイン(Nexusに公開)をすべてのプロジェクトで利用できるようにするには、ボイラープレート/複製を最小限に抑えます(それらはすべてのプロジェクトで利用可能であり、プロジェクトが気にするのは、使用しているバージョンのみです)。
  • 魔法はありません-すべてがどのように構成されているかは開発者には明らかです

私の現在のソリューションは、次のようなinitスクリプトを使用したカスタムGradleディストリビューションです。

  • mavenLocal()とNexusリポジトリをプロジェクトリポジトリに追加します(Gradle init script documentation example に非常に似ていますが、リポジトリを追加するとともに検証する点が異なります)。
  • gradleプラグインをbuildscriptクラスパスに追加できるようにする拡張機能を構成します( この回避策 を使用)。また、プラグインがホストされている場所として、Nexusリポジトリをbuildscriptリポジトリとして追加します。さまざまなボイラープレート用のかなりの数のプラグイン(Netflixの優れた nebulaプラグイン に基づいて構築)があります。標準のプロジェクトセットアップ(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による Githubのコメント を読むことによってトリガーされました。これは、ビルドはinitスクリプトに依存せずに機能する必要があることを示しています。無許可レポなどの防止.

私のアイデアは、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スクリプトは、魔法のプラグインとリポジトリの構成を隠すのではなく、単純な検証を実行できました。拡張機能は変更する必要がないため、プラグインが変更されたときに新しいGradleディストリビューションをリリースする必要はありません。

私が試したこと:

  • 私のjarをテストプロジェクトのbuildscriptクラスパス(つまり_buildscript.dependencies_)に追加します-動作しません(おそらく、依存関係をbuildscriptに追加しているように思われないため、仕様上これは動作しません)同じブロックで参照されている)
  • 関数をbuildSrcに配置します(これは通常のプロジェクトdeps/reposでは機能しますが、buildscriptでは機能しませんが、ボイラープレートを移動するだけなので、実際のソリューションではありません)
  • 配布のlibフォルダーにjarをドロップする

だから私の質問は本当に次のように要約されます:

  • 私が達成しようとしていることは可能ですか(カスタムクラス/関数をbuildScriptブロックから見えるようにすることは可能ですか)?
  • 企業のNexusリポジトリを構成し、カスタムプラグイン(Nexusに公開)を最小限のボイラープレート構成で多数の個別のプロジェクト(つまり、完全に異なるコードベース)で利用できるようにするためのより良いアプローチはありますか?
31
James Bassett

私は@eskatosに戻って彼の答えについてフィードバックすることを約束しました-ここにあります!

私の最終的な解決策は次のとおりです:

  • プロジェクトごとのGradle 4.7ラッパー(ミラーの http://services.gradle.org/distributions Nexusで生のプロキシリポジトリとして設定、つまり、Vanilla Gradleですが、Nexus経由でダウンロードされます)
  • Nexusリポジトリにプラグインマーカーとともに公開されたカスタムGradleプラグイン( Java Gradleプラグイン開発プラグイン によって生成されます)
  • NexusリポジトリでGradleプラグインポータルをミラーリングする(つまり、 https://plugins.gradle.org/m2 を指すプロキシリポジトリ)
  • MavenリポジトリとGradleプラグインポータルミラー(両方ともNexus内)をプラグイン管理リポジトリとして構成するプロジェクトごとのsettings.gradle.ktsファイル。

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経由でプロキシされるようになり、プラグインマーカーもNexusに公開されるため、GradleはプラグインをIDで検索することを意味します。 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 wrapperを設定して、ミラーリングされたディストリビューションを使用します。これを上記と組み合わせると、すべて(gradle、プラグイン、依存関係)がすべてNexus経由で送られます):

tasks {
    "wrapper"(Wrapper::class) {
        distributionUrl = "https://nexus.example.com/repository/gradle-distributions/gradle-4.7-bin.Zip"
    }
}

settings.gradle.ktsのリモートURLからスクリプトを適用するという@eskatosの提案を使用して、設定ファイルのボイラープレートを回避したいと思っていました。つまり.

apply { from("https://nexus.example.com/repository/maven-public/com/example/gradle/corporate-settings/1.2.3/corporate-settings-1.2.3.kts" }

以下のテンプレート化されたスクリプト(プラグインと一緒に公開)を生成することもできました。

  • プラグインリポジトリを設定しました(上記の設定スクリプトのように)
  • 要求されたプラグインIDがプラグインの1つであり、バージョンが提供されていない場合は、解決戦略を使用して、スクリプトに関連付けられたプラグインのバージョンを適用します(したがって、IDで適用できます)

ただし、ボイラープレートは削除されましたが、URLから適用されたスクリプトがキャッシュされていても、GradleはHEADを実行しているようで、Nexusリポジトリへの接続に依存していることを意味します=とにかく変更を確認するように要求します。また、ローカルのmavenディレクトリのスクリプトを手動で指定する必要があったため、プラグインの変更をローカルでテストするのも面倒でした。現在の構成では、プラグインをローカルのmavenに公開するだけで、プロジェクトのバージョンを更新します。

私は現在の設定にとても満足しています-プラグインがどのように適用されるかは、開発者にとってはるかに明白です。また、Gradleとプラグインを個別にアップグレードすることがはるかに簡単になり、2つの間に依存関係がなくなりました(カスタムのGradleディストリビューションは必要ありません)。

3
James Bassett

すべての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. を使用できます。すべてのビルドで同じプラグインバージョンを使用する必要がある場合は、settings.gradle.ktsを使用してpluginManagement.resolutionStrategyにバージョンを設定することもできます。

また、これがすべて機能するためには、プラグインを プラグインマーカーアーティファクト で公開する必要があります。これは、Java-gradle-pluginプラグインを使用して簡単に実行できます。

11
eskatos

私は自分のビルドでこのようなことをしてきました

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.ktsbuildSrcプロジェクトからも使用できます。つまり、リポジトリ宣言を再利用できます。


更新しました:

たとえば、次のようにして、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レポのカスタムフォークを作成し、このカスタムバージョンのGradleを使用して、すべてのプロジェクトに会社の特別なソースを追加します。

1

共通の構成がすべてのプロジェクトで複製されるときに、同様の問題が発生しました。 initスクリプトで定義された一般的な設定を使用して、カスタムのgradleディストリビューションで解決しました。

そのようなカスタムディストリビューションを準備するためのgradleプラグインを作成しました- custom-gradle-dist 。それは私のプロジェクト、例えばライブラリプロジェクトのbuild.gradleは次のようになります(これは完全なファイルです)。

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