Background
On Windows babashka.fs/which
will automatically find foo.exe
(and other :win-exts
) for foo
.
This is very helpful. We often don't really care about the Windows executable extension.
Problem
which
currently throws on non-relative paths.
For example, let's say I want to find the executable for java
on Windows.
Instead of finding the java
executable on the PATH
, I want to use JAVA_HOME
.
From a Windows 10 in PowerShell:
PS C:\Users\lee> clj --version
Clojure CLI version (deps.clj) 1.11.1.1252
PS C:\Users\lee> clj -Sdeps "{:deps {babashka/fs {:mvn/version \`"0.3.17\`"}}}"
Clojure 1.11.1
user=> (require '[babashka.fs :as fs])
nil
Finding java on the path works great:
user=> (fs/which "java")
#object[sun.nio.fs.WindowsPath 0x3e3861d7 "C:\\Users\\lee\\scoop\\apps\\graalvm22-jdk17\\current\\bin\\java.exe"]
But maybe we want to find it via JAVA_HOME
.
First, let's show that java.exe
exists through JAVA_HOME
:
user=> (fs/exists? (fs/file (System/getenv "JAVA_HOME") "bin" "java.exe"))
true
Ok, now let's try to find java.exe
through JAVA_HOME
using which
:
user=> (fs/which (fs/file (System/getenv "JAVA_HOME") "bin" "java"))
Execution error (IllegalArgumentException) at babashka.fs/path (fs.cljc:57).
C:\Users\lee\scoop\apps\graalvm22-jdk17\current\bin\java.com is not a relative path
This does not work because which
does not currently work on non-relative paths.
But if I contrive a relative path I can get which
to find the exe:
user=> (System/getProperty "user.dir")
"C:\\Users\\lee"
user=> (fs/which (fs/file (fs/relativize (System/getProperty "user.dir") (System/getenv "JAVA_HOME")) "bin" "java"))
#object[sun.nio.fs.WindowsPath 0x61af1510 "scoop\\apps\\graalvm22-jdk17\\current\\bin\\java.exe"]
Proposal
On Windows, which
could resolve the executable extension even for non-relative paths.
Additional Idea
Perhaps which
should allow non-relative paths in general?
If the passed in program
already resolves to an executable, which
could return it.
Given a foobar.bat
in the current directory, we see this behaviour when looking up programs that specify a relative path:
user=> (fs/which "./foobar")
#object[sun.nio.fs.WindowsPath 0x11b455e5 ".\\foobar.bat"]
user=> (fs/which "./foobar.bat")
#object[sun.nio.fs.WindowsPath 0x6d025d1d ".\\foobar.bat"]
(Tangent: I wonder though, should we be returning the absolute or canonical path here?)
Operating on absolute paths seems to match the which
zsh/bash behaviour on macOS/Linux:
$ which java
/home/lee/.sdkman/candidates/java/current/bin/java
β― which /home/lee/.sdkman/candidates/java/current/bin/java
/home/lee/.sdkman/candidates/java/current/bin/java
And the get-command
behaviour on Windows PowerShell
PS C:\Users\lee> get-command java
CommandType Name Version Source
----------- ---- ------- ------
Application java.exe 17.0.7.0 C:\Users\lee\scoop\apps\graalvm22-jdk1...
PS C:\Users\lee> get-command C:\\Users\\lee\\scoop\\apps\\graalvm22-jdk17\\current\\bin\\java.exe
CommandType Name Version Source
----------- ---- ------- ------
Application java.exe 17.0.7.0 C:\Users\lee\scoop\apps\graalvm22-jdk17\current\bin\java.exe
And I guess kinda sorta the where
behaviour Windows CMD shell:
C:\Users\lee>where java
C:\Users\lee\scoop\apps\graalvm22-jdk17\current\bin\java.exe
C:\Users\lee\scoop\apps\temurin19-jdk\current\bin\java.exe
C:\Users\lee\scoop\apps\temurin8-jdk\current\bin\java.exe
C:\Users\lee\scoop\apps\temurin11-jdk\current\bin\java.exe
C:\Users\lee>where "C:\Users\lee\scoop\apps\graalvm22-jdk17\current\bin:java.exe"
C:\Users\lee\scoop\apps\graalvm22-jdk17\current\bin\java.exe
Next Steps
Lemme know if I've missed some alternatives/nuances,etc that you see.
If this idea makes sense to you, happy to help out with a PR.