KotlinでGradleカスタムタスクを作成する完全ガイド

KotlinでGradleを使用する際、ビルドプロセスを効率的に管理するためには、カスタムタスクの作成が有効です。Gradleタスクは、ビルドやデプロイの各ステップを自動化する役割を持っていますが、デフォルトのタスクでは対応できない場合があります。そんなときに役立つのが「カスタムタスク」です。Kotlinを使えば、柔軟かつ型安全にGradleタスクをカスタマイズできます。

本記事では、Gradleカスタムタスクの作成方法、基本構造、実用的な活用例まで、ステップごとにわかりやすく解説します。KotlinでGradleタスクを自作することで、ビルドプロセスを自分のプロジェクトに合わせて最適化できるようになります。

目次

Gradleカスタムタスクとは何か


Gradleカスタムタスクとは、Gradleビルドシステムにおいて独自の処理を定義したタスクのことです。通常、Gradleにはビルド、テスト、デプロイなどの標準タスクが備わっていますが、プロジェクトの要件に応じてこれらを拡張したり、新しいタスクを作成したりする必要がある場合があります。

カスタムタスクの役割


カスタムタスクは以下のような役割を果たします。

  1. ビルドプロセスの自動化
    独自の処理をビルドの一部として自動化します。
  2. 繰り返し作業の効率化
    手動で行う手間のかかる処理をタスクとして定義し、効率的に実行します。
  3. プロジェクトの柔軟性向上
    プロジェクト固有の要件に応じたタスクを追加できます。

Gradle標準タスクとカスタムタスクの違い


Gradleの標準タスクはビルドツールにあらかじめ用意されたもので、buildtestといった一般的な処理を行います。一方、カスタムタスクは開発者が独自のロジックを定義し、プロジェクトのニーズに合わせて自由にカスタマイズできるものです。

カスタムタスクを作成することで、複雑なビルド要件や自動化された作業フローを実現することができます。

Gradleタスクの基本構造


KotlinでGradleタスクを書くには、タスクの基本構造を理解することが重要です。GradleではタスクはTaskクラスを継承し、処理内容を@TaskActionアノテーションを付けたメソッド内に記述します。

シンプルなタスクの構造


以下は、Kotlinで書かれたシンプルなGradleタスクの例です。

import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction

open class HelloTask : DefaultTask() {

    @TaskAction
    fun greet() {
        println("Hello, Gradle!")
    }
}

// タスクをビルドスクリプトに登録
tasks.register("hello", HelloTask::class)

各構成要素の説明

  1. DefaultTaskクラス
    Gradleタスクの基本クラスで、これを継承することでカスタムタスクを作成できます。
  2. @TaskActionアノテーション
    タスクが実行されたときに呼び出されるメソッドに付けます。このメソッドにはタスクの処理内容を書きます。
  3. tasks.register
    タスクをGradleに登録するためのメソッドです。"hello"はタスク名、HelloTask::classは作成したタスククラスを示します。

タスクの実行方法


Gradleタスクを実行するには、コマンドラインで以下のように入力します。

./gradlew hello

実行すると、以下の出力が表示されます。

> Task :hello
Hello, Gradle!

この基本構造を理解することで、プロジェクトに応じた柔軟なカスタムタスクを作成できます。

カスタムタスクの作成手順


KotlinでGradleのカスタムタスクを作成する手順を具体的に見ていきましょう。ここでは、独自の処理を含むタスクを作成し、ビルドスクリプトで登録・実行する方法を説明します。

ステップ1: タスククラスの作成


まず、DefaultTaskクラスを継承した新しいクラスを作成します。以下は、シンプルなメッセージを表示するタスクの例です。

import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction

open class PrintMessageTask : DefaultTask() {
    @TaskAction
    fun printMessage() {
        println("This is a custom Gradle task written in Kotlin!")
    }
}

ステップ2: タスクを`build.gradle.kts`に登録


作成したタスククラスをビルドスクリプトで登録します。以下はbuild.gradle.ktsの例です。

tasks.register<PrintMessageTask>("printMessage")

ここで、"printMessage"はタスクの名前です。任意の名前に変更できます。

ステップ3: タスクの実行


ターミナルから以下のコマンドでカスタムタスクを実行します。

./gradlew printMessage

実行すると、次のような出力が表示されます。

