Bug 1154006 - [mach] Ability to lazy load parsers passed in via the @Command decorator, r=gps
authorAndrew Halberstadt <ahalberstadt@mozilla.com>
Mon, 13 Apr 2015 15:36:56 -0400
changeset 239689 15b5a4242cf2d6ff9fa875bb1bb1f10327210117
parent 239688 cf8981c64f564160c881699a8c644c6930980b24
child 239690 905f210de15b765757b461479fa61dac3b838a68
push id58615
push userahalberstadt@mozilla.com
push dateFri, 17 Apr 2015 18:18:34 +0000
treeherdermozilla-inbound@15b5a4242cf2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgps
bugs1154006
milestone40.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1154006 - [mach] Ability to lazy load parsers passed in via the @Command decorator, r=gps
python/mach/mach/base.py
python/mach/mach/decorators.py
python/mach/mach/dispatcher.py
testing/mochitest/mach_commands.py
--- a/python/mach/mach/base.py
+++ b/python/mach/mach/base.py
@@ -78,16 +78,17 @@ class MethodHandler(object):
         'description',
 
         # Functions used to 'skip' commands if they don't meet the conditions
         # in a given context.
         'conditions',
 
         # argparse.ArgumentParser instance to use as the basis for command
         # arguments.
+        '_parser',
         'parser',
 
         # Arguments added to this command's parser. This is a 2-tuple of
         # positional and named arguments, respectively.
         'arguments',
 
         # Argument groups added to this command's parser.
         'argument_group_names',
@@ -103,13 +104,22 @@ class MethodHandler(object):
         subcommand_handlers=None):
 
         self.cls = cls
         self.method = method
         self.name = name
         self.category = category
         self.description = description
         self.conditions = conditions or []
-        self.parser = parser
         self.arguments = arguments or []
         self.argument_group_names = argument_group_names or []
         self.pass_context = pass_context
         self.subcommand_handlers = subcommand_handlers or {}
+        self._parser = parser
+
+    @property
+    def parser(self):
+        # creating cli parsers at command dispatch time can potentially be
+        # expensive, make it possible to lazy load them.
+        if callable(self._parser):
+            self._parser = self._parser()
+        return self._parser
+
--- a/python/mach/mach/decorators.py
+++ b/python/mach/mach/decorators.py
@@ -142,18 +142,19 @@ class Command(object):
     The decorator accepts arguments that define basic attributes of the
     command. The following arguments are recognized:
 
          category -- The string category to which this command belongs. Mach's
              help will group commands by category.
 
          description -- A brief description of what the command does.
 
