Bug 1267454 - Add a parameter to find_program and check_prog to allow searching the given paths instead of $PATH.
MozReview-Commit-ID: F3lke9Q5rRR
--- a/build/moz.configure/checks.configure
+++ b/build/moz.configure/checks.configure
@@ -76,26 +76,31 @@ def checking(what, callback=None):
# - `what` is a human readable description of what is being looked for. It
# defaults to the lowercase version of `var`.
# - `input` is a string reference to an existing option or a reference to a
# @depends function resolving to explicit input for the program check.
# The default is to create an option for the environment variable `var`.
# This argument allows to use a different kind of option (possibly using a
# configure flag), or doing some pre-processing with a @depends function.
# - `allow_missing` indicates whether not finding the program is an error.
+# - `paths` is a list of paths or @depends function returning a list of paths
+# that will cause the given path(s) to be searched rather than $PATH. Input
+# paths may either be individual paths or delimited by os.pathsep, to allow
+# passing $PATH (for example) as an element.
#
# The simplest form is:
# check_prog('PROG', ('a', 'b'))
# This will look for 'a' or 'b' in $PATH, and set_config PROG to the one
# it can find. If PROG is already set from the environment or command line,
# use that value instead.
@template
@imports(_from='mozbuild.shellutil', _import='quote')
@imports(_from='mozbuild.configure', _import='DependsFunction')
-def check_prog(var, progs, what=None, input=None, allow_missing=False):
+def check_prog(var, progs, what=None, input=None, allow_missing=False,
+ paths=None):
if input:
# Wrap input with type checking and normalization.
@depends(input)
def input(value):
if not value:
return
if isinstance(value, str):
return (value,)
@@ -104,32 +109,34 @@ def check_prog(var, progs, what=None, in
configure_error('input must resolve to a tuple or a list with a '
'single element, or a string')
else:
option(env=var, nargs=1,
help='Path to %s' % (what or 'the %s program' % var.lower()))
input = var
what = what or var.lower()
+ # Trick to make a @depends function out of an immediate value.
if not isinstance(progs, DependsFunction):
- # Trick to make a @depends function out of an immediate value.
progs = depends('--help')(lambda h: progs)
+ if not isinstance(paths, DependsFunction):
+ paths = depends('--help')(lambda h: paths)
- @depends_if(input, progs)
+ @depends_if(input, progs, paths)
@checking('for %s' % what, lambda x: quote(x) if x else 'not found')
- def check(value, progs):
+ def check(value, progs, paths):
if progs is None:
progs = ()
if not isinstance(progs, (tuple, list)):
configure_error('progs must resolve to a list or tuple!')
for prog in value or progs:
log.debug('%s: Trying %s', var.lower(), quote(prog))
- result = find_program(prog)
+ result = find_program(prog, paths)
if result:
return result
if not allow_missing or value:
raise FatalCheckError('Cannot find %s' % what)
@depends_if(check, progs)
def normalized_for_config(value, progs):
--- a/build/moz.configure/util.configure
+++ b/build/moz.configure/util.configure
@@ -25,24 +25,35 @@ def is_absolute_or_relative(path):
return True
return os.sep in path
@imports(_import='mozpack.path', _as='mozpath')
def normsep(path):
return mozpath.normsep(path)
-
+# Locates the given program using which, or returns the given path if it
+# exists.
+# The `paths` parameter may be passed to search the given paths instead of
+# $PATH.
@imports(_from='which', _import='which')
@imports(_from='which', _import='WhichError')
-def find_program(file):
+@imports('itertools')
+@imports(_from='os', _import='pathsep')
+def find_program(file, paths=None):
if is_absolute_or_relative(file):
return os.path.abspath(file) if os.path.isfile(file) else None
try:
- return normsep(which(file))
+ if paths:
+ if not isinstance(paths, list):
+ die("Paths provided to find_program must be a list of strings, "
+ "not %r" % paths)
+ paths = list(itertools.chain(
+ *(p.split(pathsep) for p in paths if p)))
+ return normsep(which(file, path=paths))
except WhichError:
return None
def unique_list(l):
result = []
for i in l:
if l not in result:
--- a/python/mozbuild/mozbuild/test/configure/common.py
+++ b/python/mozbuild/mozbuild/test/configure/common.py
@@ -104,21 +104,21 @@ class ConfigureTestSandbox(ConfigureSand
CalledProcessError=subprocess.CalledProcessError,
check_output=self.check_output,
PIPE=subprocess.PIPE,
Popen=self.Popen,
)
return super(ConfigureTestSandbox, self)._get_one_import(what)
- def which(self, command):
- for parent in self._search_path:
- path = mozpath.join(parent, command)
- if self.OS.path.exists(path):
- return path
+ def which(self, command, path=None):
+ for parent in (path or self._search_path):
+ candidate = mozpath.join(parent, command)
+ if self.OS.path.exists(candidate):
+ return candidate
raise WhichError()
def Popen(self, args, stdin=None, stdout=None, stderr=None, **kargs):
try:
program = self.which(args[0])
except WhichError:
raise OSError(errno.ENOENT, 'File not found')
--- a/python/mozbuild/mozbuild/test/configure/test_checks_configure.py
+++ b/python/mozbuild/mozbuild/test/configure/test_checks_configure.py
@@ -121,28 +121,30 @@ class TestChecksConfigure(unittest.TestC
out.truncate(0)
foo(['foo', 'bar'])
self.assertEqual(out.getvalue(), 'checking for a thing... foo bar\n')
KNOWN_A = mozpath.abspath('/usr/bin/known-a')
KNOWN_B = mozpath.abspath('/usr/local/bin/known-b')
KNOWN_C = mozpath.abspath('/home/user/bin/known c')
+ OTHER_A = mozpath.abspath('/lib/other/known-a')
def get_result(self, command='', args=[], environ={},
prog='/bin/configure'):
config = {}
out = StringIO()
paths = {
self.KNOWN_A: None,
self.KNOWN_B: None,
self.KNOWN_C: None,
}
environ = dict(environ)
environ['PATH'] = os.pathsep.join(os.path.dirname(p) for p in paths)
+ paths[self.OTHER_A] = None
sandbox = ConfigureTestSandbox(paths, config, environ, [prog] + args,
out, out)
base_dir = os.path.join(topsrcdir, 'build', 'moz.configure')
sandbox.include_file(os.path.join(base_dir, 'util.configure'))
sandbox.include_file(os.path.join(base_dir, 'checks.configure'))
status = 0
try:
@@ -391,11 +393,59 @@ class TestChecksConfigure(unittest.TestC
'foo = depends("--help")(lambda h: {"a": "b"})\n'
'check_prog("FOO", ("known-a",), input=foo)'
)
self.assertEqual(e.exception.message,
'input must resolve to a tuple or a list with a '
'single element, or a string')
+ def test_check_prog_with_path(self):
+ config, out, status = self.get_result('check_prog("A", ("known-a",), paths=["/some/path"])')
+ self.assertEqual(status, 1)
+ self.assertEqual(config, {})
+ self.assertEqual(out, textwrap.dedent('''\
+ checking for a... not found
+ DEBUG: a: Trying known-a
+ ERROR: Cannot find a
+ '''))
+
+ config, out, status = self.get_result('check_prog("A", ("known-a",), paths=["%s"])' %
+ os.path.dirname(self.OTHER_A))
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {'A': self.OTHER_A})
+ self.assertEqual(out, textwrap.dedent('''\
+ checking for a... %s
+ ''' % self.OTHER_A))
+
+ dirs = map(mozpath.dirname, (self.OTHER_A, self.KNOWN_A))
+ config, out, status = self.get_result(textwrap.dedent('''\
+ check_prog("A", ("known-a",), paths=["%s"])
+ ''' % os.pathsep.join(dirs)))
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {'A': self.OTHER_A})
+ self.assertEqual(out, textwrap.dedent('''\
+ checking for a... %s
+ ''' % self.OTHER_A))
+
+ dirs = map(mozpath.dirname, (self.KNOWN_A, self.KNOWN_B))
+ config, out, status = self.get_result(textwrap.dedent('''\
+ check_prog("A", ("known-a",), paths=["%s", "%s"])
+ ''' % (os.pathsep.join(dirs), self.OTHER_A)))
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {'A': self.KNOWN_A})
+ self.assertEqual(out, textwrap.dedent('''\
+ checking for a... %s
+ ''' % self.KNOWN_A))
+
+ config, out, status = self.get_result('check_prog("A", ("known-a",), paths="%s")' %
+ os.path.dirname(self.OTHER_A))
+
+ self.assertEqual(status, 1)
+ self.assertEqual(config, {})
+ self.assertEqual(out, textwrap.dedent('''\
+ checking for a...
+ DEBUG: a: Trying known-a
+ ERROR: Paths provided to find_program must be a list of strings, not %r
+ ''' % mozpath.dirname(self.OTHER_A)))
if __name__ == '__main__':
main()