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