-         parser -- an optional argparse.ArgumentParser instance to use as
-             the basis for the command arguments.
+         parser -- an optional argparse.ArgumentParser instance or callable
+             that returns an argparse.ArgumentParser instance to use as the
+             basis for the command arguments.
 
     For example:
 
         @Command('foo', category='misc', description='Run the foo action')
         def foo(self):
             pass
     """
     def __init__(self, name, category=None, description=None, conditions=None,
--- a/python/mach/mach/dispatcher.py
+++ b/python/mach/mach/dispatcher.py
@@ -166,16 +166,17 @@ class CommandAction(argparse.Action):
 
         parser_args = {
             'add_help': False,
             'usage': usage,
         }
 
         if handler.parser:
             subparser = handler.parser
+            subparser.context = self._context
         else:
             subparser = argparse.ArgumentParser(**parser_args)
 
         remainder = None
 
         for arg in handler.arguments:
             # Remove our group keyword; it's not needed here.
             group_name = arg[1].get('group')
@@ -320,16 +321,17 @@ class CommandAction(argparse.Action):
         # the help output.
         parser_args = {
             'formatter_class': CommandFormatter,
             'add_help': False,
         }
 
         if handler.parser:
             c_parser = handler.parser
+            c_parser.context = self._context
             c_parser.formatter_class = NoUsageFormatter
             # Accessing _action_groups is a bit shady. We are highly dependent
             # on the argparse implementation not changing. We fail fast to
             # detect upstream changes so we can intelligently react to them.
             group = c_parser._action_groups[1]
 
             # By default argparse adds two groups called "positional arguments"
             # and "optional arguments". We want to rename these to reflect standard
--- a/testing/mochitest/mach_commands.py
+++ b/testing/mochitest/mach_commands.py
@@ -789,18 +789,16 @@ def setup_argument_parser():
 
     b2g_args = parser.add_argument_group('B2G Arguments', 'Arguments specific \
         to running mochitest on B2G devices and emulator')
     b2g_args = add_mochitest_b2g_args(b2g_args)
 
     structured.commandline.add_logging_group(parser)
     return parser
 
-_st_parser = setup_argument_parser()
-
 
 # condition filters
 
 def is_platform_in(*platforms):
     def is_platform_supported(cls):
         for p in platforms:
             c = getattr(conditions, 'is_{}'.format(p), None)
             if c and c(cls):
@@ -834,56 +832,56 @@ class MachCommands(MachCommandBase):
         for attr in ('b2g_home', 'xre_path', 'device_name', 'target_out'):
             setattr(self, attr, getattr(context, attr, None))
 
     @Command(
         'mochitest-plain',
         category='testing',
         conditions=[is_platform_in('firefox', 'mulet', 'b2g', 'b2g_desktop', 'android')],
         description='Run a plain mochitest (integration test, plain web page).',
-        parser=_st_parser)
+        parser=setup_argument_parser)
     def run_mochitest_plain(self, test_paths, **kwargs):
         if is_platform_in('firefox', 'mulet')(self):
             return self.run_mochitest(test_paths, 'plain', **kwargs)
         elif conditions.is_emulator(self):
             return self.run_mochitest_remote(test_paths, **kwargs)
         elif conditions.is_b2g_desktop(self):
             return self.run_b2g_desktop(test_paths, **kwargs)
         elif conditions.is_android(self):
             return self.run_mochitest_android(test_paths, **kwargs)
 
     @Command(
         'mochitest-chrome',
         category='testing',
         conditions=[is_platform_in('firefox', 'emulator', 'android')],
         description='Run a chrome mochitest (integration test with some XUL).',
-        parser=_st_parser)
+        parser=setup_argument_parser)
     def run_mochitest_chrome(self, test_paths, **kwargs):
         if conditions.is_firefox(self):
             return self.run_mochitest(test_paths, 'chrome', **kwargs)
         elif conditions.is_b2g(self) and conditions.is_emulator(self):
             return self.run_mochitest_remote(test_paths, chrome=True, **kwargs)
         elif conditions.is_android(self):
             return self.run_mochitest_android(test_paths, chrome=True, **kwargs)
 
     @Command(
         'mochitest-browser',
         category='testing',
         conditions=[conditions.is_firefox],
         description='Run a mochitest with browser chrome (integration test with a standard browser).',
-        parser=_st_parser)
+        parser=setup_argument_parser)
     def run_mochitest_browser(self, test_paths, **kwargs):
         return self.run_mochitest(test_paths, 'browser', **kwargs)
 
     @Command(
         'mochitest-devtools',
         category='testing',
         conditions=[conditions.is_firefox],
         description='Run a devtools mochitest with browser chrome (integration test with a standard browser with the devtools frame).',
-        parser=_st_parser)
+        parser=setup_argument_parser)
     def run_mochitest_devtools(self, test_paths, **kwargs):
         return self.run_mochitest(test_paths, 'devtools', **kwargs)
 
     @Command('jetpack-package', category='testing',
              conditions=[conditions.is_firefox],
              description='Run a jetpack package test.')
     def run_mochitest_jetpack_package(self, test_paths, **kwargs):
         return self.run_mochitest(test_paths, 'jetpack-package', **kwargs)
@@ -894,49 +892,49 @@ class MachCommands(MachCommandBase):
     def run_mochitest_jetpack_addon(self, test_paths, **kwargs):
         return self.run_mochitest(test_paths, 'jetpack-addon', **kwargs)
 
     @Command(
         'mochitest-metro',
         category='testing',
         conditions=[conditions.is_firefox],
         description='Run a mochitest with metro browser chrome (tests for Windows touch interface).',
-        parser=_st_parser)
+        parser=setup_argument_parser)
     def run_mochitest_metro(self, test_paths, **kwargs):
         return self.run_mochitest(test_paths, 'metro', **kwargs)
 
     @Command('mochitest-a11y', category='testing',
              conditions=[conditions.is_firefox],
              description='Run an a11y mochitest (accessibility tests).',
-             parser=_st_parser)
+             parser=setup_argument_parser)
     def run_mochitest_a11y(self, test_paths, **kwargs):
         return self.run_mochitest(test_paths, 'a11y', **kwargs)
 
     @Command(
         'webapprt-test-chrome',
         category='testing',
         conditions=[conditions.is_firefox],
         description='Run a webapprt chrome mochitest (Web App Runtime with the browser chrome).',
-        parser=_st_parser)
+        parser=setup_argument_parser)
     def run_mochitest_webapprt_chrome(self, test_paths, **kwargs):
         return self.run_mochitest(test_paths, 'webapprt-chrome', **kwargs)
 
     @Command(
         'webapprt-test-content',
         category='testing',
         conditions=[conditions.is_firefox],
         description='Run a webapprt content mochitest (Content rendering of the Web App Runtime).',
-        parser=_st_parser)
+        parser=setup_argument_parser)
     def run_mochitest_webapprt_content(self, test_paths, **kwargs):
         return self.run_mochitest(test_paths, 'webapprt-content', **kwargs)
 
     @Command('mochitest', category='testing',
              conditions=[conditions.is_firefox],
              description='Run any flavor of mochitest (integration test).',
-             parser=_st_parser)
+             parser=setup_argument_parser)
     @CommandArgument('-f', '--flavor', choices=FLAVORS.keys(),
                      help='Only run tests of this flavor.')
     def run_mochitest_general(self, test_paths, flavor=None, test_objects=None,
                               **kwargs):
         self._preruntest()
 
         from mozbuild.testing import TestResolver