Runnable jar file without ant java task - how is this possible? - ant

I've inherited some code from a previous developer, which gets built using Ant into an executable jar file that runs by double clicking. The application runs, but under some conditions crashes with an OutOfMemoryError. To investigated this, I'd like to add the -XX:+HeapDumpOnOutOfMemory jvm arg to the Ant buildfile, and as I understand it, the <jvmarg value="-XX:+HeapDumpOnOutOfMemory" /> element needs to go under a <java ...> task. However, there is no <java ...> task to be found in this or any other Ant buildfiles in this code base.
How is this even possible? How can the jar file be executable without a <java ...> task?
I'm asking primarily to find out what in fact makes my jar file executable so that I can figure out where to put that <jvmarg /> element to debug the OOME.
Thanks!

A <java> task doesn't create an executable jar file. It executes a Java program.
I don't think it's possible to specify VM parameters when starting an executable jar file by double-clicking on it. If you want to pass VM parameters, open a command prompt and execute the jar this way:
java -XX:+HeapDumpOnOutOfMemory -jar nameOfTheJar.jar

Your jar is executable because it has a Main-Class defined in the META-INF/MANIFEST.MF file. Double clicking it to run doesn't do anything with Ant. Ant is simply used to package the jar.
In order to add the parameter and still launch via a double click you could create a shortcut that runs the command in JB Nizet's answer

Related

Running ANT with Different JDKs via EXEC (APT/WSGEN workaround)

