author | Gregory Szorc <gps@mozilla.com> |
Wed, 10 Oct 2012 11:08:09 -0700 | |
changeset 117722 | 2a0e2af364bc635500fc8fe7129b217119c5ff03 |
parent 117721 | ec10630b1a5406c28d1ac84bd314938374404d04 |
child 117723 | 428846e73299df3194ebb8a9172429e2356704ee |
push id | 1997 |
push user | akeybl@mozilla.com |
push date | Mon, 07 Jan 2013 21:25:26 +0000 |
treeherder | mozilla-beta@4baf45cdcf21 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | jhammel |
bugs | 799262 |
milestone | 19.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
|
mach | file | annotate | diff | comparison | revisions | |
python/mach/mach/main.py | file | annotate | diff | comparison | revisions |
--- a/mach +++ b/mach @@ -39,9 +39,10 @@ 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() sys.exit(mach.run(sys.argv[1:]))
--- a/python/mach/mach/main.py +++ b/python/mach/mach/main.py @@ -8,16 +8,17 @@ from __future__ import unicode_literals import argparse import codecs import imp import logging import os import sys +import uuid from mozbuild.base import BuildConfig from mozbuild.config import ConfigSettings from mozbuild.logger import LoggingManager from mach.registrar import populate_argument_parser @@ -35,18 +36,16 @@ CONSUMED_ARGUMENTS = [ 'logfile', 'log_interval', 'command', 'cls', 'method', 'func', ] -MODULES_SCANNED = False - class ArgumentParser(argparse.ArgumentParser): """Custom implementation argument parser to make things look pretty.""" def error(self, message): """Custom error reporter to give more helpful text on bad commands.""" if not message.startswith('argument command: invalid choice'): argparse.ArgumentParser.error(self, message) @@ -108,20 +107,64 @@ 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) - if not MODULES_SCANNED: - self._load_modules() + 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) - MODULES_SCANNED = True + 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': + continue + + full_path = os.path.join(path, f) + module_name = 'mach.commands.%s' % f[0:-3] + + self.load_commands_from_file(full_path, module_name=module_name) + + 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: + module_name = 'mach.commands.%s' % uuid.uuid1().get_hex() + + imp.load_source(module_name, path) def run(self, argv): """Runs mach with arguments provided from the command line. Returns the integer exit code that should be used. 0 means success. All other values indicate failure. """ @@ -212,45 +255,16 @@ To see more help for a specific command, return result def log(self, level, action, params, format_str): """Helper method to record a structured log event.""" self.logger.log(level, format_str, extra={'action': action, 'params': params}) - def _load_modules(self): - """Scan over Python modules looking for mach command providers.""" - - # 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 - - # We only support loading modules in the immediate mach.commands - # module, not sub-modules. Walking the tree would be trivial to - # implement if it were ever desired. - for f in sorted(os.listdir(commands_path)): - if not f.endswith('.py') or f == '__init__.py': - continue - - full_path = os.path.join(commands_path, f) - module_name = 'mach.commands.%s' % f[0:-3] - - imp.load_source(module_name, full_path) - - def load_settings(self, args): """Determine which settings files apply and load them. Currently, we only support loading settings from a single file. Ideally, we support loading from multiple files. This is supported by the ConfigSettings API. However, that API currently doesn't track where individual values come from, so if we load from multiple sources then save, we effectively do a full copy. We don't want this. Until