T-Bull T-Bull - 7 months ago 51
Java Question

use system classpath for ant javac task

I want the javac task to use jars from the system classpath, by which I mean the classpath that is set in the shell's environment before ant is started. That classpath is

CLASSPATH=D:\local\lib\java\*;.;C:\lib\java\*;C:\lib\java\db\*


on my system. I have popular jars there that are used by many projects. The basic snippet I use in the build file is

<target name="build">
<mkdir dir="${obj}" />
<javac srcdir="${src}" destdir="${obj}"
includes="**/*.java"
excludes="**/package-info.java **/deprecated/*.java"
includeAntRuntime="no" debug="true" debuglevel="source,lines"
>
<compilerarg value="-Xlint"/>
</javac>
</target>


That way ant only passes the output directory as classpath.

[javac] '-classpath'
[javac] 'D:\dev\tbull-projects\jsonc\obj'


(jsonc is the project I'm working on, and
D:\dev\tbull-projects\jsonc
is the working directory.)
I browsed the documentation for a while and came up with two attempts. First one was adding the attribute
classpath="${java.class.path}"
to the javac tag. That would pass a tremendously long classpath to the compiler, listing every single jar from ant's own lib directory and finally tools.jar from the JDK. Not the classpath that I wanted.

The second shot was setting

<property name="build.sysclasspath" value="first" />


before javac was invoked, and that got me in the right direction. Now these lines were among the output:

dropping D:\dev\tbull-projects\jsonc\D:\local\lib\java\* from path as it doesn't exist
dropping D:\dev\tbull-projects\jsonc\C:\lib\java\* from path as it doesn't exist
dropping D:\dev\tbull-projects\jsonc\C:\lib\java\db\* from path as it doesn't exist
dropping D:\dev\tbull-projects\jsonc\C:\Program Files\Java\jdk1.6.0_18\jre\lib\sunrsasign.jar from path as it doesn't exist
dropping D:\dev\tbull-projects\jsonc\C:\Program Files\Java\jdk1.6.0_18\jre\classes from path as it doesn't exist


Well, you can imagine that these paths really don't exist. I just don't get why ant constructed them this way. It would know how to do path arithmetic on Windows, would it?

Maybe my approach is flawed more fundamentally, so I'll let you know what I'm actually after. So I'm developing this project (a library), which uses another library. The project is gonna be open source, so I want other developers to be able to build it after they have downloaded the dependency library and placed it somewhere in their classpath.

From what I saw in other questions about ant+classpath, it appears that it's a custom fashion to distribute the dependency libs with the source code (so the classpath can be just like ./libs). But I surely don't want to have jars in my git repo. So how could that be done?

Answer

Soo... seems I have to answer the question myself. Passing the original classpath to the javac task can be achieved with this:

<!-- load environment into the env property -->
<property environment="env" />

<javac srcdir="${src}" destdir="${obj}"
    includes="**/*.java"
    excludes="**/package-info.java **/deprecated/*.java"
    includeAntRuntime="no" includeJavaRuntime="no"
    debug="true" debuglevel="source,lines"
>
    <!-- add -classpath option manually -->
    <compilerarg value="-classpath" />
    <compilerarg value="${env.CLASSPATH}" />
    <compilerarg value="-Xlint"/>
</javac>

That does the trick at least so far that the javac task now gets passed the correct classpath. Yet it will still not work, javac now spits these complaints:

[javac] warning: [path] bad path element "D:\local\lib\java\*": no such file or directory
[javac] warning: [path] bad path element "C:\lib\java\*": no such file or directory
[javac] warning: [path] bad path element "C:\lib\java\db\*": no such file or directory

This is a straight lie, these paths do very much exist. I use them all the time, and if I manually craft an equivalent javac invocation at the shell, it works like a charm. I suspect ant's javac doesn't resolve the jar files in those directories. I have to examine that.

UPDATE

It is indeed as I suspected, the wildcard is not resolved to the individual present jar files by the javac task. I managed to do the resolving manually, and now it works as it should. And that resolving was in fact a struggle on its own. So I'll leave the solution here for those poor souls fighting the same stupidity, hopefully before they ask people that have nothing else to do than bullshitting around (yes Anon, talking about you).

Turns out, ant lacks the most basic functionality that you would expect from a build tool. Also turns out that I'm not the first one to notice that. While solutions are rare, there is a very good post about Using JavaScript to make Apache Ant less painful, which really saved my day. Yes, ant can indeed be scripted, which seems not to be widely known, although it is not kept secret. You can safely assume, that Javascript is already available without installing additional libraries if you run ant on Java 6.

Soo... down to business. Here is the thing:

<target name="expand_classpath">
    <script language="javascript"><![CDATA[
        // the original classpath
        var ocp = java.lang.System.getenv("CLASSPATH");
        //  ... split in parts
        var ocp_parts = ocp.split(project.getProperty("path.separator"));

        // where our individual jar filenames go,
        //  together with pure directories from ocp_parts
        var expanded_parts = [ ];

        for each (var part in ocp_parts) {
            if (part.endsWith('*')) {
                var dir = part.substring(0, part.length() - 1);
                var f = new java.io.File(dir);

                // don't know how to construct a java.io.FilenameFilter,
                //  therefore filter the filenames manually
                for each (var file in f.listFiles())
                    if (file.getPath().endsWith('.jar'))
                        expanded_parts.push(file.getPath());
            } else
                expanded_parts.push(part);
        }

        var expanded = expanded_parts.join(project.getProperty("path.separator"));
        project.setProperty("classpath.expanded", expanded);
    ]]></script>

    <!-- <echo message="classpath.expanded = ${classpath.expanded}" /> -->
</target>

<target name="build" depends="expand_classpath">
    <mkdir dir="${obj}" />

    <javac srcdir="${src}" destdir="${obj}"
        classpath="${classpath.expanded}"
        includes="**/*.java"
        excludes="**/package-info.java **/deprecated/*.java"
        includeAntRuntime="no" includeJavaRuntime="no"
        debug="true" debuglevel="source,lines"
    >
        <compilerarg value="-Xlint"/>
        <compilerarg value="-Xlint:-fallthrough"/>
    </javac>
</target>