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