> Task :printMessage
This is a custom Gradle task written in Kotlin!

ステップ4: タスクにパラメータを追加


さらにタスクにパラメータを追加することもできます。例えば、メッセージを引数として渡せるようにするには以下のようにします。

import org.gradle.api.DefaultTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskAction

open class PrintMessageTask : DefaultTask() {
    @Input
    var message: String = "Default message"

    @TaskAction
    fun printMessage() {
        println(message)
    }
}

// タスク登録時にパラメータを設定
tasks.register<PrintMessageTask>("printMessage") {
    message = "Hello from a parameterized task!"
}

この手順で、柔軟なカスタムタスクを作成し、Gradleビルドの自動化を効率化することができます。

入力と出力の設定


Gradleカスタムタスクでは、入力データと出力データを設定することで、タスクの効率性や依存関係を管理できます。これにより、タスクが無駄な再実行を避け、ビルド時間を短縮できます。

入力の設定


タスクの入力は、@Input@InputFile@InputDirectoryアノテーションを使用して定義します。

例: 文字列の入力を設定する場合

import org.gradle.api.DefaultTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskAction

open class PrintMessageTask : DefaultTask() {

    @Input
    var message: String = "Default Message"

    @TaskAction
    fun printMessage() {
        println(message)
    }
}

// タスク登録時に入力値を指定
tasks.register<PrintMessageTask>("printMessage") {
    message = "Hello from a custom input!"
}

ターミナルでの実行結果:

> Task :printMessage  
Hello from a custom input!

ファイルの入力設定


ファイルを入力として受け取る場合、@InputFileアノテーションを使用します。

import org.gradle.api.DefaultTask
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.TaskAction
import java.io.File

open class ReadFileTask : DefaultTask() {

    @InputFile
    lateinit var inputFile: File

    @TaskAction
    fun readFile() {
        println(inputFile.readText())
    }
}

// タスク登録時にファイルを指定
tasks.register<ReadFileTask>("readFile") {
    inputFile = file("example.txt")
}

出力の設定


タスクの出力は、@OutputFile@OutputDirectoryアノテーションを使って定義します。これにより、Gradleは出力ファイルが存在し変更されていない場合にタスクの再実行をスキップします。

例: ファイルへの出力を設定する場合

import org.gradle.api.DefaultTask
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import java.io.File

open class WriteFileTask : DefaultTask() {

    @OutputFile
    lateinit var outputFile: File

    @TaskAction
    fun writeFile() {
        outputFile.writeText("This is a sample output.")
    }
}

// タスク登録時に出力ファイルを指定
tasks.register<WriteFileTask>("writeFile") {
    outputFile = file("output.txt")
}

入力と出力を組み合わせたタスク


入力ファイルの内容を読み取り、出力ファイルに書き込むタスクの例です。

import org.gradle.api.DefaultTask
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import java.io.File

open class CopyFileTask : DefaultTask() {

    @InputFile
    lateinit var sourceFile: File

    @OutputFile
    lateinit var targetFile: File

    @TaskAction
    fun copyFile() {
        targetFile.writeText(sourceFile.readText())
    }
}

// タスク登録時に入力ファイルと出力ファイルを指定
tasks.register<CopyFileTask>("copyFile") {
    sourceFile = file("input.txt")
    targetFile = file("output.txt")
}

入力と出力の活用ポイント

  • 再実行の最適化: 入力や出力が変更されていなければ、Gradleはタスクをスキップします。
  • 依存関係の管理: 入力と出力を明示することで、タスクの依存関係が明確になります。

入力と出力の設定を活用することで、ビルドパフォーマンスの向上や効率的なタスク管理が可能になります。

複数タスクの連携方法


Gradleでは、複数のタスクを連携させて順序通りに実行することが可能です。タスクの依存関係を定義することで、複数の処理を一連のワークフローとして構築できます。

タスク間の依存関係の設定


タスク間の依存関係は、dependsOnメソッドを使って定義します。これにより、あるタスクを実行する前に、他のタスクが必ず実行されるようになります。

例: 2つのタスクを連携させる

tasks.register("taskA") {
    doLast {
        println("Executing Task A")
    }
}

tasks.register("taskB") {
    dependsOn("taskA")
    doLast {
        println("Executing Task B")
    }
}

実行結果:

