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 .

Ben McCann
fonte

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 {.

Ben McCann
fonte
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

blootsvoets
fonte
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.

Felix
fonte
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
Aron
fonte
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'
}
Italo Borssatto
fonte
5

Suluição simples

jar {
    manifest {
        attributes 'Main-Class': 'cova2.Main'
    } 
    doFirst {
        from { configurations.runtime.collect { it.isDirectory() ? it : zipTree(it) } }
    }
}
Jonas Mayer
fonte
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) }
  }
}
Algoritmo
fonte
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.

MiguelSlv
fonte
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()
}

NickUnuchek
fonte
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) }
    }
}
Alex Ureche
fonte
-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')
        } 
}
mig
fonte