I hope this finds an ANT build master who has some experience running ANT with different JDK versions concurrently!
I have inherited a fairly old set of libraries & ANT build files that currently work under Java 6/7. In attempting to modernize this system to utilize Java 8 or any future JDK upgrades, I've been working on a means of refactoring these build files to work with Java 8.
The current build system has the following characteristic:
Ant 1.7.0 & Java 1.7.0_55
Usage of the deprecated APT tool via ANT APT tasks
Usage of wsgen (jaxws related libraries) via ANT WSGEN tasks (e.g. classname="com.sun.tools.ws.ant.WsGen")
Effectively the build files mix code generation and compilation to create our desired output.
The APT tool was deprecated in Java 1.7, one needs to move to using JAVAC in 1.8 and beyond!
In attempting to replace the usages of APT with JAVAC I found that not only is the tool removed but so are the numerous packages/class/interfaces associated with the APT tool. I've seen this topic discussed here in multiple Q&A's, however most of the proposed solutions were non-starters.
The one suggestion I did find which had promise was to separate the code generation (usages of APT) from the compilation (JAVAC) to reside in different ANT build files. After separating the build file into a "main" build file and a "codegen" build file, this seems to work just fine.
Example:
<property name="apt.output.file" value="codegen.apt.${java.version}.log" /> <!-- A different log file will exist when I run the "main" ant build file using Java 7 vs 8 for comparison purposes. -->
<exec executable="${exec.ant.cmd}" osfamily="unix" failonerror="true" output="${apt.output.file}" >
<arg line="${ant.apt.cmd.line.options}" />
<env key="JAVA_HOME" value="${exec.java.home}"/> <!-- Java 1.7 -->
<env key="ANT_HOME" value="${ant.home.path}"/> <!-- Ant 1.7.0 -->
<env key="PATH" value="${exec.java.home}/bin" /> <!-- Reset PATH to be certain -->
<!-- The various properties are defined in the build file, and would be fairly unsurprising. -->
</exec>
Now that the "codegen" pieces were self-contained, this allows me to:
Run the "main" ANT build file with Java 8.
For the "codegen" pieces, use an EXEC as per above to specify the ANT/JAVA version to do the *.java code generation.
The good news, for those of you in similar situations, is that this seems to work fine for APT. I changed my JAVA_HOME & PATH to point at Java 8, and it seems to work fine (assuming APT is limited to just *.java source generation, no compilation).
Now here's the rub and what I hope someone might have some expertise on. I found that the APT tool was also used to create an ANT build file (wsgen-build.xml). Said file defines the WSGEN task and then uses it a number of times:
<project name="some-wsgen-fragment" default="wsgenall">
<target name="wsgenall">
<taskdef name="wsgen" classname="com.sun.tools.ws.ant.WsGen" >
<classpath>
<pathelement path="${jaxws.home}/lib/jaxws-tools.jar" />
</classpath>
</taskdef>
<echo message="wsgen for SomeWebServiceImpl" />
<wsgen destdir="${classes.server.gen.dir}" sourcedestdir="${something.gen.dir}" sei="com.something.or.other.SomeWebServiceImpl">
<classpath>
<pathelement path="${classes.server.gen.dir}" />
<path refid="classpath.all" />
</classpath>
</wsgen>
... Repeated usages of wsgen exactly as above, with different classes
Similar to the APT tasks, the "main" build file invokes an EXEC to run an "old" ANT with appropriate parameters to invoke this generated build file under the same environment it works under.
Under Java 1.7 & ANT 1.7.0, this works fine.
When I change Java to 1.8, I change the JDK for JAVA_HOME and the "java.target" to 1.8, I get the following mysterious error:
[echo] wsgen for SomeWebServiceImpl
Finding class com.something.or.other.SomeWebServiceImpl
[antcall] Exiting codegen-buildfile.xml.
BUILD FAILED
codegen-buildfile.xml:58: The following error occurred while executing this line:
wsgen-build.xml:9: Requires JDK 5.0 or later. Please download it from http://java.sun.com/j2se/1.5/
at org.apache.tools.ant.ProjectHelper.addLocationToBuildException(ProjectHelper.java:541)
at org.apache.tools.ant.taskdefs.Ant.execute(Ant.java:418)
at org.apache.tools.ant.taskdefs.CallTarget.execute(CallTarget.java:105)
The issue to me "feels" like the following:
Why does an ANT running Java 8 interfere with an ANT running Java 7 and how might this be remedied?
Some Q&A that has lead me to this question:
Have you tried using ANT -v -d for figuring this out?
Yes, I'm generating a log file based on the ${java.version} used for the "main" build file. I generate a file for Java 8 and one for Java 7, and diff the two. There are no environment/property/settings difference between the two (Java 7 vs Java 8) until I hit the error referenced above. There are minor variations in the order classes get loaded, but that is it.
When you change between Java 7 and Java 8, are you making sure that change isn't affecting your EXEC tasks?
The diff of the log files as referenced above verifies this, but when I invoke EXEC the properties in the first example above are effectively hard-coded with the ENV tags being used to ensure the environment variables are also the same regardless of the Java version used in the "main" build file.
Surely there is some environment variable difference causing this issue?
I'm logging a number of variables attempting to diagnose the issue. So far the "codegen" ANT build file has everything the same, regardless if the "main" ANT is run in either Java 7 or 8: ANT_HOME, JAVA_HOME, PATH, ant.version, java.version, java.vm.version, java.class.path, java.ext.dirs. The verbose ANT log bears this out as well, no property/environment/classpath issues between them.
I can't tell from your EXEC example, but are you using full paths? Perhaps the PATH variable is an issue?
While not shown, I'm specifying the full paths to ANT and JAVA since I need to specify the ANT/JDK exactly to ensure I'm running the specific versions desired.
How are you invoking the generated build file with the wsgen tasks (wsgen-build.xml) that is having the problem?
The "main" build file invokes an EXEC on the "codegen" build file, as per the example above. The only difference is a different build target, one target is used for APT, another is used for the WSGEN piece. In said WSGEN target within the "codegen" build file, the wsgen-build.xml is currently imported and the target invoked via ANTCALL. I've also tried using just the ANT task, both have the same result.
Can you run your EXEC commands from the command shell?
Yes, if I run the step on the command line, it either works or not depending on if Java 7 or 8 is set as JAVA_HOME and which appears first on the PATH. For example, if I update JAVA_HOME to be Java 8, I get the following error: java.lang.NoClassDefFoundError: com/sun/mirror/apt/AnnotationProcessorFactory. This is correct since that class no longer exists in Java 8 (but does exist in 7).
Have you tried using a script or batch file instead of using EXEC on the ANT executable?
Yes, same error resulted. I tried this with a simple .bat file that set the requisite environment variables and invoked the command from the shell.
What OS are you on, have you tried a different machine/OS to see if this is environment specific?
I have a Win7 & a RHEL 7 environment, this problem happens in both and I'm using the osfamily attribute to conditional-ize the EXEC command per platform.
Ant 1.7.0 is pretty old, have you tried an updated Ant?
Same issue occurs using Ant 1.9.4 as well. The 1.9.4 Ant only runs for the "main" build file, the "codegen" build file will use 1.7.0 since I'm explicitly setting that via the EXEC tasks. I did try 1.9.4 even for the "codegen" ant build file, but it made no difference in the error.
If you managed to read thru this in its entirety, my hat off to you sir or madam! Thank you for any insight/advice you may have!

