Gradle

ビルドツールJavaKotlinDSL依存関係管理プロジェクト管理GroovyDSLKotlinDSL

ビルドツール

Gradle

概要

Gradleは、Javaエコシステムを中心とした強力なビルド自動化ツールです。2008年にHans Dockter氏とAdam Murdoch氏によって開発され、Apache AntとApache Mavenの制約を解決するために設計されました。GradleはGroovy DSLやKotlin DSLを使用してビルドスクリプトを記述し、宣言的な構成と命令的なロジックの両方をサポートします。増分ビルド、ビルドキャッシュ、並列実行などの高度な最適化機能により、大規模プロジェクトでも高速なビルドを実現します。Android開発の標準ビルドツールとしても採用され、現在はJava、Kotlin、Scala、C++、Swift等の多言語開発に対応しています。

詳細

主要機能

  • DSLベース設定: GroovyまたはKotlin DSLによる柔軟なビルドスクリプト
  • 高性能ビルド: 増分ビルド、ビルドキャッシュ、並列実行による最適化
  • 依存関係管理: 強力な依存関係解決とコンフリクト処理
  • マルチプロジェクト: 大規模なマルチモジュールプロジェクトサポート
  • プラグインエコシステム: 豊富なコミュニティプラグインと公式プラグイン
  • IDE統合: IntelliJ IDEA、Eclipse、Android Studioでの優れたサポート
  • 多言語対応: Java、Kotlin、Scala、C++、Swift、JavaScript等

アーキテクチャ

タスクベースの実行エンジン、DAG(有向非環グラフ)による依存関係管理、Gradleデーモンによる高速化、設定フェーズと実行フェーズの分離。

エコシステム

Gradle Plugin Portal、Android Gradle Plugin、Spring Boot Gradle Plugin、Kotlin Gradle Plugin、豊富なサードパーティプラグイン群。

メリット・デメリット

メリット

  • 高いパフォーマンス: 増分ビルドとキャッシュによる高速化
  • 柔軟性: 命令的ロジックと宣言的設定の両方をサポート
  • Kotlin DSL: 型安全なビルドスクリプト作成
  • Android標準: Android開発のデファクトスタンダード
  • 拡張性: プラグインによる高い拡張性
  • 多言語サポート: Java以外の言語もサポート
  • モダンなアーキテクチャ: 最新のビルドシステム設計

デメリット

  • 学習曲線: DSLとGradleの概念理解が必要
  • 複雑性: 大規模プロジェクトでは設定が複雑化
  • メモリ使用量: Mavenより多くのメモリを消費
  • ビルドスクリプトデバッグ: DSLのデバッグが困難な場合
  • バージョン互換性: Gradleバージョン間の互換性問題
  • 初期化時間: 初回起動時のコールドスタート時間

参考ページ

書き方の例

インストールとプロジェクトセットアップ

# Gradle インストール確認
gradle --version

# 新しいGradleプロジェクト作成
gradle init

# プロジェクトタイプ選択(対話式)
# 1: basic
# 2: application
# 3: library
# 4: Gradle plugin

# 手動プロジェクト作成
mkdir my-gradle-project
cd my-gradle-project

# Gradle Wrapper 生成
gradle wrapper

# Wrapperを使用したビルド(推奨)
./gradlew build
./gradlew test
./gradlew clean
./gradlew tasks --all    # 利用可能なタスク一覧

# ビルド情報表示
./gradlew projects
./gradlew dependencies
./gradlew properties

基本的なbuild.gradle(Groovy DSL)

plugins {
    id 'java'
    id 'application'
}

// プロジェクト情報
group = 'com.example'
version = '1.0.0'

// Java設定
java {
    sourceCompatibility = JavaVersion.VERSION_17
    targetCompatibility = JavaVersion.VERSION_17
}

// アプリケーション設定
application {
    mainClass = 'com.example.Main'
}

// リポジトリ設定
repositories {
    mavenCentral()
    mavenLocal()
    google()
    gradlePluginPortal()
}

