Sharing functions between Grails (Gant scripts)

classic Classic list List threaded Threaded
23 messages Options
12
Reply | Threaded
Open this post in threaded view
|

Sharing functions between Grails (Gant scripts)

elvanor
Hi, this is basically the same question of a two years old thread I have found, that was basically unanswered.


What's the best/preferred way of sharing functions between scripts?

 I have an helper class in scripts/ that contain some common helper functions. How can I call them from targets in the main scripts?

I tried included the helper Groovy file, but it does not work: the functions are never found. I would also not know how to do that in Groovy; if you launch a Groovy script file, how can you include another class or groovy file to get access to its functions?

Jean-Noel
Reply | Threaded
Open this post in threaded view
|

Re: Sharing functions between Grails (Gant scripts)

pledbrook
> Hi, this is basically the same question of a two years old thread I have
> found, that was basically unanswered.
>
>
> What's the best/preferred way of sharing functions between scripts?
>
>  I have an helper class in scripts/ that contain some common helper
> functions. How can I call them from targets in the main scripts?
>
> I tried included the helper Groovy file, but it does not work: the functions
> are never found. I would also not know how to do that in Groovy; if you
> launch a Groovy script file, how can you include another class or groovy
> file to get access to its functions?

This is a tricky one. Here are a few approaches:

1. Write a method in one of the scripts. This can be called by any
code in the script, but not by anything in a different script.

2. Assign a closure to a script variable. This will be available as a
global "method" in all scripts. Downside is namespace pollution.

3. Assign a helper object to a script variable. The helper object can
contain as many methods as you like. You could even create it on
demand within the targets, thus avoiding pollution of the global
namespace.

4. Use a class with static methods. This works OK, but you can't then
take advantage of Spring injection in your application.

Once upon a time, I think 3 and 4 had class loader problems, but they
should certainly work now. I'm looking at using approach 3 from now
on, unless it turns out to be problematic.

Hope that helps,

Peter

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: Sharing functions between Grails (Gant scripts)

elvanor
Hi Peter, thanks for your helpful reply. But I am still stuck :)

Method 1 works, but is not what I need since I want to define a function in another script to better modularize my code.

Method 2 was also suggest by Russel on the groovy ML, but strangely it does not work... My exact test code:

In Example.groovy:

includeTargets << grailsScript("_GrailsSettings")
includeTargets << new File("${grailsSettings.baseDir}/scripts/Helper.groovy")

target("default": "Calls helper method")
{
    callHelperClosure()
}

In Helper.groovy:

def callHelperClosure =
{
    println "Hello World"
}

I get:

Error executing script Example: groovy.lang.MissingMethodException: No signature of method: Example.callHelperClosure() is applicable for argument types: () values: []

Am I doing something wrong?

Method 3 I did not understand what you meant. Would you have a snippet of code?

