マルチモジュールプロジェクトの構成

バックグラウンド



時々、私が先延ばしにするとき、私は掃除をします:私はテーブルを掃除し、物を出し、部屋を掃除します。実際、私は環境を整えました-それはあなたにエネルギーを与え、仕事のために準備します。プログラミングの場合も同じ状況ですが、プロジェクトをクリーンアップするだけです。リファクタリングを実行し、さまざまなツールを作成し、自分自身と同僚の生活を楽にするために最善を尽くします。



少し前に、Androidチームの私たちは、プロジェクトの1つであるWalletをマルチモジュラーにすることを決定しました。これにより、多くの利点と問題の両方が発生しました。その1つは、各モジュールを最初から構成する必要があることです。もちろん、構成をモジュールからモジュールにコピーすることもできますが、何かを変更したい場合は、すべてのモジュールを繰り返す必要があります。



私はこれが好きではありません、チームはそれが好きではありません、そしてこれが私たちの生活を簡素化し、構成を維持しやすくするために私たちが取ったステップです。







最初の反復-ライブラリバージョンの引き出し



実際、これは私の前のプロジェクトにすでにあり、あなたはこのアプローチを知っているかもしれません。開発者がそれを使用しているのをよく見かけます。



このアプローチでは、ライブラリのバージョンをプロジェクトの個別のグローバルプロパティに移動する必要があります。その後、ライブラリはプロジェクト全体で使用できるようになり、再利用に役立ちます。これは通常、プロジェクトレベルのbuild.gradleファイルで実行されますが、これらの変数が別の.gradleファイルに取り出され、メインのbuild.gradleに含まれる場合もあります。



ほとんどの場合、プロジェクトでそのようなコードをすでに見たことがあるでしょう。魔法はありません。ExtraPropertiesExtensionと呼ばれるGradle拡張機能の1つにすぎませ。つまり、プロジェクトオブジェクトのextで使用できるのはMap <String、Object>だけです。、およびその他すべて-オブジェクト、構成ブロックなどを操作するかのように機能します-Gradleの魔法。例:

.gradle .gradle.kts
// creation
ext {
  dagger = '2.25.3'
  fabric = '1.25.4'
  mindk = 17
}

// usage
println(dagger)
println(fabric)
println(mindk)


// creation
val dagger by extra { "2.25.3" }
val fabric by extra { "1.25.4" }
val minSdk by extra { 17 }

// usage
val dagger: String by extra.properties
val fabric: String by extra.properties
val minSdk: Int by extra.properties




このアプローチで私が気に入っているのは、非常にシンプルで、バージョンの実行を維持するのに役立つことです。ただし、欠点があります。開発者がこのセットのバージョンを使用していることを確認する必要があります。これでも、多くのものをコピーする必要があるため、新しいモジュールの作成が大幅に簡素化されるわけではありません。



ちなみに、ExtraPropertiesExtensionの代わりにgradle.propertiesを使用しても同様の効果が得られますが注意してください。-Pフラグを使用してビルドするときにバージョンが上書きされる可能性があり、groovy-scriptsで名前だけで変数を参照すると、gradle.propertiesが置き換えられます。そしてそれら。gradle.propertiesとオーバーライドの



// grdle.properties
overriden=2

// build.gradle
ext.dagger = 1
ext.overriden = 1

// module/build.gradle
println(rootProject.ext.dagger)   // 1
println(dagger)                   // 1

println(rootProject.ext.overriden)// 1
println(overriden)                // 2


2回目の繰り返し-project.subprojects



コードをコピーして各モジュールの構成を処理したくないという私の好奇心から、次のステップに進みましたルートbuild.gradleに、デフォルトで生成されるブロック(allprojects)があることを思い出しました



allprojects {
    repositories {
        google()
        jcenter()
    }
}


私はに行ってきましたドキュメンテーション、あなたがこのプロジェクトとすべてのネストされたプロジェクトを設定することをそれにコードのブロックを渡すことができることを見出しました。しかし、これは私が必要としていたものではないので、さらにスクロールしてサブプロジェクトを見つけました。これは、ネストされたすべてのプロジェクトを一度に構成する方法です。私はいくつかのチェックを追加する必要がありました、そしてこれは起こったことです。



project.subprojectsを介してモジュールを構成する例
subprojects { project ->
    afterEvaluate {
        final boolean isAndroidProject =
            (project.pluginManager.hasPlugin('com.android.application') ||
                project.pluginManager.hasPlugin('com.android.library'))

        if (isAndroidProject) {
            apply plugin: 'kotlin-android'
            apply plugin: 'kotlin-android-extensions'
            apply plugin: 'kotlin-kapt'
            
            android {
                compileSdkVersion rootProject.ext.compileSdkVersion
                
                defaultConfig {
                    minSdkVersion rootProject.ext.minSdkVersion
                    targetSdkVersion rootProject.ext.targetSdkVersion
                    
                    vectorDrawables.useSupportLibrary = true
                }

                compileOptions {
                    encoding 'UTF-8'
                    sourceCompatibility JavaVersion.VERSION_1_8
                    targetCompatibility JavaVersion.VERSION_1_8
                }

                androidExtensions {
                    experimental = true
                }
            }
        }

        dependencies {
            if (isAndroidProject) {
                // android dependencies here
            }
            
            // all subprojects dependencies here
        }

        project.tasks
            .withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile)
            .all {
                kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8.toString()
            }
    }
}




