使用系统类路径来进行Ant javac任务

6

我希望javac任务能够使用系统类路径中的jar包,这里所说的系统类路径是指在启动ant之前在shell环境中设置的类路径。该类路径为:

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

在我的系统上,我有许多项目都使用的流行jar包。我在构建文件中使用的基本代码片段是:

<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>

那样,ant只会将输出目录作为类路径传递。
[javac] '-classpath'
[javac] 'D:\dev\tbull-projects\jsonc\obj'

(jsonc是我正在工作的项目,D:\dev\tbull-projects\jsonc是工作目录。)我浏览了一段时间的文档,并尝试了两种方法。第一种是在javac标签中添加属性 classpath =“ $ {java.class.path}”。这将向编译器传递一个非常长的类路径,列出ant自己的lib目录中的每个单独的jar文件,最终是来自JDK的tools.jar。这不是我想要的类路径。

第二次尝试是设置

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

在调用javac之前,这让我朝着正确的方向前进。现在这些行是输出中的一部分:

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

好的,你可以想象这些路径实际上并不存在。我不明白为什么Ant会这样构建它们。难道它不知道如何在Windows上进行路径运算吗?
也许我的方法在更根本的方面存在缺陷,所以我会告诉你我实际上想要什么。我正在开发这个项目(一个库),它使用另一个库。该项目将是开源的,因此我希望其他开发人员在下载依赖库并将其放置在他们的类路径中之后能够构建它。
从我在其他关于Ant+classpath的问题中看到的情况来看,似乎将依赖库与源代码一起分发是一种自定义方式(因此类路径可以像./libs一样)。但我肯定不想在我的git repo中有jar文件。那怎么办呢?

7
如果针对每个使用“CLASSPATH”环境变量导致构建失败的人我都能得到一个美元,我就可以退休了。不要这样做。无论你认为自己得到了什么好处,事实上你并没有,而且你会给自己或别人带来麻烦。 - Anon
2
Maven可能是很多东西,但易用性不是其中之一。它强制你按照它的方式去做事情。你的类路径建议是正确的,Anon,但我不同意关于Maven的评论。 - duffymo
2
不是我——我的已经正常工作了,而且它足够通用,所以我可以在各个地方重复使用它。对于我来说,这真的是一个 XML make 文件。我讨厌 Maven 强制我符合他们的目录布局——毫无例外。请告诉我有没有一种定制它的方法,我会善意地考虑你和 Maven。 - duffymo
2
我更喜欢一个不被强制的世界。我有想要按照自己的方式做事的充分理由,这与类路径无关。谢谢,我知道如何很好地管理它。 - duffymo
2
不感兴趣。Maven 不是它。 - duffymo
显示剩余7条评论
8个回答

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

+1 是为了至少尝试回答我的问题,与该帖子中无知的人形成对比。不幸的是,答案是不正确的。includeJavaRuntime 通过 JRE jars 的路径(例如在我的情况下为 C:\Program Files\Java\jdk1.6.0_18\jre\lib\rt.jar)扩展了 classpath 参数。原始定义的 cp 不会出现。 - T-Bull

1
为什么你不在Ant中设置CLASSPATH呢?它非常适合做这件事。如果你做其他的事情,那么你就犯了一个错误。不仅它可以工作,而且你的build.xml也会记录下要求。

我认为每个开发者都应该有定义自己的类路径(或任何其他环境变量)的可能性(毕竟,这就是它们存在的原因)。 (我不是在谈论公司网络,而是独立的开发者。) - T-Bull
附加说明:如果我在构建文件中硬编码cp,每个开发者都必须为自己的环境进行编辑,这不仅令人不悦,而且在合并更改时也会导致冲突。我们不是都学过硬编码是不好的吗? - T-Bull
不,依赖项应该在你的项目中的/lib和/test-lib文件中。我同意开发人员依赖类路径是不好的,但这不是我推荐的。 - duffymo

1
当javac编译代码时,它会尝试在一个叫做ct.sym的符号文件中查找rt.jar中的文件(该文件也存在于lib目录中)。但是,该符号文件中缺少一些文件。我需要添加一个编译选项来忽略符号文件并直接查找rt.jar。因此,我使用了-XDignore.symbol.file选项,在ant中将该值放入javac标签中。如果您使用eclipse或其他任何ide,则它可以完美地工作。
<compilerarg value="-XDignore.symbol.file"/> 

如果在使用rt.jar中的类时出现ClassNotFoundException错误,并且该类仍然存在,则可以尝试在Java编译器中添加此参数。

要从ant引用rt.jar,您可以使用以下方法:

<fileset dir="${java.home}/lib" includes="rt.jar"/>

这里找到了原始细节:http://www.javaroots.com/2013/01/javac-error-using-classes-from-rtjar.html


0

如果有人对Java/ANT世界还是新手,建议使用Maven的人是白痴,KISS原则呢?

楼主,不要用那个JavaScript的可耻东西了,试试这个。

<project default="build">
<property name="src" value="src" />
<property name="obj" value="obj" />

<property name="parent.dir" value="/jakarta-tomcat/common/lib" />
<path id="project.class.path">
    <pathelement location="lib/" />
    <fileset dir="${parent.dir}" includes="**/*.jar" />
</path>

<target name="build">
    <delete dir="${obj}" />
    <mkdir dir="${obj}" />
    <javac srcdir="${src}" destdir="${obj}" includes="**/*.java" excludes="**/package-info.java **/deprecated/*.java" debug="true" debuglevel="source,lines" classpathref="project.class.path" />
