blob: fb8298580efae18edec877a12fe4adbe09a57de2 [file] [log] [blame]
Mike Frysingerf1ba7ad2022-09-12 05:42:57 -04001# Copyright 2012 The ChromiumOS Authors
Chris Sosa90c78502012-10-05 17:07:42 -07002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
David Purselle19f83b2015-07-01 12:57:07 -07005"""This implements the entry point for the `cros` CLI toolset.
Don Garrett25f309a2014-03-19 14:02:12 -07006
David Purselle19f83b2015-07-01 12:57:07 -07007This script is invoked by chromite/bin/cros, which sets up the
David Pursellffb90042015-03-23 09:21:41 -07008proper execution environment and calls this module's main() function.
Don Garrett25f309a2014-03-19 14:02:12 -07009
10In turn, this script looks for a subcommand based on how it was invoked. For
David Pursellffb90042015-03-23 09:21:41 -070011example, `cros lint` will use the cli/cros/cros_lint.py subcommand.
Don Garrett25f309a2014-03-19 14:02:12 -070012
David Pursellffb90042015-03-23 09:21:41 -070013See cli/ for actual command implementations.
Don Garrett25f309a2014-03-19 14:02:12 -070014"""
15
Mike Frysinger49c6e1f2022-04-14 15:41:40 -040016import argparse
Chris McDonald59650c32021-07-20 15:29:28 -060017import logging
18
David Pursellf1c27c12015-03-18 09:51:38 -070019from chromite.cli import command
Chris Sosab445f792012-10-11 15:26:39 -070020from chromite.lib import commandline
Chris Sosa90c78502012-10-05 17:07:42 -070021
22
Mike Frysinger1a470812019-11-07 01:19:17 -050023def GetOptions(cmd_name=None):
Alex Klein1699fab2022-09-08 08:46:06 -060024 """Returns the parser to use for commandline parsing.
Chris Sosa90c78502012-10-05 17:07:42 -070025
Alex Klein1699fab2022-09-08 08:46:06 -060026 Args:
Trent Apted66736d82023-05-25 10:38:28 +100027 cmd_name: The subcommand to import & add.
David Pursell643ed342015-07-07 09:24:32 -070028
Alex Klein1699fab2022-09-08 08:46:06 -060029 Returns:
Trent Apted66736d82023-05-25 10:38:28 +100030 A commandline.ArgumentParser object.
Alex Klein1699fab2022-09-08 08:46:06 -060031 """
32 parser = commandline.ArgumentParser(
33 caching=True, default_log_level="notice"
34 )
David Pursell643ed342015-07-07 09:24:32 -070035
Alex Klein1699fab2022-09-08 08:46:06 -060036 subparsers = parser.add_subparsers(title="Subcommands", dest="subcommand")
37 subparsers.required = True
Mike Frysinger1a470812019-11-07 01:19:17 -050038
Alex Klein1699fab2022-09-08 08:46:06 -060039 # 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,
Mike Frysinger57a8feb2023-02-02 08:48:38 -050050 dryrun=class_def.use_dryrun_options,
Li-Yu Yuafe7ea92022-12-21 13:17:35 +080051 filter=class_def.use_filter_options,
Alex Klein1699fab2022-09-08 08:46:06 -060052 formatter_class=argparse.RawDescriptionHelpFormatter,
53 )
54 class_def.AddParser(sub_parser)
55 else:
56 subparsers.add_parser(subcommand, add_help=False)
Chris Sosa90c78502012-10-05 17:07:42 -070057
Alex Klein1699fab2022-09-08 08:46:06 -060058 help_parser = subparsers.add_parser("help", add_help=False)
59 help_parser.add_argument(
60 "help_subcommand", nargs="?", help="The command to show help for"
61 )
Harry Cuttsbf3ca762020-09-15 05:13:26 -070062
Alex Klein1699fab2022-09-08 08:46:06 -060063 return parser
Chris Sosa90c78502012-10-05 17:07:42 -070064
65
Ryan Cui47f80e42013-04-01 19:01:54 -070066def _RunSubCommand(subcommand):
Alex Klein1699fab2022-09-08 08:46:06 -060067 """Helper function for testing purposes."""
68 return subcommand.Run()
Ryan Cui47f80e42013-04-01 19:01:54 -070069
70
Mike Frysinger9ad5fab2013-05-30 13:37:26 -040071def main(argv):
Aviv Keshet01a82e92017-03-02 17:39:59 -080072 try:
Alex Klein1699fab2022-09-08 08:46:06 -060073 # The first time we parse the commandline is only to figure out what
74 # subcommand the user wants to run. This allows us to avoid importing
75 # all subcommands which can be quite slow. This works because there is
76 # no way in Python to list all subcommands and their help output in a
77 # single run.
78 parser = GetOptions()
79 if not argv:
80 parser.print_help()
81 return 1
Mike Frysinger684d36e2015-06-03 05:03:34 -040082
Alex Klein1699fab2022-09-08 08:46:06 -060083 namespace, _ = parser.parse_known_args(argv)
David Purselldfbfbc82015-03-05 10:59:16 -080084
Alex Klein1699fab2022-09-08 08:46:06 -060085 if namespace.subcommand == "help":
86 if namespace.help_subcommand is None:
87 parser.print_help()
88 return
89
90 parser = GetOptions(namespace.help_subcommand)
91 parser.parse_args([namespace.help_subcommand, "--help"])
92
Alex Klein4507b172023-01-13 11:39:51 -070093 # The user has selected a subcommand now, so get the full parser after
94 # we import the single subcommand.
Alex Klein1699fab2022-09-08 08:46:06 -060095 parser = GetOptions(namespace.subcommand)
96 namespace = parser.parse_args(argv)
97 namespace.command_class.ProcessOptions(parser, namespace)
98 subcommand = namespace.command_class(namespace)
99 namespace.Freeze()
100 try:
101 code = _RunSubCommand(subcommand)
102 except (commandline.ChrootRequiredError, commandline.ExecRequiredError):
103 # The higher levels want these passed back, so oblige.
104 raise
105 except Exception as e:
106 code = 1
107 logging.error(
108 "cros %s failed before completing.", namespace.subcommand
109 )
110 if namespace.debug:
111 raise
112 else:
113 logging.error(e)
114 logging.error("(Re-run with --debug for more details.)")
115
116 if code is not None:
117 return code
118
119 return 0
120 except KeyboardInterrupt:
121 logging.debug("Aborted due to keyboard interrupt.")
122 return 1