W3docs

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:

PluginO que adiciona
javacompileJava, test, jar, source sets, as configurações de dependencies
applicationrun e uma distribuição empacotada com scripts de inicialização
java-libraryA distinção api vs implementation para bibliotecas
org.springframework.bootbootJar, bootRun para aplicações Spring Boot
jacocoRelató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.

java— editable, runs on the server

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. compileJava e processResources vêm antes de classes, que vem antes de jar e test, que vêm antes de build — exatamente a ordem que o Gradle imprime com o prefixo :taskName, pois uma tarefa só pode ser executada depois que tudo de que ela dependsOn tiver terminado.
  • Um diamante no grafo executa um pré-requisito compartilhado uma vez, não duas. Tanto jar quanto test dependem de classes, mas classes aparece apenas uma vez na ordem — o conjunto done é 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, processResources e classes estão UP-TO-DATE e são ignoradas, portanto apenas 3 tarefas 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ões 2.0.9 (direta) e 1.7.36 (transitiva via guava), mas o classpath resolvido a lista uma única vez em 2.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.7 como se fosse lida do gradle-wrapper.properties. Em um projeto real, o wrapper armazena essa versão no controle de versão, de modo que ./gradlew build usa o mesmo Gradle para todos — o build é reproduzível independentemente do que esteja instalado na máquina.

Prática

Prática
Um projeto Java com Gradle declara 'org.slf4j:slf4j-api:2.0.9' diretamente, enquanto uma dependência transitiva puxa 'org.slf4j:slf4j-api:1.7.36'. Com a estratégia de resolução padrão do Gradle, o que fica no classpath?
Um projeto Java com Gradle declara 'org.slf4j:slf4j-api:2.0.9' diretamente, enquanto uma dependência transitiva puxa 'org.slf4j:slf4j-api:1.7.36'. Com a estratégia de resolução padrão do Gradle, o que fica no classpath?
Was this page helpful?