2010-09-22 22 views
36

Ho appena "ereditato" un progetto Java e non proviene da uno sfondo Java. Sono un po 'perso a volte. Eclipse viene utilizzato per eseguire il debug e l'esecuzione dell'applicazione durante lo sviluppo. Ho attraverso Eclipse riusciti a creare un .jar-file 'include' tutti i vasi esterni necessari come Log4J, XMLRPC server, ecc Questo grande .jar può quindi essere eseguito con successo utilizzando:Inclusione di file jar esterni in una nuova build di file jar con Ant

java -jar myjar.jar 

Il mio prossimo passo è quello di automatizzare le build usando Ant (versione 1.7.1), quindi non devo coinvolgere Eclipse per fare build e deployment. Ciò ha dimostrato di essere una sfida a causa della mia mancanza di conoscenza della java. La radice del progetto è simile al seguente:

|-> jars (where external jars have been placed) 
|-> java 
| |-> bin (where the finished .class/.jars are placed) 
| |-> src (Where code lives) 
| |-> ++files like build.xml etc 
|-> sql (you guessed it; sql!) 

mio build.xml contiene quanto segue:

<?xml version="1.0" encoding="UTF-8" standalone="no"?> 
<project basedir="." default="build" name="Seraph"> 
    <property environment="env"/> 
    <property name="debuglevel" value="source,lines,vars"/> 
    <property name="target" value="1.6"/> 
    <property name="source" value="1.6"/> 

    <property name="build.dir"  value="bin"/> 
    <property name="src.dir"  value="src"/> 
    <property name="lib.dir"  value="../jars"/> 
    <property name="classes.dir" value="${build.dir}/classes"/> 
    <property name="jar.dir"  value="${build.dir}/jar"/> 
    <property name="jar.file"  value="${jar.dir}/seraph.jar"/> 
    <property name="manifest.file" value="${jar.dir}/MANIFEST.MF"/> 

    <property name="main.class" value="no.easyconnect.seraph.core.SeraphCore"/> 

    <path id="external.jars"> 
     <fileset dir="${lib.dir}" includes="**/*.jar"/> 
    </path> 

    <path id="project.classpath"> 
     <pathelement location="${src.dir}"/> 
     <path refid="external.jars" /> 
    </path> 

    <target name="init"> 
     <mkdir dir="${build.dir}"/> 
     <mkdir dir="${classes.dir}"/> 
     <mkdir dir="${jar.dir}"/> 
     <copy includeemptydirs="false" todir="${build.dir}"> 
      <fileset dir="${src.dir}"> 
       <exclude name="**/*.launch"/> 
       <exclude name="**/*.java"/> 
      </fileset> 
     </copy> 
    </target> 

    <target name="clean"> 
     <delete dir="${build.dir}"/> 
    </target> 

    <target name="cleanall" depends="clean"/> 

    <target name="build" depends="init"> 
     <echo message="${ant.project.name}: ${ant.file}"/> 
     <javac debug="true" debuglevel="${debuglevel}" destdir="bin" source="${source}" target="${target}" classpathref="project.classpath"> 
      <src path="${src.dir}"/> 
     </javac> 
    </target> 

    <target name="build-jar" depends="build"> 
     <delete file="${jar.file}" /> 
     <delete file="${manifest.file}" /> 

     <manifest file="${manifest.file}" > 
      <attribute name="built-by" value="${user.name}" /> 
      <attribute name="Main-Class" value="${main.class}" /> 
     </manifest> 

     <jar destfile="${jar.file}" 
      basedir="${build.dir}" 
      manifest="${manifest.file}"> 
      <fileset dir="${classes.dir}" includes="**/*.class" /> 
      <fileset dir="${lib.dir}" includes="**/*.jar" /> 
     </jar> 
    </target> 
</project> 

ho quindi eseguire: ant clean accumulo vaso

e un serafino file chiamato .jar si trova nella directory java/bin/jar. Allora provo a eseguire questo barattolo con il seguente comando: java-jar bin/jar/seraph.jar