これで、com.android.applicationまたはcom.android.libraryプラグインが接続されているモジュールについて、プラグインプラグイン、プラグイン構成、依存関係など、何でも構成できます。



いくつかの問題がなければ、すべてがうまくいくでしょう。モジュールのサブプロジェクトで指定されたいくつかのパラメーターをオーバーライドしたい場合、モジュールはサブプロジェクトを適用する前に構成されているため、これを行うことはできません(afterEvaluateのおかげで)。また、この自動構成を個々のモジュールに適用したくない場合は、サブプロジェクトブロックに多くの追加チェックが表示され始めます。それで私はさらに考え始めました。



3回目の反復-buildSrcとプラグイン



これまで、buildSrcについて何度か聞いたことがあり、この記事の最初のステップの代わりにbuildSrcが使用された例を見ました。また、gradleプラグインについても聞いたので、この方向に掘り始めました。すべてが非常に単純であることが判明しました。Gradleには、すべてが記述されたカスタムプラグイン開発するためのドキュメントがあります。



少し理解した後、必要に応じて変更できるように、変更が必要なすべてのものを構成できるプラグインを作成しました



プラグインコード
import org.gradle.api.JavaVersion
import org.gradle.api.Plugin
import org.gradle.api.Project

class ModulePlugin implements Plugin<Project> {
    @Override
    void apply(Project target) {
        target.pluginManager.apply("com.android.library")
        target.pluginManager.apply("kotlin-android")
        target.pluginManager.apply("kotlin-android-extensions")
        target.pluginManager.apply("kotlin-kapt")

        target.android {
            compileSdkVersion Versions.sdk.compile

            defaultConfig {
                minSdkVersion Versions.sdk.min
                targetSdkVersion Versions.sdk.target

                javaCompileOptions {
                    annotationProcessorOptions {
                        arguments << ["dagger.gradle.incremental": "true"]
                    }
                }
            }

            // resources prefix: modulename_
            resourcePrefix "${target.name.replace("-", "_")}_"

            lintOptions {
                baseline "lint-baseline.xml"
            }

            compileOptions {
                encoding 'UTF-8'
                sourceCompatibility JavaVersion.VERSION_1_8
                targetCompatibility JavaVersion.VERSION_1_8
            }

            testOptions {
                unitTests {
                    returnDefaultValues true
                    includeAndroidResources true
                }
            }
        }

        target.repositories {
            google()
            mavenCentral()
            jcenter()
            
            // add other repositories here
        }

        target.dependencies {
            implementation Dependencies.dagger.dagger
            implementation Dependencies.dagger.android
            kapt Dependencies.dagger.compiler
            kapt Dependencies.dagger.androidProcessor

            testImplementation Dependencies.test.junit
            
            // add other dependencies here
        }
    }
}




今のような新しいプロジェクトのルックスの構成は⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠'ru.yandex.money.moduleを':プラグイン適用しているのです。 androidまたはdependenciesブロックに独自の追加を行ったり、プラグインを追加したり、それらをカスタマイズしたりできますが、重要なことは、新しいモジュールが1行で構成され、その構成は常に関連性があり、製品開発者はセットアップについて考える必要がないことです。



マイナス面のうち、このソリューションには追加の時間と資料の調査が必要であることに注意しますが、私の観点からは、それだけの価値があります。将来、プラグインを別のプロジェクトとして移動したい場合は、プラグイン内のモジュール間に依存関係を設定することはお勧めしません



重要なポイント:4.0未満のandroid gradleプラグインを使用している場合、kotlinスクリプトで実行するのが非常に難しいことがいくつかあります。少なくともandroidブロックはgroovyスクリプトで構成する方が簡単です。一部のタイプはコンパイル時に使用できず、groovyは動的にタイプされるという事実に問題があり、彼にとっては問題ではありません=)



次へ-スタンドアロンプ​​ラグインまたはmonorepo



もちろん、3番目のステップがすべてではありません。完璧に制限はないので、次にどこに行くかについてのオプションがあります。



最初のオプションは、gradle用スタンドアロンプ​​ラグインです。3番目のステップの後、それほど難しくはありません。別のプロジェクトを作成し、そこにコードを転送して、パブリケーションを構成する必要があります。



長所:プラグインは複数のプロジェクト間で使用できます。これにより、1つのプロジェクトではなく、エコシステムでの作業が簡素化されます。



短所:バージョン管理-プラグインを更新する場合、一度に複数のプロジェクトでその機能を更新して確認する必要があり、これには時間がかかる場合があります。ちなみに、バックエンド開発の私の同僚は、このトピックに関して優れたソリューションを持っています。キーワードは、モダナイザーです。これは、リポジトリをウォークスルーして依存関係を更新するツールです。私はこれに長い間こだわるつもりはありません、彼ら自身が言うならばそれはより良いでしょう。



モノレポ-大音量に聞こえますが、私はそれを使った経験がありませんが、buildSrcのような1つのプロジェクトを他の複数のプロジェクトで一度に使用できるという考慮事項しかありません。これは、バージョン管理の問題の解決に役立つ可能性があります。突然モノレポを使った経験がある場合は、コメントで共有して、私や他の読者がそれについて何かを学ぶことができるようにしてください。



合計



新しいプロジェクトでは、3番目のステップであるbuildSrcとpluginをすぐに実行します特にコードを添付しているので、誰にとっても簡単になりますそして、2番目のステップであるproject.subprojectsは、共通のモジュールを相互に接続するために使用されます。



追加または反対するものがある場合は、コメントを書き込むか、ソーシャルネットワークで私を探してください。



All Articles