How to convert picocli groovy-grape script to native standalone app? - groovy

I have a made my first groovy CLI app with picocli. Now, I want it to be available for use without any JVM installed on the client machine, maybe with the use of GraalVM.
This is for an opensource project:
https://github.com/kchaitanya863/db2csv

Another easy option is to dockerize your script (read this blog about how to do it https://groovy-lang.gitlab.io/101-scripts/docker/basico-en.html)
If you want to build a linux executable you need to change your project:
convert to a gradle project (maven is also an option but gradle has a lot of plugins)
change your script to a class with a tipical main (and move it to the standard directory src/main/groovy/mypackage)
add some tasks into you build.gradle similar to these https://gitlab.com/snippets/1797638

You will need to:
statically compile your groovy script
make the args variable available after static compilation with
final String[] args = getProperty("args") as String[]
specify a reflection configuration file for the classes dynamically loaded/invoked using reflection by Groovy (this may be useful)
specify a reflection configuration file for the classes loaded/invoked using reflection by picocli. The picocli-codegen module provides a picocli.codegen.aot.graalvm.ReflectionConfigGenerator tool to generate the configuration file.
If your script has any #Grape dependencies, you may need to turn off the Grape dependency manager with -Dgroovy.grape.enabled=false and add all dependencies to the classpath manually instead
Credit: I got most of these tips from this article by Szymon Stepniak

If you want to use Graal with Groovy, check out this article:
https://e.printstacktrace.blog/graalvm-and-groovy-how-to-start/

Related

How can I use Groovy classes in other packages without JAR?

I'm pretty new to Groovy (coming from Java), so this may be a stupid question :-)
Nonetheless: I'd like to structure a couple of Groovy scripts using packages. And I'd like to import some general Groovy classes from some other package.
How can I make sure that my Groovy scripts finds the other classes in the other packages? The only classpath related files I can remember are JARs.
If you from java:
Groovy loads classes as java and also includes non-compiled with extension .groovy.
So, you have to place your classes relative to classpath according to their package name.
The command line should be something like this:
groovy -cp "path_to_classes_root" "path_and_name_of_main_groovy_script.groovy"

Groovy Imports Taking Several Seconds

I'm completely new to Groovy, so apologize in advance if I'm missing something obvious.
I'm trying to do some simple REST API scripting in Groovy, but first wanted to understand it's performance for requests/JSON parsing vs Python. I wrote the following script - and am seeing that the imports are taking ~7 seconds. Is there any way to 'include' those in the script, so it doesn't take so long on each run?
def now = new Date()
println now.format("yyyyMMdd-HH:mm:ss.SSS", TimeZone.getTimeZone('UTC'))
#Grab('org.codehaus.groovy.modules.http-builder:http-builder:0.7')
#Grab('oauth.signpost:signpost-core:1.2.1.2')
#Grab('oauth.signpost:signpost-commonshttp4:1.2.1.2')
import groovyx.net.http.RESTClient
import static groovyx.net.http.ContentType.*
for (i = 0; i <1; i++) {
def Client = new RESTClient("http://www.mocky.io/v2/59821b4a110000a9103964eb" )
def resp = Client.get(contentType: JSON)
def myResponseObject = resp.getData()
println myResponseObject.items[i].id
}
now = new Date()
println now.format("yyyyMMdd-HH:mm:ss.SSS", TimeZone.getTimeZone('UTC'))
I get this output:
~$ time groovy Requests.groovy
20170802-18:36:24.556
10
20170802-18:36:25.290
real 0m7.173s
user 0m4.986s
sys 0m0.329s
Just the first few lines of Grabs and imports are taking the majority of the runtime , and that's what I'd like to cut down.
It's not the import that takes time, but #Grab annotation which comes from Grape - a Groovy dependency management system. Those 3 lines:
#Grab('org.codehaus.groovy.modules.http-builder:http-builder:0.7')
#Grab('oauth.signpost:signpost-core:1.2.1.2')
#Grab('oauth.signpost:signpost-commonshttp4:1.2.1.2')
define your script dependencies. Those dependencies are 3rd party libraries provided as a JAR files. Some of them may even have their own dependencies which will be also downloaded to satisfy dependency you have defined (e.g. http-builder requires Apache's HTTP client and core lib).
Running this script takes some time (about 1 second on my laptop), because Groovy has to determine all dependencies and add them to the classpath to satisfy all imports. Keep in mind that your script uses a lot more dependencies than those 3 and all of them have to be resolved.
Using Grape is actually a compromise between using 3rd party libraries in the most easiest way and some overhead that is delegated to Groovy. Alternatively you could run your script with:
groovy -classpath ${GROOVY_CLASSPATH} Request.groovy
where ${GROOVY_CLASSPATH} contains paths to all JAR files you need to successfully run the script. And believe me - you will have to add at least 15 libraries instead of those 3 grapes. Then you will be able to remove all #Grab annotations (they are not needed in this case because you will satisfy groovy script with providing all libs in the classpath) and your script will execute in the blink of an eye - there will be no overhead caused by resolving and loading all dependencies.
Another alternative solution is to use Gradle to manage all dependencies and create so called "fat JAR" that contains all mandatory dependencies inside - in this case you will be able to run your program with java command and all imports will be in place without any dependencies resolving mechanism.
Final conclusion. Grape is a powerful Groovy's feature that has it's own limitations. It allows you to handle dependencies-hell pretty easily, but it comes with its own cost. I hope this answer will help you making a good choice.

How to make a GDSL file in a jar get picked up in another project in Intellij?

I have a project with a GDSL file that describes a DSL delegate like:
def ctx = context pathRegexp: ".*installer\\.groovy", scope: scriptScope()
contributor(ctx) {
delegatesTo(findClass("com.whatever.InstallerBase"))
}
I package this file up in the jar (just in the root of the jar) using maven.
In a separate project I have a maven dependency on my jar artifact containing the gdsl. However, my autocompletion doesn't work. It works fine with the sample scripts in the first project (with the GDSL).
Is there a step that I'm missing in order for the GDSL to be picked up? Do I need to place it in a special folder in the jar?
The problem was indeed what #PeterGromov indicated in the comment on the question:
ensure that the library jar is only attached as classes and not library source as well
both the source and library were configured and thus IDEA doesn't include it. I have opened a youtrack issue to fix this here:
https://youtrack.jetbrains.com/issue/IDEA-137411

Groovlet classpath

I'm writing a simple groovlet web application.
I have some groovy libraries (*.groovy), and all groovlets share this libraries.
Where should I put the groovy libraries for using in groovlets?
(I don't want to compile the library *.groovy because it could be changed many times)
See "Setting up groovylets [sic]" at this page.
To summarize the idea, assuming your webapp is called Pets:
edit ~/webapps/Pets/WEB-INF/web.xml as described
Groovy jars reside in ~/webapps/Pets/WEB-INF/lib as described
your Groovlets (*.groovy files) reside in ~/webapps/Pets
your custom Groovy jars/libraries reside in ~/webapps/Pets/WEB-INF/lib, per standard Servlet spec

Run groovy script from within gradle

What's the best way to create a gradle task, which runs a groovy script? I realize that gradle build files are groovy, so I would think it would be possible to do something like this:
task run << {
Script app = new GroovyShell().parse(new File("examples/foo.groovy"))
// or replace .parse() w/ a .evalulate()?
app.run()
}
I get all kinds of whacky errors when I try this if bar.groovy is using #Grab annotations or even doing simple imports. I want to create a gradle task to handle this, so that I can hopefully reuse the classpath definition.
Would it be better to move the examples directory into the src directory somewhere? What's a best practice?
Or you could do:
new GroovyShell().run(file('somePath'))
You could try using GroovyScriptEngine instead of the GroovyShell. I've used it previously with #Grab annotations. You will need all of groovy on the classpath, the groovy-all.jar will not be enough. I'm guessing Ivy isn't packaged in the groovy-all.jar. Something like this should to the trick:
This script presumes a groovy script at /tmp/HelloWorld.groovy
def pathToFolderOfScript = '/tmp'
def gse = new GroovyScriptEngine([pathToFolderOfScript] as String[])
gse.run('HelloWorld.groovy', new Binding())
http://wayback.archive.org/web/20131006153845/http://docs.codehaus.org/display/GRADLE/Cookbook#Cookbook-runningthingsfromGradle
ant.java(classname: 'com.my.classname', fork: true,
classpath: "${sourceSets.main.runtimeClasspath.asPath}")
I think you probably need to run the script as a new process... e.g.,
["groovy","examples/foo.groovy"].execute()
I would guess that the way Gradle is executed is not via invoking groovy, so the setup that makes #Grab work never happens. It could also be the the version of Groovy that Gradle uses doesn't support #Grab.

Resources