Bug 1030717 - Don't try to create the mach state directory until it's actually needed. r=gps
authorMike Hommey <mh+mozilla@glandium.org>
Thu, 03 Jul 2014 07:15:31 +0900
changeset 191952 59a323e670baba5f2b9f114ef7ac8b80ea5be7a8
parent 191951 e1e163c9485211adf0734df5b3e89633f9b3a641
child 191953 3ee4eaf0c38e0480514c0f9ea42fd80ae61efc0e
push id45713
push usermh@glandium.org
push dateWed, 02 Jul 2014 22:16:23 +0000
treeherdermozilla-inbound@ce1c57e03b88 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgps
bugs1030717
milestone33.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 1030717 - Don't try to create the mach state directory until it's actually needed. r=gps
build/mach_bootstrap.py
python/mach/mach/main.py
python/mach/mach/test/test_conditions.py
python/mozbuild/mozbuild/mach_commands.py
--- a/build/mach_bootstrap.py
+++ b/build/mach_bootstrap.py
@@ -140,52 +140,57 @@ def bootstrap(topsrcdir, mozilla_dir=Non
     # Global build system and mach state is stored in a central directory. By
     # default, this is ~/.mozbuild. However, it can be defined via an
     # environment variable. We detect first run (by lack of this directory
     # existing) and notify the user that it will be created. The logic for
     # creation is much simpler for the "advanced" environment variable use
     # case. For default behavior, we educate users and give them an opportunity
     # to react. We always exit after creating the directory because users don't
     # like surprises.
-    state_user_dir = os.path.expanduser('~/.mozbuild')
-    state_env_dir = os.environ.get('MOZBUILD_STATE_PATH', None)
-    if state_env_dir:
-        if not os.path.exists(state_env_dir):
-            print('Creating global state directory from environment variable: %s'
-                % state_env_dir)
-            os.makedirs(state_env_dir, mode=0o770)
-            print('Please re-run mach.')
-            sys.exit(1)
-        state_dir = state_env_dir
-    else:
-        if not os.path.exists(state_user_dir):
-            print(STATE_DIR_FIRST_RUN.format(userdir=state_user_dir))
-            try:
-                for i in range(20, -1, -1):
-                    time.sleep(1)
-                    sys.stdout.write('%d ' % i)
-                    sys.stdout.flush()
-            except KeyboardInterrupt:
-                sys.exit(1)
-
-            print('\nCreating default state directory: %s' % state_user_dir)
-            os.mkdir(state_user_dir)
-            print('Please re-run mach.')
-            sys.exit(1)
-        state_dir = state_user_dir
-
     try:
         import mach.main
     except ImportError:
         sys.path[0:0] = [os.path.join(mozilla_dir, path) for path in SEARCH_PATHS]
         import mach.main
 
-    def populate_context(context):
-        context.state_dir = state_dir
-        context.topdir = topsrcdir
+    def populate_context(context, key=None):
+        if key is None:
+            return
+        if key == 'state_dir':
+            state_user_dir = os.path.expanduser('~/.mozbuild')
+            state_env_dir = os.environ.get('MOZBUILD_STATE_PATH', None)
+            if state_env_dir:
+                if not os.path.exists(state_env_dir):
+                    print('Creating global state directory from environment variable: %s'
+                        % state_env_dir)
+                    os.makedirs(state_env_dir, mode=0o770)
+                    print('Please re-run mach.')
+                    sys.exit(1)
+                state_dir = state_env_dir
+            else:
+                if not os.path.exists(state_user_dir):
+                    print(STATE_DIR_FIRST_RUN.format(userdir=state_user_dir))
+                    try:
+                        for i in range(20, -1, -1):
+                            time.sleep(1)
+                            sys.stdout.write('%d ' % i)
+                            sys.stdout.flush()
+                    except KeyboardInterrupt:
+                        sys.exit(1)
+
+                    print('\nCreating default state directory: %s' % state_user_dir)
+                    os.mkdir(state_user_dir)
+                    print('Please re-run mach.')
+                    sys.exit(1)
+                state_dir = state_user_dir
+
+            return state_dir
+        if key == 'topdir':
+            return topsrcdir
+        raise AttributeError(key)
 
     mach = mach.main.Mach(os.getcwd())
     mach.populate_context_handler = populate_context
 
     for category, meta in CATEGORIES.items():
         mach.define_category(category, meta['short'], meta['long'],
             meta['priority'])
 
--- a/python/mach/mach/main.py
+++ b/python/mach/mach/main.py
@@ -138,31 +138,58 @@ class ArgumentParser(argparse.ArgumentPa
             real_start = start + len('Commands:\n')
             real_end = end + len('}\n')
 
             text = text[0:real_start] + text[real_end:]
 
         return text
 
 
+class ContextWrapper(object):
+    def __init__(self, context, handler):
+        object.__setattr__(self, '_context', context)
+        object.__setattr__(self, '_handler', handler)
+
+    def __getattribute__(self, key):
+        try:
+            return getattr(object.__getattribute__(self, '_context'), key)
+        except AttributeError as e:
+            try:
+                ret = object.__getattribute__(self, '_handler')(self, key)
+            except AttributeError, TypeError:
+                # TypeError is in case the handler comes from old code not
+                # taking a key argument.
+                raise e
+            setattr(self, key, ret)
+            return ret
+
+    def __setattr__(self, key, value):
+        setattr(object.__getattribute__(self, '_context'), key, value)
+
+
 @CommandProvider
 class Mach(object):
     """Main mach driver type.
 
     This type is responsible for holding global mach state and dispatching
     a command from arguments.
 
     The following attributes may be assigned to the instance to influence
     behavior:
 
         populate_context_handler -- If defined, it must be a callable. The
-            callable will be called with the mach.base.CommandContext instance
-            as its single argument right before command dispatch. This allows
-            modification of the context instance and thus passing of
-            arbitrary data to command handlers.
+            callable signature is the following:
+                populate_context_handler(context, key=None)
+            It acts as a fallback getter for the mach.base.CommandContext
+            instance.
+            This allows to augment the context instance with arbitrary data
+            for use in command handlers.
+            For backwards compatibility, it is also called before command
+            dispatch without a key, allowing the context handler to add
+            attributes to the context instance.
 
         require_conditions -- If True, commands that do not have any condition
             functions applied will be skipped. Defaults to False.
 
     """
 
     USAGE = """%(prog)s [global arguments] command [command arguments]
 
@@ -338,16 +365,17 @@ To see more help for a specific command,
 
     def _run(self, argv):
         context = CommandContext(cwd=self.cwd,
             settings=self.settings, log_manager=self.log_manager,
             commands=Registrar)
 
         if self.populate_context_handler:
             self.populate_context_handler(context)
+            context = ContextWrapper(context, self.populate_context_handler)
 
         parser = self.get_argument_parser(context)
 
         if not len(argv):
             # We don't register the usage until here because if it is globally
             # registered, argparse always prints it. This is not desired when
             # running with --help.
             parser.usage = Mach.USAGE
--- a/python/mach/mach/test/test_conditions.py
+++ b/python/mach/mach/test/test_conditions.py
@@ -8,19 +8,24 @@ import os
 
 from mach.base import MachError
 from mach.main import Mach
 from mach.test.common import TestBase
 
 from mozunit import main
 
 
-def _populate_context(context):
-    context.foo = True
-    context.bar = False
+def _populate_context(context, key=None):
+    if key is None:
+        return
+    if key == 'foo':
+        return True
+    if key == 'bar':
+        return False
+    raise AttributeError(key)
 
 class TestConditions(TestBase):
     """Tests for conditionally filtering commands."""
 
     def _run_mach(self, args, context_handler=None):
         return TestBase._run_mach(self, args, 'conditions.py',
                                   context_handler=context_handler)
 
--- a/python/mozbuild/mozbuild/mach_commands.py
+++ b/python/mozbuild/mozbuild/mach_commands.py
@@ -917,24 +917,25 @@ class Makefiles(MachCommandBase):
 
 @CommandProvider
 class MachDebug(MachCommandBase):
     @Command('environment', category='build-dev',
         description='Show info about the mach and build environment.')
     @CommandArgument('--verbose', '-v', action='store_true',
         help='Print verbose output.')
     def environment(self, verbose=False):
+        state_dir = self._mach_context.state_dir
         import platform
         print('platform:\n\t%s' % platform.platform())
         print('python version:\n\t%s' % sys.version)
         print('python prefix:\n\t%s' % sys.prefix)
         print('mach cwd:\n\t%s' % self._mach_context.cwd)
         print('os cwd:\n\t%s' % os.getcwd())
         print('mach directory:\n\t%s' % self._mach_context.topdir)
-        print('state directory:\n\t%s' % self._mach_context.state_dir)
+        print('state directory:\n\t%s' % state_dir)
 
         try:
             mb = MozbuildObject.from_environment(cwd=self._mach_context.cwd)
         except ObjdirMismatchException as e:
             print('Ambiguous object directory detected. We detected that '
                 'both %s and %s could be object directories. This is '
                 'typically caused by having a mozconfig pointing to a '
                 'different object directory from the current working '