Calling Ant from Nant without batch

We have an automated build process that uses Nant scripts, and we just inherited a Java project that has an existing, working Ant script. I have searched all over and it seems most people use a batch file to call Ant scripts from Nant. This seems a little hacky to me. Is there a way to call Ant scripts directly from Nant in task format?
This similar question addresses it with a batch file but I want to do it without.
<!-- calling the Ant build Process-->
<target name="AntBuildProcess" description="Created to call the Ant build during this NAnt build process" >
<echo message="Starting the Ant Build Process..." />
<exec program="ant" commandline='-buildfile YourAntBuild.xml' failonerror="true"/>
</target>
Then during your Build process, you just call this target at the point you need it to be built. <call target="AntBuildProcess" />
Ant is a batch file. Take a look, and you'll see a file called ant.bat in the %ANT_HOME%\bin directory.
Ant is actually a Java program, so you could launch it from the java command by running the class org.apache.tools.ant.launch.Launcher which is basically what the ant.bat file is doing.
However, why reinvent the wheel? The ant.bat runs the Java command the right way, gives you options to change the way it's executed, and makes sure everything is setup correctly.
Addendum
I see, Nant, unlike Ant, will always call cmd.exe and use suffixes and %PATHEXEC% to figure out if something is a batch script or other type of script. Thus, if you want to run Ant using Ant as a batch script via <exec/> you would do this:
<exec executable="cmd.exe"
dir="${working.dir}">
<arg value="/c"/>
<arg value="ant.bat"/>
</exec>
However, in Nant you can simply do it this way:
<exec program="ant"
workingdir=${working.dir}"/>

#Grab annotation fails under Ant

I'm using a #Grab annotation to grab the definition of an Html parser I can give to the XMLSlurper (I think it's the tagsoup parser) and all is good when I run my script from the cmd line. If I invoke the same script from Ant I get an Ivy NoClassDefFound error. I think it may berelated to having Ivy in Antlib. Is there another way to parse Html without customizing the slurper via #Grab?
This:
#Grab(group='org.ccil.cowan.tagsoup', module='tagsoup', version='1.2' )
doc = new XmlSlurper(new org.ccil.cowan.tagsoup.Parser()).parse(confluenceWebPageInputStream)
Works just fine from the command line but when I run it from an Ant build target:
<target name="update-wiki-chart">
<echo message="Will update chart for version ${version}"/>
<java dir="${basedir}" classname="groovy.lang.GroovyShell">
<arg value="ParseWikiPage.groovy"/>
<classpath refid="groovylib"/>
</java>
</target>
where groovyLib is a path ref pointing to the Groovy-1.8.6 jar downloaded from our internal Nexus repo, I get the NoClassDefFound error. I'm thinking this is probably due to having Ivy installed in Antlib causing the class loader to find it in two places. I just thought of something while writing this post. I can probably run java in forked mode or do something to cause it to not see/share Ant's classpath.It's been a few years since I've wrestled w/ Ant and class loader issues. My project is a little delinquent due to the bug and I'm looking for a quick/easy fix.
I just tried running my groovy on the cmd line via the "java" cmd and loading groovy-all jar in the class path and I realized that I get the NoClassDefFound error there as well. It has nothing to do with collisions with Ivy under Antlib. Rather, I am missing Ivy altogether. I had assumed it was included in Groovy-all.jar. I just need a clever way of passing Ivy from AntLib into my java task to get this all up and running.
Sounds like you're missing one or more jars from the classpath. I'd suggest digging around the classpathref you've labelled "groovylib".
A less error prone way to launch groovy from within ANT using the groovy ANT task.
Here's a similar example to your use-case:
Parse HTML using with an Ant Script
My example uses ivy directly to manage all build dependencies. The Grab annotations are still supported but obviously these would only manage the dependencies of the groovy script.

