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.
pull/659/head
Nirbheek Chauhan 8 years ago
parent 4a92b78e6e
commit 577b6dfdf6
  1. 87
      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

Loading…
Cancel
Save