Usando Gradle para criar um jar com dependências


122

Eu tenho uma compilação de multiprojetos e coloquei uma tarefa para criar um jar de gordura em um dos subprojetos. Criei a tarefa semelhante à descrita no livro de receitas .

jar {
  from configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
  manifest { attributes 'Main-Class': 'com.benmccann.gradle.test.WebServer' }
}

A execução resulta no seguinte erro:

Causa: você não pode alterar uma configuração que não esteja no estado não resolvido!

Não sei ao certo o que esse erro significa. Eu também relatei isso no Gradle JIRA, caso seja um bug .

Respostas:


195

Atualização: nas versões mais recentes do Gradle (4+), o compilequalificador é descontinuado em favor do novo apie das implementationconfigurações. Se você os usar, o seguinte deve funcionar para você:

// Include dependent libraries in archive.
mainClassName = "com.company.application.Main"

jar {
  manifest { 
    attributes "Main-Class": "$mainClassName"
  }  

  from {
    configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
  }
}

Para versões antigas do gradle, ou se você ainda usa o qualificador "compilar" para suas dependências, isso deve funcionar:

// Include dependent libraries in archive.
mainClassName = "com.company.application.Main"

jar {
  manifest { 
    attributes "Main-Class": "$mainClassName"
  }  

  from {
    configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
  }
}

Observe que mainClassNamedeve aparecer ANTES jar {.


4
Eu tive que modificar isso para configurations.runtime.collect para o meu projeto, pois também tenho dependências de tempo de execução.
— vextorspace

2
Eu tive que adicionar def mainClassNamepara fazer o trabalho de código ... Eu estava recebendo Não foi possível definir propriedade desconhecida 'mainClassName' para o projeto raiz
— hanskoff

1
Como você lida com colisões de nomes de arquivos? Arquivos no mesmo caminho em JARs diferentes serão substituídos.
— WST

3
Infelizmente isso não funciona mais. Eu uso o gradle 4.10 e a nova implementationconfiguração em vez da agora obsoleta compile. O código acima cria um pequeno jar sem as dependências. Quando eu mudá-lo ( from { configurations.implementation.collect {...} }), ocorre um erro dizendo que a resolução de configuração 'implementação' diretamente não é permitido
— Bastian Voigt

1
O @BastianVoigt configurations.compileClasspathcorrigirá todos os implementations, mas deixará de fora as apidependências afik. Encontrado aqui em outra resposta a solução runtimeClasspath. Isso inclui também as apidependências.
— rekire 14/01

64

A resposta de @felix quase me trouxe lá. Eu tive dois problemas:

  1. Com o Gradle 1.5, a tag de manifesto não era reconhecida dentro da tarefa fatJar, portanto, o atributo Main-Class não pôde ser definido diretamente
  2. o jar tinha arquivos META-INF externos conflitantes.

A configuração a seguir resolve isso

jar {
  manifest {
    attributes(
      'Main-Class': 'my.project.main',
    )
  }
}

task fatJar(type: Jar) {
  manifest.from jar.manifest
  classifier = 'all'
  from {
    configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
  } {
    exclude "META-INF/*.SF"
    exclude "META-INF/*.DSA"
    exclude "META-INF/*.RSA"
  }
  with jar
}

Para adicionar isso à tarefa de montagem ou construção padrão, adicione:

artifacts {
    archives fatJar
}

Edit: thanks to @mjaggard: nas versões recentes do Gradle, altere configurations.runtimeparaconfigurations.runtimeClasspath


3
Isso também corrigiu um problema em que um dos meus frascos de dependência foi assinado. Os arquivos de assinatura foram colocados no META-INF do meu jar, mas a assinatura não correspondia mais ao conteúdo.
— Flavin

2
Agradecimentos especiais para artifacts: exatamente o que eu estava procurando.
— AlexR

Quando você executa gradle fatJaras dependências de tempo de execução, elas não parecem ser compiladas e, portanto, não podem ser copiadas.
— mjaggard

64

Se você deseja que a jartarefa se comporte normalmente e também tenha uma fatJartarefa adicional , use o seguinte:

task fatJar(type: Jar) {
    classifier = 'all'
    from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
    with jar
}

A parte importante é with jar. Sem ele, as classes deste projeto não são incluídas.


1
Consulte também o seguinte problema se você estiver usando jars assinados para incluí-los e tiver um problema com assinaturas: stackoverflow.com/questions/999489/…
— Peter N. Steinmetz

6
Isso não funciona. O arquivo manifesto está vazio com esta solução.
— Jonas

4
Meus 2 centavos: É melhor definir um classificador do que mudar o nome. Coloque classificador = 'all' em vez de baseName = project.name + '-all'. Dessa forma, você mantém o nome do artefato em conformidade com as políticas do Maven / Nexus.
— precisa saber é

1
Adicione group "build"e esta tarefa estará em buildgrupo (com outras tarefas, ou seja, jartarefa.
— MAGx2 20/16

1
Não consigo encontrar nenhum tipo de documentação sobre a with jarpalavra - chave, o que exatamente ela faz?
— Philipp Hemmelmayr

9

Este trabalho é bom para mim.

Minha classe principal:

package com.curso.online.gradle;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;

public class Main {

    public static void main(String[] args) {
        Logger logger = Logger.getLogger(Main.class);
        logger.debug("Starting demo");

        String s = "Some Value";

        if (!StringUtils.isEmpty(s)) {
            System.out.println("Welcome ");
        }

        logger.debug("End of demo");
    }

}

E é o conteúdo do meu arquivo build.gradle:

apply plugin: 'java'

apply plugin: 'eclipse'

repositories {
    mavenCentral()
}

dependencies {
    compile group: 'commons-collections', name: 'commons-collections', version: '3.2'
    testCompile group: 'junit', name: 'junit', version: '4.+'
    compile  'org.apache.commons:commons-lang3:3.0'
    compile  'log4j:log4j:1.2.16'
}

task fatJar(type: Jar) {
    manifest {
        attributes 'Main-Class': 'com.curso.online.gradle.Main'
    }
    baseName = project.name + '-all'
    from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
    with jar
}

E escrevo o seguinte no meu console:

java -jar ProyectoEclipseTest-all.jar

E a saída é ótima:

log4j:WARN No appenders could be found for logger (com.curso.online.gradle.Main)
.
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more in
fo.
Welcome

6

Para gerar um JAR gordo com uma classe executável principal, evitando problemas com JARs assinados, sugiro o plugin gradle-one-jar . Um plugin simples que usa o projeto One-JAR .

Fácil de usar:

apply plugin: 'gradle-one-jar'

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.github.rholder:gradle-one-jar:1.0.4'
    }
}