// 依存関係
dependencies {
    // コンパイル時依存関係
    implementation 'org.apache.commons:commons-lang3:3.12.0'
    implementation 'com.google.guava:guava:31.1-jre'
    implementation 'org.slf4j:slf4j-api:2.0.6'
    
    // ランタイム依存関係
    runtimeOnly 'ch.qos.logback:logback-classic:1.4.6'
    
    // テスト依存関係
    testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
    testImplementation 'org.mockito:mockito-core:5.1.1'
    testImplementation 'org.assertj:assertj-core:3.24.2'
    
    // Annotation processor
    annotationProcessor 'org.projectlombok:lombok:1.18.26'
    compileOnly 'org.projectlombok:lombok:1.18.26'
}

// テスト設定
test {
    useJUnitPlatform()
    
    testLogging {
        events "passed", "skipped", "failed"
        exceptionFormat "full"
    }
    
    // テストカバレッジ
    finalizedBy jacocoTestReport
}

// JaCoCo設定
jacoco {
    toolVersion = "0.8.8"
}

jacocoTestReport {
    dependsOn test
    reports {
        xml.required = true
        html.required = true
    }
}

// JAR設定
jar {
    manifest {
        attributes(
            'Implementation-Title': project.name,
            'Implementation-Version': project.version,
            'Main-Class': application.mainClass
        )
    }
    
    // Fat JAR作成
    from {
        configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
    }
    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}

// カスタムタスク
task copyDocs(type: Copy) {
    description = 'ドキュメントファイルをコピー'
    group = 'documentation'
    
    from 'src/docs'
    into 'build/docs'
    include '**/*.md'
}

// プロパティファイル処理
processResources {
    expand(project.properties)
}

build.gradle.kts(Kotlin DSL)

plugins {
    java
    application
    id("org.springframework.boot") version "3.1.0"
    id("io.spring.dependency-management") version "1.1.0"
    id("org.jetbrains.kotlin.jvm") version "1.8.21"
    id("org.jetbrains.kotlin.plugin.spring") version "1.8.21"
}

group = "com.example"
version = "1.0.0"

java {
    sourceCompatibility = JavaVersion.VERSION_17
}

application {
    mainClass.set("com.example.ApplicationKt")
}

repositories {
    mavenCentral()
}

dependencies {
    // Spring Boot
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    implementation("org.springframework.boot:spring-boot-starter-security")
    implementation("org.springframework.boot:spring-boot-starter-validation")
    
    // Kotlin
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    
    // データベース
    runtimeOnly("com.h2database:h2")
    runtimeOnly("org.postgresql:postgresql")
    
    // テスト
    testImplementation("org.springframework.boot:spring-boot-starter-test") {
        exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
    }
    testImplementation("org.springframework.security:spring-security-test")
    testImplementation("io.kotest:kotest-runner-junit5:5.5.5")
    testImplementation("io.kotest:kotest-assertions-core:5.5.5")
    testImplementation("io.mockk:mockk:1.13.4")
}

tasks {
    test {
        useJUnitPlatform()
    }
    
    withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
        kotlinOptions {
            freeCompilerArgs = listOf("-Xjsr305=strict")
            jvmTarget = "17"
        }
    }
    
    bootJar {
        archiveFileName.set("${project.name}-${project.version}.jar")
    }
    
    register<Copy>("generateConfig") {
        description = "設定ファイル生成"
        group = "build setup"
        
        from("src/main/templates")
        into("src/main/resources")
        
        expand(project.properties)
        filteringCharset = "UTF-8"
    }
}

// 開発用タスク
tasks.register("dev") {
    description = "開発サーバー起動"
    group = "application"
    
    dependsOn("bootRun")
    doFirst {
        println("開発サーバーを起動しています...")
    }
}

マルチプロジェクト設定

// settings.gradle
rootProject.name = 'multi-project-example'

include 'core'
include 'web'
include 'data'
include 'common'

