O Gradle e o sistema de build padrao para projetos Kotlin, tanto no Android quanto no backend. Com a adocao do Kotlin DSL nos scripts de build (build.gradle.kts em vez de build.gradle), a configuracao ganha autocomplete, type safety e toda a expressividade do Kotlin. Compreender o Gradle profundamente e essencial para qualquer desenvolvedor Kotlin, pois ele controla compilacao, dependencias, testes, empacotamento e publicacao. Neste guia, vamos desde a estrutura basica ate configuracoes avancadas com version catalogs, convention plugins e otimizacao de performance de build.

Gradle com Kotlin DSL: Por Que Migrar

O Kotlin DSL oferece vantagens significativas sobre o Groovy DSL tradicional. Autocomplete no IDE funciona perfeitamente, erros de sintaxe sao detectados em tempo de compilacao e a refatoracao e segura. A configuracao se torna codigo Kotlin real, com tipagem forte e navegacao de codigo.

// build.gradle.kts - Kotlin DSL (recomendado)
plugins {
    kotlin("jvm") version "1.9.22"
    application
}

group = "com.exemplo"
version = "1.0.0"

application {
    mainClass.set("com.exemplo.MainKt")
}

repositories {
    mavenCentral()
}

dependencies {
    implementation("io.ktor:ktor-server-core:2.3.7")
    testImplementation(kotlin("test"))
}

tasks.test {
    useJUnitPlatform()
}

Estrutura de um Projeto Multi-Modulo

Projetos maiores se beneficiam de multiplos modulos Gradle:

// settings.gradle.kts
rootProject.name = "meu-projeto"

include(":app")
include(":core:domain")
include(":core:data")
include(":core:network")
include(":feature:produtos")
include(":feature:pedidos")

// Configuracao de resolucao de plugins
pluginManagement {
    repositories {
        google()
        mavenCentral()
        gradlePluginPortal()
    }
}

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
    }
}

Version Catalogs

O Version Catalog centraliza todas as versoes de dependencias em um unico arquivo libs.versions.toml:

// gradle/libs.versions.toml
[versions]
kotlin = "1.9.22"
ktor = "2.3.7"
exposed = "0.46.0"
koin = "3.5.3"
coroutines = "1.7.3"
logback = "1.4.14"
junit = "5.10.1"
mockk = "1.13.9"

[libraries]
ktor-server-core = { module = "io.ktor:ktor-server-core-jvm", version.ref = "ktor" }
ktor-server-netty = { module = "io.ktor:ktor-server-netty-jvm", version.ref = "ktor" }
ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation-jvm", version.ref = "ktor" }
ktor-serialization-json = { module = "io.ktor:ktor-serialization-kotlinx-json-jvm", version.ref = "ktor" }

exposed-core = { module = "org.jetbrains.exposed:exposed-core", version.ref = "exposed" }
exposed-dao = { module = "org.jetbrains.exposed:exposed-dao", version.ref = "exposed" }
exposed-jdbc = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "exposed" }

koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
koin-ktor = { module = "io.insert-koin:koin-ktor", version.ref = "koin" }

coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }

junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" }
mockk = { module = "io.mockk:mockk", version.ref = "mockk" }

[bundles]
ktor-server = ["ktor-server-core", "ktor-server-netty", "ktor-server-content-negotiation", "ktor-serialization-json"]
exposed = ["exposed-core", "exposed-dao", "exposed-jdbc"]
testing = ["junit-jupiter", "mockk", "coroutines-test"]

[plugins]
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
ktor = { id = "io.ktor.plugin", version.ref = "ktor" }

Usando no build.gradle.kts:

plugins {
    alias(libs.plugins.kotlin.jvm)
    alias(libs.plugins.kotlin.serialization)
    alias(libs.plugins.ktor)
}

dependencies {
    implementation(libs.bundles.ktor.server)
    implementation(libs.bundles.exposed)
    implementation(libs.koin.core)
    implementation(libs.koin.ktor)
    implementation(libs.coroutines.core)

    testImplementation(libs.bundles.testing)
}

Convention Plugins

Convention plugins permitem compartilhar configuracao entre modulos sem duplicacao:

// buildSrc/build.gradle.kts
plugins {
    `kotlin-dsl`
}

repositories {
    mavenCentral()
}

// buildSrc/src/main/kotlin/kotlin-library-conventions.gradle.kts
plugins {
    kotlin("jvm")
}

group = "com.exemplo"

kotlin {
    jvmToolchain(17)
}

tasks.test {
    useJUnitPlatform()
}

dependencies {
    testImplementation("org.junit.jupiter:junit-jupiter:5.10.1")
    testImplementation("io.mockk:mockk:1.13.9")
}