Il risultato è questa uscita alla console:

Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/log4j/Logger 
    at no.easyconnect.seraph.core.SeraphCore.<clinit>(SeraphCore.java:23) 
Caused by: java.lang.ClassNotFoundException: org.apache.log4j.Logger 
    at java.net.URLClassLoader$1.run(URLClassLoader.java:200) 
    at java.security.AccessController.doPrivileged(Native Method) 
    at java.net.URLClassLoader.findClass(URLClassLoader.java:188) 
    at java.lang.ClassLoader.loadClass(ClassLoader.java:307) 
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301) 
    at java.lang.ClassLoader.loadClass(ClassLoader.java:252) 
    at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:320) 
    ... 1 more 
Could not find the main class: no.easyconnect.seraph.core.SeraphCore. Program will exit. 

Ho il sospetto che ho fatto qualcosa incredibilmente stupido nel file build.xml e hanno trascorso la maggior parte dei due giorni a provare variazioni sulla configurazione, senza alcun risultato. Ogni aiuto su come ottenere questo lavoro è molto apprezzato.

Oh, e mi dispiace se ho lasciato fuori alcune informazioni cruciali. Questa è la mia prima pubblicazione qui a SO.

+0

Se fossi in te avrei usato Maven 2 o Maven 3 invece di formica, ma sarebbe anche una sfida. –

+0

Sono riuscito a fingere un modo ok per realizzare ciò che volevo. Vedi sotto per i dettagli. – Christopher

risposta

15

Con il consigli utili da parte di persone che hanno risposto qui ho iniziato a scavare in One- Vaso. Dopo alcuni vicoli ciechi (e alcuni risultati che erano esattamente come i miei risultati precedenti sono riuscito a farlo funzionare. Per gli altri popoli riferimento sto elencando il build.xml che ha lavorato per me.

<?xml version="1.0" encoding="UTF-8" standalone="no"?> 
<project basedir="." default="build" name="<INSERT_PROJECT_NAME_HERE>"> 
    <property environment="env"/> 
    <property name="debuglevel" value="source,lines,vars"/> 
    <property name="target" value="1.6"/> 
    <property name="source" value="1.6"/> 

    <property name="one-jar.dist.dir" value="../onejar"/> 
    <import file="${one-jar.dist.dir}/one-jar-ant-task.xml" optional="true" /> 

    <property name="src.dir"   value="src"/> 
    <property name="bin.dir"   value="bin"/> 
    <property name="build.dir"  value="build"/> 
    <property name="classes.dir"  value="${build.dir}/classes"/> 
    <property name="jar.target.dir" value="${build.dir}/jars"/> 
    <property name="external.lib.dir" value="../jars"/> 
    <property name="final.jar"  value="${bin.dir}/<INSERT_NAME_OF_FINAL_JAR_HERE>"/> 

    <property name="main.class"  value="<INSERT_MAIN_CLASS_HERE>"/> 

    <path id="project.classpath"> 
     <fileset dir="${external.lib.dir}"> 
      <include name="*.jar"/> 
     </fileset> 
    </path> 

    <target name="init"> 
     <mkdir dir="${bin.dir}"/> 
     <mkdir dir="${build.dir}"/> 
     <mkdir dir="${classes.dir}"/> 
     <mkdir dir="${jar.target.dir}"/> 
     <copy includeemptydirs="false" todir="${classes.dir}"> 
      <fileset dir="${src.dir}"> 
       <exclude name="**/*.launch"/> 
       <exclude name="**/*.java"/> 
      </fileset> 
     </copy> 
    </target> 

    <target name="clean"> 
     <delete dir="${build.dir}"/> 
     <delete dir="${bin.dir}"/> 
    </target> 

    <target name="cleanall" depends="clean"/> 

    <target name="build" depends="init"> 
     <echo message="${ant.project.name}: ${ant.file}"/> 
     <javac debug="true" debuglevel="${debuglevel}" destdir="${classes.dir}" source="${source}" target="${target}"> 
      <src path="${src.dir}"/> 
      <classpath refid="project.classpath"/> 
     </javac> 
    </target> 

    <target name="build-jar" depends="build"> 
     <delete file="${final.jar}" /> 
     <one-jar destfile="${final.jar}" onejarmainclass="${main.class}"> 
      <main> 
       <fileset dir="${classes.dir}"/> 
      </main> 
      <lib> 
       <fileset dir="${external.lib.dir}" /> 
      </lib> 
     </one-jar> 
    </target> 
</project> 

spero che qualcuno

+1

Ho provato anche One-Jar. Funziona come pubblicizzato. Lo adoro. – djangofan

+0

Ciao, vecchia risposta, lo so, ma come dovrebbe manifest il tuo esempio? Edit: capito, one-jar lo costruisce da solo. –

0

Due opzioni, fare riferimento ai nuovi vasi nel percorso di classe o decomprimere tutte le classi nei contenitori che racchiudono e riutilizzare l'intero lotto! Per quanto ne so, i vasi di imballaggio all'interno dei barattoli non sono consigliati e avrai sempre la classe non trovata eccezione!

+0

Devo solo credervi su questo. Il mio java-fu non è così forte. Tuttavia, avevo comunque pensato che il processo fosse banale dal momento che Eclipse riusciva a farlo nel modo giusto. ;) – Christopher

