| 20 | |
| 21 | |
| 22 | def find_executable( |
| 23 | exe: str, *, env: Mapping[str, str] | None = None, |
| 24 | ) -> str | None: |
| 25 | exe = os.path.normpath(exe) |
| 26 | if os.sep in exe: |
| 27 | return exe |
| 28 | |
| 29 | environ = env if env is not None else os.environ |
| 30 | |
| 31 | if 'PATHEXT' in environ: |
| 32 | exts = environ['PATHEXT'].split(os.pathsep) |
| 33 | possible_exe_names = tuple(f'{exe}{ext}' for ext in exts) + (exe,) |
| 34 | else: |
| 35 | possible_exe_names = (exe,) |
| 36 | |
| 37 | for path in environ.get('PATH', '').split(os.pathsep): |
| 38 | for possible_exe_name in possible_exe_names: |
| 39 | joined = os.path.join(path, possible_exe_name) |
| 40 | if os.path.isfile(joined) and os.access(joined, os.X_OK): |
| 41 | return joined |
| 42 | else: |
| 43 | return None |
| 44 | |
| 45 | |
| 46 | def normexe(orig: str, *, env: Mapping[str, str] | None = None) -> str: |