// Uso em qualquer modulo
// core/domain/build.gradle.kts
plugins {
    id("kotlin-library-conventions")
}

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
}

Tasks Customizadas

O Gradle permite criar tasks personalizadas em Kotlin:

// Task simples
tasks.register("limparLogs") {
    group = "manutencao"
    description = "Remove arquivos de log antigos"

    doLast {
        val logDir = file("logs")
        if (logDir.exists()) {
            logDir.listFiles()
                ?.filter { it.extension == "log" }
                ?.forEach { it.delete() }
            println("Logs removidos com sucesso")
        }
    }
}

// Task tipada com inputs e outputs
abstract class GerarRelatorioTask : DefaultTask() {

    @get:InputDirectory
    abstract val sourceDir: DirectoryProperty

    @get:OutputFile
    abstract val reportFile: RegularFileProperty

    @TaskAction
    fun gerar() {
        val fontes = sourceDir.get().asFile.walkTopDown()
            .filter { it.extension == "kt" }
            .toList()

        val relatorio = buildString {
            appendLine("Relatorio do Projeto")
            appendLine("Total de arquivos Kotlin: ${fontes.size}")
            appendLine("Total de linhas: ${fontes.sumOf { it.readLines().size }}")
        }

        reportFile.get().asFile.writeText(relatorio)
    }
}

tasks.register<GerarRelatorioTask>("gerarRelatorio") {
    sourceDir.set(file("src/main/kotlin"))
    reportFile.set(file("build/relatorio.txt"))
}

Configuracao para Android

Projetos Android possuem configuracoes especificas:

// build.gradle.kts (app)
plugins {
    id("com.android.application")
    kotlin("android")
    kotlin("plugin.serialization")
    id("com.google.dagger.hilt.android")
    kotlin("kapt")
}

android {
    namespace = "com.exemplo.app"
    compileSdk = 34

    defaultConfig {
        applicationId = "com.exemplo.app"
        minSdk = 24
        targetSdk = 34
        versionCode = 1
        versionName = "1.0.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            isMinifyEnabled = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
        debug {
            isDebuggable = true
            applicationIdSuffix = ".debug"
        }
    }

    buildFeatures {
        compose = true
        buildConfig = true
    }

    composeOptions {
        kotlinCompilerExtensionVersion = "1.5.8"
    }

    kotlinOptions {
        jvmTarget = "17"
    }
}

Otimizacao de Performance do Build

Builds lentos prejudicam a produtividade. Configure o gradle.properties para otimizar:

// gradle.properties
org.gradle.jvmargs=-Xmx4g -XX:+UseParallelGC
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.configuration-cache=true
kotlin.incremental=true
kotlin.caching.enabled=true

// Para projetos Android
android.useAndroidX=true
android.nonTransitiveRClass=true

Boas Praticas com Gradle e Kotlin

  • Use Kotlin DSL: migre de Groovy para Kotlin DSL para obter type safety e autocomplete.
  • Centralize versoes com Version Catalogs: elimina inconsistencias de versoes entre modulos.
  • Crie convention plugins: evita duplicacao de configuracao em projetos multi-modulo.
  • Configure cache e builds paralelos: melhora significativamente o tempo de build.
  • Minimize dependencias transitivas: use implementation em vez de api quando a dependencia nao precisa ser exposta ao consumidor.
  • Atualize o Gradle regularmente: cada versao traz melhorias de performance e novos recursos.
  • Use buildSrc ou composite builds: para logica de build complexa e compartilhada.

Erros Comuns e Armadilhas

  • Confundir implementation e api: api expoe a dependencia aos consumidores do modulo, aumentando o tempo de compilacao. Use implementation por padrao.
  • Nao configurar cache: sem cache, builds incrementais nao aproveitam resultados anteriores.
  • Version conflicts: dependencias transitivas podem trazer versoes incompativeis. Use resolutionStrategy ou constraints para resolver.
  • buildSrc invalida todo o cache: qualquer mudanca no buildSrc recompila todo o projeto. Para projetos grandes, prefira composite builds.
  • Ignorar warnings de depreciacao: warnings no Gradle frequentemente antecedem breaking changes em versoes futuras. Resolva-os proativamente.

Conclusao e Proximos Passos

Dominar o Gradle e tao importante quanto dominar a linguagem Kotlin em si. Uma configuracao de build bem estruturada acelera o desenvolvimento, facilita a colaboracao e prepara o projeto para CI/CD. Explore nossos guias sobre CI/CD e Docker para integrar o Gradle em pipelines de deploy automatizado e leve a automacao do seu projeto ao proximo nivel.