https://kotlinlang.org logo
Title
s

Stefan Oltmann

10/13/2021, 11:53 AM
How do I find out the current directory of my EXE on Windows?
File(".").absolutePath
returns the working directory which I could change in a symlink.
Server.class.getProtectionDomain().getCodeSource().getLocation().getPath()
results in NullPointer if started from the EXE. Is there something compose specific to find that out?
1
t

Tomasz Krakowiak

10/13/2021, 3:08 PM
Not sure why you're looking for something "compose specific". Maybe natvie specific. And if you want to support it for JVM, than do you mean to find java.exe path?
s

Stefan Oltmann

10/13/2021, 3:10 PM
I was thinking that maybe Jetbrains has made a nice API for that I'm not aware of. The pure Java ways are somewhat hacky and some of them don't work from that EXE. I meant "Compose for Desktop specific"
Think of the very nice way they implemented to detect if the dark mode is active... You can do this on yourself, but sometimes Compose for Desktop already has a nice util for stuff like that ready to use. 😉
val path = Thread.currentThread().contextClassLoader.getResource("icon.ico")

val jarPath = path.path.substring(6, path.path.length - 10)

val pathToDir = File(jarPath)
  .parentFile
  .parentFile
  .canonicalPath
  .replace("%20", " ")
Not nice, but seems to work.
e

ephemient

10/13/2021, 5:41 PM
a more reliable way to get the JAR file from a resource URL would be
File((path.openConnection() as JarURLConnection).jarFileURL.toURI())
👍 1
and a more reliable way to get the JAR file containing a class in general would be
java.io.File(this::class.java.protectionDomain.codeSource.location.toURI())
s

Stefan Oltmann

10/13/2021, 5:43 PM
No, that does not work. "codesource" is NULL executed in the EXE
e

ephemient

10/13/2021, 5:44 PM
which class are you using for that? it will be null if it's a system class, but if it's one of your own I would expect it to work… unless something unusual is going on with the packaging
🙈 1
s

Stefan Oltmann

10/13/2021, 5:44 PM
I improved my solution a bit.
val url = Thread.currentThread().contextClassLoader.getResource("icon.ico")

val jarPath = url.path.substring(0, url.path.length - 10)

val pathToExe = Paths.get(URI(jarPath))
  .resolve("../../MyApp.exe")
  .toFile()
  .canonicalPath
Oh, well... Yes, I used a system class. I wasn't aware that this will make a difference 😅
e

ephemient

10/14/2021, 10:41 PM
it makes a huge difference; it comes from the parent classloader, after all. in any case I would consider
File(.toURI())
to be far more reliable than
.replace("%20", " ")
s

Stefan Oltmann

10/15/2021, 8:34 AM
Yes, you are right. Even URLDecoder.decode() is better than that.
I ended up with this and tested it even for UNC paths:
val url = Thread.currentThread().contextClassLoader.getResource("icon.ico")

val jarPath = url.path.substring(0, url.path.length - 10)

val pathToExe = Paths.get(URI(jarPath))
    .resolve("../../MyApp.exe")
    .toFile()
    .canonicalPath

<http://Log.info|Log.info>("App location: $pathToExe")
Other solutions may work well, too, but I think this is good enough. I can change the working dir in a link and it still finds the correct place of the EXE file. Is there any case where this code could break? 🤔
I would really love JetBrains to provide a variable with the path to the EXE and I think based on the Gradle native distribution configuration they could provide it. All they need is the name of the EXE which is configured and unless the user actively changes it I see no reason why this can fail.
But if the user starts to rename files in my distribution a lot of other things should fail as well 😄