Bug 1259960 - Make check_prog more flexible about the input it receives. r=chmanchester
authorMike Hommey <mh+mozilla@glandium.org>
Sat, 26 Mar 2016 09:39:27 +0900
changeset 290984 58ea10aa8c8975d72b5a931d76077f74212d2746
parent 290983 273794fe2b2cbaa28a52d17ceae069aa26bcb9d1
child 290985 7c01ff2b685af859ce62233984a6b4b65cb418d9
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerschmanchester
bugs1259960
milestone48.0a1
Bug 1259960 - Make check_prog more flexible about the input it receives. r=chmanchester
build/moz.configure/checks.configure
python/mozbuild/mozbuild/configure/__init__.py
python/mozbuild/mozbuild/test/configure/test_checks_configure.py
--- a/build/moz.configure/checks.configure
+++ b/build/moz.configure/checks.configure
@@ -69,37 +69,56 @@ def checking(what, callback=None):
 
 
 # Template to check for programs in $PATH.
 # - `var` is the name of the variable that will be set with `set_config` when
 #   the program is found.
 # - `progs` is a list (or tuple) of program names that will be searched for.
 # - `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.
 #
 # 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
 @advanced
-def check_prog(var, progs, what=None, allow_missing=False):
+def check_prog(var, progs, what=None, input=None, allow_missing=False):
     from mozbuild.shellutil import quote
 
-    option(env=var, nargs=1,
-           help='Path to %s' % (what or 'the %s program' % var.lower()))
+    if input:
+        # Wrap input with type checking and normalization.
+        @depends(input)
+        def input(value):
+            if not value:
+                return
+            if isinstance(value, str):
+                return (value,)
+            if isinstance(value, (tuple, list)) and len(value) == 1:
+                return value
+            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()
 
     if not isinstance(progs, (tuple, list)):
         configure_error('progs should be a list or tuple!')
     progs = list(progs)
 
-    @depends(var)
+    @depends(input)
     @checking('for %s' % what, lambda x: quote(x) if x else 'not found')
     def check(value):
         if value:
             progs[:] = value
         for prog in progs:
             log.debug('%s: Trying %s', var.lower(), quote(prog))
             result = find_program(prog)
             if result:
--- a/python/mozbuild/mozbuild/configure/__init__.py
+++ b/python/mozbuild/mozbuild/configure/__init__.py
@@ -78,22 +78,23 @@ class ConfigureSandbox(dict):
     will be stored.
 
         config = {}
         sandbox = ConfigureSandbox(config)
         sandbox.run(path)
         do_stuff(config)
     """
 
-    # The default set of builtins.
+    # The default set of builtins. We expose unicode as str to make sandboxed
+    # files more python3-ready.
     BUILTINS = ReadOnlyDict({
         b: __builtins__[b]
         for b in ('None', 'False', 'True', 'int', 'bool', 'any', 'all', 'len',
                   'list', 'tuple', 'set', 'dict', 'isinstance')
-    }, __import__=forbidden_import)
+    }, __import__=forbidden_import, str=unicode)
 
     # Expose a limited set of functions from os.path
     OS = ReadOnlyNamespace(path=ReadOnlyNamespace(**{
         k: getattr(mozpath, k, getattr(os.path, k))
         for k in ('abspath', 'basename', 'dirname', 'exists', 'isabs', 'isdir',
                   'isfile', 'join', 'normpath', 'realpath', 'relpath')
     }))
 
--- a/python/mozbuild/mozbuild/test/configure/test_checks_configure.py
+++ b/python/mozbuild/mozbuild/test/configure/test_checks_configure.py
@@ -165,18 +165,69 @@ class TestChecksConfigure(unittest.TestC
         self.assertEqual(status, 1)
         self.assertEqual(config, {})
         self.assertEqual(out, 'checking for the target C compiler... not found\n'
                               'DEBUG: cc: Trying unknown\n'
                               'DEBUG: cc: Trying unknown-2\n'
                               "DEBUG: cc: Trying 'unknown 3'\n"
                               'ERROR: Cannot find the target C compiler\n')
 
+    def test_check_prog_input(self):
+        config, out, status = self.get_result(
+            'option("--with-ccache", nargs=1, help="ccache")\n'
+            'check_prog("CCACHE", ("known-a",), input="--with-ccache")',
+            ['--with-ccache=known-b'])
+        self.assertEqual(status, 0)
+        self.assertEqual(config, {'CCACHE': '/usr/local/bin/known-b'})
+        self.assertEqual(out, 'checking for ccache... /usr/local/bin/known-b\n')
+
+        script = (
+            'option(env="CC", nargs=1, help="compiler")\n'
+            '@depends("CC")\n'
+            'def compiler(value):\n'
+            '    return value[0].split()[0] if value else None\n'
+            'check_prog("CC", ("known-a",), input=compiler)'
+        )
+        config, out, status = self.get_result(script)
+        self.assertEqual(status, 0)
+        self.assertEqual(config, {'CC': '/usr/bin/known-a'})
+        self.assertEqual(out, 'checking for cc... /usr/bin/known-a\n')
+
+        config, out, status = self.get_result(script, ['CC=known-b'])
+        self.assertEqual(status, 0)
+        self.assertEqual(config, {'CC': '/usr/local/bin/known-b'})
+        self.assertEqual(out, 'checking for cc... /usr/local/bin/known-b\n')
+
+        config, out, status = self.get_result(script, ['CC=known-b -m32'])
+        self.assertEqual(status, 0)
+        self.assertEqual(config, {'CC': '/usr/local/bin/known-b'})
+        self.assertEqual(out, 'checking for cc... /usr/local/bin/known-b\n')
+
     def test_check_prog_configure_error(self):
         with self.assertRaises(ConfigureError) as e:
             self.get_result('check_prog("FOO", "foo")')
 
         self.assertEqual(e.exception.message,
                          'progs should be a list or tuple!')
 
+        with self.assertRaises(ConfigureError) as e:
+            self.get_result(
+                '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')
+
+        with self.assertRaises(ConfigureError) as e:
+            self.get_result(
+                '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')
+
 
 if __name__ == '__main__':
     main()