7

I have an Ant build.xml file that works just fine on the command line: it compiles, builds the JAR, and I am able to execute the main method from the JAR just fine. The build.xml file references several thirdparty libraries that are scattered here and there. When building the JAR, the script doesn't include all the thirdparty libraries into the JAR itself. Instead, it puts their path into the JAR's manifest. This helps to keep my JAR slim and tidy.

I'd like to be able to edit and debug my project in Eclipse, but I can't find an easy way to do so. I can have my project use the Ant file to build the project, and that seems to work. However, Eclipse is having trouble finding the thirdparty libaries, and thus Eclipse is having two problems:

  1. it shows (in the text editor) lots of compile errors, because lots of classes are undefined, and
  2. it can't execute the JAR.

I can solve both of the above problems by specifying by hand, in two difference places (i.e., the build path via Properties->Java Build Path->Libraries, and the execution classpath via Run Configurations->Classpath), all the third party libraries. But it seems like I shouldn't have to do this manually, since all the third party libraries are already listed in my JAR's manifest. What am I doing wrong?

Here's my build.xml file:

<!-- Set global properties for this build -->
<property name="src"         location="./src" />
<property name="build"       location="./build"/>
<property name="dist"        location="./dist"/>
<property name="logs"        location="./logs"/>
<property name="docs"        location="./docs"/>
<property name="jar"         location="${dist}/dynamic_analyzer.jar"/>
<property name="lib"         location="../../thirdparty/lib"/>
<property name="hive-util"   location="../../hive-utils/dist"/>
<property name="hpdb"        location="../../hive-db/hpdb/dist"/>
<property name="static"      location="../../hive-backend/static_analyzer/dist"/>
<property name="mainclass"   value="com.datawarellc.main.DynamicMain"/>

<path id="dep.runtime">
    <fileset dir="${lib}"       includes="**/*.jar"/>
    <fileset dir="${hive-util}" includes="**/*.jar"/>
    <fileset dir="${hpdb}"      includes="**/*.jar"/>
    <fileset dir="${static}"    includes="**/*.jar"/>
</path>

<target name="clean">
    <delete dir="${build}"/>
    <delete dir="${dist}"/>
    <delete dir="${docs}"/>
    <delete dir="${logs}"/>
</target>

<target name="init">
    <tstamp/>
    <mkdir dir="${build}"/>
    <mkdir dir="${dist}"/>
    <mkdir dir="${logs}"/>
</target>

<target name="compile" depends="init">
    <javac srcdir="${src}" destdir="${build}" debug="on" includeantruntime="false">
        <classpath refid="dep.runtime" />
    </javac>

    <!-- Debug output of classpath -->
    <property name="myclasspath" refid="dep.runtime"/>
    <echo message="Classpath = ${myclasspath}"/>

</target>

<target name="jar" depends="compile">
    <!-- Put the classpath in the manifest -->
    <manifestclasspath property="manifest_cp" jarfile="${jar}" maxParentLevels="10">
        <classpath refid="dep.runtime" />
    </manifestclasspath>

    <jar jarfile="${jar}" basedir="${build}">
        <manifest>
            <attribute name="Main-Class" value="${mainclass}"/>
            <attribute name="Class-Path" value="${manifest_cp}"/>
        </manifest>
        <zipfileset dir="${src}" includes="**/*.xml" />
    </jar>
</target>

You can see that I have third-party libraries in several directories (${lib}, ${hive-util}, ${hpdb}, and ${static}). I use these to create a path called dep.runtime. I then include dep.runtime in the manifest when building my jar. How can I get Eclipse to use the same dep.runtime for the build path and the classpath when executing?

5
  • Check the documentation here: help.eclipse.org/juno/… (I'd make this an answer, but I don't have an Eclipse installation on hand) Commented Jun 4, 2013 at 12:28
  • @leeand00 I don't see how that documentation helps me. The menu that the link talks about is exactly the menu I'm using to manually add all the third party libraries (as a temporary workaround), but that's precisely what I'm trying to avoid. Commented Jun 4, 2013 at 12:35
  • There should be a button that says add directory or add classpath in there, and that should do the trick...let me know if it doesn't. Commented Jun 4, 2013 at 12:40
  • Oh wait here: stackoverflow.com/questions/3357215/… Commented Jun 4, 2013 at 12:41
  • @leeand00 Let me see if I understand what you're trying to say: I should write a script that edits the .classpath file. I should then call the script from my build.xml file, passing the contents of the dep.runtime variable to the script. Something like that? Commented Jun 4, 2013 at 13:02