./gradlew taskB
> Task :taskA  
Executing Task A  
> Task :taskB  
Executing Task B  

この例では、taskBを実行する際に、taskAが先に実行されます。

複数タスクの順序指定


複数のタスクの順序を指定するには、mustRunAfterfinalizedByメソッドを使用します。

  • mustRunAfter: 指定したタスクが前に実行されることを保証しますが、依存関係は作成しません。
  • finalizedBy: 指定したタスクの後に必ず実行するタスクを定義します。

例: 複数の順序指定

tasks.register("taskC") {
    doLast {
        println("Executing Task C")
    }
}

tasks.register("taskD") {
    mustRunAfter("taskC")
    doLast {
        println("Executing Task D")
    }
}

tasks.register("taskE") {
    finalizedBy("taskD")
    doLast {
        println("Executing Task E")
    }
}

実行結果:

./gradlew taskE
> Task :taskE  
Executing Task E  
> Task :taskD  
Executing Task D  

タスクグループの作成


タスクをグループ化することで、関連するタスクをまとめて実行できます。

tasks.register("cleanBuild") {
    group = "build"
    dependsOn("clean", "build")
    doLast {
        println("Clean and build completed.")
    }
}

実行コマンド:

./gradlew cleanBuild

カスタムタスクを連携した実用例


以下は、複数のカスタムタスクを連携して、ビルド前にクリーンアップ、処理、結果の出力を行う例です。

open class CleanTask : DefaultTask() {
    @TaskAction
    fun clean() {
        println("Cleaning project...")
    }
}

open class ProcessTask : DefaultTask() {
    @TaskAction
    fun process() {
        println("Processing files...")
    }
}

open class OutputTask : DefaultTask() {
    @TaskAction
    fun output() {
        println("Generating output...")
    }
}

tasks.register<CleanTask>("cleanTask")
tasks.register<ProcessTask>("processTask") {
    dependsOn("cleanTask")
}
tasks.register<OutputTask>("outputTask") {
    dependsOn("processTask")
}

実行結果:

./gradlew outputTask
> Task :cleanTask  
Cleaning project...  
> Task :processTask  
Processing files...  
> Task :outputTask  
Generating output...  

まとめ


タスクの依存関係や順序指定をうまく活用することで、複雑なビルドや処理フローを効率的に管理できます。これにより、プロジェクトの自動化と生産性向上が実現できます。

実用的なカスタムタスク例


KotlinでGradleのカスタムタスクを作成する場合、実際のプロジェクトで役立つタスクを知っておくと効率的です。ここでは、いくつかの実用的なカスタムタスクの例を紹介します。


1. ファイルの一括リネームタスク


特定のディレクトリ内のファイルを一括でリネームするカスタムタスクです。

import org.gradle.api.DefaultTask
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.TaskAction
import java.io.File

open class RenameFilesTask : DefaultTask() {
    @InputDirectory
    lateinit var targetDir: File

    @TaskAction
    fun renameFiles() {
        targetDir.listFiles()?.forEach { file ->
            if (file.isFile) {
                val newName = "renamed_${file.name}"
                file.renameTo(File(targetDir, newName))
                println("Renamed: ${file.name} -> $newName")
            }
        }
    }
}

// タスクの登録
tasks.register<RenameFilesTask>("renameFiles") {
    targetDir = file("src/files")
}

実行コマンド:

./gradlew renameFiles

2. HTMLレポート生成タスク


ビルド情報や解析結果をHTMLレポートとして出力するタスクです。

import org.gradle.api.DefaultTask
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import java.io.File

open class GenerateReportTask : DefaultTask() {
    @OutputFile
    lateinit var reportFile: File

    @TaskAction
    fun generateReport() {
        val content = """
            <html>
                <head><title>Build Report</title></head>
                <body>
                    <h1>Build Successful</h1>
                    <p>Generated on: ${java.time.LocalDateTime.now()}</p>
                </body>
            </html>
        """.trimIndent()
        reportFile.writeText(content)
        println("Report generated at: ${reportFile.absolutePath}")
    }
}

// タスクの登録
tasks.register<GenerateReportTask>("generateReport") {
    reportFile = file("build/reports/buildReport.html")
}

実行コマンド:

./gradlew generateReport

3. 重複ファイル検出タスク


指定したディレクトリ内で重複するファイルを検出するタスクです。

