アイリッジ開発者ブログ

アイリッジに所属するエンジニアが技術情報を発信していきます。

Ktlintの導入方法

※この記事はKtlintの基本的な導入方法の説明を目的としており、Ktlint公式の手順を踏襲しています(が、単純な抄訳というわけでもありません)。

開発部第1グループの高橋です。

複数人でソフトウェア開発を進めるにあたり、メンテナンス性や可読性を担保するためにはコーディングガイドラインが重要になります。

コーディングガイドラインを遵守するにはまずそれを熟知し実践する必要がありますが、各人の習熟レベルに依存していたり、プロジェクトによりガイドライン自体異なる場合があるため、一般にLinterと呼ばれるチェックシステムやFormatterと呼ばれる整形システムに任せてしまうのが合理的です。

Androidの開発においては幸いなことに、IDEであるAndroid Studio自体にLinter / Formatterが組み込まれていますが、この機能を CIから利用するのは困難です。

そこで、よりCIから利用しやすい Ktlintという(おそらく広く知られている)OSSの登場です。

以下、Ktlintの導入手順を記述していきます。

なお、記事中ではAndroidの開発環境を前提としていますが、それ以外のGradle&Kotlinを使用した環境にも適用できるかもしれません。

共通手順

Linter / Formatter どちらとしての利用でも共通して設定するものとなります。

モジュールレベルの build.gradle または build.gradle.kts へ、以下のように依存関係を追加します。

  • build.gradle
configurations {
    ktlint
}

dependencies {
    ktlint("com.pinterest:ktlint:0.48.2") {
        attributes {
            attribute(Bundling.BUNDLING_ATTRIBUTE, getObjects().named(Bundling, Bundling.EXTERNAL))
        }
    }
}
  • build.gradle.kts
val ktlint by configurations.creating
dependencies {
    ktlint("com.pinterest:ktlint:0.48.2") {
        attributes {
            attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EXTERNAL))
        }
    }
}

Lint/Formatルール設定

基本的なルール設定は、EditorConfig と同じ .editorconfig ファイルへ記述することで行います。

※ここではCustom Rule Setの記述方法については述べません。

プロジェクトルート、またはモジュールのディレクトリへ .editorconfig を追加し、以下の例のように記述します。

設定例)

root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.kt{,s}]
ktlint_code_style = official
indent_size = 4
indent_style = space
max_line_length = 120          <== デフォルトは 100

上記の設定項目について

  • ktlint_code_style のみがKtlint固有の設定項目。ここでは公式ルールを適用させています。
  • max_line_length 等を設定することで、公式ルールのうち一部のルールの上書きが可能です。

留意事項として

  • .editorconfig ファイルはKtlint以外のツール(Android Studio同梱のプラグインやその他テキストエディタ等)からも読み込まれるため、EditorConfig由来の設定項目も記述しておくことが無難かもしれません。

[Optional] Android Studio上での設定

Ktlint公式ルールでは、Wildcard Importが禁止されています。

Android Studioのデフォルト設定では Import補完を使用するとWildcard Importになる場合があるため、これを抑制する必要があります。

以下の設定項目により入力補完によるWildcard Importを抑制できます。

この設定は .idea/codeStyles/Projects.xml に保存されるため、適宜Git管理対象に追加してください。

Linterとしての設定と実行方法

モジュールレベルの build.gradle または build.gradle.kts へ、以下のようにしてGradleタスク ktlintCheck を追加します。

  • build.gradle
tasks.register("ktlintCheck", JavaExec) {
    group = "verification"
    description = "Check Kotlin code style."
    classpath = configurations.ktlint
    mainClass = "com.pinterest.ktlint.Main"
    // see https://pinterest.github.io/ktlint/install/cli/#command-line-usage for more information
    args "src/**/*.kt", "**.kts", "!**/build/**"
}
  • build.gradle.kts
val ktlintCheck by tasks.creating(JavaExec::class) {
    val outputDir = "${project.buildDir}/reports/ktlint/"
    val inputFiles = project.fileTree(mapOf("dir" to "src", "include" to "**/*.kt"))

    inputs.files(inputFiles)
    outputs.dir(outputDir)

    description = "Check Kotlin code style."
    classpath = ktlint
    mainClass.set("com.pinterest.ktlint.Main")
    // see https://pinterest.github.io/ktlint/install/cli/#command-line-usage for more information
    args = listOf("src/**/*.kt")
}