// プロジェクト構造を定義
project(':core').projectDir = file('modules/core')
project(':web').projectDir = file('modules/web')
project(':data').projectDir = file('modules/data')
project(':common').projectDir = file('modules/common')
// ルートプロジェクトのbuild.gradle
plugins {
    id 'java'
    id 'org.springframework.boot' version '3.1.0' apply false
    id 'io.spring.dependency-management' version '1.1.0' apply false
}

// 全サブプロジェクトに適用
allprojects {
    group = 'com.example.multiproject'
    version = '1.0.0'
    
    repositories {
        mavenCentral()
    }
}

// サブプロジェクトのみに適用
subprojects {
    apply plugin: 'java'
    apply plugin: 'io.spring.dependency-management'
    
    java {
        sourceCompatibility = JavaVersion.VERSION_17
    }
    
    dependencies {
        // 共通依存関係
        implementation 'org.slf4j:slf4j-api'
        testImplementation 'org.junit.jupiter:junit-jupiter'
    }
    
    test {
        useJUnitPlatform()
    }
}

// プロジェクト間依存関係
project(':web') {
    apply plugin: 'org.springframework.boot'
    
    dependencies {
        implementation project(':core')
        implementation project(':data')
        implementation project(':common')
        
        implementation 'org.springframework.boot:spring-boot-starter-web'
    }
}

project(':core') {
    dependencies {
        implementation project(':common')
        implementation project(':data')
    }
}

project(':data') {
    dependencies {
        implementation project(':common')
        implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    }
}

project(':common') {
    dependencies {
        implementation 'org.apache.commons:commons-lang3'
    }
}

カスタムプラグインとタスク

// カスタムタスククラス
class CustomBuildTask extends DefaultTask {
    @Input
    String inputMessage = "Default message"
    
    @OutputFile
    File outputFile
    
    @TaskAction
    void execute() {
        println "Executing custom build task: ${inputMessage}"
        
        if (!outputFile.parentFile.exists()) {
            outputFile.parentFile.mkdirs()
        }
        
        outputFile.text = """
Build executed at: ${new Date()}
Message: ${inputMessage}
Project: ${project.name}
Version: ${project.version}
"""
        
        logger.lifecycle("Custom build completed: ${outputFile.absolutePath}")
    }
}

// カスタムプラグイン
class CustomBuildPlugin implements Plugin<Project> {
    void apply(Project project) {
        // 拡張機能追加
        project.extensions.create('customBuild', CustomBuildExtension)
        
        // カスタムタスク追加
        project.tasks.register('customBuild', CustomBuildTask) {
            description = 'カスタムビルドタスクの実行'
            group = 'custom'
            
            inputMessage = project.customBuild.message
            outputFile = new File(project.buildDir, 'custom/build-info.txt')
        }
        
        // 既存タスクへの依存関係設定
        project.tasks.named('build') {
            dependsOn 'customBuild'
        }
    }
}

// 拡張機能クラス
class CustomBuildExtension {
    String message = "Hello from custom plugin"
    boolean enabled = true
    List<String> environments = ['dev', 'test', 'prod']
}

// プラグイン適用
apply plugin: CustomBuildPlugin

// プラグイン設定
customBuild {
    message = "Custom build for ${project.name}"
    enabled = true
    environments = ['development', 'staging', 'production']
}

// 複数環境向けタスク生成
customBuild.environments.each { env ->
    tasks.register("deploy${env.capitalize()}", Copy) {
        description = "${env}環境へのデプロイ"
        group = 'deployment'
        
        from 'build/libs'
        into "deploy/${env}"
        include '*.jar'
        
        doLast {
            println "Deployed to ${env} environment"
        }
    }
}

高度な設定とビルド最適化

// gradle.properties
# ビルド最適化
org.gradle.daemon=true
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.configureondemand=true

# JVM設定
org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=1g -XX:+UseG1GC

