Bug 808346 - Don't scan sys.path to discover mach commands; r=jhammel
authorGregory Szorc <gps@mozilla.com>
Tue, 06 Nov 2012 17:00:19 -0800
changeset 112403 f561a4ffeeb9
parent 112402 4f96fdb5e4f9
child 112404 e57bd488af4c
push id23821
push usergszorc@mozilla.com
push dateWed, 07 Nov 2012 01:02:22 +0000
treeherdermozilla-central@e57bd488af4c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjhammel
bugs808346
milestone19.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 808346 - Don't scan sys.path to discover mach commands; r=jhammel All mach modules are now explicitly listed in the mach driver.
mach
python/mach/README.rst
python/mach/mach/main.py
python/mozbuild/mach/commands/build.py
python/mozbuild/mach/commands/warnings.py
python/mozbuild/mozbuild/mach_commands.py
--- a/mach
+++ b/mach
@@ -35,29 +35,29 @@ SEARCH_PATHS = [
     'testing/mozbase/mozinfo',
 ]
 
 # Individual files providing mach commands.
 MACH_MODULES = [
     'layout/tools/reftest/mach_commands.py',
     'python/mozboot/mozboot/mach_commands.py',
     'python/mozbuild/mozbuild/config.py',
+    'python/mozbuild/mozbuild/mach_commands.py',
     'testing/mochitest/mach_commands.py',
     'testing/xpcshell/mach_commands.py',
 ]
 
 our_dir = os.path.dirname(os.path.abspath(__file__))
 
 try:
     import mach.main
 except ImportError:
     sys.path[0:0] = [os.path.join(our_dir, path) for path in SEARCH_PATHS]
 
     import mach.main
 
 # All of the code is in a module because EVERYTHING IS A LIBRARY.
 mach = mach.main.Mach(our_dir)
-mach.load_commands_from_sys_path()
 
 for path in MACH_MODULES:
     mach.load_commands_from_file(os.path.join(our_dir, path))
 
 sys.exit(mach.run(sys.argv[1:]))
--- a/python/mach/README.rst
+++ b/python/mach/README.rst
@@ -48,21 +48,17 @@ When the module is loaded, the decorator
 When mach runs, it takes the assembled metadata from these handlers and
 hooks it up to the command line driver. Under the hood, arguments passed
 to the decorators are being used as arguments to
 *argparse.ArgumentParser.add_parser()* and
 *argparse.ArgumentParser.add_argument()*. See the documentation in the
 *mach.base* module for more.
 
 The Python modules defining mach commands do not need to live inside the
-main mach source tree. If a path on *sys.path* contains a *mach/commands*
-directory, modules will be loaded automatically by mach and any classes
-containing the decorators described above will be detected and loaded
-automatically by mach. So, to add a new subcommand to mach, you just need
-to ensure your Python module is present on *sys.path*.
+main mach source tree.
 
 Minimizing Code in Mach
 -----------------------
 
 Mach is just a frontend. Therefore, code in this package should pertain to
 one of 3 areas:
 
 1. Obtaining user input (parsing arguments, prompting, etc)
--- a/python/mach/mach/main.py
+++ b/python/mach/mach/main.py
@@ -140,38 +140,16 @@ To see more help for a specific command,
 
         self.cwd = cwd
         self.log_manager = LoggingManager()
         self.logger = logging.getLogger(__name__)
         self.settings = ConfigSettings()
 
         self.log_manager.register_structured_logger(self.logger)
 
-    def load_commands_from_sys_path(self):
-        """Discover and load mach command modules from sys.path.
-
-        This iterates over all paths on sys.path. If the path contains a
-        "mach/commands" subdirectory, all .py files in that directory will be
-        loaded and examined for mach commands.
-        """
-        # Create parent module otherwise Python complains when the parent is
-        # missing.
-        if b'mach.commands' not in sys.modules:
-            mod = imp.new_module(b'mach.commands')
-            sys.modules[b'mach.commands'] = mod
-
-        for path in sys.path:
-            # We only support importing .py files from directories.
-            commands_path = os.path.join(path, 'mach', 'commands')
-
-            if not os.path.isdir(commands_path):
-                continue
-
-            self.load_commands_from_directory(commands_path)
-
     def load_commands_from_directory(self, path):
         """Scan for mach commands from modules in a directory.
 
         This takes a path to a directory, loads the .py files in it, and
         registers and found mach command providers with this mach instance.
         """
         for f in sorted(os.listdir(path)):
             if not f.endswith('.py') or f == '__init__.py':
@@ -185,16 +163,22 @@ To see more help for a specific command,
     def load_commands_from_file(self, path, module_name=None):
         """Scan for mach commands from a file.
 
         This takes a path to a file and loads it as a Python module under the
         module name specified. If no name is specified, a random one will be
         chosen.
         """
         if module_name is None:
