Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Compile groovy script statically with command line arguments

I am trying to statically compile a groovy script to speed up it's execution, but am not able to get it to work if command line arguments are used. My actual script is much longer, but the one-line script I use for this question perfectly reproduces my error.

Using the following script (test.groovy)

println(args.length)

This can be compiled with the command groovyc test.groovy and ran by the java command java -cp .;%GROOVY_HOME%\lib\* test and will simply print the number of command line arguments used.

Now, if I provide the script (config.groovy)

withConfig(configuration) {
    ast(groovy.transform.CompileStatic)
}

and compile with groovyc -configscript config.groovy test.groovy, I get an error

org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
testing.groovy: 1: [Static type checking] - The variable [args] is undeclared.
 @ line 1, column 9.
   println(args.length)
           ^

1 error

This error only occurs when I attempt to compile statically. I can get it to work by wrapping the script in a class and putting my code in a main method (which, of course, is what the compiler does with a script), but not when I try to just use the script (which is what I prefer to do). For some reason, the variable args is unknown when compiled statically. I've tried this.args but still receive the error. If I try to declare a type for args (String[] args), it no longer receives the command line arguments.

Is there a way to still get the command line arguments when a script is compiled statically this way?

I am using Groovy version 2.4.10 on Windows 7 with Java 8.

like image 367
Matthew Avatar asked Mar 09 '23 15:03

Matthew


2 Answers

The Script works via dynamic evaluation of the bindings object. If you want to use static compilation, you need to use the explicit form, changing your test.groovy script into the following:

String[] args = (String[])binding.getVariable('args')
println args.length

Using your already provided configuration script you do get a static compiled Script. I tested running it this way:

groovyc --configscript config.groovy test.groovy
java -cp .;%GROOVY_HOME%\lib\groovy-2.5.3.jar test 1 2 3

This prints 3.

If you want to not modify test.groovy at all, you can create a new base class:

import groovy.transform.CompileStatic

@CompileStatic
abstract class StaticBase extends Script {
    StaticBase() {
    }

    StaticBase(Binding binding) {
        super(binding)
    }

    String[] getArgs() {
        (String[]) getBinding().getVariable("args")
    }
}

Since the base class has a method getArgs, then when the test.groovy refers to args, the static compiler picks up the call to that method.

groovyc --configscript config.groovy -b StaticBase test.groovy
java -cp .;%GROOVY_HOME%\lib\groovy-2.5.3.jar test 1 2

The code in test.class has a run method whose code represents this.println(this.getArgs().length)

like image 158
Jason Winnebeck Avatar answered Apr 26 '23 08:04

Jason Winnebeck


There's difference in executing Groovy class and running simple script. It's not correct that compiler simply wraps your script in main method, the body of the script will be copied into a run method.

Example:

println(args.length)

will be converted to

import org.codehaus.groovy.runtime.InvokerHelper
class Main extends Script {                     
    def run() {                                 
        println(args.length)              
    }
    static void main(String[] args) {           
        InvokerHelper.runScript(Main, args)     
    }
}

This compiles fine due to dynamic types. Now, if we add @CompileStatic annotation to that class, we'll get the error of undeclared variable.

So, you have to wrap your code in class in order to use static compiling.

You can read more about Scripts versus classes in documentation.

like image 24
kirill-a Avatar answered Apr 26 '23 07:04

kirill-a