# Kotlin設定
kotlin.code.style=official
kotlin.incremental=true
kotlin.daemon.jvm.options=-Xmx2g
// 高度なbuild.gradle設定
plugins {
    id 'java'
    id 'jacoco'
    id 'checkstyle'
    id 'com.github.spotbugs' version '5.0.13'
    id 'org.sonarqube' version '4.0.0.2929'
}

// ビルドキャッシュ設定
buildCache {
    local {
        enabled = true
    }
    remote(HttpBuildCache) {
        url = 'https://example.com/build-cache/'
        enabled = true
        push = true
        credentials {
            username = findProperty('buildCacheUsername')
            password = findProperty('buildCachePassword')
        }
    }
}

// コード品質設定
checkstyle {
    toolVersion = '10.7.0'
    configFile = file("${rootDir}/config/checkstyle/checkstyle.xml")
    ignoreFailures = false
}

spotbugs {
    toolVersion = '4.7.3'
    effort = 'max'
    reportLevel = 'low'
    excludeFilter = file("${rootDir}/config/spotbugs/exclude.xml")
}

jacoco {
    toolVersion = '0.8.8'
}

jacocoTestReport {
    reports {
        xml.required = true
        html.required = true
        csv.required = false
    }
    
    afterEvaluate {
        classDirectories.setFrom(files(classDirectories.files.collect {
            fileTree(dir: it, exclude: [
                '**/Application.class',
                '**/config/**',
                '**/dto/**',
                '**/entity/**'
            ])
        }))
    }
}

// SonarQube設定
sonarqube {
    properties {
        property 'sonar.projectKey', 'my-project'
        property 'sonar.projectName', 'My Project'
        property 'sonar.host.url', 'https://sonarcloud.io'
        property 'sonar.organization', 'my-org'
        
        property 'sonar.java.coveragePlugin', 'jacoco'
        property 'sonar.coverage.jacoco.xmlReportPaths', 'build/reports/jacoco/test/jacocoTestReport.xml'
        property 'sonar.junit.reportPaths', 'build/test-results/test'
    }
}

// 環境別プロファイル
gradle.taskGraph.whenReady { taskGraph ->
    if (taskGraph.hasTask(':bootRun')) {
        // 開発環境設定
        bootRun {
            environment 'SPRING_PROFILES_ACTIVE', 'development'
            environment 'DEBUG', 'true'
        }
    }
}

// 条件付きタスク実行
if (project.hasProperty('env') && project.env == 'production') {
    jar {
        exclude '**/application-dev.yml'
        exclude '**/logback-spring.xml'
    }
}

// Gradle バージョンカタログ使用(Gradle 7.0+)
dependencyResolutionManagement {
    versionCatalogs {
        libs {
            library('spring-boot-starter-web', 'org.springframework.boot', 'spring-boot-starter-web').version('3.1.0')
            library('junit-jupiter', 'org.junit.jupiter', 'junit-jupiter').version('5.9.2')
            bundle('testing', ['junit-jupiter', 'mockito-core', 'assertj-core'])
        }
    }
}

dependencies {
    implementation libs.spring.boot.starter.web
    testImplementation libs.bundles.testing
}

プロファイルとタスク制御

# 環境別実行
./gradlew build -Penv=production
./gradlew bootRun -Pspring.profiles.active=dev

# タスク並列実行
./gradlew test jacocoTestReport --parallel

# キャッシュ制御
./gradlew build --build-cache
./gradlew clean --build-cache

# 詳細ログ
./gradlew build --info
./gradlew build --debug

# 特定タスクのみ実行
./gradlew :web:test
./gradlew compileJava compileTestJava

# 品質チェック実行
./gradlew check
./gradlew sonarqube

# プロジェクト情報
./gradlew dependencies --configuration compileClasspath
./gradlew dependencyInsight --dependency org.springframework.boot
./gradlew projects
./gradlew tasks --group build

# パフォーマンス解析
./gradlew build --profile
./gradlew build --scan

# ビルド継続実行
./gradlew build --continue

# オフライン実行
./gradlew build --offline