I want to use compile-time AspectJ with Ant in NetBeans. I want to run it on Google App Engine, but it is not essential at the moment. AspectJ is annotation based.
I prefer compile-time weave (modification, instrumentation? of classes). I wouldn't like to use a custom classloader. How to achieve this?
What I already have:
I tried AspectJ Annotation Tutorial with NetBeans. I modified build.xml to process aspectj (using iajc Ant task) as described here. The problem is that it requires adding -javaagent:lib/aspectjweaver.jar (it is not possible on GAE).
Running my build generates this output:
info compiling C:\NetBeansProjects\TryAspectJ\src\net\andrewewhite\examples\HelloWorld.java
weaveinfo Join point 'method-call(void java.io.PrintStream.println(java.lang.String))' in Type 'net.andrewewhite.examples.HelloWorld' (HelloWorld.java:9) advised by before advice from 'net.andrewewhite.aspects.BasicAspect' (BasicAspect.class:17(from BasicAspect.java))
weaveinfo Join point 'method-call(void java.io.PrintStream.println(java.lang.String))' in Type 'net.andrewewhite.examples.HelloWorld' (HelloWorld.java:9) advised by after advice from 'net.andrewewhite.aspects.BasicAspect' (BasicAspect.class:23(from BasicAspect.java))
info woven class net.andrewewhite.examples.HelloWorld (from C:\NetBeansProjects\TryAspectJ\src\net\andrewewhite\examples\HelloWorld.java)
info Compiler took 2547ms
When I run my project with -javaagent parameter it works OK. (In NetBeans: Click on project>Properties>Run>VM Options: -javaagent:./dist/lib/aspectjweaver.jar). Otput for code from tutorial:
run:
About to make call to print Hello World
Hello World!
Just made call to print Hello World
BUILD SUCCESSFUL (total time: 0 seconds)
Without agent (VM Options cleared) code runs as if it was without AspectJ:
run:
Hello World!
BUILD SUCCESSFUL (total time: 0 seconds)
Sources:
\TryAspectJ\src\META-INF\aop.xml
<?xml version="1.0" encoding="UTF-8"?>
<aspectj>
<aspects>
<aspect name="net.andrewewhite.aspects.BasicAspect" />
</aspects>
</aspectj>
\TryAspectJ\src\net\andrewewhite\aspects\BasicAspect.java
package net.andrewewhite.aspects;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
#Aspect
public class BasicAspect {
#Before(" call(void java.io.PrintStream.println(java.lang.String)) " +
"&& !within(net.andrewewhite.aspects..*)")
public void beforePrintlnCall() {
System.out.println("About to make call to print Hello World");
}
#After(" call(void java.io.PrintStream.println(java.lang.String)) " +
"&& !within(net.andrewewhite.aspects..*)")
public void afterPrintlnCall() {
System.out.println("Just made call to print Hello World");
}
}
\TryAspectJ\src\net\andrewewhite\examples\HelloWorld.java
package net.andrewewhite.examples;
public class HelloWorld {
public static void main(String[] argv) {
System.out.println("Hello World!");
}
}
\TryAspectJ\build.xml
<?xml version="1.0" encoding="UTF-8"?>
<project name="TryAspectJ" default="default" basedir=".">
<description>Builds, tests, and runs the project TryAspectJ.</description>
<import file="nbproject/build-impl.xml"/>
<taskdef classpath="lib/aspectj/aspectjtools.jar" resource="org/aspectj/tools/ant/taskdefs/aspectjTaskdefs.properties"/>
<target name="aspectj">
<echo level="info">--- aspectj (start) ---</echo>
<iajc destDir="${build.classes.dir}" source="1.6" target="1.6" showweaveinfo="true" verbose="true" >
<inpath>
<pathelement location="lib/aspectj/aspectjrt.jar"/>
<pathelement location="${build.classes.dir}" />
</inpath>
<sourceroots>
<pathelement location="${src.dir}"/>
</sourceroots>
<classpath>
<pathelement location="${javac.classpath}"/>
<pathelement location="${j2ee.platform.classpath}"/>
</classpath>
</iajc>
<echo level="info">--- aspectj finished ---</echo>
</target>
<target name="-post-compile" depends="aspectj"></target>
</project>
What do I have to add or change?
Found solution.
This is the correct build.xml fragment:
<taskdef classpath="lib/aspectjtools.jar" resource="org/aspectj/tools/ant/taskdefs/aspectjTaskdefs.properties"/>
<target name="aspectj">
<echo level="info">--- aspectj (start) ---</echo>
<!-- begin fix classpath for this bug https://issues.apache.org/bugzilla/show_bug.cgi?id=40291 -->
<condition property="targetos" value="windows" else="unix">
<os family="windows"/>
</condition>
<!-- converting classpath -->
<pathconvert targetos="${targetos}" property="javac.convertedClasspath" >
<path path="${javac.classpath}" />
</pathconvert>
<!-- end fix classpath -->
<iajc source="1.6" target="1.6" showweaveinfo="true" verbose="true" destdir="${build.classes.dir}" >
<inpath>
<pathelement location="${build.classes.dir}"/>
</inpath>
<classpath>
<pathelement location="${javac.convertedClasspath}" />
</classpath>
</iajc>
<echo level="info">--- aspectj (finished) ---</echo>
</target>
<target name="-post-compile" depends="aspectj"></target>
Calling java -classpath ./lib/aspectjrt.jar -jar TryAspectJ.jar works OK. What is strange is that from NetBeans (Right clik on project->Run) results are as if they were without AspectJ. I think that NetBeans runs projects from build/classes dir not as jar. But it's not a problem.
You need to add an aspect path element as a subelement to the iajc element.
<aspectpath>
<pathelement location="..." />
</aspectpath>
The aop.xml file is only necessary for AspectJ load-time weaving.
Related
I've started using Mockito to mock some classes in my unit tests, for various reasons, and as I was developing, I got everything to work. Once I finished my changes, I pushed to our build server, and discovered that the unit tests are broken when run through Ant. I've been able to reproduce this in a simple stripped-down case, in which I'm using Mockito to test the mock the static method of a dependency of the class under test. Below I've pasted the source files, the test file, the Ant script, and the Ivy dependencies file describing the versions of each library I'm using (all of them up to date at this time), followed by the verbose error I'm seeing.
But if I create these several files in a directory (with the two classes under src/com/example and the unit test under tests/com/example and open it up as an IntelliJ IDEA project, using all the same dependencies (including JUnit) and run all tests, it succeeds. The only change from the standard module template is specifying the JDK path and Java level to 1.8. That's part of what's making it so puzzling. For what it's worth, Ant acts the same whether invoked from within IntelliJ IDEA's built-in Ant integration, or separately on the command line.
BusinessClass.java
package com.example;
public class BusinessClass {
public String returnString() {
StaticDependency.DoSomething();
return "Answer";
}
}
StaticDependency.java
package com.example;
public class StaticDependency {
public static void DoSomething() {
throw new RuntimeException("Real code causes side effects I'm isolating from");
}
}
BusinessClassTest.java
package com.example;
import org.junit.Test;
import org.mockito.MockedStatic;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mockStatic;
public class BusinessClassTest {
#Test
public void returnString() {
final BusinessClass classUnderTest = new BusinessClass();
try (final MockedStatic<StaticDependency> ignored = mockStatic(StaticDependency.class)) {
assertEquals("Answer", classUnderTest.returnString());
}
}
}
ivy.xml
<ivy-module version="2.0">
<info organisation="com.example" module="MockitoTest"/>
<dependencies>
<dependency org="junit" name="junit" rev="4.13.2"/>
<!-- Mockito and dependencies -->
<dependency org="org.mockito" name="mockito-inline" rev="4.2.0"/>
<dependency org="net.bytebuddy" name="byte-buddy-dep" rev="1.12.6"/>
<dependency org="org.objenesis" name="objenesis" rev="3.2"/>
</dependencies>
</ivy-module>
build.xml
<project default="all" xmlns:ivy="antlib:org.apache.ivy.ant">
<property name="lib" location="Lib"/>
<property name="src" location="src"/>
<property name="tests" location="tests"/>
<property name="build-test" location="build-test"/>
<property name="build" location="build"/>
<path id="build.classpath">
<fileset dir="${lib}">
<include name="**/*.jar"/>
</fileset>
</path>
<target name="all" depends="clean, resolve, compile, test"/>
<target name="clean">
<delete dir="${build}"/>
<delete dir="${build-test}"/>
<delete dir="${lib}"/>
</target>
<target name="resolve" description="Download dependencies">
<ivy:resolve file="ivy.xml"/>
<mkdir dir="${lib}"/>
<ivy:retrieve pattern="${lib}/[artifact]-[type].[ext]"/>
</target>
<target name="compile" description="Compile the source code">
<mkdir dir="${build}"/>
<javac destdir="${build}">
<src path="${src}"/>
<classpath refid="build.classpath"/>
</javac>
<mkdir dir="${build-test}"/>
<javac destdir="${build-test}">
<src path="${src}"/>
<src path="${tests}"/>
<classpath refid="build.classpath"/>
</javac>
</target>
<target name="test" depends="compile" description="Run all unit tests">
<junit>
<classpath>
<path refid="build.classpath"/>
<path path="${build-test}"/>
</classpath>
<formatter type="plain" usefile="false"/>
<test name="com.example.BusinessClassTest" />
</junit>
</target>
</project>
JUnit output
Testsuite: com.example.BusinessClassTest
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.889 sec
Testcase: returnString took 0.779 sec
Caused an ERROR
Could not modify all classes [class com.example.StaticDependency]
org.mockito.exceptions.base.MockitoException: Could not modify all classes [class com.example.StaticDependency]
at com.example.BusinessClassTest.returnString(Unknown Source)
Caused by: java.lang.IllegalStateException:
Byte Buddy could not instrument all classes within the mock's type hierarchy
This problem should never occur for javac-compiled classes. This problem has been observed for classes that are:
- Compiled by older versions of scalac
- Classes that are part of the Android distribution
at org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.triggerRetransformation(InlineBytecodeGenerator.java:280)
at org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.mockClassStatic(InlineBytecodeGenerator.java:221)
at org.mockito.internal.creation.bytebuddy.TypeCachingBytecodeGenerator.mockClassStatic(TypeCachingBytecodeGenerator.java:63)
at org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker.createStaticMock(InlineDelegateByteBuddyMockMaker.java:560)
at org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker.createStaticMock(InlineByteBuddyMockMaker.java:83)
at org.mockito.internal.util.MockUtil.createStaticMock(MockUtil.java:147)
at org.mockito.internal.MockitoCore.mockStatic(MockitoCore.java:142)
at org.mockito.Mockito.mockStatic(Mockito.java:2164)
at org.mockito.Mockito.mockStatic(Mockito.java:2101)
Caused by: java.lang.AbstractMethodError: org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator$ParameterWritingVisitorWrapper.wrap(Lnet/bytebuddy/description/type/TypeDescription;Lorg/objectweb/asm/ClassVisitor;Lnet/bytebuddy/implementation/Implementation$Context;Lnet/bytebuddy/pool/TypePool;Lnet/bytebuddy/description/field/FieldList;Lnet/bytebuddy/description/method/MethodList;II)Lorg/objectweb/asm/ClassVisitor;
at net.bytebuddy.asm.AsmVisitorWrapper$Compound.wrap(AsmVisitorWrapper.java:746)
at net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining$WithFullProcessing$RedefinitionClassVisitor.visit(TypeWriter.java:4906)
at org.objectweb.asm.ClassReader.accept(ClassReader.java:569)
at org.objectweb.asm.ClassReader.accept(ClassReader.java:424)
at net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining.create(TypeWriter.java:3951)
at net.bytebuddy.dynamic.scaffold.TypeWriter$Default.make(TypeWriter.java:2213)
at net.bytebuddy.dynamic.scaffold.inline.RedefinitionDynamicTypeBuilder.make(RedefinitionDynamicTypeBuilder.java:224)
at net.bytebuddy.dynamic.scaffold.inline.AbstractInliningDynamicTypeBuilder.make(AbstractInliningDynamicTypeBuilder.java:123)
at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase.make(DynamicType.java:3661)
at org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.transform(InlineBytecodeGenerator.java:394)
at sun.instrument.TransformerManager.transform(TransformerManager.java:188)
at sun.instrument.InstrumentationImpl.transform(InstrumentationImpl.java:428)
at sun.instrument.InstrumentationImpl.retransformClasses0(Native Method)
at sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:144)
at org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.triggerRetransformation(InlineBytecodeGenerator.java:276)
It turns out my problem was the counterproductive byte-buddy-dep dependency in my ivy.xml file. As #temp-droid pointed out in a GitHub issue I raised on the Mockito project posing this same problem, replacing that with byte-buddy and byte-buddy-agent would work instead.
I then realized that Ivy doesn't require me to explicitly list sub-dependencies (I wasn't aware it knew how to resolve them), and so I was able to update to this much simpler ivy.xml, which works in Ant and IntelliJ alike:
<ivy-module version="2.0">
<info organisation="com.example" module="MockitoTest"/>
<dependencies>
<dependency org="junit" name="junit" rev="4.13.2"/>
<dependency org="org.mockito" name="mockito-inline" rev="4.2.0"/>
</dependencies>
</ivy-module>
I am creating a java agent that will be used to to do some bytecode modification to some classes org.eclipse.jdt.core.JDTCompilerAdapter is one of them. I am using javassit to modify some the execute() method of org.eclipse.jdt.core.JDTCompilerAdapter. So I have included ecj as in my agent project (using gradle)
compile group: 'org.eclipse.jdt.core.compiler' ,name: 'ecj', version :'4.3.1'
As I need to use some classes from ecj.
The goal of the agent is to intercept the calls to execute method, modify the execute method to add some calls to some of my classes in the aim of triggering some processing.
I am testing the agent against a Simple java project with 2 classes. the project is builded with ant and uses JDTCompilerAdapter as a compiler.
Here is the build.xml file
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project basedir="." default="build" name="TestProject">
<property file="build.properties" />
<property name="debuglevel" value="source,lines,vars"/>
<property name="target" value="1.7"/>
<property name="source" value="1.7"/>
<path id="PClasspath">
<pathelement location="bin"/>
</path>
<target name="init">
<mkdir dir="bin"/>
<copy includeemptydirs="false" todir="bin">
<fileset dir="src">
<exclude name="**/*.java"/>
</fileset>
</copy>
</target>
<target name="clean">
<delete dir="bin"/>
</target>
<target depends="clean" name="cleanall"/>
<target depends="init" name="build">
<javac debug="true" debuglevel="${debuglevel}" destdir="bin" includeantruntime="false" source="${source}" target="${target}">
<src path="src"/>
<classpath refid="PClasspath"/>
</javac>
</target>
<!--
<target description="copy Eclipse compiler jars to ant lib directory" name="init-eclipse-compiler">
<copy todir="${ant.library.dir}">
<fileset dir="${ECLIPSE_JDT_CORE}" includes="*.jar"/>
</copy>
</target>-->
<target name="build-e" >
<property name="build.compiler" value="org.eclipse.jdt.core.JDTCompilerAdapter"/>
<antcall target="build"/>
</target>
The agent is to be used when building a project.
So for testing the agent I use this command:
java -jar agent-wrapper.jar --outdir ./out --exec ./build_wrapper.sh
build_wrapper.sh contains this (I have added ecj dependency so I could compile the project with JDTCompilerAdapter as I have in bulid.xml <property name="build.compiler" value="org.eclipse.jdt.core.JDTCompilerAdapter"/> :
../ant/bin/ant -lib ../eclipse/plugins/ecj-4.3.1.jar build-e
The idea is that the agent-wrapper will parse the argument (outdir is used to generate some stuff and exec is a script used to launch a the build of my test project) get the command to be executed from build_wrapper.sh (in this case ../ant/bin/ant -lib ../eclipse/plugins/ecj-4.3.1.jar build-e) and add it self as java agent to the command.
The problem occurs during the execution of the agent. Here is the output:
java -jar custom-agent.jar --outdir ./out --exec ./build_wrapper.sh [10:18:53]
Picked up JAVA_TOOL_OPTIONS: -javaagent:/Users/dev/TestAgent/project/custom-agent.jar=OUTDIR=/Users/dev/TestAgent/project/./out
objc[30474]: Class JavaLaunchHelper is implemented in both /Library/Java/JavaVirtualMachines/jdk1.7.0_51.jdk/Contents/Home/jre/bin/java and /Library/Java/JavaVirtualMachines/jdk1.7.0_51.jdk/Contents/Home/jre/lib/libinstrument.dylib. One of the two will be used. Which one is undefined.
Buildfile: /Users/dev/TestAgent/project/build.xml
build-e:
init:
[mkdir] Created dir: /Users/dev/TestAgent/project/bin
build:
BUILD FAILED
/Users/dev/TestAgent/project/build.xml:47: The following error occurred while executing this line:
/Users/dev/TestAgent/project/build.xml:32: Class org.eclipse.jdt.core.JDTCompilerAdapter could not be loaded because of an invalid dependency.
Total time: 2 seconds
abnormal termination, exit code: 1
When I don't use ecj-4.3.1.jar inside my agent project, the build runs well I intercept the call to execute() method but I can't use the other classes from ecj jar.
The show stopper error is "Class org.eclipse.jdt.core.JDTCompilerAdapter could not be loaded because of an invalid dependency."
First hint at the fault might be found from reading this link
http://help.eclipse.org/mars/index.jsp?topic=%2Forg.eclipse.jdt.doc.user%2Ftasks%2Ftask-ant_javac_adapter.htm
The second hint might be that one of the jars required for running the JDTCompilerAdapter is missing.
To get the JDTCompilerAdapter to work I copied both the JDTCompilerAdapter.jar and org.eclipse.jdt.core.jar into the ant/lib folder.
There are differences based on version of eclipse and the version of java which are documented in the link mentioned above.
I have created an aspect with AspectJ, to build it and weave my classes during compile time with it I use ANT task. I have also created test class that is intended to test how that aspect works. Here is the problem: my test classes are not being weaved. And I do not why.
My source folder is src/main/java and my test folder is src/test/java
When I move my test class to the source folder everything works fine..
Update 1: I am not using AspectJ in conjunction with Spring. Also I am not using aop.xml, my aspects are being annotated.
Update 2: my ant compile-aspectj-tests target looks like this:
<target name="compile-aspectj-tests" depends="compile">
<taskdef resource="org/aspectj/tools/ant/taskdefs/aspectjTaskdefs.properties">
<classpath>
<pathelement location="${ivy.install.dir}/../cache/org.aspectj/aspectjtools/jars/aspectjtools-1.7.3.jar" />
</classpath>
</taskdef>
<iajc srcDir="${test.dir}/java" destDir="${compiled.tests}" debug="on" Xlintwarnings="true"
showWeaveInfo="true" source="${targetVersion}" target="${targetVersion}" >
<classpath>
<path refid="test.classpath" />
<pathelement location="${compiled.src}" />
</classpath>
<aspectpath location="${compiled.src}" />
</iajc>
</target>
This is what I see in the console:
compile-aspectj-tests:
[iajc] warning at com/.../cache/aspects/RetryJedisConnectionExceptionAspect.java::0 no match for this type name: RetryJedisConnectionException [Xlint:invalidAbsoluteTypeName]
Any help would be appreciated.
I am using the following demonstration script:
<?xml version="1.0" encoding="UTF-8"?>
<project name="test" basedir="." xmlns:deploy="antlib:net.sf.antcontrib">
<target name="default">
<taskdef resource="net/sf/antcontrib/antlib.xml">
<classpath>
<pathelement location="lib/ant-contrib-1.0b3.jar"/>
</classpath>
</taskdef>
<deploy:if>
<isset property="defaultprop"/>
<then>
<echo message="it's set!"/>
</then>
</deploy:if>
</target>
</project>
When I run this build script (with target default), the error is
build.xml:9: Problem: failed to create task or type antlib:net.sf.antcontrib:if
The pathelement lib/ant-contrib-1.0b3.jar exists, and ant is picking it up. I'm thinking the problem is how I'm using the xmlns. I'm taking this from another example that I have that also doesn't work for me (it works on a particular server, though!), and trying to figure out what the magic sauce is.
Your taskdef where you're adding ant-contrib needs to declare a URI the same as the namespace you're defining and prefixing in the project. Similar to how the taskdef over here works.
<project name="test" basedir="." xmlns:deploy="antlib:net.sf.antcontrib">
<target name="default">
<taskdef uri="antlib:net.sf.antcontrib" resource="net/sf/antcontrib/antlib.xml">
<classpath>
<pathelement location="lib/ant-contrib-1.0b3.jar"/>
</classpath>
</taskdef>
<deploy:if>
<isset property="defaultprop"/>
<then>
<echo message="it's set!"/>
</then>
</deploy:if>
</target>
</project>
Well, the error has if on the end, and it's talking about line 9. I think it's a problem with the syntax of this tag:
<deploy:if>
I can't find any documentation on a "deploy:if" tag, or even a "deploy" tag. I think there is no 'deploy' task in Ant - you need to make a 'deploy' target.
How about trying this:
<if>
<isset property="defaultprop"/>
<then>
<antcall target="deploy" />
</then>
</if>
As I read it, this will check the isset, then call the "deploy" target if it's set.
Of course, you need to make the 'deploy' target now :)
I'm completely new to Ant and need to add a couple jars to my classpath, compile a couple .java files, and run a junit test, all in Ant. I've been looking at a few online tutorials and manuals, but can't seem to wrap my head around the entire xml writing process.
All the previously-written code resides in a single directory called XXX.
In XXX there are two jars I need to add to my classpath with export CLASSPATH=$CLASSPATH:jar1:jar2, two java files I compile with javac *.java, one of which contains several junit tests that I run with java org.junit.runner.JUnitCore Tests. The build.xml would reside in XXX as well (I believe).
So far I have the following for just compiling, although I think there's a lot missing.
<?xml version="1.0"?>
<project name="EtlAutomation" default="compile" basedir=".">
<property name="src" value="${basedir}"/>
<target name="compile">
<!-- Compile .java files -->
<javac srcdir="${src}" destdir="${src}"/>
</target>
</project>
What else do I need to add to compile *.java in the current directory? How can I run the export CLASSPATH command, and finally the junit commend?
I'm not asking for anyone to write my code, but it would be appreciated. If anyone knows a good beginner tutorial for a unix environment, that would be awesome. I'm a total beginner with ant so I'll take what I can get.
Here is a previous question addressing this. And this may work for you:
<project name="EtlAutomation" default="compile" basedir=".">
<property name="src" value="${basedir}"/>
<path id="compile.classpath">
<fileset dir="./">
<include name="*.jar"/>
</fileset>
</path>
<target name="compile" >
<javac destdir="${src}" srcdir="${src}">
<classpath refid="compile.classpath"/>
</javac>
</target>
<target name="run" depends="compile">
<junit>
<classpath refid="compile.classpath" />
<test name="TestExample" />
</junit>
</target>
</project>