Ant Javac Task Deletes Method - java

I have run into some trully bizarre behavior in a Java project. The situation is that javac seems to be removing a method from a class during compilation.
The class looks something like this:
public class MessedUp extends X {
//a bunch of variables here
//a bunch of methods here
public void thisDisappears(String arg){
}
//a bunch more methods here
}
There is another class that instantiates and calls this method:
public class WontCompile {
public void doSomething(){
MessedUp mu = new MessedUp();
mu.thisDisappears("something");
}
}
The first class compiles just fine, but the second doesn't. javac outputs something like the following:
[javac] C:\mypath\WontCompile.java:251: error: cannot find symbol
[javac] mu.thisDisappears("something");
[javac] ^
[javac] symbol: method thisDisappears(String)
[javac] location: variable mu of type MessedUp
I know that the code is fine because I have been using it in Eclipse for a couple of years (I'm tracking down this problem as I try to use the ant file Eclipse produced). However, once in a while Eclipse will highlight the call to thisDisappears, saying it doesn't exist, and offering to create it. If accept the offer, then Eclipse complains that there are two methods with the same name. After a little bit of finagling which apparently causes a rebuild or something, the error goes away.
After being driven up the while for a while, I decided to check the actual class file for MessedUp.java. Using the Java Decompiler GUI, I found that thisDisappears isn't present in the class file!
Below is my ant file:
<project basedir="." default="build" name="MyProject">
<property name="LIB_HOME" value="C:\dev\LibSuite-9.3.1"/>
<property environment="env"/>
<property name="debuglevel" value="source,lines,vars"/>
<property name="target" value="1.7"/>
<property name="source" value="1.7"/>
<path id="MyProject.classpath">
<pathelement location="bin"/>
<pathelement location="lib/edu.mit.jwi_2.1.4.jar"/>
<pathelement location="lib/edu.sussex.nlp.jws.beta.11.jar"/>
<pathelement location="lib/jaws-bin.jar"/>
<pathelement location="lib/junit-4.11.jar"/>
<!--External Jars-->
<pathelement location="${LIB_HOME}/share/java/abc.jar"/>
<pathelement location="${LIB_HOME}/share/java/def-9.3.1.jar"/>
</path>
<target name="init">
<mkdir dir="bin"/>
<copy includeemptydirs="false" todir="bin">
<fileset dir="main/src">
<exclude name="**/*.launch"/>
<exclude name="**/*.java"/>
</fileset>
</copy>
<copy includeemptydirs="false" todir="bin">
<fileset dir="main/test">
<exclude name="**/*.launch"/>
<exclude name="**/*.java"/>
</fileset>
</copy>
</target>
<target name="clean">
<delete dir="bin"/>
</target>
<target depends="init" name="build">
<echo message="${ant.project.name}: ${ant.file}"/>
<javac debug="true" debuglevel="${debuglevel}" destdir="bin" includeantruntime="false" source="${source}" target="${target}">
<classpath refid="MyProject.classpath"/>
<src path="main/src"/>
<src path="main/test"/>
<compilerarg value="-Xlint"/>
</javac>
</target>
<!--Test the app-->
<target depends="build" name="regression">
<junit>
<classpath refid="MyProject.classpath"/>
<test name="uni.my.app.TestSuite"/>
</junit>
</target>
<!--Run the app-->
<target depends="build" name="run">
<java classname="uni.my.app.Application">
<classpath refid="MyProject.classpath"/>
<arg value="sentences.txt"/>
</java>
</target>
</project>
Unfortunately, I was unable to put together a minimum breaking example. The code I am working on is not yet released to the public, so I can't share it all yet. It references several jars (with no conflicting namespaces), some with native methods. I have no idea what exact combination of classes and jars causes the error. I had the same problem using jdk 1.6.0_25 and 1.7.21.
Does anyone have any experience or ideas on how to solve this problem?

This is a guess.
Ant operates outside of eclipse, and therefore any changes it makes are not reflected in eclipse's massively cached view of the world. For all I know, eclipse's changes are not reflected in files that ant uses, either.
I would do a project clean, then a project build, then a project refresh. I would see if you have this problem from within eclipse. Then I would run ant (if I must), do another refresh, and see if the problem exists then.
Any time you do anything in either eclipse or ant, before you use the other program, refresh the eclipse project.
Let us know how it goes.
p.s. trust that the java compiler is not removing any methods. I agree you have something strange going on, but that isn't it.

The problem was that, for whatever reason, another user had turned the project into the VCS with all of the class files in the same folders as their corresponding sources. On my machine, Eclipse puts the binaries into the bin folder, so I don't know why they were placed with the source on the other user's machine.
The init target generated by Eclipse is meant to copy over resources used by the code. It copies over anything but java or launch files from the source directories. However, since the class files were in the source directories, it copied them over, too. Then, when the build target was executed, it didn't bother compiling anything because it seemed that there were already fresh class files present (but they were actually just fresh copies of old code).
The solution was to 1) remove all of the class files from the source folders and 2) add exclude rules for **/*.class to the resource copying code, for good measure.

Related

Compiling 1 file with Javac / Ant also compiles imported file/class in specified file

The task at hand is to separately compile Java classes and their associated JUnit 'Test' classes using Ant-script.
The regular classes are stored in 'src', while the test classes are stored in 'test/src'. Regular classes should be compiled to 'bin' and test classes to 'test/bin'. Both are in the same package.
My Ant script looks as follows:
<javac
includeantruntime="false"
classpathref="master-classpath"
destdir="${test.class.build.dir}"
>
<src path="${src.dir}"/>
<src path="${test.class.dir}"/>
<include name="**/*Test*.java"/>
</javac>
And running the script shows me only one file will be compiled:
[javac] Compiling 1 source file to C:\Users\AK_Flex\Desktop\HW01\test\bin
However, the test class as well as the regular class it imports (already compiled in 'bin') are being compiled and outputted to the 'test/bin' folder.
The regular classes do not import the test classes, so 'bin' looks as desired. (code not depicted)
Is there any way to circumvent this behavior of the compiler?
Since you want compiled classes in two different folders, you need two compilation steps.
<javac includeantruntime="false"
srcdir="src"
destdir="bin"
classpathref="master-classpath">
</javac>
<javac includeantruntime="false"
srcdir="test/src"
destdir="test/bin">
<classpath>
<pathelement location="bin"/>
<path refid="master-classpath"/>
</classpath>
</javac>

