Compile groovy script statically with command line arguments - groovy

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.

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)

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.

Related

Using classpath when debugging groovy scripts

I'm new to Java and Groovy, and this is a really simple question on how to use classpath when debugging groovy scripts.
I setup my Groovy project according to https://gist.github.com/tknerr/42258e761f2a0f95a92b:
$ find src
src
src/main
src/main/groovy
src/main/groovy/impl
src/main/groovy/impl/FooImpl.groovy
src/test
src/test/groovy
src/test/groovy/main.groovy
$ head src/main/groovy/impl/FooImpl.groovy src/test/groovy/main.groovy
==> src/main/groovy/impl/FooImpl.groovy <==
package impl
class FooImpl {
def foo() {
"hello?!"
}
}
==> src/test/groovy/main.groovy <==
println new impl.FooImpl().foo()
And I have already build everything:
$ find build/classes
build/classes
build/classes/groovy
build/classes/groovy/main
build/classes/groovy/main/impl
build/classes/groovy/main/impl/FooImpl.class
build/classes/groovy/test
build/classes/groovy/test/main.class
My goal is to use existing classes in classpath without packaging them into .jar file each time when debugging my groovy scripts, but am wondering why I'm getting the following errors:
cd src/test/groovy
$ ls ../../../build/classes/
groovy
$ groovy --classpath ../../../build/classes/ main.groovy
.../src/test/groovy/main.groovy: 1: unable to resolve class impl.FooImpl
# line 1, column 9.
println new impl.FooImpl().foo()
^
1 error
Same with groovysh.
How can I make it working?

How to invoke a java program with classpath from Python 3.x

I am trying to execute an external java program from a python 3.7 program using the java command with classpath. I am using subprocess.Popen module in Python. Somehow I am not able to get it working! Appreciate any assistance!
cmd = ['java',
'-classpath', 'C:/Users/Documents/MqTransfer.jar', 'C:/Users/Documents/com.ibm.mq.commonservices.jar',
'C:/Users/Documents/com.ibm.mq.headers.jar', 'C:/Users/Documents/com.ibm.mq.jar',
'C:/Users/Documents/com.ibm.mq.jmqi.jar', 'C:/Users/Documents/com.ibm.mq.pcf.jar',
'C:/Users/Documents/connector.jar', 'C:/Users/Documents/xerces.jar',
'MyMqTransfer', 'C:/Users/Documents/queueTransfer.properties']
jproc = subprocess.Popen(cmd, stdout=PIPE, stderr=PIPE)
output, errors = jproc.communicate()
print(output, errors)
I am getting the below error
b'' b'Error: Could not find or load main class C:.Users.Documents.com.ibm.mq.commonservices.jar\r\n'
When I try to run the java program from my batch script it runs fine! This is the command I use in my batch script. The issue is with my python code!
java -classpath MqTransfer.jar;com.ibm.mq.commonservices.jar;com.ibm.mq.headers.jar;com.ibm.mq.jar;com.ibm.mq.jmqi.jar;com.ibm.mq.pcf.jar;connector.jar;xerces.jar com.ibm.my.mq.MyMqTransfer C:\Users\Documents\queueTransfer.properties
Based on the error, I believe the process being executed is something like 'java -classpath C:/Users/Documents/MqTransfer.jar c:/Users/Documents/com.ibm.mq.commonServices.jar [followed by the rest of the arguments you are passing to process]' such that java is passed MqTransfer.jar as the entire classpath argument and thinks 'C:.Users.Documents.com.ibm.mq.commonservices.jar' is your class to launch. Try combining your entire intended classpath into the 3rd argument of your launch and I think you will be good. It would look something like this:
cmd = ['java',
'-classpath', 'C:/Users/Documents/MqTransfer.jar;C:/Users/Documents/com.ibm.mq.commonservices.jar;C:/Users/Documents/com.ibm.mq.headers.jar;C:/Users/Documents/com.ibm.mq.jar;C:/Users/Documents/com.ibm.mq.jmqi.jar;C:/Users/Documents/com.ibm.mq.pcf.jar;C:/Users/Documents/connector.jar;C:/Users/Documents/xerces.jar',
'MyMqTransfer', 'C:/Users/Documents/queueTransfer.properties']

Run Groovy application

Now I need development and run simple Groovy TCP server.
Could you please help me make the right choice how I can run my application?
I know follow methods how I can run my Groovy simple application:
1) I can run:
groovy myserver.groovy
2) I can create jar-file and run it. In this case I can write follow code (accordingly documentation):
import org.codehaus.groovy.runtime.InvokerHelper
class MyApp extends Script {
def run() {
// TODO
}
static void main(String[] args) {
InvokerHelper.runScript(MyApp, args)
}
}
Please help me, which way is more effective?
For simple cases you can run your Groovy script in "listening" mode with the -l flag, like this:
groovy -l 9010 SimpleServer.groovy
This starts the SimpleServer script listening on port 9010. I took this example from mrhaki's Groovy Goodness blog here: http://mrhaki.blogspot.com/2009/12/groovy-goodness-serversocket-scripts.html. Check it out for the complete example.

