From 577b6dfdf63cde697ff33226950175a93578e2b8 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Sat, 30 Jul 2016 15:19:11 +0530 Subject: [PATCH] find_program: Find scripts without extensions on Windows Because of how files and executables work on Windows, scripts that use an interpreter must have an extension, and that extension must be associated with an interpreter. The full list of executable extensions is available in the PATHEXT environment variable. However, UNIX-like OSes use an executable bit and read the shebang to figure out what interpreter to use, and the scripts don't need to have extensions. We can now detect these scripts using find_program by manually searching in PATH and reading the shebang. --- mesonbuild/dependencies.py | 87 +++++++++++++++++++++++++++----------- 1 file changed, 62 insertions(+), 25 deletions(-) diff --git a/mesonbuild/dependencies.py b/mesonbuild/dependencies.py index 3f1e214df..b4f825b2d 100644 --- a/mesonbuild/dependencies.py +++ b/mesonbuild/dependencies.py @@ -353,37 +353,13 @@ class WxDependency(Dependency): class ExternalProgram(): def __init__(self, name, fullpath=None, silent=False, search_dir=None): self.name = name - self.fullpath = None if fullpath is not None: if not isinstance(fullpath, list): self.fullpath = [fullpath] else: self.fullpath = fullpath else: - self.fullpath = [shutil.which(name)] - if self.fullpath[0] is None and search_dir is not None: - trial = os.path.join(search_dir, name) - suffix = os.path.splitext(trial)[-1].lower()[1:] - if mesonlib.is_windows() and (suffix == 'exe' or suffix == 'com'\ - or suffix == 'bat'): - self.fullpath = [trial] - elif not mesonlib.is_windows() and os.access(trial, os.X_OK): - self.fullpath = [trial] - else: - # Now getting desperate. Maybe it is a script file that is a) not chmodded - # executable or b) we are on windows so they can't be directly executed. - try: - first_line = open(trial).readline().strip() - if first_line.startswith('#!'): - commands = first_line[2:].split('#')[0].strip().split() - if mesonlib.is_windows(): - # Windows does not have /usr/bin. - commands[0] = commands[0].split('/')[-1] - if commands[0] == 'env': - commands = commands[1:] - self.fullpath = commands + [trial] - except Exception: - pass + self.fullpath = self._search(name, search_dir) if not silent: if self.found(): mlog.log('Program', mlog.bold(name), 'found:', mlog.green('YES'), @@ -391,6 +367,67 @@ class ExternalProgram(): else: mlog.log('Program', mlog.bold(name), 'found:', mlog.red('NO')) + @staticmethod + def _shebang_to_cmd(script): + """ + Windows does not understand shebangs, so we check if the file has a + shebang and manually parse it to figure out the interpreter to use + """ + try: + first_line = open(script).readline().strip() + if first_line.startswith('#!'): + commands = first_line[2:].split('#')[0].strip().split() + if mesonlib.is_windows(): + # Windows does not have /usr/bin. + commands[0] = commands[0].split('/')[-1] + if commands[0] == 'env': + commands = commands[1:] + return commands + [script] + except Exception: + pass + return False + + @staticmethod + def _is_executable(path): + suffix = os.path.splitext(path)[-1].lower()[1:] + if mesonlib.is_windows(): + if suffix == 'exe' or suffix == 'com' or suffix == 'bat': + return True + elif os.access(path, os.X_OK): + return True + return False + + def _search_dir(self, name, search_dir): + if search_dir is None: + return False + trial = os.path.join(search_dir, name) + if not os.path.exists(trial): + return False + if self._is_executable(trial): + return [trial] + # Now getting desperate. Maybe it is a script file that is a) not chmodded + # executable or b) we are on windows so they can't be directly executed. + return self._shebang_to_cmd(trial) + + def _search(self, name, search_dir): + commands = self._search_dir(name, search_dir) + if commands: + return commands + # Do a standard search in PATH + fullpath = shutil.which(name) + if fullpath or not mesonlib.is_windows(): + # On UNIX-like platforms, the standard PATH search is enough + return [fullpath] + # On Windows, interpreted scripts must have an extension otherwise they + # cannot be found by a standard PATH search. So we do a custom search + # where we manually search for a script with a shebang in PATH. + search_dirs = os.environ.get('PATH', '').split(';') + for search_dir in search_dirs: + commands = self._search_dir(name, search_dir) + if commands: + return commands + return [None] + def found(self): return self.fullpath[0] is not None