56

I have a groovy script that needs a library in a jar. How do I add that to the classpath? I want the script to be executable so I'm using #!/usr/bin/env groovy at the top of my script.

timdisney
  • 5,287
  • 9
  • 35
  • 31
  • 2
    Looks like this was already asked: http://stackoverflow.com/questions/254385/how-do-i-auto-load-a-database-jar-in-groovy-without-using-the-cp-switch – timdisney Nov 20 '08 at 17:45
  • As stated in answers below, it is best to use `#!/bin/sh` and then call `groovy` with whatever arguments you want. The minimum would be `#!/bin/sh\n groovy -cp PATH-OR-JAR $@` to pass all of the arguments passed on the command line to the groovy command. In Windows it would be `groovy -cp PATH %*` – PatS Apr 02 '21 at 16:48

9 Answers9

58

Starting a groovy script with #!/usr/bin/env groovy has a very important limitation - No additional arguments can be added. No classpath can be configured, no running groovy with defines or in debug. This is not a groovy issue, but a limitation in how the shebang (#!) works - all additional arguments are treated as single argument so #!/usr/bin/env groovy -d is telling /usr/bin/env to run the command groovy -d rathen then groovy with an argument of d.

There is a workaround for the issue, which involves bootstrapping groovy with bash in the groovy script.

#!/bin/bash                                                                                                                                                                 
//usr/bin/env groovy  -cp extra.jar:spring.jar:etc.jar -d -Dlog4j.configuration=file:/etc/myapp/log4j.xml "$0" $@; exit $?

import org.springframework.class.from.jar
//other groovy code
println 'Hello'

All the magic happens in the first two lines. The first line tells us that this is a bash script. bash starts running and sees the first line. In bash # is for comments and // is collapsed to / which is the root directory. So bash will run /usr/bin/env groovy -cp extra.jar:spring.jar:etc.jar -d -Dlog4j.configuration=file:/etc/myapp/log4j.xml "$0" $@ which starts groovy with all our desired arguments. The "$0" is the path to our script, and $@ are the arguments. Now groovy runs and it ignores the first two lines and sees our groovy script and then exits back to bash. bash then exits (exit $?1) with status code from groovy.

Patrick
  • 3,901
  • 1
  • 25
  • 30
  • Can this be done with a Windows batch file? – djangofan Nov 15 '12 at 22:37
  • 2
    Nice tip - although it doesn't appear to work in a Windows Cygwin shell, as the double slash in //usr/bin/env is not collapsed. – Henry Nov 29 '12 at 12:39
  • 4
    On Linux (BSD needs more logic to do readlink -f), the following picks out the current scripts path and includes that in the classpath. `// 2>/dev/null; SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"; exec groovy -cp "$SCRIPT_DIR" "$0"` – foozbar Aug 07 '14 at 18:43
39

If you really have to you can also load a JAR at runtime with:

this.getClass().classLoader.rootLoader.addURL(new File("file.jar").toURL())
Matt
  • 74,352
  • 26
  • 153
  • 180