Method 4 would be perfect for me but I also cannot get it to work. If I define a class with static methods, and import it via the Gant include mechanism, I cannot call the methods of the class... (If my class is named Helper, I get

Error executing script TreatI18N: No such property: Helper for class: Example when I write:

Helper.myStaticMethod()

I am using Grails 1.1.1 btw.

On Mon, Aug 17, 2009 at 9:39 PM, Peter Ledbrook <[hidden email]> wrote:
> Hi, this is basically the same question of a two years old thread I have
> found, that was basically unanswered.
>
>
> What's the best/preferred way of sharing functions between scripts?
>
>  I have an helper class in scripts/ that contain some common helper
> functions. How can I call them from targets in the main scripts?
>
> I tried included the helper Groovy file, but it does not work: the functions
> are never found. I would also not know how to do that in Groovy; if you
> launch a Groovy script file, how can you include another class or groovy
> file to get access to its functions?

This is a tricky one. Here are a few approaches:

1. Write a method in one of the scripts. This can be called by any
code in the script, but not by anything in a different script.

2. Assign a closure to a script variable. This will be available as a
global "method" in all scripts. Downside is namespace pollution.

3. Assign a helper object to a script variable. The helper object can
contain as many methods as you like. You could even create it on
demand within the targets, thus avoiding pollution of the global
namespace.

4. Use a class with static methods. This works OK, but you can't then
take advantage of Spring injection in your application.

Once upon a time, I think 3 and 4 had class loader problems, but they
should certainly work now. I'm looking at using approach 3 from now
on, unless it turns out to be problematic.

Hope that helps,

Peter

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

   http://xircles.codehaus.org/manage_email

Reply | Threaded
Open this post in threaded view
|

Re: Sharing functions between Grails (Gant scripts)

elvanor
Another thing I noticed: If I manually clear the scriptCache (grails clean does not do this, which I did not know... I think there should be an option for it), I get a different error with method 3:

Error executing script Example: No signature of method: Helper.main() is applicable for argument types: ([Ljava.lang.String;) values: [[]]

so it seems importing a class does not work out well...
Reply | Threaded
Open this post in threaded view
|

Re: Sharing functions between Grails (Gant scripts)

pledbrook
> Another thing I noticed: If I manually clear the scriptCache (grails clean
> does not do this, which I did not know... I think there should be an option
> for it), I get a different error with method 3:
>
> Error executing script Example: No signature of method: Helper.main() is
> applicable for argument types: ([Ljava.lang.String;) values: [[]]
>
> so it seems importing a class does not work out well...

Could you raise an issue with a reproducable example?

Thanks,

Peter

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: Sharing functions between Grails (Gant scripts)

Ian Roberts
In reply to this post by elvanor
Jean-Noël Rivasseau wrote:
> In Helper.groovy:
>
> def callHelperClosure =

Drop the "def", i.e. just do

callHelperClosure = { ... }

def introduces a local variable in the scope of that particular script.
 Without the def it will create a "global" variable shared between all
scripts.

Ian

--
Ian Roberts               | Department of Computer Science
[hidden email]  | University of Sheffield, UK

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: Sharing functions between Grails (Gant scripts)

pledbrook
In reply to this post by elvanor
> Method 3 I did not understand what you meant. Would you have a snippet of
> code?

target(default: "Do something") {
    def helper = new MyHelper()
    def modules = helper.findModules("src/java")
    ...
}

You can also make it available globally:

helper = new MyHelper()

target(default: "Do something") { ... }

> Method 4 would be perfect for me but I also cannot get it to work. If I
> define a class with static methods, and import it via the Gant include
> mechanism, I cannot call the methods of the class... (If my class is named
> Helper, I get

What do you mean by the Gant include mechanism? You should be able to
use a straight Java/Groovy import statement.

Cheers,

Peter

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: Sharing functions between Grails (Gant scripts)

elvanor
In reply to this post by Ian Roberts
Ha yes, this worked. So I got method 2 working, thanks.

On Tue, Aug 18, 2009 at 11:03 AM, Ian Roberts <[hidden email]> wrote:
Jean-Noël Rivasseau wrote:
> In Helper.groovy:
>
> def callHelperClosure =

Drop the "def", i.e. just do

callHelperClosure = { ... }

def introduces a local variable in the scope of that particular script.
 Without the def it will create a "global" variable shared between all
scripts.

Ian

--
Ian Roberts               | Department of Computer Science
[hidden email]  | University of Sheffield, UK

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

   http://xircles.codehaus.org/manage_email
Reply | Threaded
Open this post in threaded view
|

Re: Sharing functions between Grails (Gant scripts)

elvanor
In reply to this post by pledbrook
> Method 4 would be perfect for me but I also cannot get it to work. If I
> define a class with static methods, and import it via the Gant include
> mechanism, I cannot call the methods of the class... (If my class is named
> Helper, I get

What do you mean by the Gant include mechanism? You should be able to
use a straight Java/Groovy import statement.

I meant:

includeTargets << new File("${grailsSettings.baseDir}/scripts/Helper.groovy")

My helper script is not in any packaged (default package) so I dont need to use an import statement I think... but I still got the NoSuchProperty exception when trying to use method 4 with a class with static methods (which would be my prefered way of doing things).

Method 3 also does not work since it does not find the helper class:

def tmp = new Helper2()

produces:

org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed, Example: 8: unable to resolve class Helper2

Reply | Threaded
Open this post in threaded view
|

Re: Sharing functions between Grails (Gant scripts)

pledbrook
> I meant:
>
> includeTargets << new
> File("${grailsSettings.baseDir}/scripts/Helper.groovy")
>
> My helper script is not in any packaged (default package) so I dont need to
> use an import statement I think... but I still got the NoSuchProperty
> exception when trying to use method 4 with a class with static methods
> (which would be my prefered way of doing things).

Ah, sorry, I meant a helper class under "src/groovy" or "src/java" -
not a script.

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: Sharing functions between Grails (Gant scripts)

elvanor
I see. No way to do that with a script helper class? It would be better, but I think I can cope with a "normal" helper class.

On Tue, Aug 18, 2009 at 11:55 AM, Peter Ledbrook <[hidden email]> wrote:
> I meant:
>
> includeTargets << new
> File("${grailsSettings.baseDir}/scripts/Helper.groovy")
>
> My helper script is not in any packaged (default package) so I dont need to
> use an import statement I think... but I still got the NoSuchProperty
> exception when trying to use method 4 with a class with static methods
> (which would be my prefered way of doing things).

Ah, sorry, I meant a helper class under "src/groovy" or "src/java" -
not a script.

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

   http://xircles.codehaus.org/manage_email
Reply | Threaded
Open this post in threaded view
|

Re: Sharing functions between Grails (Gant scripts)

pledbrook
> I see. No way to do that with a script helper class? It would be better, but
> I think I can cope with a "normal" helper class.

Only method 2 works with helper scripts, such as "_GwtInternal.groovy"
that comes with the GWT plugin.

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: Sharing functions between Grails (Gant scripts)

elvanor
Thanks for anyone who helped in this thread.

I finally went for the static methods way in a helper class in src/groovy.

I still have a problem with that and felt a JIRA should be raised (I did it, GRAILS-5007):

Everytime I change the helper class in src/groovy (and recompile it via depends(compile) in my script), and rerun the script it produces the following error:

java.lang.NoClassDefFoundError: com.example.Helper

I have to manually got to ~/.grails/1.1.1/scriptCache and remove all the files there. Then in the next run it's OK, but if I ever touch the helper file again, I have to delete all the script caches.

This should not happen (besides, even grails clean does not clean the script cache), so this IMHO is a bug.

Jean-Noel


Reply | Threaded
Open this post in threaded view
|

Re: Sharing functions between Grails (Gant scripts)

elvanor
Hmm, Graeme just closed the issue stating that since a script can exist prior to compilation, it cannot pretend to access src/groovy classes directly (which makes sense). He suggested that I use the classLoader directly to load my class.

How can one achieve that? I already got the compile part covered.

Jean-Noel


On Wed, Aug 19, 2009 at 10:07 AM, Jean-Noël Rivasseau <[hidden email]> wrote:
Thanks for anyone who helped in this thread.

I finally went for the static methods way in a helper class in src/groovy.

I still have a problem with that and felt a JIRA should be raised (I did it, GRAILS-5007):

Everytime I change the helper class in src/groovy (and recompile it via depends(compile) in my script), and rerun the script it produces the following error:

java.lang.NoClassDefFoundError: com.example.Helper

I have to manually got to ~/.grails/1.1.1/scriptCache and remove all the files there. Then in the next run it's OK, but if I ever touch the helper file again, I have to delete all the script caches.

This should not happen (besides, even grails clean does not clean the script cache), so this IMHO is a bug.

Jean-Noel

Reply | Threaded
Open this post in threaded view
|

Re: Sharing functions between Grails (Gant scripts)

elvanor
I tried

includeTargets << grailsScript("_GrailsClasspath")

and then depending on the classpath target, no luck though. Class is still not found...

On Wed, Aug 19, 2009 at 11:05 AM, Jean-Noël Rivasseau <[hidden email]> wrote:
Hmm, Graeme just closed the issue stating that since a script can exist prior to compilation, it cannot pretend to access src/groovy classes directly (which makes sense). He suggested that I use the classLoader directly to load my class.

How can one achieve that? I already got the compile part covered.

Jean-Noel

Reply | Threaded
Open this post in threaded view
|

Re: Sharing functions between Grails (Gant scripts)

pledbrook
> I tried
>
> includeTargets << grailsScript("_GrailsClasspath")
>
> and then depending on the classpath target, no luck though. Class is still
> not found...

You need to call "classLoader.loadClass(...)", where 'classLoader' is
a script variable available to all Grails scripts.

Cheers,

Peter

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: Sharing functions between Grails (Gant scripts)

elvanor
I did that, but it has no effect at all (although the call succeeds). After that, I still get a class not found error. UNLESS I remove the script caches before, which really makes me think it is a Grails bug after all...

In fact, I noticed that it succeeds only the first time I run the script when the cache is empty. Everytime after that, it fails, even if I dont touch anything.

Jean-Noel

On Wed, Aug 19, 2009 at 5:02 PM, Peter Ledbrook <[hidden email]> wrote:
> I tried
>
> includeTargets << grailsScript("_GrailsClasspath")
>
> and then depending on the classpath target, no luck though. Class is still
> not found...

You need to call "classLoader.loadClass(...)", where 'classLoader' is
a script variable available to all Grails scripts.

Cheers,

Peter

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

   http://xircles.codehaus.org/manage_email
Reply | Threaded
Open this post in threaded view
|

Re: Sharing functions between Grails (Gant scripts)

pledbrook
> I did that, but it has no effect at all (although the call succeeds). After
> that, I still get a class not found error. UNLESS I remove the script caches
> before, which really makes me think it is a Grails bug after all...

Once you've loaded the class via the class loader, how do you access
the methods on it? You should have something like:

def helperClass = classLoader.loadClass("org.example.MyHelper")
helperClass.someMethod()

I think that second line should work, since the code is Groovy.

Cheers,

Peter

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: Sharing functions between Grails (Gant scripts)

elvanor
Thanks Peter. Worked at last :)

I realize that I have near zero knowledge in the areas of Java / Groovy classloading etc. Is there some useful documentation somewhere?

In particular I would like to know the following:

* Why am I forced to load the class via the class loader? Isnt it something that is automatically done in plain java via import statements? In this case, why import statements did not work on this Groovy file? Because it was a script, not a class?

* How is classloading related to the classpath? I guess you can load a class only if it is present (compiled) on the classpath. Or are all the classes on the cp automagically loaded?

* What's the difference between Groovy class loading and normal Java classloading?

Any pointers welcomed!

Cheers
Jean-Noel

On Wed, Aug 19, 2009 at 5:57 PM, Peter Ledbrook <[hidden email]> wrote:
> I did that, but it has no effect at all (although the call succeeds). After
> that, I still get a class not found error. UNLESS I remove the script caches
> before, which really makes me think it is a Grails bug after all...

Once you've loaded the class via the class loader, how do you access
the methods on it? You should have something like:

def helperClass = classLoader.loadClass("org.example.MyHelper")
helperClass.someMethod()

I think that second line should work, since the code is Groovy.

Cheers,

Peter

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

   http://xircles.codehaus.org/manage_email

Reply | Threaded
Open this post in threaded view
|

Re: Sharing functions between Grails (Gant scripts)

Ian Roberts
There isn't really any difference between the way Java and Groovy
classes handle class loading.  The thing that makes it *appear*
different is the fact that Groovy does everything by dynamic dispatch.
In Groovy, method calls on an object of type java.lang.Class (apart from
methods of the object itself such as toString) get dispatched to static
methods of the class that the Class object represents, i.e. the Groovy

Class cl = classLoader.loadClass("com.example.MyClass")
cl.foo()

does effectively the same as the Java

Class cl = classLoader.loadClass("com.example.MyClass");
Method m = cl.getMethod("foo");
m.invoke();

> * Why am I forced to load the class via the class loader? Isnt it
> something that is automatically done in plain java via import
> statements? In this case, why import statements did not work on this
> Groovy file? Because it was a script, not a class?
>
> * How is classloading related to the classpath? I guess you can load a
> class only if it is present (compiled) on the classpath. Or are all the
> classes on the cp automagically loaded?

You can only make a static reference to class X from class Y, such as
"import X" or "new X()", if the classloader that defined X is the same
or an ancestor of the one that defined Y.  If Y is on the classpath
(i.e. in the system classloader) and X is in a custom classloader (e.g.
the classLoader variable in a grails script) you can't make a static
reference, but you can load X dynamically and invoke its methods by
reflection (or groovily, as above).

I hope this has made things clearer rather than more confusing,
classloaders are a bit of a minefield at the best of times...

Ian

--
Ian Roberts               | Department of Computer Science
[hidden email]  | University of Sheffield, UK

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


12