Script de Build Gradle para Java
Anatomia de um script de build Gradle para Java — plugins, dependências, tarefas e fundamentos do DSL.
Um script de build Gradle descreve como compilar, testar e empacotar um projeto — não como um documento XML rígido, mas como código. O Gradle lê um arquivo build.gradle (Groovy DSL) ou build.gradle.kts (Kotlin DSL), transforma as declarações em um grafo de tarefas e executa apenas as tarefas que seu comando precisa, na ordem correta. Enquanto o Maven oferece um ciclo de vida fixo, o Gradle oferece um programável: aplicar um plugin, declarar uma dependência e definir uma tarefa são instruções comuns em uma linguagem real.
Esta página pressupõe que você já sabe o que é o Gradle em alto nível — caso contrário, comece com a introdução ao Gradle. Aqui desmontamos um build.gradle real bloco por bloco: plugins, repositórios, dependências e tarefas.
A anatomia do build.gradle
Um script de build Java mínimo tem quatro blocos: quais plugins aplicar, onde buscar dependências, quais são essas dependências e um pouco de configuração. Veja um exemplo completo e idiomático no Kotlin DSL:
plugins {
java
application
}
group = "com.example"
version = "1.0.0"
repositories {
mavenCentral()
}
dependencies {
implementation("com.google.guava:guava:32.1.3-jre")
testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
}
application {
mainClass = "com.example.App"
}
tasks.test {
useJUnitPlatform()
}O plugin java sozinho fornece compileJava, test, jar e mais uma dúzia de tarefas gratuitamente. O plugin application adiciona run e installDist. A linha tasks.test { useJUnitPlatform() } configura a tarefa test para executar testes do JUnit 5 — sem ela, o Gradle usa o motor legado do JUnit 4 e não executa nada silenciosamente. Todo o restante é a configuração do que essas tarefas fazem.
Plugins, repositórios e o ciclo de vida do build
Quase nada no Gradle é embutido — as capacidades chegam como plugins. O plugin java é a base para o trabalho com JVM; outros se acumulam sobre ele:
| Plugin | O que adiciona |
|---|---|
java | compileJava, test, jar, source sets, as configurações de dependencies |
application | run e uma distribuição empacotada com scripts de inicialização |
java-library | A distinção api vs implementation para bibliotecas |
org.springframework.boot | bootJar, bootRun para aplicações Spring Boot |
jacoco | Relatório de cobertura de código integrado ao test |
repositories { } informa ao Gradle de onde baixar dependências — mavenCentral() é a escolha habitual. Sem um repositório, nenhuma dependência externa pode ser resolvida.
Declarando dependências e seus escopos
As dependências são declaradas com uma configuração que controla onde aparecem no classpath. Escolher a correta mantém seu classpath de compilação limpo e seus builds rápidos:
dependencies {
// On the compile and runtime classpath, but NOT exposed to consumers
implementation("org.apache.commons:commons-lang3:3.14.0")
// Part of this library's public API — leaks to consumers (java-library only)
api("com.google.guava:guava:32.1.3-jre")
// Needed to compile, but provided at runtime by the environment
compileOnly("org.projectlombok:lombok:1.18.30")
// Only on the test classpath
testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
// Only at runtime (e.g. a JDBC driver loaded by name)
runtimeOnly("org.postgresql:postgresql:42.7.1")
}O formato de coordenadas é group:name:version — as mesmas coordenadas que o Maven usa em seu pom.xml. Quando duas dependências exigem o mesmo módulo em versões diferentes, a estratégia padrão do Gradle coloca uma única versão mais alta no classpath — o exemplo executável abaixo modela exatamente isso.
Tarefas: a unidade de trabalho
Cada ação que o Gradle executa é uma tarefa, e as tarefas declaram dependências entre si. Executar gradle build não faz uma coisa só; ele percorre um grafo e executa cada pré-requisito uma vez. Você também pode definir as suas próprias:
tasks.register("printVersion") {
group = "help"
description = "Prints the project version."
doLast {
println("Project version is $version")
}
}
// Make the jar task wait for our custom task
tasks.named("jar") {
dependsOn("printVersion")
}Dois outros fatos tornam o Gradle rápido. Primeiro, ele é incremental: uma tarefa cujas entradas e saídas não foram alteradas é reportada como UP-TO-DATE e ignorada. Segundo, o Gradle Wrapper (./gradlew, respaldado por gradle/wrapper/gradle-wrapper.properties) fixa uma versão do Gradle por projeto, de modo que cada desenvolvedor e máquina de CI faz o build com o mesmo toolchain — você nunca instala o Gradle globalmente.
Um exemplo prático: um build modelado em Java puro
O Gradle em si não está disponível neste executor, então o programa abaixo modela as três ideias que fazem um script de build funcionar — o grafo de tarefas e sua ordem de execução, o incremental com pulo de tarefas atualizadas, e a resolução de conflitos de versão de dependências — usando apenas o JDK. Este é o modelo mental que gradle build executa de verdade.
O que aprender com a execução:
- A lista de tarefas para
gradle buildé computada por uma ordenação topológica, não escrita manualmente.compileJavaeprocessResourcesvêm antes declasses, que vem antes dejaretest, que vêm antes debuild— exatamente a ordem que o Gradle imprime com o prefixo:taskName, pois uma tarefa só pode ser executada depois que tudo de que eladependsOntiver terminado. - Um diamante no grafo executa um pré-requisito compartilhado uma vez, não duas. Tanto
jarquantotestdependem declasses, masclassesaparece apenas uma vez na ordem — o conjuntodoneé o que impede o Gradle de recompilar o mesmo código para cada tarefa downstream. - A segunda execução mostra o comportamento incremental do Gradle:
compileJava,processResourceseclassesestãoUP-TO-DATEe são ignoradas, portanto apenas3tarefas são de fato executadas. É por isso que um projeto sem alterações reconstrói em milissegundos — o Gradle compara entradas e saídas das tarefas e não faz trabalho que pode evitar. - A resolução de dependências colapsa um conflito de versão para um único vencedor:
slf4j-apié solicitado nas versões2.0.9(direta) e1.7.36(transitiva via guava), mas o classpath resolvido a lista uma única vez em2.0.9. A estratégia padrão do Gradle é a versão mais alta vence, portanto um único jar consistente acaba no classpath em vez de duas cópias conflitantes. - A linha final nomeia a versão do Gradle
8.7como se fosse lida dogradle-wrapper.properties. Em um projeto real, o wrapper armazena essa versão no controle de versão, de modo que./gradlew buildusa o mesmo Gradle para todos — o build é reproduzível independentemente do que esteja instalado na máquina.