From 92c65d0e5e32c438b2c61fb978d715e75c3a9ca9 Mon Sep 17 00:00:00 2001 From: Jonah Beckford <71855677+jonahbeckford@users.noreply.github.com> Date: Sat, 10 Feb 2024 21:51:29 -0800 Subject: [PATCH] Windows [find_executable] fixes + Use ';' as PATH seperator on Windows. + On Windows Sys.file_exists can return true even if Sys.is_directory is true. And an executable ends in `.exe` Signed-off-by: Jonah Beckford <71855677+jonahbeckford@users.noreply.github.com> --- CHANGES.md | 3 +++ process-lib/src/env.ml | 27 ++++++++++++++++++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 4eabb68..6e3723c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,3 +3,6 @@ - Add `Process.call_xxx` functions that are equivalent to their `Process.run_xxx` counterpart except that they take a single string list as argument (#8, David Chemouil) +- Use `;` as PATH seperator on Windows. Find executables with `.exe` + extension on Windows. Workaround `Sys.file_exists` being true for + directories on Windows (#16, Jonah Beckford) diff --git a/process-lib/src/env.ml b/process-lib/src/env.ml index d425f2f..e9d7982 100644 --- a/process-lib/src/env.ml +++ b/process-lib/src/env.ml @@ -142,7 +142,8 @@ module Uenv = struct let split_path str = let len = String.length str in - let is_sep c = c = ':' in + let sep = if Sys.win32 then ';' else ':' in + let is_sep c = c = sep in let rec loop i j = if j = len then [ String.sub str ~pos:i ~len:(j - i) ] @@ -257,12 +258,32 @@ let chdir t dir = { t with cwd; unix_env } ;; -let find_executable t exe = +let get_executable_if_exists fn = + if Sys.win32 then + (* Only some file extensions are executable. `.exe` is always + executable. The extensions in PATHEXT (`.exe`, `.bat`, `.cmd`, + etc.) are also executable by the Command Prompt shell. We'll + just use `.exe` since it is the only correct extension in all + contexts. *) + let fn_exe = (Filename.remove_extension fn) ^ ".exe" in + (* [Sys.file_exists] can return true even if [Sys.is_directory] + is true. *) + if Sys.file_exists fn_exe && not (Sys.is_directory fn_exe) then + Some fn_exe + else + None + else + (* Unix *) + if Sys.file_exists fn then Some fn else None + +let find_executable t exe = let rec loop = function | [] -> None | path :: rest -> let fn = path ^/ exe in - if Sys.file_exists fn then Some fn else loop rest + match get_executable_if_exists fn with + | Some found_fn -> Some found_fn + | None -> loop rest in if not (Filename.is_relative exe) then Some exe