Why won't Groovy honor my command-line switches?

I have the following Groovy class:
class MyApp {
public static void main(String[] args) {
String name = System.getProperty("name")
println "My name is ${name}."
}
}
When I package it up as an executable JAR and then run it (java -jar myapp.jar -Dname=Earl), here is the output I get:
My name is null.
Why is name null and not "Earl"?
The java executable considers everything that comes after the main class/Jar as an argument to the main method. Try java -Dname=Earl -jar myapp.jar. (Unless the Jar is a fat Jar or its manifest class path points to the Groovy Jar, you'll have to pass that as well using -cp).

How do you get the path of the running script in groovy?

I'm writing a groovy script that I want to be controlled via a properties file stored in the same folder. However, I want to be able to call this script from anywhere. When I run the script it always looks for the properties file based on where it is run from, not where the script is.
How can I access the path of the script file from within the script?
You are correct that new File(".").getCanonicalPath() does not work. That returns the working directory.
To get the script directory
scriptDir = new File(getClass().protectionDomain.codeSource.location.path).parent
To get the script file path
scriptFile = getClass().protectionDomain.codeSource.location.path
As of Groovy 2.3.0 the #SourceURI annotation can be used to populate a variable with the URI of the script's location. This URI can then be used to get the path to the script:
import groovy.transform.SourceURI
import java.nio.file.Path
import java.nio.file.Paths
#SourceURI
URI sourceUri
Path scriptLocation = Paths.get(sourceUri)
Note that this will only work if the URI is a file: URI (or another URI scheme type with an installed FileSystemProvider), otherwise a FileSystemNotFoundException will be thrown by the Paths.get(URI) call. In particular, certain Groovy runtimes such as groovyshell and nextflow return a data: URI, which will not typically match an installed FileSystemProvider.
This makes sense if you are running the Groovy code as a script, otherwise the whole idea gets a little confusing, IMO. The workaround is here: https://issues.apache.org/jira/browse/GROOVY-1642
Basically this involves changing startGroovy.sh to pass in the location of the Groovy script as an environment variable.
As long as this information is not provided directly by Groovy, it's possible to modify the groovy.(sh|bat) starter script to make this property available as system property:
For unix boxes just change $GROOVY_HOME/bin/groovy (the sh script) to do
export JAVA_OPTS="$JAVA_OPTS -Dscript.name=$0"
before calling startGroovy
For Windows:
In startGroovy.bat add the following 2 lines right after the line with
the :init label (just before the parameter slurping starts):
#rem get name of script to launch with full path
set GROOVY_SCRIPT_NAME=%~f1
A bit further down in the batch file after the line that says "set
JAVA_OPTS=%JAVA_OPTS% -Dgroovy.starter.conf="%STARTER_CONF%" add the
line
set JAVA_OPTS=%JAVA_OPTS% -Dscript.name="%GROOVY_SCRIPT_NAME%"
For gradle user
I have same issue when I'm starting to work with gradle. I want to compile my thrift by remote thrift compiler (custom by my company).
Below is how I solved my issue:
task compileThrift {
doLast {
def projectLocation = projectDir.getAbsolutePath(); // HERE is what you've been looking for.
ssh.run {
session(remotes.compilerServer) {
// Delete existing thrift file.
cleanGeneratedFiles()
new File("$projectLocation/thrift/").eachFile() { f ->
def fileName=f.getName()
if(f.absolutePath.endsWith(".thrift")){
put from: f, into: "$compilerLocation/$fileName"
}
}
execute "mkdir -p $compilerLocation/gen-java"
def compileResult = execute "bash $compilerLocation/genjar $serviceName", logging: 'stdout', pty: true
assert compileResult.contains('SUCCESSFUL')
get from: "$compilerLocation/$serviceName" + '.jar', into: "$projectLocation/libs/"
}
}
}
}
One more solution. It works perfect even you run the script using GrovyConsole
File getScriptFile(){
new File(this.class.classLoader.getResourceLoader().loadGroovySource(this.class.name).toURI())
}
println getScriptFile()
workaround: for us it was running in an ANT environment and storing some location parent (knowing the subpath) in the Java environment properties (System.setProperty( "dirAncestor", "/foo" )) we could access the dir ancestor via Groovy's properties.get('dirAncestor').
maybe this will help for some scenarios mentioned here.

Resources