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