import org.gradle.api.DefaultTask
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.TaskAction
import java.io.File

open class FindDuplicateFilesTask : DefaultTask() {
    @InputDirectory
    lateinit var targetDir: File

    @TaskAction
    fun findDuplicates() {
        val fileMap = mutableMapOf<String, MutableList<String>>()

        targetDir.walk().forEach { file ->
            if (file.isFile) {
                val content = file.readText()
                fileMap.computeIfAbsent(content) { mutableListOf() }.add(file.absolutePath)
            }
        }

        fileMap.filter { it.value.size > 1 }.forEach { (content, files) ->
            println("Duplicate files found:")
            files.forEach { println(it) }
        }
    }
}

// タスクの登録
tasks.register<FindDuplicateFilesTask>("findDuplicates") {
    targetDir = file("src/files")
}

実行コマンド:

./gradlew findDuplicates

4. 依存関係一覧表示タスク


プロジェクトの依存関係を一覧表示するタスクです。

import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction

open class ListDependenciesTask : DefaultTask() {
    @TaskAction
    fun listDependencies() {
        project.configurations.forEach { config ->
            println("Configuration: ${config.name}")
            config.dependencies.forEach { dep ->
                println(" - ${dep.group}:${dep.name}:${dep.version}")
            }
        }
    }
}

// タスクの登録
tasks.register<ListDependenciesTask>("listDependencies")

実行コマンド:

./gradlew listDependencies

まとめ


これらのカスタムタスクは、日常的なプロジェクト管理やビルド処理の効率化に役立ちます。ファイル操作やレポート生成、依存関係管理など、GradleとKotlinの組み合わせで柔軟に自動化できます。

依存関係とタスクの順序管理


Gradleでは、タスク間の依存関係や実行順序を明確に設定することで、ビルドフローを効率的に管理できます。Kotlinでカスタムタスクを作成する際に、依存関係や順序の管理は欠かせません。


タスク依存関係の定義


タスクの依存関係はdependsOnメソッドを使用して定義します。これにより、あるタスクが実行される前に他のタスクが実行されることが保証されます。

例: 依存関係を設定する

tasks.register("compile") {
    doLast {
        println("Compiling source code...")
    }
}

tasks.register("test") {
    dependsOn("compile")
    doLast {
        println("Running tests...")
    }
}

tasks.register("package") {
    dependsOn("test")
    doLast {
        println("Packaging application...")
    }
}

実行コマンド:

./gradlew package

実行結果:

> Task :compile  
Compiling source code...  
> Task :test  
Running tests...  
> Task :package  
Packaging application...

この例では、packageタスクを実行すると、compiletestタスクが順番に実行されます。


タスクの実行順序を制御する


依存関係を作成せずにタスクの実行順序だけを制御したい場合、mustRunAftershouldRunAfterを使用します。

  • mustRunAfter: 順序を強制します。
  • shouldRunAfter: 順序を推奨しますが、強制はしません。

例: タスクの順序指定

tasks.register("taskA") {
    doLast {
        println("Executing Task A")
    }
}

tasks.register("taskB") {
    mustRunAfter("taskA")
    doLast {
        println("Executing Task B")
    }
}

tasks.register("taskC") {
    shouldRunAfter("taskB")
    doLast {
        println("Executing Task C")
    }
}

実行コマンド:

./gradlew taskB taskA taskC

実行結果:

> Task :taskA  
Executing Task A  
> Task :taskB  
Executing Task B  
> Task :taskC  
Executing Task C

タスク完了後に別のタスクを実行する


タスクが完了した後に別のタスクを実行したい場合、finalizedByを使用します。

例: finalizedByを使ったタスク連携

tasks.register("build") {
    doLast {
        println("Building project...")
    }
}

tasks.register("cleanup") {
    doLast {
        println("Cleaning up temporary files...")
    }
}

// buildタスクが完了したらcleanupタスクを実行
tasks.named("build") {
    finalizedBy("cleanup")
}

実行コマンド:

./gradlew build

実行結果:

> Task :build  
Building project...  
> Task :cleanup  
Cleaning up temporary files...

依存関係の可視化


Gradleでは、依存関係を可視化することでビルドの流れを確認できます。

依存関係ツリーを表示するコマンド:

./gradlew tasks --all