task myjar(type: OneJar) {
    mainClass = 'com.benmccann.gradle.test.WebServer'
}

5

Suluição simples

jar {
    manifest {
        attributes 'Main-Class': 'cova2.Main'
    } 
    doFirst {
        from { configurations.runtime.collect { it.isDirectory() ? it : zipTree(it) } }
    }
}

5

A resposta do @ben quase funciona para mim, exceto que minhas dependências são muito grandes e recebi o seguinte erro

Execution failed for task ':jar'.
> archive contains more than 65535 entries.

  To build this archive, please enable the zip64 extension.

Para corrigir esse problema, eu tenho que usar o seguinte código

mainClassName = "com.company.application.Main"

jar {
  manifest { 
    attributes "Main-Class": "$mainClassName"
  }  
  zip64 = true
  from {
    configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
  }
}

1

Para quem precisa construir mais de um frasco do projeto.

Crie uma função no gradle:

void jarFactory(Jar jarTask, jarName, mainClass) {
    jarTask.doFirst {
        println 'Build jar ' + jarTask.name + + ' started'
    }

    jarTask.manifest {
        attributes(
                'Main-Class':  mainClass
        )
    }
    jarTask.classifier = 'all'
    jarTask.baseName = jarName
    jarTask.from {
        configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
    }
    {
        exclude "META-INF/*.SF"
        exclude "META-INF/*.DSA"
        exclude "META-INF/*.RSA"
    }
    jarTask.with jar 
    jarTask.doFirst {
        println 'Build jar ' + jarTask.name + ' ended'
    }
}

então ligue:

task makeMyJar(type: Jar) {
    jarFactory(it, 'MyJar', 'org.company.MainClass')
}

Trabalhos no gradle 5.

Jar será colocado em ./build/libs.


0

Eu uso tarefa shadowJarpor plugin. com.github.jengelman.gradle.plugins:shadow:5.2.0

O uso que acabou de executar o ./gradlew app::shadowJar arquivo de resultados será emMyProject/app/build/libs/shadow.jar

build.gradlearquivo de nível superior :

 apply plugin: 'kotlin'

buildscript {
    ext.kotlin_version = '1.3.61'

    repositories {
        mavenLocal()
        mavenCentral()
        jcenter()
    }

    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath 'com.github.jengelman.gradle.plugins:shadow:5.2.0'
    }
}

build.gradlearquivo no nível do módulo de aplicativo

apply plugin: 'java'
apply plugin: 'kotlin'
apply plugin: 'kotlin-kapt'
apply plugin: 'application'
apply plugin: 'com.github.johnrengelman.shadow'

sourceCompatibility = 1.8

kapt {
    generateStubs = true
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation "org.seleniumhq.selenium:selenium-java:4.0.0-alpha-4"
    shadow "org.seleniumhq.selenium:selenium-java:4.0.0-alpha-4"

    implementation project(":module_remote")
    shadow project(":module_remote")
}

jar {
    exclude 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA', 'META-INF/*.MF'
    manifest {
        attributes(
                'Main-Class': 'com.github.kolyall.TheApplication',
                'Class-Path': configurations.compile.files.collect { "lib/$it.name" }.join(' ')
        )
    }
}

shadowJar {
    baseName = 'shadow'
    classifier = ''
    archiveVersion = ''
    mainClassName = 'com.github.kolyall.TheApplication'

    mergeServiceFiles()
}


0

Gradle 6.3, biblioteca Java. O código de "jar task" adiciona as dependências à "build / libs / xyz.jar" ao executar a tarefa " gradle build ".

plugins {
    id 'java-library'
}

jar {
    from {
        configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
    }
}

-1

Se você está acostumado a formiga, pode tentar o mesmo com Gradle:

task bundlemyjava{
    ant.jar(destfile: "build/cookmyjar.jar"){
        fileset(dir:"path to your source", includes:'**/*.class,*.class', excludes:'if any')
        } 
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.