Java es un gran lenguaje, y como todos tiene ventajas y desventajas. Al momento de distribuir nuestra aplicación, se pueden realizar (y mejor aún, automatizar) algunas adecuaciones para ofrecer un paquete fácil de usar, pues una de las desventajas en Java es la falta de una integración adecuada para cada una de las plataformas soportadas.

Siguiendo esta guía vamos a generar un proceso en Ant que construirá paquetes para Windows, OS X, Linux y multiplataforma. Los paquetes específicos para plataforma tendrán un ejecutable nativo, en tanto que el multiplataforma llevará el JAR optimizado. Los tres paquetes incluirán también archivos varios necesarios para la distribución como licencias o instructivos.
El JAR de la aplicación usado para las distribuciones será optimizado en tamaño, desempeño y código usando Proguard, que es una excelente herramienta para pulir un JAR a distribuir. Para generar el ejecutable de Windows se usará la herramienta Launch4j. Para la app de OS X se usará jarbundler.
Primero hay que crear un directorio para almacenar las herramientas a usar, un buen lugar puede ser dentro de la raiz del proyecto pero puedes ponerla donde mejor te ajuste. Descarga Proguard y coloca el JAR (proguard-*.**.jar) en tu directorio de herramientas de construcción. Ahora Descarga Launch4j y descomprime el paquete en el mismo lugar donde hayas colocado Proguard. Finalmente desarcarga appblundler y coloca el jar en el mismo directorio que las herramientas anteriores.
Si usas Linux x64 y en caso de que quieras hacer el ejecutable de Windows, es muy probable que debas agregar soporte para procesar librerías de 32 bits (por el binario windres incluido en la distribución de Linux). Si usas una distro basada en Debian puedes habilitar el uso de paquetes de 32 bits con las siguientes instrucciones:
|
1 2 3 |
$ sudo dpkg --add-architecture i386 $ sudo aptitude update $ sudo aptitude install ia32-libs |
Notas: Esta guia la he implementado en #! 11 x64 y NetBeans 8 y posiblemente necesites hacer adaptaciones dependiendo de tu SO e IDE. Al ir pegando el código, revisa los valores de las variables o propiedades para ajustarlas a tus necesidades, algunos valores incluyen rutas que deberás actualizar.
Primero vamos a definir un nuevo archivo build que contendrá la automatización del proceso. Para esto necesitamos crear un nuevo archivo: build-dist.xml. Hazlo copiando el archivo build.xml en la misma ubicación (la raiz del proyecto).
Edita build.xml dejándolo con algo como:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
<?xml version="1.0" encoding="UTF-8"?> <project default="default" basedir="."> <description>Builds, tests, and runs the application.</description> <import file="nbproject/build-impl.xml"/> <import file="build-dist.xml" /> <target name="-post-jar"> <echo message="${line.separator}*** Run the Ant target 'dist-generate-pack' to generate ditribution packages${line.separator} " /> <antcall target="dist-generate-pack"/> </target> </project> |
Nota la importación de build-dist.xml y el llamado antcall para controlar la ejecución del proceso.
Ahora edita build-dist.xml para dejarlo con la siguiente estructura inicial:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
<?xml version="1.0" encoding="UTF-8"?> <project default="default" basedir="."> <description>Generate distributable packages.</description> <!-- Import project properties --> <property file="nbproject/project.properties"/> <!-- Directory containing resources to add --> <property name="resources.dir" value="resources"/> <!-- Directory containing external tools --> <property name="build.ext.dir" value="build-ext"/> <!-- Directory resources for --> <property name="ext.resources.dir" value="${build.ext.dir}/resources"/> <property name="newline" value="${line.separator}"/> <target name="dist-generate-pack"> <property file="nbproject/project.properties"/> <echo message="${line.separator} - Distribution generation process will start...${line.separator}${line.separator}"/> <antcall target="dist-optimize-app"/> <!--<antcall target="dist-single-jar"/>--> <!--<antcall target="dist-wrap-win"/>--> <!--<antcall target="dist-wrap-osx"/>--> <!--<antcall target="dist-wrap-linux"/>--> <!--<antcall target="dist-pack-win"/>--> <!--<antcall target="dist-pack-osx"/>--> <!--<antcall target="dist-pack-linux"/>--> <!--<antcall target="dist-pack-multi"/>--> <!--<antcall target="dist-clean"/>--> </target> </project> |
Lo que haremos ahora será agregar cada bloque correspondiente a cada llamado antcall. Descomenta/comenta los llamados según vayas avanzando para hacer pruebas.
Lo primero será pulir nuestro JAR utilizando Proguard. Esta aplicación se encargará de remover código sin usar, optimizar a nivel bytecode nuestra aplicación y además obfuscar la estructura del fuente, todo esto nos viene genial.
Agrega el siguiente target a build-dist.xml:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
<!-- Shrink, optimize and obfuscate application --> <target name="dist-optimize-app"> <!-- Config --> <!-- JRE directory location --> <property name="ob.jre.dir" value="/opt/java-oracle/jre"/> <!-- vm.jar for IBM's JVM, classes.jar for OS X --> <property name="ob.runtime.jar" value="${ob.jre.dir}/lib/rt.jar"/> <!-- location of proguard relative to project root dir --> <property name="ob.proguard.jar" value="${build.ext.dir}/proguard-4.11.jar"/> <!-- Javac classpath, defined in nbproject/project.properties --> <property name="ob.javac.cp" value="${javac.classpath}"/> <!-- The jar app location, defined in nbproject/project.properties --> <property name="ob.input.jar" value="${dist.jar}"/> <!-- New name (w/o ext) for input jar file --> <property name="ob.input.name" value="${application.title}-orig"/> <!-- The resultant jar location --> <property name="ob.output.dir" value="${dist.dir}"/> <!-- Name (w/o ext) for the resultant .jar & .map files --> <property name="ob.output.name" value="${application.title}"/> <!-- 5 optimization pases are enough --> <property name="ob.optimizations" value="5"/> <!-- Routine --> <dirname property="ob.input.dir" file="${ob.input.jar}"/> <property name="ob.input.mov.jar" value="${ob.input.dir}/${ob.input.name}.jar"/> <move file="${ob.input.jar}" tofile="${ob.input.mov.jar}"/> <property name="ob.output.jar" value="${ob.output.dir}/${ob.output.name}.jar"/> <property name="ob.output.map" value="${ob.output.dir}/${ob.output.name}.map"/> <echo message="${newline}** Shrinking, optimizing and obfuscating application..."/> <echo message=" Input jar: ${ob.input.jar}"/> <echo message=" Input jar new name: ${ob.input.mov.jar}"/> <echo message=" Output jar: ${ob.output.jar}"/> <echo message=" Mapping file: ${ob.output.map}"/> <mkdir dir="${ob.output.dir}"/> <taskdef resource="proguard/ant/task.properties" classpath="${ob.proguard.jar}"/> <proguard printmapping="${ob.output.map}" overloadaggressively="true" ignorewarnings="false" optimize="true" optimizationpasses="${ob.optimizations}" obfuscate="true" shrink="true" verbose="false" renamesourcefileattribute="SourceFile" repackageclasses="" printseeds="on"> <libraryjar path="${ob.javac.cp}"/> <libraryjar file="${ob.runtime.jar}"/> <!-- for javax.crypto classes --> <libraryjar file="${ob.jre.dir}/lib/jce.jar"/> <injar file="${ob.input.mov.jar}"/> <outjar file="${ob.output.jar}"/> <!-- ### Specific configuration ### --> <keep name="com.fugadigital.util.Money"/> <!-- I added some JDialog subclasses that I want to keep --> <keep access="public" extends="javax.swing.JDialog"> <method access="public protected"/> <constructor access="public" parameters="java.awt.Frame"/> </keep> <!-- ### Standard application configuration ### --> <adaptclassstrings /> <adaptresourcefilenames /> <!-- Preserve all annotations --> <keepattribute name="*Annotation*"/> <keepattribute name="InnerClasses"/> <keepattribute name="Signature"/> <keepattribute name="Deprecated"/> <keepattribute name="EnclosingMethod"/> <keepattribute name="SourceFile"/> <keepattribute name="LineNumberTable"/> <keep implements="java.sql.Driver"/> <keep extends="ch.qos.logback.core.filter.Filter"/> <!-- Preserve main method --> <keep name="**${application.title}"> <method access ="public static" type ="void" name ="main" parameters="java.lang.String[]"/> </keep> <keep extends="javax.swing.plaf.ComponentUI"> <method access ="public static" type ="javax.swing.plaf.ComponentUI" name ="createUI" parameters="javax.swing.JComponent"/> </keep> <keep annotation="javax.persistence.*" type="class"> <field /> <method /> </keep> <!-- Preserve all native method names and the names of their classes. --> <keepclasseswithmembernames> <method access="native"/> </keepclasseswithmembernames> <!-- Preserve the methods that are required in all enumeration classes. --> <keepclassmembers extends="java.lang.Enum"> <method access="public static" type="**[]" name="values" parameters=""/> <method access="public static" type="**" name="valueOf" parameters="java.lang.String"/> </keepclassmembers> <!-- Explicitly preserve all serialization members. The Serializable interface is only a marker interface. If code contains serializable classes that have to be backward compatible, refer to the manual. --> <keepnames implements="java.io.Serializable"/> <keepclassmembers implements="java.io.Serializable"> <field access ="static final" type ="long" name ="serialVersionUID"/> <field access ="static final" type ="java.io.ObjectStreamField[]" name ="serialPersistentFields"/> <method access ="private" type ="void" name ="writeObject" parameters="java.io.ObjectOutputStream"/> <method access ="private" type ="void" name ="readObject" parameters="java.io.ObjectInputStream"/> <method type ="java.lang.Object" name ="writeReplace" parameters=""/> <method type ="java.lang.Object" name ="readResolve" parameters=""/> </keepclassmembers> </proguard> <echo message=" DONE"/> </target> |
Verifica que la configuración de las propiedades sea correcta y construye tu paquete. Después de compilar y con ‘algo de suerte’ se generará un nuevo JAR con el nombre definido en la propiedad ob.jar.name, este JAR es la aplicación optimizada. Observa que he dejado en la sección de código configuraciones de ejemplo particulares a una aplicación. En caso de que tengas errores consulta la traza y agrega las configuraciones propias para tu aplicación. Consulta el manual de uso en el sitio de Proguard para esto.
El segundo paso es generar un solo JAR que contenga la aplicación procesada y las librerías. Apendiza el siguiente target en build-dist.xml:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
<!-- Generate fat jar --> <target name="dist-single-jar"> <!-- Config --> <!-- The library dir --> <property name="single.lib.dir" value="${dist.dir}/lib"/> <!-- The application JAR --> <property name="single.input.jar" value="${dist.jar}"/> <!-- New name (w/o ext) for input jar file --> <property name="single.input.name" value="${application.title}-slim"/> <!-- The location of the directory for the unified JAR --> <property name="single.output.dir" value="${dist.dir}"/> <!-- Name (w/o ext) for the resultant .jar file --> <property name="single.output.name" value="${application.title}"/> <!-- Routine --> <dirname property="single.input.dir" file="${single.input.jar}"/> <property name="single.input.mov.jar" value="${single.input.dir}/${single.input.name}.jar"/> <move file="${single.input.jar}" tofile="${single.input.mov.jar}"/> <property name="single.output.jar" value="${single.output.dir}/${single.output.name}.jar"/> <echo message="${newline}** Generating fat jar..."/> <echo message=" Input jar: ${single.input.jar}"/> <echo message=" Input jar new name: ${single.input.mov.jar}"/> <echo message=" lib dir: ${single.lib.dir}"/> <echo message=" Output jar: ${single.output.jar}"/> <jar jarfile="${single.output.jar}"> <zipfileset src="${single.input.mov.jar}" excludes="META-INF/*"/> <zipgroupfileset dir="${single.lib.dir}" includes="*.jar" excludes="META-INF/*"/> <manifest> <attribute name="SplashScreen-Image" value="com/recipeman/resources/splashscreen.png"/> <attribute name="Main-Class" value="${main.class}"/> </manifest> </jar> <echo message=" DONE"/> </target> |
Igualmente, verifica la configuración de las propiedades del target y compila. El proceso de construcción ahora generará un nuevo archivo que contendrá a nuestra aplicación y sus librerías en un solo .jar.
Desde este momento tenemos un JAR más facil de distribuir. La tarea ahora es ofrecer métodos para ejecutar nuestra aplicación de una manera más amigable, algo que conseguiremos creando lanzadores nativos para algunas plataformas.
Para Windows generaremos un .exe a través de Launch4j. Crea un archivo .xml dentro del directorio del Launch4j para establecer la configuración necesaria. El siguiente ejemplo generará un ejecutable que requirá una JVM 1.6.0+ instalada para funcionar, redireccionando a la página de descarga en español para instalar JAVA cuando no esté disponible, y con preferencia por una JVM de 64bits:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<?xml version="1.0" encoding="UTF-8"?> <launch4jConfig> <dontWrapJar>false</dontWrapJar> <headerType>gui</headerType> <errTitle></errTitle> <cmdLine></cmdLine> <chdir></chdir> <priority>normal</priority> <downloadUrl>http://java.com/es/download</downloadUrl> <supportUrl></supportUrl> <stayAlive>false</stayAlive> <manifest></manifest> <icon>../resources/app_multi.ico</icon> <jre> <path></path> <bundledJre64Bit>false</bundledJre64Bit> <minVersion>1.6.0</minVersion> <maxVersion></maxVersion> <jdkPreference>preferJre</jdkPreference> <runtimeBits>64/32</runtimeBits> </jre> </launch4jConfig> |
Agrega el ícono que usarás para el .exe en el mismo directorio en que has puesto la configuración anterior. Consulta la documentación para configuraciones más específicas.
Ahora, para ejecutar el proceso, agrega a build-dist.xml el siguiente target:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
<!-- Generate the Windows wrapper --> <target name="dist-wrap-win"> <!-- Config --> <!-- Jar to use --> <property name="wrap.input.jar" value="${dist.jar}"/> <!-- The resultant files location --> <property name="wrap.output.dir" value="${dist.dir}"/> <!-- -Name for the resultant executable file --> <property name="wrap.output.exe" value="${application.title}.exe"/> <!-- Application main class --> <property name="wrap.main.class" value="${main.class}"/> <!-- Launch4j base directory --> <property name="wrap.launch4j.dir" value="${build.ext.dir}/launch4j"/> <!-- Routine --> <echo message="${newline}** Generating wrapper..."/> <echo message=" Launch4j jar: ${wrap.launch4j.dir}/launch4j.jar"/> <echo message=" Input jar: ${wrap.input.jar}"/> <echo message=" Output file: ${dist.dir}/${wrap.output.exe}"/> <taskdef name="launch4j" classname="net.sf.launch4j.ant.Launch4jTask" classpath="${wrap.launch4j.dir}/launch4j.jar :${wrap.launch4j.dir}/lib/xstream.jar"/> <launch4j configfile="${wrap.launch4j.dir}/conf-nb.xml" jar="${wrap.input.jar}" outfile="${dist.dir}/${wrap.output.exe}" /> <echo message=" DONE"/> </target> |
Para generar la aplicación de OS X usaremos el siguiente target:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
<!-- Generate the OS X wrapper --> <taskdef name="bundleapp" classname="com.oracle.appbundler.AppBundlerTask" classpath="build-ext/appbundler-1.0.jar" /> <target name="dist-wrap-osx"> <!-- Config --> <!-- Jar to use --> <property name="wrap.input.jar" value="${dist.jar}"/> <!-- The resultant files location --> <property name="wrap.output.dir" value="${dist.dir}"/> <!-- Name for the resultant application directory --> <property name="wrap.output.app" value="${application.title}.app"/> <!-- Resources --> <property name="wrap.icon.file" value="GenericApp.icns"/> <!-- Routine --> <property name="app.dir" value="${dist.dir}/${wrap.output.app}"/> <echo message="${newline}** Generating wrapper..."/> <echo message=" Input jar: ${wrap.input.jar}"/> <echo message=" Output file: ${app.dir}"/> <bundleapp outputdirectory="dist" name="${application.title}" displayname="${application.title}" identifier="components.${application.title}" mainclassname="${main.class}"> <classpath file="${wrap.input.jar}"/> </bundleapp> <delete file="${app.dir}/Contents/Resources/${wrap.icon.file}"/> <copy todir="${app.dir}/Contents/Resources"> <fileset dir="${ext.resources.dir}" includes="${wrap.icon.file}"/> </copy> </target> |
Para linux usaremos la estrategia de agregar payloads a scripts, que apendizará un lanzador y el JAR en un solo archivo. Crea el lanzador dentro del directorio de herramientas de construcción con el siguiente contenido:
|
1 2 3 4 5 6 7 8 9 10 |
#!/bin/sh MYSELF=`which "$0" 2>/dev/null` [ $? -gt 0 -a -f "$0" ] && MYSELF="./$0" java=java if test -n "$JAVA_HOME"; then java="$JAVA_HOME/bin/java" fi exec "$java" $java_args -jar $MYSELF "$@" exit 1 |
Ahora, agrega a build-dist.xml un nuevo tarjet con la ejecución del cat que unirá el lanzador y el JAR:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
<!-- Generate the GNU/Linux wrapper --> <target name="dist-wrap-linux"> <!-- Config --> <!-- Jar to use --> <property name="wrap.input.jar" value="${dist.jar}"/> <!-- launcher script location --> <property name="wrap.nixwrap.path" value="${build.ext.dir}/jar2nix.sh"/> <!-- The resultant files location --> <property name="wrap.output.dir" value="${dist.dir}"/> <!-- -Name for the resultant .run file --> <property name="wrap.output.run" value="${application.title}.run"/> <!-- Routine --> <echo message="${newline}** Generating wrapper..."/> <echo message=" Input jar: ${wrap.input.jar}"/> <echo message=" Output file: ${dist.dir}/${wrap.output.run}"/> <exec executable="sh" output="/dev/null"> <arg value="-c"/> <arg value="cat ${wrap.nixwrap.path} ${wrap.input.jar} > ${dist.dir}/${wrap.output.run}"/> </exec> <exec executable="chmod"> <arg value="+x"/> <arg value="${dist.dir}/${wrap.output.run}"/> </exec> <echo message=" DONE"/> </target> |
(Si te preguntas por que usar sh como ejecutable en lugar de cat directamente, es porque la manera en que se envían los parametros impide la redirección y tanto el atributo output de <exec> como el uso del subelemento <redirector> modifican la salida generando archivos corruptos.)
Para terminar vamos a agregar las instrucciones que generan los archivos de ditribución: un ZIP para Windows, un TAR.GZ para OS X, un TAR.BZ2 para Linux y un TAR.GZ multiplataforma.
Los tres target siguientes generarán archivos comprimidos que contendrán un directorio raiz, éste a su vez contendrá el wrapper o jar y un directorio con documentos varios.
Para Windows agrega el siguiente target:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
<!-- Generate Windows zip file --> <target name="dist-pack-win"> <!-- Config --> <!-- Location of the wrapper --> <property name="pack.wrap.dir" value="${dist.dir}"/> <!-- Wrapper filename --> <property name="pack.wrap.file" value="${application.title}.exe"/> <!-- Directory with resources to include --> <property name="pack.resources.dir" value="${resources.dir}/app"/> <!-- Name for included resurces directoy --> <property name="pack.resources.name" value="app"/> <!-- Location of the compressed file --> <property name="pack.output.dir" value="${dist.dir}"/> <!-- compressed package filename --> <property name="pack.output.file" value="${application.title}-win.zip"/> <!-- Directory name of the root compressed file contents --> <property name="pack.root.dir" value="${application.title}"/> <!-- Routine --> <echo message="${newline}** Generating distribution..."/> <echo message=" Wrapper file: ${pack.wrap.dir}/${pack.wrap.file}"/> <echo message=" Resources dir: ${pack.resources.dir}"/> <echo message=" Internal root dir: ${pack.root.dir}"/> <zip destfile="${pack.output.dir}/${pack.output.file}"> <zipfileset prefix="${pack.root.dir}" dir="${pack.wrap.dir}" includes="${pack.wrap.file}"/> <zipfileset prefix="${pack.root.dir}/${pack.resources.name}" dir="${pack.resources.dir}" includes="**"/> </zip> <echo message=" DONE"/> </target> |
Para OS X agrega el siguiente target:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
<!-- Generate OS X tar.gz file --> <target name="dist-pack-osx"> <!-- Config --> <!-- Location of the wrapper --> <property name="pack.wrap.dir" value="${dist.dir}"/> <!-- Wrapper filename --> <property name="pack.wrap.file" value="${application.title}.app"/> <!-- Directory with resources to include --> <property name="pack.resources.dir" value="${resources.dir}/app"/> <!-- Name for included resurces directoy --> <property name="pack.resources.name" value="app"/> <!-- Location of the compressed file --> <property name="pack.output.dir" value="${dist.dir}"/> <!-- compressed package filename --> <property name="pack.output.file" value="${application.title}-osx.tar.gz"/> <!-- Directory name of the root compressed file contents --> <property name="pack.root.dir" value="${application.title}"/> <!-- Routine --> <echo message="${newline}** Generating distribution..."/> <echo message=" Wrapper file: ${pack.wrap.dir}/${pack.wrap.file}"/> <echo message=" Resources dir: ${pack.resources.dir}"/> <echo message=" Internal root dir: ${pack.root.dir}"/> <tar destfile="${pack.output.dir}/${pack.output.file}" compression="gzip"> <tarfileset prefix="${pack.root.dir}" dir="${pack.wrap.dir}" excludes="**/JavaAppLauncher" includes="${pack.wrap.file}/**"/> <tarfileset prefix="${pack.root.dir}/${pack.wrap.file}/Contents/MacOS" dir="${pack.wrap.dir}/${pack.wrap.file}/Contents/MacOS" mode="755" includes="JavaAppLauncher"/> <tarfileset prefix="${pack.root.dir}/${pack.resources.name}" dir="${pack.resources.dir}" includes="**"/> </tar> <echo message=" DONE"/> </target> |
Para Linux agrega el siguiente target:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
<!-- Generate Linux tar.bz2 file --> <target name="dist-pack-linux"> <!-- Config --> <!-- Location of the wrapper --> <property name="pack.wrap.dir" value="${dist.dir}"/> <!-- Wrapper filename --> <property name="pack.wrap.file" value="${application.title}.run"/> <!-- Directory with resources to include --> <property name="pack.resources.dir" value="${resources.dir}/app"/> <!-- Name for included resurces directoy --> <property name="pack.resources.name" value="app"/> <!-- Location of the compressed file --> <property name="pack.output.dir" value="${dist.dir}"/> <!-- compressed package filename --> <property name="pack.output.file" value="${application.title}-linux.tar.bz2"/> <!-- Directory name of the root compressed file contents --> <property name="pack.root.dir" value="${application.title}"/> <!-- Routine --> <echo message="${newline}** Generating distribution..."/> <echo message=" Wrapper file: ${pack.wrap.dir}/${pack.wrap.file}"/> <echo message=" Resources dir: ${pack.resources.dir}"/> <echo message=" Internal root dir: ${pack.root.dir}"/> <tar destfile="${pack.output.dir}/${pack.output.file}" compression="bzip2"> <tarfileset prefix="${pack.root.dir}" dir="${pack.wrap.dir}" includes="${pack.wrap.file}" mode="755"/> <tarfileset prefix="${pack.root.dir}/${pack.resources.name}" dir="${pack.resources.dir}" includes="**"/> </tar> <echo message=" DONE"/> </target> |
Para la distribución multiplataforma agrega el siguiente target:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
<!-- Generate multiplatform tar.gz file --> <target name="dist-pack-multi"> <!-- Config --> <!-- Location of the jar --> <property name="pack.jar.dir" value="${dist.dir}"/> <!-- Wrapper filename --> <property name="pack.jar.file" value="${application.title}.jar"/> <!-- Directory with resources to include --> <property name="pack.resources.dir" value="${resources.dir}/app_multi"/> <!-- Name for included resurces directoy --> <property name="pack.resources.name" value="app"/> <!-- Location of the compressed file --> <property name="pack.output.dir" value="${dist.dir}"/> <!-- compressed package filename --> <property name="pack.output.file" value="${application.title}-multi.tar.gz"/> <!-- Directory name of the root compressed file contents --> <property name="pack.root.dir" value="${application.title}"/> <!-- Routine --> <echo message="${newline}** Generating distribution..."/> <echo message=" Wrapper file: ${pack.jar.dir}/${pack.jar.file}"/> <echo message=" Resources dir: ${pack.resources.dir}"/> <echo message=" Internal root dir: ${pack.root.dir}"/> <tar destfile="${pack.output.dir}/${pack.output.file}" compression="gzip"> <tarfileset prefix="${pack.root.dir}" dir="${pack.jar.dir}" includes="${pack.jar.file}"/> <tarfileset prefix="${pack.root.dir}" dir="${resources.dir}" includes="run.*" mode="755"/> <tarfileset prefix="${pack.root.dir}/${pack.resources.name}" dir="${pack.resources.dir}" includes="**"/> </tar> <echo message=" DONE"/> </target> |
En este target además estoy agregando archivos run.* que son lanzadores comunes para ejecutar un JAR en diferentes plataformas.
Este último paso es realmente innecesario y elimina todos los archivos y directorios excepto nuestros paquetes distribuibles:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<!-- Delete leftovers --> <target name="dist-clean"> <property file="nbproject/project.properties"/> <echo message="${newline}** Deleting leftovers..."/> <delete includeemptydirs="true"> <fileset dir="${dist.dir}" excludes="**/${application.title}-win.zip, **/${application.title}-osx.tar.gz, **/${application.title}-linux.tar.bz2, **/${application.title}-multi.tar.gz" /> </delete> <echo message=" DONE"/> </target> |
El archivo de construcción seguro es ahora considerablemente más largo pero muy versatil y adaptable. Aun cuando hay muchas mejoras posibles, esta es una buena base, así que agrega los cambios propios para tu proyecto y facilita la vida a tus usuarios (y la tuya por supuesto).