Compiling with ANT using external libs

I'm new with ANT and I'm trying to compile part of my Eclipse project. I have many classes but i need to pack only a part of it (please don't ask why). My problem is that one of these classes references to an external library placed in the <project_root>/libs folder and I did not find out how to link it. I found examples on the web but I was not able to arrange it.
<path id="classpath">
<fileset dir="libs" includes="**/*.jar"/>
</path>
<target name="compile">
<mkdir dir="client/classes"/>
<javac srcdir="src" destdir="client/classes" sourcepath="classpath">
//include needed java files
</javac>
</target>
I'm using the annotation #Remote of EJB. It's in the javax.ejb package. I get the error:
package javax.ejb does not exist
[javac] import javax.Ejb.Remote;
If I understand your project structure correctly, the issue with your ant file is that you reference the classpath as beeing the sourcepath.
sourcepath / sourcepathref point to locations where source can be found. I suppose what you want is classpathref="classpath".
<javac srcdir="src" destdir="client/classes" classpathref="classpath">
//include needed java files
</javac>
try this one
<classpath>
<fileset dir="libs">
<include name="**/*.jar"/>
</fileset>
</classpath>

Package Problem with Ant

I'm having a problem getting the javac used by Ant to find and use certain packages. When I invoke javac directly from the command line the packages are found and used.
The .jar files are located in my home directory under lib/java. This is my classpath:
/home/bliskovs/lib/java/*:/home/bliskovs/vendor/cytoscape-v2.7.0/cytoscape.jar
This is the relevant section in my build.xml:
<target name="compile">
<javac srcdir="." debug="true"/>
<javac srcdir="tools/" debug="true"/>
<javac srcdir="core/" debug="true"/>
</target>
How can I get Ant to recognize these packages?
Check out this.
<property name="build.classes.dir" location="build/classes"/>
<path id="compile.classpath">
<fileset dir="lib"/>
<pathelement location="/home/bliskovs/vendor/cytoscape-v2.7.0"/>
</path>
<target name="compile" description="Compile src dir">
<javac destdir="${build.classes.dir}" debug="true" includeantruntime="true">
<src location="src"/>
<classpath refid="compile.classpath"/>
</javac>
</target>
Define a classpath for the javac task. Relying on the CLASSPATH environment variable is a bad practice. It's even more true for the build process of a project, which should work without having to setup a whole lot of environment variables. If you start developing three or four projects at once, you'll understand why using a single CLASSPATH env variable is a bad idea.
See http://ant.apache.org/manual/Tasks/javac.html to know how to define a classpath inside the build.xml and use it in the javac task.

How to copy .java sources to Ant javac destFolder

I know how to use Ant to copy files and folders but what I'm interested in is if, and how, I can have the javac task copy the same sources it's compiling to the output directory.
Basically, it's very similar to the option to include your sources in the jar task.
Why not simply use the copy task, along with the javac one ?
You can even use ant's macro to define your own copyingjavac task, that performs the two operations, with the only problem to correctly handle filesets, to copy exactly the set of files being compiled.
If you want to only copy a file when compilation succeeded, you will have to either build a custom ant task (extending the default javac task), or to play with ant_contrib foreach task.
The macrodef could look like:
<macrodef name="copyingjavac">
<attribute name="srcdir"/>
<attribute name="destdir""/>
<element name="arginclude"/>
<sequential>
<javac srcdir="#{srcdir}" destdir="#{destdir}" updatedProperty="build.success">
<arginclude/>
</javac>
<copy todir="#{destdir}">
<fileset dir="#{srcdir}">
<arginclude/>
</fileset>
</copy>
<fail unless="build.success">
Build failed. Check the output...
</fail>
</sequential>
</macrodef>
I found this answer on Ant's website (you can remove the "excludes" part to copy the .java sources along the compiled versions):
...
<target name="compile">
<mkdir dir="${classes.dir}"/>
<javac srcdir="${src.dir}" destdir="${classes.dir}" classpathref="classpath"/>
<copy todir="${classes.dir}">
<fileset dir="${src.dir}" excludes="**/*.java"/>
</copy>
</target>
...
This copies all resources (as long as they haven't the suffix ".java") to the build directory, so we could start the application from that directory and these files will included into the jar.

Conditionally Delete in Ant

I want to delete the directory if the property "delete-compiled-dir" is set to true. If the property is false then do not execute it. Right now I have
<target name="deleted-after-compilation" depends="compile,jar">
<condition property="${delete-compiled-dir}" value="true">
<delete dir="${compilation-dir}" />
</condition>
<echo> Deleting Compiled Directory Classes </echo>
</target>
I get the error message :
condition doesn't support the nested "delete" element.
You can add the condition to your target using if (see manual).
The target will only be executed when the property compilation-dir is set (to any value, e.g. false).
<target name="deleted-after-compilation" depends="compile,jar"
if="${compilation-dir}">
<delete dir="${compilation-dir}" />
<echo> Deleting Compiled Directory Classes </echo>
</target>
To only execute it when a property is set to true, you need to set another property first and check this one in the if. You could add both as dependency the another target:
<target name="set-delete-property">
<condition property="delete-compilation-dir">
<istrue value="${compilation-dir}"/>
</condition>
</target>
<target name="deleted-after-compilation"
depends="compile,jar" if="${compilation-dir}">
....
<target name="some-target"
depends="set-delete-property,deleted-after-compilation">
</target>
There are a few ways to do this:
Use conditions on the target entity
Targets can contain if and unless conditions. The target will execute depending whether or not the property is set. (Not set to true, just set). This is a common way to see if you need to do something or not:
<target name="deleted.after.compilation"
if="delete.compiled.dir"
depends="jar">
<delete dir="${compilation-dir}" />
<echo> Deleting Compiled Directory Classes </echo>
</target>
You can set the property on the command line:
$ ant -Ddelete.compiled.dir all
Note: I use periods as separators for the names of properties and targets. Also note that I only depend upon the target jar since jar is also dependent upon compile, there's no need to have them both.
Use Ant's 1.9.1 conditional clauses
As of Ant 1.9.1, Ant has conditional attributes that can be added to tasks. You need to add a Namepsace declaration in your <project> entity:
<project ...
xmlns:if="ant:if"
xmlns:unless="ant:unless">
<target name="deleted.after.compilation"
depends="jar">
<delete dir="${compilation-dir}" if:true="${delete.compiled.dir}"/>
<echo if:true="${delete.compiled.dir}"> Deleting Compiled Directory Classes </echo>
</target>
Use Ant-Contrib's If Statement
Ha ha, that wacky Ant-Contrib library. No one knows who maintains it, and it hasn't been touched in years, but many people depend so heavily on it.
<project ...>
<taskdef resource="net/sf/antcontrib/antlib.xml">
<classpath>
<fileset dir="${ivy.dir}/antcontrib">
<include name="ant-contrib*.jar"/>
</fileset>
</classpath>
</taskdef>
<target name="deleted.after.compilation"
depends="jar">
<if>
<istrue value="${delete.compiled.dir}"/>
<then>
<delete dir="${compilation-dir}"/>
<echo>Deleting Compiled Directory Classes </echo>
</then?
</if>
</target>
You can see why Ant-Contrib is popular. It contains a lot of power, and we all know it. Plus, if someone is still using Ant 1.8 or 1.7, this will still work.
if you have get the property ,you can just use it in a target.
<target name="delete" if="${delete-compiled-dir}">
<delete dir="${compilation-dir}" />
</target>
As of Ant 1.9.1, you can use conditionals on any task. Described here.
Add this namespace to your project element:
<project name="yourproject" xmlns:if="ant:if">
Then add this to your delete:
<delete dir="${compilation-dir}" if:true="${delete-compiled-dir}"/>

Categories