2 Answers 2

3

An alternative to perl is to use an embedded groovy task:

<project name="demo" default="eclipse-files">

    <property name="src.dir"     location="src"/>
    <property name="classes.dir" location="build/classes"/>

    <path id="dep.runtime">
       <fileset dir="${lib}"       includes="**/*.jar"/>
       <fileset dir="${hive-util}" includes="**/*.jar"/>
       <fileset dir="${hpdb}"      includes="**/*.jar"/>
       <fileset dir="${static}"    includes="**/*.jar"/>
    </path>

    <target name="bootstrap">
        <mkdir dir="${user.home}/.ant/lib"/>
        <get dest="${user.home}/.ant/lib/groovy-all.jar" src="http://search.maven.org/remotecontent?filepath=org/codehaus/groovy/groovy-all/2.1.4/groovy-all-2.1.4.jar"/>
    </target>

    <target name="eclipse-files">
        <taskdef name="groovy" classname="org.codehaus.groovy.ant.Groovy"/>
        <groovy>
            import groovy.xml.MarkupBuilder

            project.log "Creating .classpath"

            new File(".classpath").withWriter { writer ->
                def xml = new MarkupBuilder(writer)

                xml.classpath() {
                    classpathentry(kind:"src",    path:properties["src.dir"])
                    classpathentry(kind:"output", path:properties["classes.dir"])
                    classpathentry(kind:"con",    path:"org.eclipse.jdt.launching.JRE_CONTAINER")

                    project.references."dep.runtime".each {
                        classpathentry(kind:"lib", path:it)
                    }
                }
            }
        </groovy>
    </target>

    <target name="clean">
        <delete file=".classpath"/>
    </target>

</project>

Notes:

  • The bootstrap target will download the 3rd party groovy jar (No dependency on perl)
  • Groovy can access the "dep.runtime" ANT path directly and iterate over its contents
  • Groovy has excellent support for writing XML files.

The following answer is similar and additionally generates the Eclipse .project file.

Sign up to request clarification or add additional context in comments.

2 Comments

+1 Pretty cool approach. I'm no expert in Groovy, but it's nice to have an all-Ant solution.
BTW I accepted my answer instead of yours because for me, using Perl is easier, despite the advantages of Groovy. But I imagine lots of people finding your solution just as good.
2

I came up with the following workaround, inspired by the link provided by @leeand00.

First, I wrote a simple Perl script (called genClasspath.pl) that generates the .classpath file that Eclipse uses.

#!/usr/bin/perl
use strict;

if (@ARGV != 2) {
  print STDERR "Usage: $0 OUTFILE CLASSPATHSTRING\n";
  print STDERR "e.g., $0 .classpath path1:path2:path3\n";
  exit 1;
}

my $OUTFILE         = $ARGV[0];
my $CLASSPATHSTRING = $ARGV[1];

open my $out_fh, '>', $OUTFILE or die "Couldn't open output file: $!";

print $out_fh q{<?xml version="1.0" encoding="UTF-8"?>
<classpath>
    <classpathentry kind="src" path="src"/>
    <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
    <classpathentry kind="output" path="build"/>
};

my @libs = split(":", $CLASSPATHSTRING);
foreach my $thisLib (@libs){
    print $out_fh "    <classpathentry kind=\"lib\" path=\"$thisLib\"/>\n";
}
print $out_fh "</classpath>\n";

Then, I have my build.xml file call this script with the content of dep.runtime:

<target name="compile" depends="init">
    <javac srcdir="${src}" destdir="${build}" debug="on" includeantruntime="false">
        <classpath refid="dep.runtime" />
    </javac>

    <property name="myclasspath" refid="dep.runtime"/>

    <exec dir="." executable="../../scripts/genClasspath.pl" os="Linux">
        <arg value=".classpath"/>
        <arg value="${myclasspath}"/>
    </exec>

</target>

The only catch is that I need to run Ant on the command line at least once before I open the project in Eclipse. But when I do, Eclipse is able to compile and execute my project just fine, since the classpath is exactly the same as Ant's.

2 Comments

Just a note that Maven has this functionality. I realize that's probably not an option, so I'm leaving a comment, not an answer, but Maven can generate Eclipse project files, and Eclipse with m2e installed will automatically add classpath entries when you modify the dependencies.
@davidfmatheson is right (but your question address ant, so that's what I went with...)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.