Working with ant namespace / directory structure

I'm working with ant on linux using the command line. I've got a simple build step with javac. This compiles correctly but it creates a directory structure under build like my namespace.
ie: ./build/com/crosse/samplespace/SampleProgram.class
How can I get this output into one directory (where it's easier to call java on it).
I tried
<target name="output" depends="compile">
<copy todir="${output}">
<fileset dir="${build}" includes="**/*.class"/>
</copy>
</target>
but that repeats the same thing in my output directory. How can I use ant to get everything into a single directory?
Alternatively how could I use an ant step to copy another file into the root of that path (an apache commons configuration file)?
Edit: This is mainly a convenience factor, I get tired of navigating through the same 3 directories to run my program.
What about creating a jar file containing all your files (classes and other resources)?
A jar also has a 'default' class the main method of which gets executed when the user double clicks it or calls it using java -jar <jar-file>.
You can use the Ant jar task.
Beside that, it is important that you keep your directory structure (outside a jar), otherwise the java class loader won't be happy when it has to load these classes. Or did I get your question wrong?
To answer your question, you would use a flatten mapper, as carej points out. The copy task even has a shortcut for it:
<copy todir="${output}" flatten="yes">
<fileset dir="${build}" includes="**/*.class"/>
</copy>
However, I also don't understand what you hope to accomplish with this. The java runtime expects class files to be in a directory structure that mirrors the package structure. Copying the class files to the top-level directory won't work, unless the classes are in the default package (there is no package statement in the .java files).
If you want to avoid having to manually change directories, you can always add a target to your ant build file that calls the java task with the appropriate classpath and other parameters. For example:
<target name="run" depends="compile">
<java classname="com.crosse.samplespace.SampleProgram"
classpath=".:build"/>
</target>
You can accomplish what you want to do by using the flattenmapper but I'm at a loss to understand what possible valid reason you'd have for doing it.

How to load an optional task into ant without -lib or global installation?

I want to use the FTP task in ant, and I have found the appropriate jar files and got everything working fine. I have put the jar files in a "libs" directory alongside the other files used in the build. The only problem is that the user must run "ant -lib commons-net-ftp-2.0.jar" to make a build; I would really prefer that it were possible to just run "ant" with no arguments.
Reading the ant optional tasks intallation page, I see that there are five ways one can load up extra libraries in ant, and none of them are really what I'm looking for. I do not want to force the user to make any modifications to their system to run this task; it should be possible to just load it from the "libs" directory inside of our product's source folder. So that means setting the global CLASSPATH is also out (which is a bad idea anyways).
The last option, as noted in the documentation, is the preferred approach... loading the jarfiles individually from the build script itself. I have done this in the past with the ant-contrib tasks and JUnit, and would like to do that here, but I don't see how I can accomplish this. The FTP task doesn't support a nested classpath element, and I don't know the XML resource I would need to load this library via a taskdef. How can I load the libraries from within ant?
Edit: In response to the answers and questions which have been posted here so far, I'm using ant 1.7.1. Making an ftp taskdef definitely does not work; that throws the following error:
BUILD FAILED
/my/path/build.xml:13: taskdef class org.apache.tools.ant.taskdefs.optional.net.FTP cannot be found
Perhaps this is because the classname is wrong. How exactly do I find the classname I'm supposed to use if I only have a jarfile? It's not documented anywhere, and I couldn't find anything in the jar itself resembling that path.
The problem you are having is due to the different class-loaders in use. The Commons Net classes must be loaded by the same class-loader that loads the FTP task. Because the FTP task is loaded by Ant on start-up, you need to add the Commons Net to Ant's classpath so that it is loaded by the same class-loader. That's why the documentation gives you 4 different ways to do this.
I agree that none of them are ideal (the CLASSPATH environment variable being the worst). One way around this is to supply a shell script with your project that invokes Ant and passes the apporpriate -lib argument. You then get people to use this rather than invoking Ant directly. In fact, you could deviously name it 'ant' so that it gets run instead of the existing 'ant' on the path (this only works if the current directory is on the path, ahead of other directories).
The fifth option in the documentation is great in theory. They finally fixed the class-loading problems in 1.7.0. Unfortunately, as you mention, nobody retro-fitted the FTP task to take a classpath. You could try submitting an enhancement request, but this won't help in the short term.
There is one other option, which isn't any better than the others. Instead of making sure that the Commons Net classes are loaded by the class-loader that loads the FTP task, you could make sure that the FTP task is loaded by the class-loader that loads the Commons Net classes. To do this you have to remove the ant-commons-lib.jar file from the 'lib' directory of the Ant installation. This means that the FTP task won't get loaded on start-up. This is actually why the optional tasks are broken up into so many separate JARs - so that they can be individually removed. Put this JAR file alongside the Commons Net JAR file so that it can be loaded at the same time. Then you can do something like this (I tried this and it works):
<taskdef name="ftp"
classname="org.apache.tools.ant.taskdefs.optional.net.FTP">
<classpath>
<pathelement location="${basedir}/lib/ant-commons-net.jar"/>
<pathelement location="${basedir}/lib/commons-net-2.0.jar"/>
</classpath>
</taskdef>
<ftp server="yourserver.com"
userid="anonymous"
password="blah">
<fileset dir="somedirectory"/>
</ftp>
But this is probably a worse option than just using the -lib switch (with or without a wrapper script). The only other thing I can think of is to try to find a third-party FTP task to use instead of the default one.
I have a solution:
you can download a new "classloader" task from http://enitsys.sourceforge.net/ant-classloadertask/ and load it whith:
<taskdef resource="net/jtools/classloadertask/antlib.xml"
classpath="XXX/ant-classloadertask.jar"/>
Naw can do things like loading classes with the same classloader that ant use for his task:
<classloader loader="system" classpath="XXX/commons-net-2.0.jar"/>
or "loader="project""
Then you definde your task:
<taskdef name="ftp" classname="org.apache.tools.ant.taskdefs.optional.net.FTP"/>
and go :-)
So I succeeded in doing this for the ant-salesforce.jar that you get when trying to do salesforce work (fun...)
Check to see if the jar has an xml file in it that looks something like this:
<antlib>
<typedef name="compileAndTest" classname="com.salesforce.ant.CompileAndTest"/>
....
</antlib>
Then in ant give it a taskdev that reads that file from inside the given jar, like this:
<taskdef resource="com/salesforce/antlib.xml" classpath="lib/ant-salesforce.jar" />
Hope that helps some.
Ah, man, this is just so nasty. I run ant from eclipse. I don't want to reconfigure ant in eclipse for new workspaces, so here's what I decided to do, to decouple running the task and configuring ant. I extracted the ftp task to a separate build file. Next I added a native call to the command line to start a completely new ant process with the required libraries on the path:
<target name="deploy-ftp">
<exec command="ant">
<arg line="-buildfile ftp.xml deploy-ftp -lib lib/ant"/>
</exec>
</target>
Now the master build file can be run without any special arguments and no modifications are required to the ant installation. It's nasty though, since the ftp task runs in a completely clean environment. None of the properties and paths from the master build file are available. Luckily I had all of these in a separate property file anyway, so I only needed a single import.
I would like to add a big thanks to Dan Dyer. Without your extensive explanation of what's going on behind the scenes, I wouldn't have found this solution.
Will this work assuming libs is directly under you project's base directory
<taskdef name="ftp" classname="org.apache.tools.ant.taskdefs.optional.net.FTP">
<classpath>
<pathelement location="${basedir}\libs\commons-net-1.4.0.jar"/>
</classpath>
</taskdef>
Your users all have ant installed on their machines but you can't / don't want to make them add the FTP jar? Can you bundle ant with your project make tasks that call YOUR ant bundle, with the jars placed so it'll work as follows?
<taskdef name="ftp" classname="org.apache.tools.ant.taskdefs.optional.net.FTP">
<classpath>
<pathelement location="\lib\commons-net-1.4.0.jar"/>
</classpath>
</taskdef>
<target name="testFtp">
<ftp server="blah" userid="foo" password="bar">
<fileset file="test.file" />
</ftp>
</target>

Resources