2

Cheesle ha ragione. Non è possibile per il classloader trovare i jar incorporati. Se metti abbastanza comandi di debug sulla riga di comando dovresti essere in grado di vedere il comando 'java' fallendo nell'aggiungere i jar ad un classpath

Quello che vuoi fare viene talvolta chiamato un 'uberjar'. Ho trovato one-jar come uno strumento per aiutarli a realizzarli, ma non l'ho provato. Certo, ci sono molti altri approcci.

+0

Qualcuno sa esattamente cosa usa Eclipse per realizzare questo? Fino ad ora Eclipse è stato utilizzato per costruire questa applicazione e ha sempre creato un pacchetto ordinato con tutto incluso. Sarebbe meraviglioso realizzare lo stesso con Ant. Daremo un'occhiata a one-jar e jarjar per vedere se riesco ad agganciarlo nel processo ant. – Christopher

+0

Devo dire che questo tuo suggerimento è stato un salvavita per me. Grazie mille .. funziona benissimo! – djangofan

+0

Anche io vorrei sapere come fa Eclipse. –

47

Dal tuo form file di formiche, presumo che quello che vuoi sia creare un singolo archivio JAR che conterrà non solo le classi dell'applicazione, ma anche il contenuto di altri JAR richiesti dalla tua applicazione.

Tuttavia il file build-jar sta semplicemente inserendo i JAR richiesti all'interno del proprio JAR; questo non funzionerà come spiegato here (vedi nota).

tenta di modificare questo:

<jar destfile="${jar.file}" 
    basedir="${build.dir}" 
    manifest="${manifest.file}"> 
    <fileset dir="${classes.dir}" includes="**/*.class" /> 
    <fileset dir="${lib.dir}" includes="**/*.jar" /> 
</jar> 

a questo:

<jar destfile="${jar.file}" 
    basedir="${build.dir}" 
    manifest="${manifest.file}"> 
    <fileset dir="${classes.dir}" includes="**/*.class" /> 
    <zipgroupfileset dir="${lib.dir}" includes="**/*.jar" /> 
</jar> 

soluzioni più flessibili e potenti sono le JarJar o One-Jar progetti.Dai un'occhiata a quelli se quanto sopra non soddisfa le tue esigenze.

+0

