Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 4deabe1

Browse files
authored
fix(resolve_exe): allow shutil.which() to find exe without suffix in Windows (#2408)
This PR aims to address a behavior change in shutil.which (in Windows) introduced in python/cpython#127035. For more context, in test_run_model_exe_rel_path, the mf6 executable is copied to a relative path, ../bin/mf6 (without extension). Within the test, shutil.which is used to locate the executable path. However, with the new changes in shutil.which, by default, it would only search for the path with an extension in PATHEXT, excluding paths without extension.
1 parent ac9c4f9 commit 4deabe1

2 files changed

Lines changed: 27 additions & 29 deletions

File tree

autotest/test_mbase.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,10 +135,8 @@ def test_run_model_exe_rel_path(mf6_model_path, function_tmpdir, use_ext):
135135

136136
bin_dir = function_tmpdir / "bin"
137137
bin_dir.mkdir()
138-
inner_dir = function_tmpdir / "inner"
139-
inner_dir.mkdir()
140138

141-
with set_dir(inner_dir):
139+
with set_dir(ws):
142140
# copy exe to relative dir
143141
copy(mf6, bin_dir / "mf6")
144142
assert (bin_dir / "mf6").is_file()

flopy/mbase.py

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -64,31 +64,31 @@ def resolve_exe(exe_name: Union[str, os.PathLike], forgive: bool = False) -> str
6464
str: absolute path to the executable
6565
"""
6666

67-
def _resolve(exe_name):
68-
exe = which(exe_name)
69-
if exe is not None:
70-
# if which() returned a relative path, resolve it
71-
exe = which(str(Path(exe).resolve()))
72-
else:
73-
if exe_name.lower().endswith(".exe"):
74-
# try removing .exe suffix
75-
exe = which(exe_name[:-4])
76-
if exe is not None:
77-
# in case which() returned a relative path, resolve it
78-
exe = which(str(Path(exe).resolve()))
79-
else:
80-
# try tilde-expanded abspath
81-
exe = which(Path(exe_name).expanduser().absolute())
82-
if exe is None and exe_name.lower().endswith(".exe"):
83-
# try tilde-expanded abspath without .exe suffix
84-
exe = which(Path(exe_name[:-4]).expanduser().absolute())
85-
return exe
86-
87-
name = str(exe_name)
88-
exe_path = _resolve(name)
89-
if exe_path is None and on_windows and Path(name).suffix == "":
90-
# try adding .exe suffix on windows (for portability from other OS)
91-
exe_path = _resolve(f"{name}.exe")
67+
def _resolve(exe_name, checked=set()):
68+
# Prevent infinite recursion by checking if exe_name has been checked
69+
if exe_name in checked:
70+
return None
71+
checked.add(exe_name)
72+
73+
# exe_name is found (not None), ensure absolute path is returned
74+
if exe := which(str(exe_name)):
75+
return which(str(Path(exe).resolve()))
76+
77+
# exe_name is relative path
78+
if not Path(exe_name).is_absolute() and (
79+
exe := which(str(Path(exe_name).expanduser().resolve()), mode=0)
80+
):
81+
# expanduser() in case of ~ in path
82+
# mode=0 effectively allows which() to find exe without suffix in windows
83+
return exe
84+
85+
# try adding/removing .exe suffix
86+
if exe_name.lower().endswith(".exe"):
87+
return _resolve(exe_name[:-4], checked)
88+
elif on_windows and "." not in Path(exe_name).stem:
89+
return _resolve(f"{exe_name}.exe", checked)
90+
91+
exe_path = _resolve(exe_name)
9292

9393
# raise if we are unforgiving, otherwise return None
9494
if exe_path is None:
@@ -103,7 +103,7 @@ def _resolve(exe_name):
103103
f"The program {exe_name} does not exist or is not executable."
104104
)
105105

106-
return exe_path
106+
return str(exe_path)
107107

108108

109109
# external exceptions for users

0 commit comments

Comments
 (0)