</target>


你可以通过在ant文件中硬编码一个位置并包含该位置下的所有jar来提供一个手工制作的类路径。这与我的问题有什么关系呢?虽然我很重视KISS原则,并且它确实是这个问题背后的推动力量(尽管我的最终答案似乎不太适合),但我不会对你进行贬值评价。 - T-Bull

0

很明显,java和(或至少)ant的开发人员真的非常不希望看到$CLASSPATH成为用户安装的库的存储方式,而其他主流语言(如C/C++、perl、python、ruby等)中有95%的语言都使用这种方式。因此,如果您习惯于大多数其他主流语言的通用编程,那么这将是一个艰难的范例。

这种不情愿甚至到了ant 故意从环境中剥离$CLASSPATH的地步,但绕过这个问题的简单方法就是使用另一个变量。

 <property name="classpath" location="${env.JAVALIBPATH}"/>

这将可以毫不费力地与<javac><java>命令一起使用(classpath="${classpath}),这很好,因为如果你尝试使用以下内容:

 <property name="classpath" location="${env.CLASSPATH}"/>

<java> 中没有 includeAntRuntime="false" 选项可以让它工作。你无法将 $CLASSPATH加入,而且有人已经确保过了(显然没有添加乏味的 JavaScript 骇客)。

当然,这意味着你需要使用一个单独的环境变量,在你的分布式/生产版本中坚持使用 Java 的“抱歉,没有用户库!”范例。如果你使用一个名称,如果涉及到它,它几乎肯定在目标系统上未定义,那么这不是一个大问题。


-1

或者,还有Maven Ant任务。这些将允许您以比Ivy更清晰的方式使用Maven的依赖机制。但它仍然不是一个很好的解决方案。


而且,补充一下:我记得你说你正在创建一个开源库。我认识的大部分专业程序员不会使用没有在Maven中央仓库可用的库。至少,Maven Ant任务可以让你创建可部署的构件。 - Anon

-1

看来我得自己回答这个问题了。将原始的类路径传递给javac任务可以通过以下方式实现:

<!-- 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>

至少目前来看,这个技巧可以让javac任务传递正确的类路径。然而它仍然无法工作,javac现在会抛出这些投诉:

[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

这是一次直接的谎言,这些路径确实存在。我经常使用它们,如果我在shell中手动编写等效的javac调用,它可以完美运行。我怀疑ant的javac没有解析那些目录中的jar文件。我必须检查一下。

更新

正如我所怀疑的那样,通配符未被javac任务解析为单个现有的jar文件。我设法手动解决了这个问题,现在它可以像应该一样工作了。而解决这个问题本身就是一场斗争。因此,我将在此留下解决方案,供那些与相同愚蠢作斗争的可怜人使用,希望在他们询问那些没有别的事情可做而胡扯的人(是的,匿名者,我指的就是你)之前看到它。

原来,Ant缺乏你从构建工具中期望的最基本功能。而且我不是第一个注意到这一点的人。虽然解决方案很少,但确实有一篇非常好的文章(使用JavaScript使Apache Ant更轻松),真正拯救了我的一天。是的,Ant确实可以编写脚本,这似乎并不广为人知,尽管它没有保密。如果你在Java 6上运行Ant,可以安全地假设JavaScript已经可用,无需安装其他库。
所以...进入正题。以下是要点:
<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>

3
这可能是我在SO上见过的最愚蠢的帖子。首先你抱怨工具,然后又抱怨那些试图教你如何正确使用工具的人。然后你发了这个内容。如果你想要一个shell脚本,为什么不直接写一个呢?-1分,如果我能发布反赏金,我一定会的。 - kdgregory
1
+1 因为你真正地理由为什么要否决我。 :-) 但是:我已经几次要求人们教育我,但是他们拒绝这样做,因此我不得不认为他们实际上不知道自己在谈论什么。(只是说“不要那样做”而没有任何解释并不是老师说话的方式,而是白痴的说法。)关于 shell 脚本,我非常重视它们的灵活性,但它们不幸的是不跨平台。make 也是如此。我之所以选择 ant 是因为它最接近传统 make 文件。 - T-Bull

-2
我会假设你的“热门”JAR文件是众所周知的开源项目。这意味着它们可以在Maven中央仓库中获取。
尽管我认为使用Maven是这个问题的最佳答案,但你也可以使用Ant的任务来实现一些黑客技巧。例如,要下载JUnit JAR(可能会有拼写错误):
<property name="dependency.dir" value="${basedir}/dependencies"/>

<property name="junit.jar" value="junit-4.8.2.jar"/>
<property name="junit.url" value="http://search.maven.org/remotecontent?filepath=junit/junit/4.8.2/${junit.jar}"/>

<target name="download.dependencies">
    <mkdir dir="${dependency.dir}/>
    <get url="${junit.url}" dest="${dependency.dir}/${junit.jar}"/>
</target>

当然,如果你这样做,那么你必须仔细配置你的构建脚本,以便你不会在每次运行时都进行下载。而且你会增加对Maven中央仓库的负载。

1
你能告诉我在我的文本中,我确切地询问了如何下载依赖项的位置吗?可能不行。如果可以的话,我会像扣掉10分那样踩你的这篇愚蠢的帖子。拜托,请走开! - T-Bull

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接