Gradleで依存関係を宣言する:api()とimplementation()

前回のつづき。 

kdnakt.hatenablog.com

 

 

[Gradleで依存関係を宣言する]

Gradleで、あるプロジェクトが利用する依存関係にあるプロジェクトやライブラリを宣言する方法として、api()とimplementation()の2つがある(他にもtestImplementation()とかruntimeOnly()とかたくさんあるけど省略)。

 

前回参考にしたこちらのマルチプロジェクト構成を参考に見ていく。

docs.gradle.org 

 

utilitiesサブプロジェクトの依存関係の定義は以下のようapi()を利用している。

multi-project-sample/utilities/build.gradle.kts

dependencies {
    api(project(":list"))
}

 

utilitiesサブプロジェクトでは、listサブプロジェクトで定義されているLinkedListクラスを利用して、以下のような関数が定義されている。

package multi.project.sample.utilities

// listサブプロジェクトからimport
import multi.project.sample.list.LinkedList

class StringUtils {
    companion object {
        fun join(source: LinkedList): String {
            return JoinUtils.join(source)
        }

        fun split(source: String): LinkedList {
            return SplitUtils.split(source)
        }
    }
}

 

一方、appサブプロジェクトでは依存関係が以下のようにimplementation()で定義されている。

multi-project-sample/app/build.gradle.kts

dependencies {
    implementation("org.apache.commons:commons-text")
    implementation(project(":utilities"))
}

 

appサブプロジェクトの中で、main関数を持つApp.ktファイルは以下の通り。

package multi.project.sample.app

// utilitiesサブプロジェクトのクラスをimport
import multi.project.sample.utilities.StringUtils 

import org.apache.commons.text.WordUtils

fun main() {
    val tokens = StringUtils.split(MessageUtils.getMessage())
    val result = StringUtils.join(tokens)
    println(WordUtils.capitalize(result))
}

 

どちらも別のプロジェクトのクラスをimportしているが、依存関係の宣言の仕方がapi()とimplementation()でそれぞれ異なる。 

 

[api()とimplementation()]

api()とimplementation()の違いは、Java Library Pluginのページで説明されていた。

docs.gradle.org

 

まとめると以下のような違いがある。

  • api()
    • ライブラリのAPIによって公開される依存関係を宣言する場合に使う
    • 遷移的にライブラリの利用者にも公開される:利用者のコンパイル・クラスパスにも登場する
  • implementation()
    • ライブラリの内部で利用する依存関係を宣言する場合に使う
    • ライブラリの利用者には公開されない

 

先ほどの例に戻って考えてみる。

utilitiesサブプロジェクトが公開するAPIの戻り値の型が、listサブプロジェクトで定義されているLinkedListクラスになっているから、utilitiesサブプロジェクトではlistサブプロジェクトへの依存をapi()で定義する。これにより、utilitiesサブプロジェクトの利用者(つまりappサブプロジェクト)のコンパイル・クラスパスにLinkedListクラスを登場させることができる。

他方で、appサブプロジェクトを利用するプロジェクトはないので、サブプロジェクト内部で利用する依存関係のみを宣言すればよい。なので、appサブプロジェクトではimplementation()を利用してutilitiesサブプロジェクトへの依存を定義している。

 

[implementation()のメリット] 

先ほどのJava Library Pluginのページによると、可能な限りimplementation()を利用するのが好ましいとされていた。

以下のようなメリットがあるらしい。

 

  • 遷移的な依存関係に誤って依存しなくなる
  • 利用者側のクラスパスが少なくなるのでコンパイルが速くなる
  • implementation()の依存関係が変わってもライブラリ利用者側は再コンパイルが不要
  • maven-publishプラグインを利用した時に、pom.xmlでの依存関係のscopeが自動的にruntime/compileで設定される

 

これまで特に学ぶこともなく、雰囲気でmavenを利用していた。そのため、依存関係のscopeはほとんど意識せずデフォルトのcompileスコープを利用するか、JUnitなどでたまにtestスコープを選択する程度だった。

コンパイルが多少でも速くなるなら、真面目にmavenでもスコープ設定してみようかな、と思ったけどライブラリ作ることなんてそんなにないから関係ないか……。

 

[Gradle7.0未満]

Gradleは当初api()/implementation()のかわりに、compile()/runtime()という依存関係の設定方法を提供していた。

しかし、Gradle 3.4でより細かい制御が可能なapi()/implementation()が導入され、2021年4月にリリースされたGradle 7.0でcompile()/runtime()のサポートが打ち切られた。

docs.gradle.org

 

[まとめ]

  • Gradleで依存関係を定義する方法としてapi()とimplementation()がある
  • 依存関係を定義しているライブラリ/プロジェクトの利用者のクラスパスに依存関係を公開する場合はapi()を、そうでない場合はimplementation()を使う
  • サンプルコードは以下のGitHubリポジトリにまとめてある

github.com