[4.3.1] Ant javac task is unable to find CloverCompilerAdapter class

Issue #75 resolved
Marek Parfianowicz created an issue

Affects OpenClover version 4.3.0. Stack trace:

BUILD FAILED

Class not found: com.atlassian.clover.ant.taskdefs.CloverCompilerAdapter at org.apache.tools.ant.util.ClasspathUtils.newInstance(ClasspathUtils.java:257) at org.apache.tools.ant.taskdefs.compilers.CompilerAdapterFactory.resolveClassName(CompilerAdapterFactory.java:196) at org.apache.tools.ant.taskdefs.compilers.CompilerAdapterFactory.getCompiler(CompilerAdapterFactory.java:157) at org.apache.tools.ant.taskdefs.Javac.findSupportedFileExtensions(Javac.java:984) at org.apache.tools.ant.taskdefs.Javac.scanDir(Javac.java:961) at org.apache.tools.ant.taskdefs.Javac.execute(Javac.java:932) at org.apache.tools.ant.UnknownElement.execute(UnknownElement.java:292) at sun.reflect.GeneratedMethodAccessor4.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.apache.tools.ant.dispatch.DispatchUtils.execute(DispatchUtils.java:106) at org.apache.tools.ant.Task.perform(Task.java:348) at org.apache.tools.ant.Target.execute(Target.java:435) at org.apache.tools.ant.Target.performTasks(Target.java:456) at org.apache.tools.ant.Project.executeSortedTargets(Project.java:1393) at org.apache.tools.ant.Project.executeTarget(Project.java:1364) at org.apache.tools.ant.helper.DefaultExecutor.executeTargets(DefaultExecutor.java:41) at org.apache.tools.ant.Project.executeTargets(Project.java:1248) at org.apache.tools.ant.Main.runBuild(Main.java:851) at org.apache.tools.ant.Main.startAnt(Main.java:235) at org.apache.tools.ant.launch.Launcher.run(Launcher.java:280) at org.apache.tools.ant.launch.Launcher.main(Launcher.java:109) Caused by: java.lang.ClassNotFoundException: com.atlassian.clover.ant.taskdefs.CloverCompilerAdapter at org.apache.tools.ant.AntClassLoader.findClassInComponents(AntClassLoader.java:1374) at org.apache.tools.ant.AntClassLoader.findClass(AntClassLoader.java:1323) at org.apache.tools.ant.AntClassLoader.loadClass(AntClassLoader.java:1076) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:348) at org.apache.tools.ant.util.ClasspathUtils.newInstance(ClasspathUtils.java:249) ... 21 more

Cause:

In 4.3.0 we have removed CloverEnvTask which was adding clover.jar to bootclasspath (as in JDK9 such hack is no longer allowed). This clover.jar was used to load high-level targets (like "with.clover"), which can be easily declared in Ant build.xml.

Side effect is that the Ant <javac>task is unable to find the CloverCompilerAdapter specified in the build.compiler property - the classloader sees Ant JARs only...

Workaround:

Copy clover.jar to ANT_HOME\lib directory.

Fix:

See http://openclover.org/doc/manual/latest/ant--adding-to-ants-classpath.html

