Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 1 | # Copyright (c) 2014 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. |
Ruben Rodriguez Buchillon | 0902f09 | 2018-09-19 15:03:17 +0800 | [diff] [blame] | 4 | """Common code for servo parsing support.""" |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 5 | |
| 6 | import argparse |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 7 | import logging |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 8 | import os |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 9 | import textwrap |
| 10 | |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 11 | import client |
| 12 | import servo_logging |
Ruben Rodriguez Buchillon | bb65ba8 | 2020-03-20 13:27:43 -0700 | [diff] [blame] | 13 | import sversion_util |
Ruben Rodriguez Buchillon | 479bd24 | 2020-02-14 18:58:30 -0800 | [diff] [blame] | 14 | import utils.scratch |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 15 | |
Ruben Rodriguez Buchillon | 014d882 | 2018-09-20 10:12:13 +0800 | [diff] [blame] | 16 | |
| 17 | # A brief overview of the classes found here, and their abilities. |
| 18 | # Indentation indicates inheritance. |
| 19 | # |
| 20 | # _BaseServodParser: ArgumentParser with pretty-formatting for example list |
| 21 | # |
Ruben Rodriguez Buchillon | bb65ba8 | 2020-03-20 13:27:43 -0700 | [diff] [blame] | 22 | # BaseServodParser: adds common args: port, host, version, and debug |
Ruben Rodriguez Buchillon | 014d882 | 2018-09-20 10:12:13 +0800 | [diff] [blame] | 23 | # |
| 24 | # ServodRCParser: adds -name/-rcfile & overwrites parsing logic so that |
| 25 | # rc parsing & configuration is handled internally |
| 26 | # i.e. the program does not need to know anything about |
| 27 | # the servodrc system |
| 28 | # |
| 29 | # ServodClientParser: parser intended for servod clients (e.g. dut-control) |
| 30 | # - has all the BaseServodParser and ServodRCParser args |
| 31 | # - has the native rc configuration handling |
| 32 | # - adds a serialname argument that gets mapped to a |
| 33 | # port using ServoScratch |
| 34 | |
| 35 | |
| 36 | # This text file holds servod configuration parameters. This is especially |
| 37 | # handy for multi servo operation. |
| 38 | # |
| 39 | # The file format is pretty loose: |
| 40 | # - text starting with # is ignored til the end of the line |
| 41 | # - empty lines are ignored |
| 42 | # - configuration lines consist of up to 5 comma separated fields (all |
| 43 | # but the first field optional): |
| 44 | # servo-name, serial-number, port-number, board-name, board-model |
| 45 | # |
| 46 | # where |
| 47 | # . servo-name - a user defined symbolic name, just a reference |
| 48 | # to a certain servo board |
| 49 | # . serial-number - serial number of the servo board this line pertains to |
| 50 | # . port-number - desired port number for servod for this board, can be |
| 51 | # overridden by the command line switch --port or |
| 52 | # environment variable setting SERVOD_PORT |
| 53 | # NOTE: this is no longer in use, and will be ignored. |
| 54 | # . board-name - board configuration file to use, can be |
| 55 | # overridden by the command line switch --board |
| 56 | # . model-name - model override to use, if applicable. |
| 57 | # overridden by command line --model |
| 58 | # |
| 59 | # Example lines in the rc-file: |
| 60 | # nocturne_micro, SNCQ00098, , nocturne # This is a nocturne without model |
| 61 | # octopus_micro, SNCQ00098, , octopus, npcx # This an octopus that defines model |
| 62 | # |
| 63 | # As you can see, the port part is left out for now. This will be phased out |
| 64 | # giving users time to adjust their rc files. |
| 65 | # |
| 66 | # Since the same parameters could be defined using different means, there is a |
| 67 | # hierarchy of definitions (left being the highest priority): |
| 68 | # command line <- environment definition <- rc config file |
| 69 | |
| 70 | |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 71 | if os.getuid(): |
| 72 | DEFAULT_RC_FILE = '/home/%s/.servodrc' % os.getenv('USER', '') |
| 73 | else: |
| 74 | DEFAULT_RC_FILE = '/home/%s/.servodrc' % os.getenv('SUDO_USER', '') |
| 75 | |
| 76 | |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame] | 77 | PORT_ENV_VAR = 'SERVOD_PORT' |
| 78 | NAME_ENV_VAR = 'SERVOD_NAME' |
| 79 | |
| 80 | |
| 81 | ARG_BY_USER_MARKER = 'supplied_by_user' |
| 82 | |
Ruben Rodriguez Buchillon | bb65ba8 | 2020-03-20 13:27:43 -0700 | [diff] [blame] | 83 | # Keep track of both the 'version' according to PEP440 and 'sversion' our |
| 84 | # internal version system, and provide arguments to print those. |
| 85 | VERSION = '%(prog)s ' + sversion_util.setuptools_version() |
| 86 | SVERSION = '%(prog)s ' + sversion_util.extended_version() |
| 87 | |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame] | 88 | |
| 89 | def ArgMarkedAsUserSupplied(namespace, arg_name): |
| 90 | """Query whether an argument that uses StoreAndMarkAction is user supplied.""" |
| 91 | marker_name = '%s_%s' % (arg_name, ARG_BY_USER_MARKER) |
| 92 | return hasattr(namespace, marker_name) |
| 93 | |
| 94 | # pylint: disable=protected-access |
| 95 | # Need to expand the StoreAction of the parser. |
| 96 | class StoreAndMarkAction(argparse._StoreAction): |
| 97 | """Helper to mark arguments whether they were supplied by the user. |
| 98 | |
| 99 | If an argument is supplied by the user instead of using defaults or RC, |
| 100 | add another option with the name |arg|_supplied_by_user. |
| 101 | """ |
| 102 | |
| 103 | def __call__(self, parser, namespace, values, option_string=None): |
| 104 | """Extend default __call__ implementation.""" |
| 105 | # This sets the |values| to |self.dest|. |
| 106 | super(StoreAndMarkAction, self).__call__(parser=parser, namespace=namespace, |
| 107 | values=values, |
| 108 | option_string=option_string) |
| 109 | marker_name = '%s_%s' % (self.dest, ARG_BY_USER_MARKER) |
| 110 | setattr(namespace, marker_name, True) |
| 111 | |
| 112 | |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 113 | class ServodParserHelpFormatter(argparse.RawDescriptionHelpFormatter, |
| 114 | argparse.ArgumentDefaultsHelpFormatter): |
| 115 | """Servod help formatter. |
| 116 | |
| 117 | Combines ability for raw description printing (needed to have control over |
| 118 | how to print examples) and default argument printing, printing the default |
| 119 | which each argument. |
| 120 | """ |
| 121 | pass |
| 122 | |
| 123 | |
| 124 | class ServodParserError(Exception): |
| 125 | """Error class for Servod parsing errors.""" |
| 126 | pass |
| 127 | |
| 128 | |
| 129 | class _BaseServodParser(argparse.ArgumentParser): |
| 130 | """Extension to ArgumentParser that allows for examples in the description. |
| 131 | |
| 132 | _BaseServodParser allows for a list of example tuples, where |
| 133 | element[0]: is the cmdline invocation |
| 134 | element[1]: is a comment to explain what the invocation does. |
| 135 | |
| 136 | For example (loosely based on servod.) |
| 137 | ('-b board', 'Start servod with the configuation for board |board|') |
| 138 | would print the following help message: |
| 139 | ... |
| 140 | |
| 141 | Examples: |
| 142 | > servod -b board |
| 143 | Start servod with the configuration for board |board| |
| 144 | |
| 145 | Optional Arguments... |
| 146 | |
| 147 | see servod, or dut_control for more examples. |
| 148 | """ |
| 149 | |
Ruben Rodriguez Buchillon | 0902f09 | 2018-09-19 15:03:17 +0800 | [diff] [blame] | 150 | def __init__(self, description='', examples=None, **kwargs): |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 151 | """Initialize _BaseServodParser by setting description and formatter. |
| 152 | |
| 153 | Args: |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 154 | description: description of the program |
| 155 | examples: list of tuples where the first element is the cmdline example, |
| 156 | and the second element is a comment explaining the example. |
| 157 | %(prog)s will be prepended to each example if it does not |
| 158 | start with %(prog)s. |
Ruben Rodriguez Buchillon | 0902f09 | 2018-09-19 15:03:17 +0800 | [diff] [blame] | 159 | **kwargs: keyword arguments forwarded to ArgumentParser |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 160 | """ |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 161 | # Initialize logging up here first to ensure log messages from parsing |
| 162 | # can go through. |
| 163 | loglevel, fmt = servo_logging.LOGLEVEL_MAP[servo_logging.DEFAULT_LOGLEVEL] |
Ruben Rodriguez Buchillon | 56b1f3f | 2020-05-13 14:56:41 -0700 | [diff] [blame] | 164 | logging.basicConfig(level=loglevel, format=fmt) |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 165 | self._logger = logging.getLogger(type(self).__name__) |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 166 | # Generate description. |
| 167 | description_lines = textwrap.wrap(description) |
| 168 | # Setting it into the kwargs here ensures that we overwrite an potentially |
| 169 | # passed in and undesired formatter class. |
| 170 | kwargs['formatter_class'] = ServodParserHelpFormatter |
| 171 | if examples: |
| 172 | # Extra newline to separate description from examples. |
| 173 | description_lines.append('\n') |
| 174 | description_lines.append('Examples:') |
| 175 | for example, comment in examples: |
| 176 | if not example.startswith('%(prog)s'): |
| 177 | example = '%(prog)s ' + example |
| 178 | example_lines = [' > ' + example] |
| 179 | example_lines.extend(textwrap.wrap(comment)) |
| 180 | description_lines.append('\n\t'.join(example_lines)) |
| 181 | description = '\n'.join(description_lines) |
| 182 | kwargs['description'] = description |
Ruben Rodriguez Buchillon | 0902f09 | 2018-09-19 15:03:17 +0800 | [diff] [blame] | 183 | super(_BaseServodParser, self).__init__(**kwargs) |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 184 | |
| 185 | |
| 186 | class BaseServodParser(_BaseServodParser): |
| 187 | """BaseServodParser handling common arguments in the servod cmdline tools.""" |
| 188 | |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame] | 189 | def __init__(self, add_port=True, **kwargs): |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 190 | """Initialize by adding common arguments. |
| 191 | |
| 192 | Adds: |
| 193 | - host/port arguments to find/initialize a servod instance |
| 194 | - debug argument to toggle debug message printing |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 195 | |
| 196 | Args: |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame] | 197 | add_port: bool, whether to add --port to the parser. A caller might want |
| 198 | to add port themselves either to rename it (servod-port), |
| 199 | or to create mutual exclusion with serialname and name (clients) |
Ruben Rodriguez Buchillon | 0902f09 | 2018-09-19 15:03:17 +0800 | [diff] [blame] | 200 | **kwargs: keyword arguments forwarded to _BaseServodParser |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 201 | """ |
Ruben Rodriguez Buchillon | 2187edd | 2020-11-18 17:03:56 -0800 | [diff] [blame^] | 202 | # Remove version from the kwargs and add it yourself. |
| 203 | v = kwargs.pop('version', VERSION) |
Ruben Rodriguez Buchillon | 0902f09 | 2018-09-19 15:03:17 +0800 | [diff] [blame] | 204 | super(BaseServodParser, self).__init__(**kwargs) |
Ruben Rodriguez Buchillon | 2187edd | 2020-11-18 17:03:56 -0800 | [diff] [blame^] | 205 | self.add_argument('-v', '--version', action='version', version=v) |
Ruben Rodriguez Buchillon | bb65ba8 | 2020-03-20 13:27:43 -0700 | [diff] [blame] | 206 | self.add_argument('--sversion', action='version', version=SVERSION) |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 207 | self.add_argument('-d', '--debug', action='store_true', default=False, |
| 208 | help='enable debug messages') |
| 209 | self.add_argument('--host', default='localhost', type=str, |
| 210 | help='hostname of the servod server.') |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame] | 211 | if add_port: |
| 212 | BaseServodParser.AddRCEnabledPortArg(self) |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 213 | |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 214 | @staticmethod |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame] | 215 | def AddRCEnabledPortArg(parser, port_flags=['-p', '--port']): |
| 216 | """Add the port to the argparser. |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 217 | |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame] | 218 | Set the default to environment variable ENV_PORT_NAME if defined |
| 219 | |
| 220 | Note: while this helper does allow for arbitrary flags for the port |
| 221 | variable, the destination is still set to 'port'. It's on the caller to |
| 222 | ensure that there is no conflict. |
Ruben Rodriguez Buchillon | 0902f09 | 2018-09-19 15:03:17 +0800 | [diff] [blame] | 223 | |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 224 | Args: |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame] | 225 | parser: parser or group to add argument to |
| 226 | port_flags: optional, list, if the flags for the port should be different |
| 227 | than the default ones. |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 228 | """ |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame] | 229 | # pylint: disable=dangerous-default-value |
| 230 | # Having the default flags here simplifies the code logic. |
| 231 | default = os.environ.get(PORT_ENV_VAR, client.DEFAULT_PORT) |
| 232 | parser.add_argument(*port_flags, default=default, type=int, dest='port', |
| 233 | action=StoreAndMarkAction, |
| 234 | help='port of the servod server. Can also be supplied ' |
| 235 | 'through environment variable ' + PORT_ENV_VAR) |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 236 | |
| 237 | |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame] | 238 | class ServodRCParser(_BaseServodParser): |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 239 | """Base class to build Servod parsers to natively handle servorc. |
| 240 | |
| 241 | This class overwrites parse_args & parse_known_args to: |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame] | 242 | - handle NAME_ENV_VAR environment variable |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 243 | - parse & substitute in the servorc file on matches |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 244 | """ |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 245 | |
| 246 | def __init__(self, **kwargs): |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame] | 247 | super(ServodRCParser, self).__init__(**kwargs) |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 248 | self.add_argument('--rcfile', type=str, default=DEFAULT_RC_FILE, |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 249 | help='servo description file for multi-servo operation.') |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame] | 250 | # name and serialname are both ways to ID a servo device |
| 251 | self._id_group = self.add_mutually_exclusive_group() |
| 252 | self._id_group.add_argument('-s', '--serialname', default=None, type=str, |
| 253 | help='device serialname stored in eeprom.') |
| 254 | ServodRCParser.AddRCEnabledNameArg(self._id_group) |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 255 | |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 256 | @staticmethod |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame] | 257 | def AddRCEnabledNameArg(parser, name_flags=['-n', '--name']): |
| 258 | """Add the name to the argparser. |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 259 | |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame] | 260 | Set the default to environment variable ENV_VAR_NAME if defined |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 261 | |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame] | 262 | Note: while this helper does allow for arbitrary flags for the name |
| 263 | variable, the destination is still set to 'name'. It's on the caller to |
| 264 | ensure that there is no conflict. |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 265 | |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 266 | Args: |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame] | 267 | parser: parser or group to add argument to |
| 268 | name_flags: optional, list, if the flags for the name should be different |
| 269 | than the default ones. |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 270 | """ |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame] | 271 | # pylint: disable=dangerous-default-value |
| 272 | # Having the default flags here simplifies the code logic. |
| 273 | default = os.environ.get(NAME_ENV_VAR, '') |
| 274 | parser.add_argument(*name_flags, default=default, type=str, dest='name', |
| 275 | help='symbolic name of the servo board, ' |
| 276 | 'used as a config shortcut, could also be supplied ' |
| 277 | 'through environment variable ' + NAME_ENV_VAR) |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 278 | |
| 279 | @staticmethod |
| 280 | def PostProcessRCElements(options, rcpath=None, logger=logging): |
| 281 | """Handle 'name' in |options| by substituting it with the intended config. |
| 282 | |
| 283 | This replaces the name option in the options with the intended serialname |
| 284 | for that name if one can be found. If a board file is also specified in the |
| 285 | rc file it appends that to the options too, which can be ignored if not |
| 286 | needed. |
| 287 | |
| 288 | Note: this function changes the content of options. |
| 289 | |
| 290 | Args: |
| 291 | options: argparse Namespace of options to process. |
| 292 | rcpath: optional rcfile path if it's not stored under options.rcfile |
| 293 | logger: logger instance to use |
| 294 | |
| 295 | Returns: |
| 296 | Reference back to the same options passed in. |
| 297 | |
| 298 | Raises: |
| 299 | ServodParserError: if -n/--name and -s/--serialname both defined |
| 300 | ServodParserError: if name in options doesn't show up in servodrc |
| 301 | """ |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 302 | if not rcpath: |
| 303 | rcpath = options.rcfile |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame] | 304 | rcd = ServodRCParser.ParseRC(rcpath, logger=logger) |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 305 | rc = None |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame] | 306 | if not options.serialname and options.name: |
| 307 | # |name| can be set through the commandline or through an environment |
| 308 | # variable. If it's set through the commandline, serialname cannot have |
| 309 | # been set. However, if serialname is set and name is also set (through |
| 310 | # the environment variable) name gets ignored. |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 311 | if options.name not in rcd: |
| 312 | raise ServodParserError('Name %r not in rc at %r' % (options.name, |
| 313 | rcpath)) |
| 314 | rc = rcd[options.name] |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame] | 315 | # For an rc to exist, 'sn' has to be a part of it |
| 316 | setattr(options, 'serialname', rc['sn']) |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 317 | elif options.serialname: |
| 318 | # srcs meaning serialname runtime configurations (rcs). |
Ruben Rodriguez Buchillon | 24c0ad8 | 2020-03-27 19:12:11 -0700 | [diff] [blame] | 319 | srcs = [(name, rc) for name, rc in rcd.items() if |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 320 | rc['sn'] == options.serialname] |
| 321 | if srcs: |
| 322 | logger.info('Found servodrc entry %r for serialname %r. Using it.', |
| 323 | srcs[0][0], options.serialname) |
| 324 | rc = srcs[0][1] |
| 325 | if rc: |
| 326 | for elem in ['board', 'model']: |
| 327 | # Unlike serialname explicit overwrites of board and model in the |
| 328 | # cmdline are fine as the name flag is still useful to refer to a |
| 329 | # serialname. |
| 330 | if elem in rc and hasattr(options, elem): |
| 331 | if not getattr(options, elem): |
| 332 | logger.info('Setting %r to %r in the options as indicated by ' |
| 333 | 'servodrc file.', elem, rc[elem]) |
| 334 | setattr(options, elem, rc[elem]) |
| 335 | else: |
| 336 | if getattr(options, elem) != rc[elem]: |
| 337 | logger.warning('Ignoring rc configured %r name %r for servo %r. ' |
| 338 | 'Option already defined on the command line as %r', |
| 339 | elem, rc[elem], rc['sn'], getattr(options, elem)) |
| 340 | return options |
| 341 | |
| 342 | def parse_known_args(self, args=None, namespace=None): |
| 343 | """Overwrite from Argumentparser to handle servo rc. |
| 344 | |
| 345 | Note: this also overwrites parse_args as parse_args just calls |
| 346 | parse_known_args and throws an error if there's anything inside of |
| 347 | xtra. |
| 348 | |
| 349 | Args: |
| 350 | args: list of cmdline elements |
| 351 | namespace: namespace to place the results into |
| 352 | |
| 353 | Returns: |
| 354 | tuple (options, xtra) the result from parsing known args |
| 355 | """ |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame] | 356 | opts, xtra = _BaseServodParser.parse_known_args(self, args=args, |
| 357 | namespace=namespace) |
| 358 | opts = ServodRCParser.PostProcessRCElements(opts, logger=self._logger) |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 359 | return (opts, xtra) |
| 360 | |
| 361 | @staticmethod |
| 362 | def ParseRC(rc_file, logger=logging): |
| 363 | """Parse servodrc configuration file. |
| 364 | |
| 365 | The format of the configuration file is described above in comments to |
| 366 | DEFAULT_RC_FILE. If the file is not found or is mis-formatted, a warning is |
| 367 | printed but the program tries to continue. |
| 368 | |
| 369 | Args: |
| 370 | rc_file: a string, name of the file storing the configuration |
| 371 | logger: logger instance to use |
| 372 | |
| 373 | Returns: |
| 374 | a dictionary, where keys are symbolic servo names, and values are |
| 375 | dictionaries representing servo parameters read from the config file, |
| 376 | keyed by strings 'sn' (for serial number), 'port', 'board', and 'model'. |
| 377 | """ |
| 378 | |
| 379 | if not os.path.isfile(rc_file): |
| 380 | return {} |
| 381 | rcd = {} # Dictionary representing the rc file contents. |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame] | 382 | attributes = ['name', 'sn', 'port', 'board', 'model'] |
| 383 | # These attributes have to be defined for a line to be valid. |
| 384 | required_attributes = ['name', 'sn'] |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 385 | with open(rc_file) as f: |
| 386 | for rc_line in f: |
| 387 | line = rc_line.split('#')[0].strip() |
| 388 | if not line: |
| 389 | continue |
| 390 | elts = [x.strip() for x in line.split(',')] |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame] | 391 | if len(elts) < len(required_attributes): |
| 392 | logger.warning('ignoring rc line %r. Not all required ' |
| 393 | 'attributes defined %r.', rc_line.rstrip(), |
| 394 | required_attributes) |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 395 | continue |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame] | 396 | # Initialize all to None that are not in elts |
| 397 | line_content = dict(zip(attributes, elts + [None] * len(attributes))) |
| 398 | # All required attributes are defined. Store the entry. |
| 399 | name = line_content.pop('name') |
| 400 | if len(elts) > len(attributes): |
| 401 | extra_info = elts[len(attributes):] |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 402 | logger.warning('discarding %r for for %r', ', '.join(extra_info), |
| 403 | name) |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame] | 404 | rcd[name] = line_content |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 405 | return rcd |
| 406 | |
| 407 | |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame] | 408 | class ServodClientParser(ServodRCParser): |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 409 | """Parser to use for servod client cmdline tools. |
| 410 | |
| 411 | This parser adds servoscratch serialname<>port conversion to allow |
| 412 | for servod client cmdline tools to address servod using a servo device's |
| 413 | serialname as well. |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 414 | """ |
| 415 | |
Ruben Rodriguez Buchillon | 10a3a9a | 2020-02-27 16:02:54 -0800 | [diff] [blame] | 416 | def __init__(self, scratch=utils.scratch.SERVO_SCRATCH_DIR, **kwargs): |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 417 | """Create a ServodRCParser that has the BaseServodParser args. |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 418 | |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame] | 419 | (for testing) pass a scratch directory instead of the global default. |
| 420 | |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 421 | Args: |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame] | 422 | scratch: scratch directory to use |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 423 | **kwargs: keyword arguments forwarded to _BaseServodParser |
| 424 | """ |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame] | 425 | # BaseServodParser is used here to get the common arguments. Later, |
| 426 | # the ServodClientParser adds port itself, because from a client perspective |
| 427 | # there is mutual exclusion between --port/--serialname/--name as they serve |
| 428 | # one purpose: to identify an instance. |
| 429 | self._scratchdir = scratch |
| 430 | base_parser = BaseServodParser(add_port=False, add_help=False) |
| 431 | if 'parents' not in kwargs: |
| 432 | kwargs['parents'] = [] |
| 433 | kwargs['parents'].append(base_parser) |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 434 | super(ServodClientParser, self).__init__(**kwargs) |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame] | 435 | # Add --port to the |_id_group| to ensure exclusion with name and |
| 436 | # serialname. |
| 437 | BaseServodParser.AddRCEnabledPortArg(self._id_group) |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 438 | |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 439 | def _MapSNToPort(self, opts): |
| 440 | """Helper to map the serialname in opts to the port its running on. |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 441 | |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 442 | Args: |
| 443 | opts: ArgumentParser Namespace after parsing. |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 444 | |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 445 | Returns: |
| 446 | opts: reference back to passed in opts |
| 447 | |
| 448 | Raises: |
| 449 | Forces a program exit if |opts.serialname| is not found in the servo |
| 450 | scratch |
| 451 | """ |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame] | 452 | # Passing None here uses the default production logic while passing any |
| 453 | # other directory can be used for testing. No need to check whether |
| 454 | # |self._scratchdir| is None. |
Ruben Rodriguez Buchillon | 479bd24 | 2020-02-14 18:58:30 -0800 | [diff] [blame] | 455 | scratch = utils.scratch.Scratch(self._scratchdir) |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 456 | try: |
| 457 | entry = scratch.FindById(opts.serialname) |
Ruben Rodriguez Buchillon | 479bd24 | 2020-02-14 18:58:30 -0800 | [diff] [blame] | 458 | except utils.scratch.ScratchError: |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 459 | self.error('No servod instance running for device with serialname: %r' % |
| 460 | opts.serialname) |
| 461 | opts.port = int(entry['port']) |
| 462 | return opts |
| 463 | |
| 464 | def parse_known_args(self, args=None, namespace=None): |
| 465 | """Overwrite from Argumentparser to handle servo scratch logic. |
| 466 | |
| 467 | If port is not defined and serialname is defined, and serialname has |
| 468 | no scratch entry, this will raise an error & terminate the program. |
| 469 | |
| 470 | If there was neither a serialname nor a port, set the port to the |
| 471 | default port. |
| 472 | |
| 473 | Note: this also overwrites parse_args as parse_args just calls |
| 474 | parse_known_args and throws an error if there's anything inside of |
| 475 | xtra. |
| 476 | |
| 477 | Args: |
| 478 | args: list of cmdline elements |
| 479 | namespace: namespace to place the results into |
| 480 | |
| 481 | Returns: |
| 482 | tuple (opts, xtra) the result from parsing known args |
| 483 | """ |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame] | 484 | opts, xtra = _BaseServodParser.parse_known_args(self, args=args, |
| 485 | namespace=namespace) |
| 486 | opts = ServodRCParser.PostProcessRCElements(opts, logger=self._logger) |
| 487 | if opts.serialname: |
| 488 | # If serialname is set, this means that either serialname or name was used |
| 489 | # to find it, and therefore port cannot have been set by the user due to |
| 490 | # mutual exclusion. |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 491 | opts = self._MapSNToPort(opts) |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 492 | return (opts, xtra) |