実行方法

  • ターミナルから
$ ./gradlew ktlintCheck
  • Android Studioから
    • Gradleタスク ktlintCheck を実行します

実行結果例

$ ./gradlew ktlintCheck

> Task :app:ktlintCheck FAILED
... /ExampleInstrumentedTest.kt:1:1: File must end with a newline (\n) (final-newline)
... /ExampleInstrumentedTest.kt:3:1: Imports must be ordered in lexicographic order without any empty lines in-between with "java", "javax", "kotlin" and aliases in the end (import-ordering)
... /ExampleInstrumentedTest.kt:9:1: Wildcard import (cannot be auto-corrected) (no-wildcard-imports)
...

Summary error count (descending) by rule:
  final-newline: 11
  import-ordering: 3
  no-unused-imports: 3
  trailing-comma-on-declaration-site: 3
  argument-list-wrapping: 2
  no-blank-line-before-rbrace: 2
  no-empty-first-line-in-method-block: 2
  no-wildcard-imports: 2
  trailing-comma-on-call-site: 2

FAILURE: Build failed with an exception.

めっちゃ怒られてますね。

指摘箇所とその理由が詳細に出力されるため、ktlintCheckタスクが成功するまで適宜修正〜Lint実行を繰り返しましょう。

[Optional] ビルド前実行

以下をモジュールレベルの build.gradle または build.gradle.kts へ追加することにより、Linterをビルドの前処理として半自動的に実行できます。

  • build.gradle
android {
    gradle.projectsEvaluated {
        preBuild.dependsOn(ktlintCheck)
    }
}
  • build.gradle.kts
android {
    project.tasks.preBuild {
        dependsOn(ktlintCheck)
    }
}

Formatterとしての設定と実行方法

モジュールレベルの build.gradle または build.gradle.kts へ、以下のようにGradleタスク ktlintFormat を追加します。

  • build.gradle
tasks.register("ktlintFormat", JavaExec) {
    group = "formatting"
    description = "Fix Kotlin code style deviations."
    classpath = configurations.ktlint
    mainClass = "com.pinterest.ktlint.Main"
    jvmArgs "--add-opens=java.base/java.lang=ALL-UNNAMED"
    // see https://pinterest.github.io/ktlint/install/cli/#command-line-usage for more information
    args "-F", "src/**/*.kt", "**.kts", "!**/build/**"
}
  • build.gradle.kts
val ktlintFormat by tasks.creating(JavaExec::class) {
    val outputDir = "${project.buildDir}/reports/ktlint/"
    val inputFiles = project.fileTree(mapOf("dir" to "src", "include" to "**/*.kt"))

    inputs.files(inputFiles)
    outputs.dir(outputDir)

    description = "Fix Kotlin code style deviations."
    classpath = ktlint
    mainClass.set("com.pinterest.ktlint.Main")
    // see https://pinterest.github.io/ktlint/install/cli/#command-line-usage for more information
    args = listOf("-F", "src/**/*.kt")
}

実行方法

  • ターミナルから
$ ./gradlew ktlintFormat
  • Android Studioから
    • Gradleタスク ktlintFormat を実行します

実行結果例

※前述のLint実行例後の状態からスタート

$ ./gradlew ktlintFormat

> Task :app:ktlintFormat
... /ExampleInstrumentedTest.kt:5:1: Wildcard import (cannot be auto-corrected) (no-wildcard-imports)
... /ExampleUnitTest.kt:3:1: Wildcard import (cannot be auto-corrected) (no-wildcard-imports)

Summary error count (descending) by rule:
  no-wildcard-imports: 2

> Task :app:ktlintFormat FAILED

FAILURE: Build failed with an exception.

この例では、 ktlintCheck で検出されていた指摘はあらかた解消しましたが、 no-wildcard-imports ルールについては手動で修正する必要があるため、指摘として残ります。

こちらも適宜修正〜Formatter or Linter実行を繰り返しましょう。

最後に

Linter/Formatter を導入することは、あくまでもコーディングガイドラインを遵守する上での補助に過ぎません。

これを導入した結果、Lintチェックの指摘修正に追われるようでは元も子もないため、ざっくりとでもガイドラインを把握しておくことが肝要と考えます。

道具と上手な付き合い方をして、人間側も適宜アップデートしていきましょう。

以上