Comments (17)

  1. Marek Parfianowicz reporter

    The workaround is not bad actually. I will update installation instructions and upgrade guides accordingly. I also updated in-product help (e.g. in Jenkins, Hudson plugin) pointing to proper installation instruction.

  2. Marek Parfianowicz reporter

    Additional fix implemented is restoration of the <clover-env> task - however, without the bootclass loader hack. It means that it will be again possible to call <clover-env> to add high-level targets, like "with.clover".

  3. Uwe Schindler

    I don't understand your resolution: Can one now use Clover and load it e.g. from Ivy, so it works with a custom classpath from Ant? I am referring to your last comment about restoration of clover-env task. On Apache Lucene's Jenkins servers it's impossible to put it into the ~/.ant/lib directory, all tasks must be installed by Ivy during the build.

    It still works with 4.2.1 (that's the version we are currently use), but 4.3.1 still fails with this error, so it's unuseable as workaround cannot be used.

  4. Uwe Schindler

    As Lucene master upgrades to Java 11 in the next week, a relaese with support for Java 11 - including this bugfix is really needed. Otherwise we have to finally drop Clover, unfortunately!

  5. Marek Parfianowicz reporter

    Hi Uwe,

    Can one now use Clover and load it e.g. from Ivy, so it works with a custom classpath from Ant? I am referring to your last comment about restoration of clover-env task.

    I'm not very familiar with Ivy. Could you please explain in more details what problem do you have with Ivy?

    In general, the problem lies in new architecture of class loaders introduced in Java 9. It is no longer possible to programatically add a jar to a bootclasspath. And this is what originally <clover-env> task did - it was detecting path to a clover.jar file and adding it so that all Clover's classes were available for the whole application. I wasn't able to find any hack for Java 9, though. If you know any, I'd love to implement it.

    Adding to ANT_HOME\lib is one of the three methods available, see: http://openclover.org/doc/manual/latest/ant--adding-to-ants-classpath.html

    On Apache Lucene's Jenkins servers it's impossible to put it into the ~/.ant/lib directory, all tasks must be installed by Ivy during the build.

    Bamboo uses a third approach - it adds "-lib /path/to/clover.jar" (where path points to a clover.jar inside bamboo installation directory or agent's lib dir) in the command line for Ant task. It works pretty well, also on build agents. AFAIK, Jenkins Clover Plugin also uses this approach, am I correct.

  6. Marek Parfianowicz reporter

    As Lucene master upgrades to Java 11 in the next week, a relaese with support for Java 11 - including this bugfix is really needed. Otherwise we have to finally drop Clover, unfortunately!

    I'm really sad to say, but I'm afraid you will have to disable Clover in your builds, at least for a while.

    OpenClover does not support Java 11 yet and it's rather unlikely to have it available quickly. I hope to release 4.4.0 this quarter with some enhancements in clover-maven-plugin,. The release 4.5.0 with Java 10 support will be probably ready in Q4. So for Java 11 you'd have to wait for next year, unless the project attracts more contributors.

  7. Uwe Schindler

    I'm not very familiar with Ivy. Could you please explain in more details what problem do you have with Ivy?

    This has nothing to do with Ivy (in Lucene Ivy is just used to download all required dependencies, including Clover).

  8. Uwe Schindler

    So we will have to disable clover - and that is what we did.

    The problem is still there: How to load clover from an Ant build without any external configuration by making <clover-env> working again. I hope you solve it till Java 11 support comes.

  9. Marek Parfianowicz reporter

    The problem is still there: How to load clover from an Ant build without any external configuration by making <clover-env> working again.

    Could you please provide me more details about the problem you have with OpenClover 4.3.1 currently? A sample project and a build log with debug logging would be very helpful - you can attach it to the issue or email to support@openclover.org.

    I'm asking because if you run automatic integration with Ant on Jenkins, then the Jenkins plugin already adds the "-lib /path/to/clover.jar" argument for the build. And the clover.jar is available, even on build agents, as far as I know.

    And if you run an Ant build outside Jenkins, e.g. via command line, you must have clover.jar already defined in the build.xml file somehow, otherwise none of the <clover-xyz> tasks would be resolved by Ant. Which means the clover.jar is already downloaded at the time build runs, so adding a "-lib" parameter for ant command line should not be a problem either.

    I suspect I misunderstood something from your description...

    Cheers Marek

  10. Uwe Schindler

    Hi, you misunderstood me. Of course you can add clover.jar manually to the lib folder.

    The Lucene builds are using IVY for resolving all dependencies and build plugins. So like any other Maven or Gradle build, the whole thing is self contained. You just need to download the source ZIP file and then you can run "ant run-clover" without downloading anything. Like Maven, the clover dependency is downloaded from Maven Central and then using an Ant classpath passed to taskdef:

      <target name="-clover.load" depends="ivy-availability-check,ivy-configure" if="run.clover" unless="clover.loaded">
        <echo>Code coverage with OpenClover enabled.</echo>
        <ivy:cachepath organisation="org.openclover" module="clover" revision="4.2.1"
          inline="true" conf="master" pathid="clover.classpath"/>
        <taskdef resource="cloverlib.xml" classpathref="clover.classpath" />
        <mkdir dir="${clover.db.dir}"/>
        <!-- This is a hack, instead of setting "clover.loaded" to "true", we set it
         to the stringified classpath. So it can be passed down to subants,
         and reloaded by "-clover.classpath" task (see below): -->
        <pathconvert property="clover.loaded" refid="clover.classpath"/>
      </target>
    

    With your workarounds its now needed that any build server or developer has to manually download the Clover distribution and install it manually in the ~/.ant/lib folder.

    IMHO, I have the feeling the whole classloading is broken here. We worked a lot with Java 9 and if you are correctly using classloader (even AntClassLoader works with Java 9, although it's 10 years old and unmodified since them). I have the feeling Clover is maybe using Context classloader.

    In Lucene we are also using the ECJ compiler (which is also a compiler adaptor for Ant) and Ant is perfectly able to load it without being in Bootclasspath:

        <ivy:cachepath organisation="org.eclipse.jdt" module="ecj" revision="3.17.0"
         inline="true" conf="master" type="jar" pathid="ecj.classpath" />
        <componentdef classname="org.eclipse.jdt.core.JDTCompilerAdapter"
         classpathref="ecj.classpath" name="ecj-component"/>
    

    Maybe the same should be done and the component passed to javac manually? Not sure what's going wrong here!

  11. Marek Parfianowicz reporter

    Hi Uwe, this is very interesting what you wrote. I agree, I may have failed to find a correct solution for class loading.

    The <clover-env> task was using system class loader, see:

    https://bitbucket.org/openclover/clover/commits/d37ac2c74914f8653079be29969ebc9ead1c7b20 files CloverEnvTask.java and ClassPathUtil.java

    I've been trying to use JDK9 equivalents (platform / application class loader?) but it was not possible - security exceptions as far as I remember.

    I'll be happy to re-evaluate the problem and see how JDTCompilerAdapter works.

  12. Uwe Schindler

    I think what you actually have to do is to pass the ECJ compiler adaptor as a parameter to the javac task. I think I know what your problem is: The javac task is running in Ant's classloader so it can't see the Clover classes - and because of this you patched the Ant classloader to contain the clover.jar.

    IMHO the correct way to do this is to use componentdef like for ECJ:

    <componentdef classname="org.eclipse.jdt.core.JDTCompilerAdapter"
         classpathref="ecj.classpath" name="ecj-component"/>
    

    and then pass it to "javac":

          <javac ....>
            <ecj-component/>
            ....
            <compilerarg value="@{configuration}"/>
          </javac>
    

    Of course this adds manual work for the user and his build script - but this adds much more flexibility. This is IMHO the only correct way to do this: Say explicit to the javac task that it should use Clover's compiler adaptor. This also allows to not magically enable it always! We were already annoyed that clover injects itsself to every javac task without asking it to do so! Some of our's should not be instrumented, as its stuff like build tools.

    Can this be done for Clover? Don't do any automagic and only enbale clover if it's passed to the javac task as a compiler component?

  13. Uwe Schindler

    It's actually as expected: clover-setup injects the compiler adaptor via setProperty. But as it's not in Ant's default classpath, it fails.

    So the correct way would be:

    • (optionally allow to) stop clover-setup from modifying global build properties (this is IMHO a very bad thing to do)
    • do the componentdef for the compiler adaptor correctly in the cloverlib.xml
    • require user to pass the component (like for ECJ)

    This allows the user to have the choice of what adaptor he wants to use! And the adaptor is loaded correctly, as it has a correct classloader.

  14. Log in to comment