Mike Frysinger | e58c0e2 | 2017-10-04 15:43:30 -0400 | [diff] [blame] | 1 | # -*- coding: utf-8 -*- |
Chris Sosa | 90c7850 | 2012-10-05 17:07:42 -0700 | [diff] [blame] | 2 | # Copyright (c) 2012 The Chromium OS Authors. All rights reserved. |
| 3 | # Use of this source code is governed by a BSD-style license that can be |
| 4 | # found in the LICENSE file. |
| 5 | |
David Pursell | e19f83b | 2015-07-01 12:57:07 -0700 | [diff] [blame] | 6 | """This implements the entry point for the `cros` CLI toolset. |
Don Garrett | 25f309a | 2014-03-19 14:02:12 -0700 | [diff] [blame] | 7 | |
David Pursell | e19f83b | 2015-07-01 12:57:07 -0700 | [diff] [blame] | 8 | This script is invoked by chromite/bin/cros, which sets up the |
David Pursell | ffb9004 | 2015-03-23 09:21:41 -0700 | [diff] [blame] | 9 | proper execution environment and calls this module's main() function. |
Don Garrett | 25f309a | 2014-03-19 14:02:12 -0700 | [diff] [blame] | 10 | |
| 11 | In turn, this script looks for a subcommand based on how it was invoked. For |
David Pursell | ffb9004 | 2015-03-23 09:21:41 -0700 | [diff] [blame] | 12 | example, `cros lint` will use the cli/cros/cros_lint.py subcommand. |
Don Garrett | 25f309a | 2014-03-19 14:02:12 -0700 | [diff] [blame] | 13 | |
David Pursell | ffb9004 | 2015-03-23 09:21:41 -0700 | [diff] [blame] | 14 | See cli/ for actual command implementations. |
Don Garrett | 25f309a | 2014-03-19 14:02:12 -0700 | [diff] [blame] | 15 | """ |
| 16 | |
Mike Frysinger | 383367e | 2014-09-16 15:06:17 -0400 | [diff] [blame] | 17 | from __future__ import print_function |
| 18 | |
David Pursell | f1c27c1 | 2015-03-18 09:51:38 -0700 | [diff] [blame] | 19 | from chromite.cli import command |
Chris Sosa | b445f79 | 2012-10-11 15:26:39 -0700 | [diff] [blame] | 20 | from chromite.lib import commandline |
Ralph Nathan | 91874ca | 2015-03-19 13:29:41 -0700 | [diff] [blame] | 21 | from chromite.lib import cros_logging as logging |
Chris Sosa | 90c7850 | 2012-10-05 17:07:42 -0700 | [diff] [blame] | 22 | |
| 23 | |
Mike Frysinger | 1a47081 | 2019-11-07 01:19:17 -0500 | [diff] [blame] | 24 | def GetOptions(cmd_name=None): |
David Pursell | 643ed34 | 2015-07-07 09:24:32 -0700 | [diff] [blame] | 25 | """Returns the parser to use for commandline parsing. |
Chris Sosa | 90c7850 | 2012-10-05 17:07:42 -0700 | [diff] [blame] | 26 | |
David Pursell | 643ed34 | 2015-07-07 09:24:32 -0700 | [diff] [blame] | 27 | Args: |
Mike Frysinger | 1a47081 | 2019-11-07 01:19:17 -0500 | [diff] [blame] | 28 | cmd_name: The subcommand to import & add. |
David Pursell | 643ed34 | 2015-07-07 09:24:32 -0700 | [diff] [blame] | 29 | |
| 30 | Returns: |
| 31 | A commandline.ArgumentParser object. |
| 32 | """ |
Mike Frysinger | 1a47081 | 2019-11-07 01:19:17 -0500 | [diff] [blame] | 33 | # We need to omit help for the base parser so that we're able to parse out the |
| 34 | # subcommand for further parsing. |
| 35 | parser = commandline.ArgumentParser(caching=True, default_log_level='notice', |
| 36 | add_help=cmd_name is not None) |
David Pursell | 643ed34 | 2015-07-07 09:24:32 -0700 | [diff] [blame] | 37 | |
Mike Frysinger | 1a47081 | 2019-11-07 01:19:17 -0500 | [diff] [blame] | 38 | subparsers = parser.add_subparsers(title='Subcommands', dest='subcommand') |
| 39 | subparsers.required = True |
| 40 | |
| 41 | # We add all the commands so `cros --help ...` looks reasonable. |
| 42 | # We add them in order also so the --help output is stable for users. |
| 43 | for subcommand in sorted(command.ListCommands()): |
| 44 | if subcommand == cmd_name: |
| 45 | class_def = command.ImportCommand(cmd_name) |
David Pursell | 643ed34 | 2015-07-07 09:24:32 -0700 | [diff] [blame] | 46 | epilog = getattr(class_def, 'EPILOG', None) |
| 47 | sub_parser = subparsers.add_parser( |
| 48 | cmd_name, description=class_def.__doc__, epilog=epilog, |
| 49 | caching=class_def.use_caching_options, |
| 50 | formatter_class=commandline.argparse.RawDescriptionHelpFormatter) |
| 51 | class_def.AddParser(sub_parser) |
Mike Frysinger | 1a47081 | 2019-11-07 01:19:17 -0500 | [diff] [blame] | 52 | else: |
| 53 | subparsers.add_parser(subcommand, add_help=False) |
Chris Sosa | 90c7850 | 2012-10-05 17:07:42 -0700 | [diff] [blame] | 54 | |
| 55 | return parser |
| 56 | |
| 57 | |
Ryan Cui | 47f80e4 | 2013-04-01 19:01:54 -0700 | [diff] [blame] | 58 | def _RunSubCommand(subcommand): |
| 59 | """Helper function for testing purposes.""" |
Ryan Cui | de21f48 | 2013-08-06 11:50:59 -0700 | [diff] [blame] | 60 | return subcommand.Run() |
Ryan Cui | 47f80e4 | 2013-04-01 19:01:54 -0700 | [diff] [blame] | 61 | |
| 62 | |
Mike Frysinger | 9ad5fab | 2013-05-30 13:37:26 -0400 | [diff] [blame] | 63 | def main(argv): |
David Pursell | dfbfbc8 | 2015-03-05 10:59:16 -0800 | [diff] [blame] | 64 | try: |
Mike Frysinger | 1a47081 | 2019-11-07 01:19:17 -0500 | [diff] [blame] | 65 | # The first time we parse the commandline is only to figure out what |
| 66 | # subcommand the user wants to run. This allows us to avoid importing |
| 67 | # all subcommands which can be quite slow. This works because there is |
| 68 | # no way in Python to list all subcommands and their help output in a |
| 69 | # single run. |
| 70 | parser = GetOptions() |
David Pursell | dfbfbc8 | 2015-03-05 10:59:16 -0800 | [diff] [blame] | 71 | if not argv: |
| 72 | parser.print_help() |
| 73 | return 1 |
| 74 | |
Mike Frysinger | 1a47081 | 2019-11-07 01:19:17 -0500 | [diff] [blame] | 75 | namespace, _ = parser.parse_known_args(argv) |
| 76 | # The user has selected a subcommand now, so get the full parser after we |
| 77 | # import the single subcommand. |
| 78 | parser = GetOptions(namespace.subcommand) |
David Pursell | dfbfbc8 | 2015-03-05 10:59:16 -0800 | [diff] [blame] | 79 | namespace = parser.parse_args(argv) |
David Pursell | ffb9004 | 2015-03-23 09:21:41 -0700 | [diff] [blame] | 80 | subcommand = namespace.command_class(namespace) |
Aviv Keshet | 01a82e9 | 2017-03-02 17:39:59 -0800 | [diff] [blame] | 81 | try: |
| 82 | code = _RunSubCommand(subcommand) |
| 83 | except (commandline.ChrootRequiredError, commandline.ExecRequiredError): |
| 84 | # The higher levels want these passed back, so oblige. |
| 85 | raise |
| 86 | except Exception as e: |
| 87 | code = 1 |
Mike Frysinger | 1a47081 | 2019-11-07 01:19:17 -0500 | [diff] [blame] | 88 | logging.error('cros %s failed before completing.', namespace.subcommand) |
Aviv Keshet | 01a82e9 | 2017-03-02 17:39:59 -0800 | [diff] [blame] | 89 | if namespace.debug: |
Mike Frysinger | 684d36e | 2015-06-03 05:03:34 -0400 | [diff] [blame] | 90 | raise |
Aviv Keshet | 01a82e9 | 2017-03-02 17:39:59 -0800 | [diff] [blame] | 91 | else: |
| 92 | logging.error(e) |
Mike Frysinger | a46f57d | 2018-07-19 12:18:22 -0400 | [diff] [blame] | 93 | logging.error('(Re-run with --debug for more details.)') |
Mike Frysinger | 684d36e | 2015-06-03 05:03:34 -0400 | [diff] [blame] | 94 | |
Aviv Keshet | 01a82e9 | 2017-03-02 17:39:59 -0800 | [diff] [blame] | 95 | if code is not None: |
| 96 | return code |
David Pursell | dfbfbc8 | 2015-03-05 10:59:16 -0800 | [diff] [blame] | 97 | |
| 98 | return 0 |
| 99 | except KeyboardInterrupt: |
| 100 | logging.debug('Aborted due to keyboard interrupt.') |
Chris Sosa | 90c7850 | 2012-10-05 17:07:42 -0700 | [diff] [blame] | 101 | return 1 |