blob: c567b21bd06951375df94f43abd0efa48fcb04c0 [file] [log] [blame]
Brian Harring984988f2012-10-10 22:53:30 -07001# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Module that contains meta-logic related to Cros Commands.
6
David Pursellf1c27c12015-03-18 09:51:38 -07007This module contains two important definitions used by all commands:
Brian Harring984988f2012-10-10 22:53:30 -07008 CrosCommand: The parent class of all cros commands.
9 CommandDecorator: Decorator that must be used to ensure that the command shows
10 up in _commands and is discoverable by cros.
David Pursellf1c27c12015-03-18 09:51:38 -070011
12Commands can be either imported directly or looked up using this module's
13ListCommands() function.
Brian Harring984988f2012-10-10 22:53:30 -070014"""
15
Mike Frysinger383367e2014-09-16 15:06:17 -040016from __future__ import print_function
17
David Pursellf1c27c12015-03-18 09:51:38 -070018import glob
19import os
20
Bertrand SIMONNET2b1ed052015-03-02 11:17:40 -080021from chromite.lib import brick_lib
David Pursellf1c27c12015-03-18 09:51:38 -070022from chromite.lib import cros_import
23
Brian Harring984988f2012-10-10 22:53:30 -070024
25_commands = dict()
26
27
David Pursellf1c27c12015-03-18 09:51:38 -070028def _FindModules(subdir_path):
29 """Returns a list of all the relevant python modules in |sub_dir_path|"""
30 # We only load cros_[!unittest] modules.
31 modules = []
32 for file_path in glob.glob(subdir_path + '/cros_*.py'):
33 if not file_path.endswith('_unittest.py'):
34 modules.append(file_path)
35
36 return modules
37
38
39def _ImportCommands():
40 """Directly imports all cros_[!unittest] python modules.
41
42 This method imports the cros_[!unittest] modules which may contain
43 commands. When these modules are loaded, declared commands (those that use
David Pursellcfd58872015-03-19 09:15:48 -070044 the cros.CommandDecorator) will automatically get added to _commands.
David Pursellf1c27c12015-03-18 09:51:38 -070045 """
David Pursellcfd58872015-03-19 09:15:48 -070046 subdir_path = os.path.join(os.path.dirname(__file__), 'cros')
David Pursellf1c27c12015-03-18 09:51:38 -070047 for file_path in _FindModules(subdir_path):
48 file_name = os.path.basename(file_path)
49 mod_name = os.path.splitext(file_name)[0]
David Pursellcfd58872015-03-19 09:15:48 -070050 cros_import.ImportModule(('chromite', 'cli', 'cros', mod_name))
David Pursellf1c27c12015-03-18 09:51:38 -070051
52
53def ListCommands():
54 """Return a dictionary mapping command names to classes."""
55 _ImportCommands()
56 return _commands.copy()
57
58
Brian Harring984988f2012-10-10 22:53:30 -070059class InvalidCommandError(Exception):
60 """Error that occurs when command class fails sanity checks."""
61 pass
62
63
64def CommandDecorator(command_name):
65 """Decorator that sanity checks and adds class to list of usable commands."""
66
67 def InnerCommandDecorator(original_class):
68 """"Inner Decorator that actually wraps the class."""
69 if not hasattr(original_class, '__doc__'):
70 raise InvalidCommandError('All handlers must have docstrings: %s' %
71 original_class)
72
73 if not issubclass(original_class, CrosCommand):
74 raise InvalidCommandError('All Commands must derive from CrosCommand: '
75 '%s' % original_class)
76
77 _commands[command_name] = original_class
Ryan Cui47f80e42013-04-01 19:01:54 -070078 original_class.command_name = command_name
79
Brian Harring984988f2012-10-10 22:53:30 -070080 return original_class
81
82 return InnerCommandDecorator
83
84
85class CrosCommand(object):
86 """All CrosCommands must derive from this class.
87
88 This class provides the abstract interface for all Cros Commands. When
89 designing a new command, you must sub-class from this class and use the
90 CommandDecorator decorator. You must specify a class docstring as that will be
91 used as the usage for the sub-command.
92
93 In addition your command should implement AddParser which is passed in a
94 parser that you can add your own custom arguments. See argparse for more
95 information.
96 """
Ryan Cui47f80e42013-04-01 19:01:54 -070097 # Indicates whether command stats should be uploaded for this command.
98 # Override to enable command stats uploading.
99 upload_stats = False
100 # We set the default timeout to 1 second, to prevent overly long waits for
101 # commands to complete. From manual tests, stat uploads usually take
102 # between 0.35s-0.45s in MTV.
103 upload_stats_timeout = 1
104
Ryo Hashimoto8bc997b2014-01-22 18:46:17 +0900105 # Indicates whether command uses cache related commandline options.
106 use_caching_options = False
107
Brian Harring984988f2012-10-10 22:53:30 -0700108 def __init__(self, options):
109 self.options = options
Bertrand SIMONNET2b1ed052015-03-02 11:17:40 -0800110 brick = brick_lib.FindBrickInPath()
Bertrand SIMONNET79e077d2015-03-12 18:31:12 -0700111 self.curr_brick_locator = brick.brick_locator if brick else None
Brian Harring984988f2012-10-10 22:53:30 -0700112
113 @classmethod
114 def AddParser(cls, parser):
115 """Add arguments for this command to the parser."""
116 parser.set_defaults(cros_class=cls)
117
118 def Run(self):
119 """The command to run."""
120 raise NotImplementedError()