blob: fc91218f3c0e8b3c598c408d075fde2d69c7f5f0 [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
21from chromite.cbuildbot import constants
Bertrand SIMONNET2b1ed052015-03-02 11:17:40 -080022from chromite.lib import brick_lib
David Pursellf1c27c12015-03-18 09:51:38 -070023from chromite.lib import cros_import
24
Brian Harring984988f2012-10-10 22:53:30 -070025
26_commands = dict()
27
28
David Pursellf1c27c12015-03-18 09:51:38 -070029def _FindModules(subdir_path):
30 """Returns a list of all the relevant python modules in |sub_dir_path|"""
31 # We only load cros_[!unittest] modules.
32 modules = []
33 for file_path in glob.glob(subdir_path + '/cros_*.py'):
34 if not file_path.endswith('_unittest.py'):
35 modules.append(file_path)
36
37 return modules
38
39
40def _ImportCommands():
41 """Directly imports all cros_[!unittest] python modules.
42
43 This method imports the cros_[!unittest] modules which may contain
44 commands. When these modules are loaded, declared commands (those that use
45 the cros.CommandDecorator) will automatically get added to cros._commands.
46 """
47 subdir_path = os.path.join(constants.CHROMITE_DIR, 'cros', 'commands')
48 for file_path in _FindModules(subdir_path):
49 file_name = os.path.basename(file_path)
50 mod_name = os.path.splitext(file_name)[0]
51 cros_import.ImportModule(('chromite', 'cros', 'commands', mod_name))
52
53
54def ListCommands():
55 """Return a dictionary mapping command names to classes."""
56 _ImportCommands()
57 return _commands.copy()
58
59
Brian Harring984988f2012-10-10 22:53:30 -070060class InvalidCommandError(Exception):
61 """Error that occurs when command class fails sanity checks."""
62 pass
63
64
65def CommandDecorator(command_name):
66 """Decorator that sanity checks and adds class to list of usable commands."""
67
68 def InnerCommandDecorator(original_class):
69 """"Inner Decorator that actually wraps the class."""
70 if not hasattr(original_class, '__doc__'):
71 raise InvalidCommandError('All handlers must have docstrings: %s' %
72 original_class)
73
74 if not issubclass(original_class, CrosCommand):
75 raise InvalidCommandError('All Commands must derive from CrosCommand: '
76 '%s' % original_class)
77
78 _commands[command_name] = original_class
Ryan Cui47f80e42013-04-01 19:01:54 -070079 original_class.command_name = command_name
80
Brian Harring984988f2012-10-10 22:53:30 -070081 return original_class
82
83 return InnerCommandDecorator
84
85
86class CrosCommand(object):
87 """All CrosCommands must derive from this class.
88
89 This class provides the abstract interface for all Cros Commands. When
90 designing a new command, you must sub-class from this class and use the
91 CommandDecorator decorator. You must specify a class docstring as that will be
92 used as the usage for the sub-command.
93
94 In addition your command should implement AddParser which is passed in a
95 parser that you can add your own custom arguments. See argparse for more
96 information.
97 """
Ryan Cui47f80e42013-04-01 19:01:54 -070098 # Indicates whether command stats should be uploaded for this command.
99 # Override to enable command stats uploading.
100 upload_stats = False
101 # We set the default timeout to 1 second, to prevent overly long waits for
102 # commands to complete. From manual tests, stat uploads usually take
103 # between 0.35s-0.45s in MTV.
104 upload_stats_timeout = 1
105
Ryo Hashimoto8bc997b2014-01-22 18:46:17 +0900106 # Indicates whether command uses cache related commandline options.
107 use_caching_options = False
108
Brian Harring984988f2012-10-10 22:53:30 -0700109 def __init__(self, options):
110 self.options = options
Bertrand SIMONNET2b1ed052015-03-02 11:17:40 -0800111 brick = brick_lib.FindBrickInPath()
Bertrand SIMONNET79e077d2015-03-12 18:31:12 -0700112 self.curr_brick_locator = brick.brick_locator if brick else None
Brian Harring984988f2012-10-10 22:53:30 -0700113
114 @classmethod
115 def AddParser(cls, parser):
116 """Add arguments for this command to the parser."""
117 parser.set_defaults(cros_class=cls)
118
119 def Run(self):
120 """The command to run."""
121 raise NotImplementedError()