Questo estrae il jar e include i file di classe nel jar finale. Presenta lo stesso comportamento nel mio file di build. Ho postato la mia domanda [qui] (http://stackoverflow.com/questions/14072253/ant-including-both-jar-files-and-class-files-in-dest-jar). –

0

Come ha detto Cheesle, è possibile disimballare e conservare i barattoli della libreria e reinserirli tutti con la seguente modifica.

<jar destfile="${jar.file}" 
     basedir="${build.dir}" 
     manifest="${manifest.file}"> 
     <fileset dir="${classes.dir}" includes="**/*.class" /> 
     <zipgroupfileset dir="${lib.dir}" includes="**/*.jar" /> 
    </jar> 

I file jar sono in realtà solo file zip con un file manifest incorporato. È possibile estrarre e riconfezionare i gessi di dipendenza nel file Jar dell'applicazione.

http://ant.apache.org/manual/Tasks/zip.html "Il compito Zip supporta anche la fusione di più file ZIP nel file zip. Questo è possibile sia attraverso l'attributo src di qualsiasi serie di file nidificati o utilizzando lo speciale set di file zipgroupfileset nidificato."

Prestare attenzione alle licenze coinvolte con le librerie delle dipendenze. Il collegamento esternamente a una libreria e l'inclusione della libreria nella tua applicazione sono cose molto diverse dal punto di vista legale.

MODIFICA 1: Rammarica la mia lenta digitazione. Grodriguez mi ha battuto. :)

MODIFICA 2: Se si decide di non includere le dipendenze nell'applicazione, è necessario specificarle nel classpath di Jar alla riga di comando all'avvio o tramite il file Manifest. C'è un bel comando in ANT per gestire la formattazione speciale del classpath in un file Manifest per te.

<manifestclasspath property="manifest.classpath" jarfile="${jar.file}"> 
    <classpath location="${lib.dir}" /> 
</manifestclasspath> 

<manifest file="${manifest.file}" >  
     <attribute name="built-by" value="${user.name}" />  
     <attribute name="Main-Class" value="${main.class}" />  
     <attribute name="Class-Path" value="${manifest.classpath}" /> 
</manifest> 
0

Questo è un problema classpath quando si esegue un jar eseguibile come segue:

java -jar myfile.jar 

Un modo per risolvere il problema è quello di impostare il percorso di classe sulla riga di comando java come segue, aggiungendo il log4j mancanti jar:

java -cp myfile.jar:log4j.jar:otherjar.jar com.abc.xyz.MyMainClass 

Naturalmente la soluzione migliore è quella di aggiungere il percorso di classe nel manifesto vaso in modo che la si può utilizzare il "-jar" opzione java:

01.235.
<jar jarfile="myfile.jar"> 
    .. 
    .. 
    <manifest> 
     <attribute name="Main-Class" value="com.abc.xyz.MyMainClass"/> 
     <attribute name="Class-Path" value="log4j.jar otherjar.jar"/> 
    </manifest> 
</jar> 

La risposta seguito viene illustrato come è possibile utilizzare il manifestclasspath per automatizzare l'seeting del classpath manifesta ingresso

Cannot find Main Class in File Compiled With Ant

+0

Questo non è ciò che l'OP pare desidera. Dal suo file build.xml sembra che voglia effettivamente includere tutti i jar necessari nel file jar che sta generando. – Grodriguez

+0

Questo è corretto Grodriguez. Il mio obiettivo è consentire ai nostri utenti di scaricare solo un file ed eseguirlo. Avere tutto contenuto in un archivio rende semplice. – Christopher

0

Sto utilizzando NetBeans e avevo bisogno di una soluzione anche per questo: dopo googlelling in giro e partendo dalla risposta di Christopher sono riuscito a creare uno script che ti aiuta facilmente a farlo in NetBeans. le istruzioni qui nel caso in cui qualcun altro ne avesse bisogno

Quello che devi fare è scaricare un vaso. nk da qui: http://one-jar.sourceforge.net/index.php?page=getting-started&file=ant Estrarre l'archivio jar e cercare una cartella \ dist di one-jar che contiene one-jar-ant-task- .jar, one-jar-ant-task.xml e one-jar-boot- .vaso.Estrarli o copiarli in un percorso che aggiungeremo allo script sottostante, come il valore della proprietà one-jar.dist.dir.

Basta copiare il seguente script alla fine dello script build.xml (dal progetto NetBeans), subito prima del tag/project, sostituire il valore per one-jar.dist.dir con il percorso corretto ed eseguire uno- obiettivo del barattolo.

