※この記事は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チェックの指摘修正に追われるようでは元も子もないため、ざっくりとでもガイドラインを把握しておくことが肝要と考えます。
道具と上手な付き合い方をして、人間側も適宜アップデートしていきましょう。
以上