アナログ金木犀

つれづれなるまままにつれづれする

Android Gradle Plugin3系におけるマルチモジュール時のconfigurationの解決について

こんにちは。釘宮です。

マルチモジュールなプロジェクトをやって行く上で、以前は下記のような記述をよく書いていました。

devCompile project(path: 'library', configuration: 'dev')

Android Gradle Plugin3系(gradle4系)から compile(など)ではなく implementation(など)を使っていきましょうともろもろ変わりました。

なので、単純に下記のように implementation と書き直してみます。

devImplementation project(path: 'library', configuration: 'dev')

これでビルドをして見ると、がっつりエラーが出てきます

解決方法などをまとめたので今回記事として見ました。

ここに書いていあることは 公式ページ に書いてあることの一部 + αでdす。

結論から言うと implementation project('library') で良い (条件付き)

app/build.gradle library/build.gradle
android {
  :
  flavorDimensions 'api'
  productFlavors {
    dev {
      dimension 'api'
    }
    stg {
      dimension 'api'
    }
    prod {
      dimension 'api'
    }
  }
  :
}
dependencies {
  :
  implementation project(":library")
  :
}
android {
  :
  flavorDimensions 'api'
  productFlavors {
    dev {
      dimension 'api'
    }
    stg {
      dimension 'api'
    }
    prod {
      dimension 'api'
    }
  }
  :
}

このようになっている場合、appではlibraryで同じ名前のflavorのものを選択してbuildされます。

いままでは

devCompile project(path: ':library', configuration: 'dev')
stgCompile project(path: ':library', configuration: 'stg')
prodCompile project(path: ':library', configuration: 'prod')

などと書いてましたが、下記一行で済むようになりました。便利ですね。

implementation project(":library")

ただこれに関しては app側にある api-dimension に所属する dev, stg, prod をlibrary側も同じものを持っているという条件を満たしている時に限ります。

そのため、library側が余計に dev1 というflavorを持っていても使われないだけでビルドは問題なく通ります。

以下から簡略化のため、 api-dimensionに所属する dev, stg, prod のflavorがある 状態を [api => (dev, stg, prod)] と書くようにしますね。

例1. appでは[api => (dev, stg, qa, prod)]、libraryでは[ api => (dev, stg, prod)]となっている時

app側に qa があるけど library側にそれがない場合です。

appの qa 時には library では stg を選択したいとします。

こう言う時は matchingFallbacks を使用します。

app/build.gradle library/build.gradle
android {
  :
  flavorDimensions 'api'
  productFlavors {
    dev {
      dimension 'api'
    }
    stg {
      dimension 'api'
    }
    qa {
      dimension 'api'
      matchingFallbacks = ["stg"]
    }
    prod {
      dimension 'api'
    }
  }
  :
}
dependencies {
  :
  implementation project(":library")
  :
}
android {
  :
  flavorDimensions 'api'
  productFlavors {
    dev {
      dimension 'api'
    }
    stg {
      dimension 'api'
    }
    prod {
      dimension 'api'
    }
  }
  :
}

依存先に対象flavorが見つからない場合に matchingFallbacks によって優先順位を指定できます。 上記の例では stg のみ指定しいます。 (なお、 flavor dimention が一致していないと、matchingFallbacks に指定しても動きません)。 加えてこのmatchingFallbacks は下記のようにbuildTypeでも同様の使い方ができます。

app/build.gradle library/build.gradle
android {
  :
  buildTypes {
    debug {
    }
    stg {
    }
    qa {
      matchingFallbacks = ["stg"]
    }
    release {
    }
  }
  :
}
dependencies {
  :
  implementation project(":library")
  :
}
android {
  :
  buildTypes {
    debug {
    }
    stg {
    }
    release {
    }
  }
  :
}

例2. library側に知らないdimensionがある時

そもそもdimensionとは

あえて触れてこなかったんですが flavorDimensions 'api' のところの話です。

Android Gradle Plugin3.0.0より前ではなかったものです。 いままでもありましたが、3系からは指定が必須になったようです。

いままでは productFlavors とはフラットなものでして、あるflavorと他のflavorを組み合わせるということはできませんでした。これを可能にするのが flavorDimension です。

言葉だと難しいので実際に設定して見ましょう。

productFlavors {
    buildTypes {
      debug
      release
    }
    flavorDimensions 'api', 'persistence'
    dev {
        dimension 'api'
    }
    stg {
        dimension 'api'
    }
    prod {
        dimension 'api'
    }
    local {
        dimension 'persistence'
    }
    cloud {
        dimension 'persistence'
    }
}

flavorDimension を指定しない場合、 buildVariant は (dev, stg, prod, local, cloud) × (debug, release) の 5×2 = 10通りのconfigurationがとなっていました。

これが、 現状は上記のようにdimensionを使い分けることで (dev, stg, prod) × (local, cloud) × (debug, release) の 3×2×2 = 12通りconfigurationが作られるようになったと言うことです。

何が便利かと言うと、観点ごとにコードを分けれることでしょうか。上記の場合だと api は (dev, stg, prod) のどの環境を使うか、永続化(persistence)は ローカルなのかクラウドなのかという例になっています。

ざっくりここまでがdimensionの説明です。

appは [api => (dev, stg, prod)]、 libraryが [api => (dev, stg, prod), persistence => (local, cloud)] の時

appの知らない persistence dimension がlibraryにある場合を考えます。

appで、例えば stg をbuildしたい時に、 library側では stg と言っても stgLocalstgCloud の組み合わせがあるのでどっちを使えば良いのかわからないと言う状況です。;

この場合は missingDimensionStrategy を使用します

missingDimensionStrategy (dimension名) (flavor名), (flavor名),...

と言うふうに 知らないdimensionがきた時に、どのflavorを優先的に採用するかということを決めることができます。

missingDimensionStrategy 'persistence' 'local' 'cloud'

この場合は persistence dimensionがきたら local, cloud の順で採用すると言うことになります。

これを踏まえると下記のようになりました。

app/build.gradle library/build.gradle
android {
  :
  defaultConfig {
    :
    missingDimensionStrategy 'persistence', 'local', 'cloud'
    :
  }
  flavorDimensions 'api'
  productFlavors {
      dev {
          dimension 'api'
          missingDimensionStrategy 'persistence', 'local'
      }
      stg {
          dimension 'api'
          missingDimensionStrategy 'persistence', 'cloud'
      }
      prod {
          dimension 'api'
          missingDimensionStrategy 'persistence', 'cloud'
      }
  }
  :
}
dependencies {
  :
  implementation project(":library")
  :
}
android {
  :
  flavorDimensions 'api', 'persistence'
  productFlavors {
      dev {
          dimension 'api'
      }
      stg {
          dimension 'api'
      }
      prod {
          dimension 'api'
      }
      local {
          dimension 'persistence'
      }
      cloud {
          dimension 'persistence'
      }
  }
  :
}

上記のように各Flavorごとに missingDimensionStrategy を設定することができます。

まとめ

これからは configuration の解決は dependencies のところではなく flavor名、 matchingFallbacksmissingDimensionStrategy で頑張りましょう。