Per quelli di voi che non hanno familiarità con obiettivi in ​​esecuzione, questo tutorial potrebbe aiutare: http://www.oracle.com/technetwork/articles/javase/index-139904.html. Ti mostra anche come posizionare le fonti in un vaso, ma sono esplose, non compresse in barattoli.

<property name="one-jar.dist.dir" value="path\to\one-jar-ant"/> 
<import file="${one-jar.dist.dir}/one-jar-ant-task.xml" optional="true" /> 

<target name="one-jar" depends="jar"> 
<property name="debuglevel" value="source,lines,vars"/> 
<property name="target" value="1.6"/> 
<property name="source" value="1.6"/> 
<property name="src.dir"   value="src"/> 
<property name="bin.dir"   value="bin"/> 
<property name="build.dir"  value="build"/> 
<property name="dist.dir"   value="dist"/> 
<property name="external.lib.dir" value="${dist.dir}/lib"/> 
<property name="classes.dir"  value="${build.dir}/classes"/> 
<property name="jar.target.dir" value="${build.dir}/jars"/> 
<property name="final.jar"  value="${dist.dir}/${ant.project.name}.jar"/> 
<property name="main.class"  value="${main.class}"/> 

<path id="project.classpath"> 
    <fileset dir="${external.lib.dir}"> 
     <include name="*.jar"/> 
    </fileset> 
</path> 

<mkdir dir="${bin.dir}"/> 
<!-- <mkdir dir="${build.dir}"/> --> 
<!-- <mkdir dir="${classes.dir}"/> --> 
<mkdir dir="${jar.target.dir}"/> 
<copy includeemptydirs="false" todir="${classes.dir}"> 
    <fileset dir="${src.dir}"> 
     <exclude name="**/*.launch"/> 
     <exclude name="**/*.java"/> 
    </fileset> 
</copy> 

<!-- <echo message="${ant.project.name}: ${ant.file}"/> --> 
<javac debug="true" debuglevel="${debuglevel}" destdir="${classes.dir}" source="${source}" target="${target}"> 
    <src path="${src.dir}"/> 
    <classpath refid="project.classpath"/> 
</javac> 

<delete file="${final.jar}" /> 
<one-jar destfile="${final.jar}" onejarmainclass="${main.class}"> 
    <main> 
     <fileset dir="${classes.dir}"/> 
    </main> 
    <lib> 
     <fileset dir="${external.lib.dir}" /> 
    </lib> 
</one-jar> 

<delete dir="${jar.target.dir}"/> 
<delete dir="${bin.dir}"/> 
<delete dir="${external.lib.dir}"/> 

</target> 

Buona fortuna e non dimenticate di votare su se ti ha aiutato.

0

È possibile utilizzare un po 'di funzionalità già incorporata nell'attività ant jar.

Se vai a The documentation for the ant jar task e scorrere fino alla sezione "archivio fusione" C'è un frammento di inclusione degli tutti i file * .class da tutti i barattoli in te "/ main lib" directory:

<jar destfile="build/main/checksites.jar"> 
    <fileset dir="build/main/classes"/> 
    <restrict> 
     <name name="**/*.class"/> 
     <archives> 
      <zips> 
       <fileset dir="lib/main" includes="**/*.jar"/> 
      </zips> 
     </archives> 
    </restrict> 
    <manifest> 
     <attribute name="Main-Class" value="com.acme.checksites.Main"/> 
    </manifest> 
</jar> 

Questo Crea un file jar eseguibile con una classe principale "com.acme.checksites.Main" e incorpora tutte le classi da tutti i jar in lib/main.

Non farà nulla di intelligente in caso di conflitti nello spazio dei nomi, duplicati e cose del genere. Inoltre, includerà tutti i file di classe, anche quelli che non si utilizzano, quindi il file jar combinato sarà a dimensione intera.

Se avete bisogno di cose più avanzate come questo, dare un'occhiata a come one-jar e jar jar links