まとめ

  • dependsOn: タスクの依存関係を定義します。
  • mustRunAfter/shouldRunAfter: タスクの実行順序を制御します。
  • finalizedBy: タスク完了後に実行するタスクを設定します。

これらの手法を使うことで、効率的なビルドフローとタスク管理が可能になります。

デバッグとエラー処理


Gradleでカスタムタスクを作成する際、デバッグやエラー処理を適切に行うことで、ビルドの安定性と効率を向上させることができます。ここでは、Gradleカスタムタスクのデバッグ手法やエラー処理について解説します。


デバッグの基本手法


Gradleビルドやカスタムタスクをデバッグするためには、以下の方法が有効です。

1. 詳細出力の有効化


Gradleの詳細出力(--info--debugオプション)を使うことで、ビルドの内部処理を確認できます。

コマンド例:

./gradlew myTask --info
./gradlew myTask --debug
  • --info: 情報レベルのログを表示します。
  • --debug: デバッグレベルの詳細なログを表示します。

2. ログ出力の追加


タスクの処理にloggerを追加することで、実行時の状況を確認できます。

import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction

open class DebugTask : DefaultTask() {
    @TaskAction
    fun execute() {
        logger.lifecycle("Lifecycle log message")
        logger.info("Info log message")
        logger.debug("Debug log message")
    }
}

// タスクの登録
tasks.register<DebugTask>("debugTask")

実行コマンド:

./gradlew debugTask --debug

エラー処理の実装


カスタムタスクにエラー処理を組み込むことで、予期しないエラーが発生しても適切に対応できます。

1. 例外処理を追加する


try-catchブロックを使ってエラーをキャッチし、エラーメッセージを表示できます。

import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction

open class ErrorHandlingTask : DefaultTask() {
    @TaskAction
    fun execute() {
        try {
            val result = 10 / 0  // ゼロ除算エラー
            println("Result: $result")
        } catch (e: ArithmeticException) {
            logger.error("An error occurred: ${e.message}")
        }
    }
}

// タスクの登録
tasks.register<ErrorHandlingTask>("errorHandlingTask")

実行コマンド:

./gradlew errorHandlingTask

出力結果:

> Task :errorHandlingTask FAILED  
An error occurred: / by zero

2. タスクを失敗としてマークする


エラーが発生した際にタスクを失敗として終了させるには、throw GradleExceptionを使用します。

import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.tasks.TaskAction

open class FailOnErrorTask : DefaultTask() {
    @TaskAction
    fun execute() {
        val condition = false
        if (!condition) {
            throw GradleException("The condition was not met. Failing the task.")
        }
    }
}

// タスクの登録
tasks.register<FailOnErrorTask>("failOnErrorTask")

実行コマンド:

./gradlew failOnErrorTask

出力結果:

> Task :failOnErrorTask FAILED  
The condition was not met. Failing the task.

デバッグ時のよくあるエラーと対処法

  1. クラス未定義エラー
  • エラー内容: ClassNotFoundException
  • 対処法: プラグインや依存関係が正しく適用されているか確認します。
  1. ファイルパス関連エラー
  • エラー内容: FileNotFoundException
  • 対処法: 入力ファイルや出力ディレクトリが正しいか確認します。
  1. 依存関係の競合
  • エラー内容: DependencyResolutionException
  • 対処法: gradle dependenciesで依存関係のツリーを確認し、競合するバージョンを修正します。

まとめ


デバッグとエラー処理を適切に行うことで、Gradleカスタムタスクの信頼性が向上します。詳細出力やログの活用、例外処理を組み込むことで、問題の早期発見と修正が可能になります。

まとめ


本記事では、Kotlinを使ったGradleカスタムタスクの作成方法について詳しく解説しました。Gradleタスクの基本構造から、入力・出力の設定、タスク間の依存関係と順序管理、実用的なカスタムタスクの例、デバッグとエラー処理まで幅広くカバーしました。

カスタムタスクを適切に活用することで、ビルドプロセスの自動化、効率化、柔軟性を高めることができます。GradleとKotlinを組み合わせれば、型安全で読みやすいタスクを作成でき、複雑なビルド要件にも対応可能です。

今後は、これらの知識を活用し、プロジェクトに合ったカスタムタスクを設計・実装して、開発効率をさらに向上させてください。

コメント

コメントする

目次