74

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?

Noel Yap
  • 18,822
  • 21
  • 92
  • 144
Dan Woodward
  • 2,559
  • 1
  • 16
  • 19

6 Answers6

88

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
mklement0
  • 382,024
  • 64
  • 607
  • 775
seansand
  • 1,492
  • 4
  • 18
  • 23
  • 1
    Interesting. It doesn't work the way I expected it to. But that is due to the fact I am running a gant script from gant. So the codeSource is actually where gant is, not where my script is. – Dan Woodward Jul 23 '09 at 17:44
  • 1
    Doesn't work for me. `getClass().protectionDomain.codeSource` returns null. I'm using Groovy 2.0.1. – quux00 Mar 13 '14 at 21:35
  • Doesn't work on 1.11 either, returns something like: .gradle/caches/1.11/scripts/build_189dc3r2nd588m3657jv5d36h7.. – minsk Jul 23 '14 at 21:08
  • It works for me in groovy 1.8.5 on OSx with a Java 8 install. – joensson May 27 '15 at 06:37
  • Confirmed that it works in Windows with Groovy version 2.1.5. – seansand May 28 '15 at 18:37
  • I am using maven and it gives me the path of target directory... :) – Karma-yogi Aug 20 '15 at 06:48
  • 2
    @quux00: Try `MyClassName.class.protectionDomain.codeSource` instead (ref. http://stackoverflow.com/questions/11747833/getting-filesystem-path-of-class-being-executed ) – neu242 Jun 17 '16 at 10:11
  • 4
    depending on how it's run, I get either `/groovy` for the parent or the correct directory. What about this: `getClass().getResource('/${yourScript}.groovy')` That works for me assuming your script is on the classpath – Christian Bongiorno Feb 02 '18 at 05:55
22

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.

M. Justin
  • 14,487
  • 7
  • 91
  • 130
  • 6
    `java.nio.file.FileSystemNotFoundException: Provider "data" not installed` – Hugues Fontenelle Jan 28 '19 at 14:45
  • 1
    @HuguesFontenelle What's the `sourceUri` value when you get that exception? Or does that happen on the `@SourceUri` line and not when you convert it to a `Path`? – M. Justin Jan 28 '19 at 15:00
  • `sourceUri` is a map containing ```[rawAuthority:null, opaque:true, scheme:data, rawQuery:null, port:-1, rawUserInfo:null, path:null, class:class java.net.URI, absolute:true, schemeSpecificPart:,CONTENT, rawPath:null, query:null, fragment:null, host:null, authority:null, rawFragment:null, userInfo:null, rawSchemeSpecificPart:,CONTENT]``` (where CONTENT is my serialized script). The `path` and `rawPath` keys have `null` value. – Hugues Fontenelle Jan 29 '19 at 11:47
  • 1
    @HuguesFontenelle According to the docs for @SourceURI, the resulting value will be an instance of `java.net.URI`; plus the map you're showing are the properties of the `java.net.URI` class. Are you sure it's actually a map that's being set, and not actually a `URI`? Regardless, this looks like a data URL, not a file URL. Is the source of the script actually a local file, and not something that's been serialized over the network or something like that? In that case, there wouldn't even be a corresponding file, which explains why you couldn't convert it to a `Path`. – M. Justin Jan 29 '19 at 15:47
  • 1
    @HuguesFontenelle How are you running the script? Per the answers to this question ( https://stackoverflow.com/questions/26240588/how-to-use-sourceuri-annotation-to-retrieve-the-full-path-of-the-script-file-in ), the `@SourceURI` approach does not work if running from the Groovy console, which I've confirmed locally. What happens when you run the script directly from the command line? e.g. `groovy sourceUriTest.groovy`, replacing "`sourceUriTest.groovy`" with your actual script file? – M. Justin Jan 29 '19 at 15:56
  • 1
    I'm using [nextflow](https://www.nextflow.io) which uses groovy, and I just found out that the file path was available to me using `workflow.scriptFile` (or `workflow. projectDir` for the dir path). Thanks for looking into this! – Hugues Fontenelle Jan 30 '19 at 12:47
  • @HuguesFontenelle I've updated the question for the scenario with no matching `FileSystemProvider`. – M. Justin Mar 10 '19 at 00:15
11

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%" 
M. Justin
  • 14,487
  • 7
  • 91
  • 130
Joshua Davis
  • 3,499
  • 1
  • 26
  • 29
1

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/"
            }
        }
    }
}
Liem Le
  • 581
  • 7
  • 17
1

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()
Naeel Maqsudov
  • 1,352
  • 14
  • 23
0

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.

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