+            # Ensure parent module is present otherwise we'll (likely) get
+            # an error due to unknown parent.
+            if b'mach.commands' not in sys.modules:
+                mod = imp.new_module(b'mach.commands')
+                sys.modules[b'mach.commands'] = mod
+
             module_name = 'mach.commands.%s' % uuid.uuid1().get_hex()
 
         imp.load_source(module_name, path)
 
     def run(self, argv, stdin=None, stdout=None, stderr=None):
         """Runs mach with arguments provided from the command line.
 
         Returns the integer exit code that should be used. 0 means success. All
deleted file mode 100644
--- a/python/mozbuild/mach/commands/warnings.py
+++ /dev/null
@@ -1,80 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this file,
-# You can obtain one at http://mozilla.org/MPL/2.0/.
-
-from __future__ import print_function, unicode_literals
-
-import operator
-import os
-
-from mach.decorators import (
-    CommandArgument,
-    CommandProvider,
-    Command,
-)
-
-from mozbuild.base import MachCommandBase
-
-
-@CommandProvider
-class Warnings(MachCommandBase):
-    """Provide commands for inspecting warnings."""
-
-    @property
-    def database_path(self):
-        return self._get_state_filename('warnings.json')
-
-    @property
-    def database(self):
-        from mozbuild.compilation.warnings import WarningsDatabase
-
-        path = self.database_path
-
-        database = WarningsDatabase()
-
-        if os.path.exists(path):
-            database.load_from_file(path)
-
-        return database
-
-    @Command('warnings-summary',
-        help='Show a summary of compiler warnings.')
-    @CommandArgument('report', default=None, nargs='?',
-        help='Warnings report to display. If not defined, show the most '
-            'recent report.')
-    def summary(self, report=None):
-        database = self.database
-
-        type_counts = database.type_counts
-        sorted_counts = sorted(type_counts.iteritems(),
-            key=operator.itemgetter(1))
-
-        total = 0
-        for k, v in sorted_counts:
-            print('%d\t%s' % (v, k))
-            total += v
-
-        print('%d\tTotal' % total)
-
-    @Command('warnings-list', help='Show a list of compiler warnings.')
-    @CommandArgument('report', default=None, nargs='?',
-        help='Warnings report to display. If not defined, show the most '
-            'recent report.')
-    def list(self, report=None):
-        database = self.database
-
-        by_name = sorted(database.warnings)
-
-        for warning in by_name:
-            filename = warning['filename']
-
-            if filename.startswith(self.topsrcdir):
-                filename = filename[len(self.topsrcdir) + 1:]
-
-            if warning['column'] is not None:
-                print('%s:%d:%d [%s] %s' % (filename, warning['line'],
-                    warning['column'], warning['flag'], warning['message']))
-            else:
-                print('%s:%d [%s] %s' % (filename, warning['line'],
-                    warning['flag'], warning['message']))
-
rename from python/mozbuild/mach/commands/build.py
rename to python/mozbuild/mozbuild/mach_commands.py
--- a/python/mozbuild/mach/commands/build.py
+++ b/python/mozbuild/mozbuild/mach_commands.py
@@ -1,18 +1,20 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, # You can obtain one at http://mozilla.org/MPL/2.0/.
 
-from __future__ import unicode_literals
+from __future__ import print_function, unicode_literals
 
 import logging
+import operator
 import os
 
 from mach.decorators import (
+    CommandArgument,
     CommandProvider,
     Command,
 )
 
 from mozbuild.base import MachCommandBase
 
 
 @CommandProvider
@@ -50,8 +52,72 @@ class Build(MachCommandBase):
         self._run_make(srcdir=True, filename='client.mk', line_handler=on_line,
             log=False, print_directory=False)
 
         self.log(logging.WARNING, 'warning_summary',
             {'count': len(warnings_collector.database)},
             '{count} compiler warnings present.')
 
         warnings_database.save_to_file(warnings_path)
+
+
+@CommandProvider
+class Warnings(MachCommandBase):
+    """Provide commands for inspecting warnings."""
+
+    @property
+    def database_path(self):
+        return self._get_state_filename('warnings.json')
+
+    @property
+    def database(self):
+        from mozbuild.compilation.warnings import WarningsDatabase
+
+        path = self.database_path
+
+        database = WarningsDatabase()
+
+        if os.path.exists(path):
+            database.load_from_file(path)
+
+        return database
+
+    @Command('warnings-summary',
+        help='Show a summary of compiler warnings.')
+    @CommandArgument('report', default=None, nargs='?',
+        help='Warnings report to display. If not defined, show the most '
+            'recent report.')
+    def summary(self, report=None):
+        database = self.database
+
+        type_counts = database.type_counts
+        sorted_counts = sorted(type_counts.iteritems(),
+            key=operator.itemgetter(1))
+
+        total = 0
+        for k, v in sorted_counts:
+            print('%d\t%s' % (v, k))
+            total += v
+
+        print('%d\tTotal' % total)
+
+    @Command('warnings-list', help='Show a list of compiler warnings.')
+    @CommandArgument('report', default=None, nargs='?',
+        help='Warnings report to display. If not defined, show the most '
+            'recent report.')
+    def list(self, report=None):
+        database = self.database
+
+        by_name = sorted(database.warnings)
+
+        for warning in by_name:
+            filename = warning['filename']
+
+            if filename.startswith(self.topsrcdir):
+                filename = filename[len(self.topsrcdir) + 1:]
+
+            if warning['column'] is not None:
+                print('%s:%d:%d [%s] %s' % (filename, warning['line'],
+                    warning['column'], warning['flag'], warning['message']))
+            else:
+                print('%s:%d [%s] %s' % (filename, warning['line'],
+                    warning['flag'], warning['message']))
+