Eric Wendelin
  • 43,147
  • 9
  • 68
  • 92
  • Heh, definitely missed the "Adding things to the classpath" section first time I read that. – timdisney Nov 20 '08 at 17:43
  • 1
    I could not get that classloader to work... anyone else able to use it? – Zombies Apr 23 '10 at 15:18
  • 4
    There are limitations not covered in the documentation when running with a shebang(#!). I explained them further [down](http://stackoverflow.com/a/8945888/54396). @Zombies, the classloader technique only works when you don't need to import classes from that .jar. Imports are resolved at prior to the script running and importing the .jar – Patrick Jan 20 '12 at 18:10
  • @Zombies you can get it to work by delaying the instantiation using reflection, see the example here: https://gist.github.com/tknerr/42258e761f2a0f95a92b#comment-1315206 – Torben Knerr Oct 09 '14 at 12:58
  • 1
    unfortunately, the link does not work anymore – Calon May 20 '15 at 11:31
  • Link is now dead, maybe try: https://docs.gradle.org/current/userguide/dependency_management.html#sub:file_dependencies – Hugo Zaragoza Jun 20 '15 at 20:26
  • 3
    It doesn`t wor for me. java.lang.NullPointerException: Cannot invoke method addURL() on null object – fafnir1990 Aug 05 '16 at 14:24
  • It's still working, you just then need to create dynamically an instance `def h = Class.forName("com.Hello").newInstance(); `, then you should be able to use it. – Ibrahim.H Jan 06 '21 at 13:27
26

My Favorite way to do this is with Groovy Grapes. These access the Maven Central Repository, download the referenced jar, and then put it on the classpath. Then you can use the library like any other library. The syntax is really simple:

@Grab(group='com.google.collections', module='google-collections', version='1.0')

You can read more details here. One major advantage here is that you don't need to distribute your dependencies when you distribute your script. The only drawback to this method is that the Jar has to be in the Maven repository.

MeTTeO
  • 2,088
  • 16
  • 19
Spina
  • 8,986
  • 7
  • 37
  • 36
25

You can add the jars to $HOME/.groovy/lib

Ove S
  • 5,205
  • 2
  • 18
  • 7
10

You can also try out Groovy Grape. It lets you use annotations to modify the classpath. Its experimental right now, but pretty cool. See docs.groovy-lang.org/.../grape

MeTTeO
  • 2,088
  • 16
  • 19
Bob Herrmann
  • 9,458
  • 10
  • 38
  • 45
6

The same as you would in Java.

This is an example of running a MySQL status monitoring script. mysql.jar contains the MySQL connector that I call from script status.groovy.

groovy -cp mysql.jar status.groovy ct1

tessein
  • 2,945
  • 1
  • 18
  • 5
  • On Windows it did not work out for me with `-cp mysql.jar`. I had to use `-cp ./mysql.jar` instead – Kirill Feb 07 '19 at 11:40
6

Below is a combination of Patrick's solution, Maarteen Boekhold's solution, and foozbar's comment that works with both Linux and Cygwin:

#!/bin/bash
// 2>/dev/null; SCRIPT_DIR="$( cd "$( dirname "$0" )" && pwd )"
// 2>/dev/null; OPTS="-cp $SCRIPT_DIR/lib/extra.jar:$SCRIPT_DIR/lib/spring.jar"
// 2>/dev/null; OPTS="$OPTS -d"
// 2>/dev/null; OPTS="$OPTS -Dlog4j.configuration=file:/etc/myapp/log4j.xml"
// 2>/dev/null; exec groovy $OPTS "$0" "$@"; exit $?

import org.springframework.class.from.jar
//other groovy code
println 'Hello'

How it works:

  • // is a valid groovy comment, so all of the bash commands are ignored by Groovy.
  • // will return an error, but the error output is redirected to /dev/null and is therefore not displayed.
  • bash executes commands following a semicolon even though the previous command failed.
  • exec replaces the current program in the current process without forking a new process. Thus, groovy runs within the original script process (ps shows the process as the script rather than the groovy executable)
  • The exit $? statement following exec groovy prevents bash from trying to interpret the rest of the script as a bash script, and also preserves the return code from the groovy script.

The above bash trick is more convenient in some cases than the RootLoader trick because you can use regular import statements within the script. Using the RootLoader trick forces you to load all of the classes using reflection. This is fine in some situations (such as when you need to load a JDBC driver), but inconvenient in others.

If you know your script will never be executed on Cygwin, then using Patrick's or Maarteen's solution will likely result in slightly better performance because they avoid the overhead of generating and throwing away an error.

Community
  • 1
  • 1
Jim Hurne
  • 7,187
  • 4
  • 44
  • 44
3

Adding to @Patrick his answer, which helped me a lot, I recently discovered another trick.

If you add lots of jars to the classpath all on one line, things can become quite unreadable. But you can do the following!

#!/bin/bash
//bin/true && OPTS="-cp blah.jar -Dmyopt=value"
//bin/true && OPTS="$OPTS -Dmoreopts=value2"
//usr/bin/env groovy $OPTS "$0" $@; exit $?

println "inside my groovy script"

Let your imagination run wild on how complex a command line you can break down this way into manageable pieces

Maarten

Maarten Boekhold
  • 867
  • 11
  • 21
2

If you want to use it right away before the import declarations it is possible like this :) :

// printEmployees.groovy
this.class.classLoader.rootLoader.addURL(
   new URL("file:///C:/app/Dustin/product/11.1.0/db_1/jdbc/lib/ojdbc6.jar"))
import groovy.sql.Sql
sql = Sql.newInstance("jdbc:oracle:thin:@localhost:1521:orcl", "hr", "hr",
                      "oracle.jdbc.pool.OracleDataSource")
sql.eachRow("SELECT employee_id, last_name, first_name FROM employees")
{
   println "The employee's name is ${it.first_name} ${it.last_name}."
}

Taken from this javaworld.com article.

Andreas Covidiot
  • 4,286
  • 5
  • 51
  • 96