ant-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Vimil Saju <vimils...@yahoo.com>
Subject Re: controlling ClassLoader when programmatically invoking Ant
Date Sun, 22 Jul 2012 12:27:55 GMT
>So this time I actually did this. I created this parentless ClassLoader and
>created a Project object from it. And what happened? The moment I tried to
>assign this object to a Project variable, I got a ClassCastException:
>org.apache.tools.ant.Project cannot be cast to
>org.apache.tools.ant.Project. So it's sort of a chicken-and-egg problem.


I think to prevent class-cast issue, you should first create a class with just a static main
method, then use the parentless class loader to create an instance of this class and invoke
its main method through reflection.
 All the ant related code should be then written in then main method of the class that you
created.

The reason you are getting the class-cast exception is because you are creating the Project
object using say classloaderA and then trying to access that object from a class that was
loaded from another classloader. 


________________________________
 From: Mitch Gitman <mgitman@gmail.com>
To: Ant Developers List <dev@ant.apache.org> 
Sent: Saturday, July 21, 2012 6:16 PM
Subject: Re: controlling ClassLoader when programmatically invoking Ant
 
Quick update for anyone who's curious.

I'd forgotten that I'd asked much the same question on the ant-user list
back on May 31. This was back when the contamination of the child classpath
with the parent classpath was literally causing the tests to fail. And same
as with this thread, Nicolas L. was kind enough to respond. See thread,
"example of correctly consuming an Ant project programmatically."

What's funny (and sad in a way) is that today I went down much the same
path I had laid in the earlier thread without even recollecting that
thread. Here's what I wrote earlier:
***
The one way I may have to tweak this ... is to do:
new URLClassLoader(jars, null);

Passing null for the extra ClassLoader argument obviates the possibility of
the parent classloader creeping in.
***
So this time I actually did this. I created this parentless ClassLoader and
created a Project object from it. And what happened? The moment I tried to
assign this object to a Project variable, I got a ClassCastException:
org.apache.tools.ant.Project cannot be cast to
org.apache.tools.ant.Project. So it's sort of a chicken-and-egg problem.

I also tried just calling Project.setCoreLoader() with this URLClassLoader,
but that didn't change the way the Ivy JAR and such were being loaded.

At this point, I can think of a few ways to address this problem:
1. Create a parent ClassLoader just for all the Ant libraries and put JUnit
itself and the entire test classpath in a child classloader.
2. Run the Launcher class with the sandboxed ClassLoader and have a build
listener and build logger write the messages and out/err to the filesystem.
Then to do the assertions, read the files after the fact.
3. Obtain an Object instance rather than a Project instance and use
reflection to call the few methods I have to call on it.

#1 scares me! #2 is defeating much of the purpose of doing all this
programmatically. #3 ain't pretty, but so far it seems doable.

On Sat, Jul 21, 2012 at 9:33 AM, Mitch Gitman <mgitman@gmail.com> wrote:

> Nicolas, thanks. I was one class off. I was looking at Main and AntMain
> when I should have been looking at Launcher. Seeing the type names and the
> main method threw me off. And this corroborates my brief follow-up that I
> could be doing something with URLClassLoader. Better for me to try to
> leverage Launcher though than reinvent that wheel.
>
> Let me see how far I get with that.
>
> I do have to take issue with the one point you make: "But If I were you, I
> wouldn't bother trying to mimic Ant classloading in a unit test. The
> important thing is to be sure that you run against the expected version of
> Ant and Ivy."
>
> First, this is not a unit test. It's an integration test or a functional
> test, and if it doesn't emulate the real-life behavior in the wild, then
> what use is it?
>
> Even if we are treating this as a unit test, you can't really say that
> inadvertently picking up everything in the parent ClassLoader is the
> equivalent of mocking or stubbing services and components as one normally
> does when creating pure unit tests. Consider that there could be a ton of
> classes in the IDE's ClassLoader that have nothing whatsoever to do with
> the Ant Project being executed.
>
> And this brings me to my last concern. I have already encountered a
> problem where some static functionality in some superfluous classes in the
> IDE-inherited ClassLoader was causing the JUnit-invoked Project build to
> fail.
>
> Even so, to my mind, the uncontrolled success I'm seeing now is no less
> troubling than the uncontrolled failure I was seeing then. If you're a
> biologist conducting an experiment, you don't want to find out your
> cultures were growing only because the Petri dish got contaminated.
>
> On Sat, Jul 21, 2012 at 6:10 AM, Nicolas Lalevée <
> nicolas.lalevee@hibnet.org> wrote:
>
>> Hi Mitch,
>>
>> Le 21 juil. 2012 à 07:37, Mitch Gitman a écrit :
>>
>> > Technically, this message is better suited for the ant-user list, but
>> I'm
>> > thinking I'm more apt to get an answer on this list. (I'm also thinking
>> > this is the better place for me to cash in some chits for my having
>> > submitted patches for three Ivy issues I mentioned recently on this
>> list.
>> > Subject: "extends and buildlist on 2.3.0-rc1....")
>>
>> I am intended to give it a look very soon, I have seen all the activity
>> you have done, I don't want to waste it :)
>>
>> > Here's my problem. I'm trying to do a JUnit test of some Ivy
>> functionality
>> > (actually, relating to the aforementioned Ivy bugs) by programmatically
>> > creating and executing an Ant Project object. Everything runs just fine.
>> > I'm able to execute targets and even tie in a BuildListener and a
>> > BuildLogger to tap into output and error and check if the build failed.
>> >
>> > The problem is, everything works a little too well. Take a look at the
>> > following example build.xml:
>> > <project xmlns:ivy="antlib:org.apache.ivy.ant" name="resolve-test"
>> > default="build">
>> >
>> >    <target name="build">
>> >    <ivy:settings file="ivysettings.xml" />
>> >    <ivy:resolve ... />
>> >    </target>
>> > </project>
>> >
>> > I've abbreviated the ivy:resolve call, but take it from me that
>> everything
>> > works, including running the test directly from within an IDE. It
>> > shouldn't. Nowhere am I specifying the classpath containing the JAR for
>> Ivy
>> > itself. Nowhere am I even doing the taskdef for Ivy.
>>
>> In fact, just by writing xmlns:ivy="antlib:org.apache.ivy.ant", Ant does
>> a lookup for the Ivy tasks. It will look for org/apache/ivy/ant/antlib.xml
>> in the classpath, derived from the uri definition of the xmlns.
>> So I guess that in your IDE, it works since you have Ivy in your
>> classpath.
>>
>> > And in fact, if I run this build.xml straight from the command line, it
>> > fails like you would expect:
>> > Problem: failed to create task or type
>> antlib:org.apache.ivy.ant:settings
>> > ...
>>
>> This means that the Ivy jar is neither in the Ant classpath
>> ($ANT_HOME/lib/), nor in your ant user lib directory ($HOME/.ant/lib).
>>
>> > I can only think that the key method I want to take advantage of is
>> this in
>> > Project:
>> >    public void setCoreLoader(ClassLoader coreLoader) {
>> >        this.coreLoader = coreLoader;
>> >    }
>> >
>> > What's interesting is that the command-line entry point for Ant in
>> > org.apache.tools.ant.Main ends up passing null to setCoreLoader, in
>> which
>> > case "the parent classloader should be used," according to the method's
>> > Javadoc. At this point, I go take a look at the scripts that launch Ant
>> and
>> > my eyes get blurry. But I believe what I want to do is manually
>> construct a
>> > ClassLoader as if I were running "java -cp ..." with the contents of
>> Ant's
>> > lib directory. (I'm showing my ignorance of ClassLoaders here, I
>> realize.)
>> > Perhaps someone can point me to an elegant way to accomplish that.
>>
>> On the command line, the jvm is given only the ant-launcher.jar, it will
>> be the content of the system classloader.
>> Then in org.apache.tools.ant.launch.Launcher.run(String[]), we can see
>> the ant Main class is loaded and launched with this classloader:
>> new URLClassLoader(jars, Launcher.class.getClassLoader());
>> The system is the parent, the jars listed from $ANT_HOME/lib/,
>> $HOME/.ant/lib and other jars from the jvm (tools.jar for instance) will be
>> the content of that classloader. This last classloader will be the parent
>> classloader of the Project instance.
>>
>> Now in your IDE, you launch your unit test with every jar listed in your
>> classpath, so every thing is already available in the system classlaoder.
>> And that system classloader is the parent classloader of the Project
>> instance. So lookups works.
>> So if you want thing to not work, you'll have to build indeed an
>> URLClassloader, just like org.apache.tools.ant.launch.Launcher.run does.
>> But without any parent classlaoder, otherwise you end up again with the
>> full classpath of your project in the IDE.
>>
>> But If I were you, I wouldn't bother trying to mimic Ant classloading in
>> a unit test. The important thing is to be sure that you run against the
>> expected version of Ant and Ivy. And relying on the content of a
>> $HOME/.ant/lib is quite unsafe.
>>
>> Nicolas
>>
>>
>> ---------------------------------------------------------------------
>> To unsubscribe, e-mail: dev-unsubscribe@ant.apache.org
>> For additional commands, e-mail: dev-help@ant.apache.org
>>
>>
>
Mime
  • Unnamed multipart/alternative (inline, None, 0 bytes)
View raw message