blob: 553015768c31d0f96a635ad7894508fedf718e52 [file] [log] [blame]
Chris Sosa90c78502012-10-05 17:07:42 -07001# 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 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
Chris McDonald59650c32021-07-20 15:29:28 -060016import logging
17
David Pursellf1c27c12015-03-18 09:51:38 -070018from chromite.cli import command
Chris Sosab445f792012-10-11 15:26:39 -070019from chromite.lib import commandline
Chris Sosa90c78502012-10-05 17:07:42 -070020
21
Mike Frysinger1a470812019-11-07 01:19:17 -050022def GetOptions(cmd_name=None):
David Pursell643ed342015-07-07 09:24:32 -070023 """Returns the parser to use for commandline parsing.
Chris Sosa90c78502012-10-05 17:07:42 -070024
David Pursell643ed342015-07-07 09:24:32 -070025 Args:
Mike Frysinger1a470812019-11-07 01:19:17 -050026 cmd_name: The subcommand to import & add.
David Pursell643ed342015-07-07 09:24:32 -070027
28 Returns:
29 A commandline.ArgumentParser object.
30 """
Pi-Hsun Shihfd5a91e2019-11-28 15:06:06 +080031 parser = commandline.ArgumentParser(caching=True, default_log_level='notice')
David Pursell643ed342015-07-07 09:24:32 -070032
Mike Frysinger1a470812019-11-07 01:19:17 -050033 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 Pursell643ed342015-07-07 09:24:32 -070041 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 Frysinger1a470812019-11-07 01:19:17 -050047 else:
48 subparsers.add_parser(subcommand, add_help=False)
Chris Sosa90c78502012-10-05 17:07:42 -070049
Harry Cuttsbf3ca762020-09-15 05:13:26 -070050 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 Sosa90c78502012-10-05 17:07:42 -070054 return parser
55
56
Ryan Cui47f80e42013-04-01 19:01:54 -070057def _RunSubCommand(subcommand):
58 """Helper function for testing purposes."""
Ryan Cuide21f482013-08-06 11:50:59 -070059 return subcommand.Run()
Ryan Cui47f80e42013-04-01 19:01:54 -070060
61
Mike Frysinger9ad5fab2013-05-30 13:37:26 -040062def main(argv):
David Purselldfbfbc82015-03-05 10:59:16 -080063 try:
Mike Frysinger1a470812019-11-07 01:19:17 -050064 # 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 Purselldfbfbc82015-03-05 10:59:16 -080070 if not argv:
71 parser.print_help()
72 return 1
73
Mike Frysinger1a470812019-11-07 01:19:17 -050074 namespace, _ = parser.parse_known_args(argv)
Harry Cuttsbf3ca762020-09-15 05:13:26 -070075
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 Frysinger1a470812019-11-07 01:19:17 -050084 # 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 Purselldfbfbc82015-03-05 10:59:16 -080087 namespace = parser.parse_args(argv)
Mike Frysinger57bb7b82021-12-14 10:22:37 -050088 namespace.command_class.ProcessOptions(parser, namespace)
David Pursellffb90042015-03-23 09:21:41 -070089 subcommand = namespace.command_class(namespace)
Mike Frysinger25829e22021-12-14 12:01:31 -050090 namespace.Freeze()
Aviv Keshet01a82e92017-03-02 17:39:59 -080091 try:
92 code = _RunSubCommand(subcommand)
93 except (commandline.ChrootRequiredError, commandline.ExecRequiredError):
94 # The higher levels want these passed back, so oblige.
95 raise
96 except Exception as e:
97 code = 1
Mike Frysinger1a470812019-11-07 01:19:17 -050098 logging.error('cros %s failed before completing.', namespace.subcommand)
Aviv Keshet01a82e92017-03-02 17:39:59 -080099 if namespace.debug:
Mike Frysinger684d36e2015-06-03 05:03:34 -0400100 raise
Aviv Keshet01a82e92017-03-02 17:39:59 -0800101 else:
102 logging.error(e)
Mike Frysingera46f57d2018-07-19 12:18:22 -0400103 logging.error('(Re-run with --debug for more details.)')
Mike Frysinger684d36e2015-06-03 05:03:34 -0400104
Aviv Keshet01a82e92017-03-02 17:39:59 -0800105 if code is not None:
106 return code
David Purselldfbfbc82015-03-05 10:59:16 -0800107
108 return 0
109 except KeyboardInterrupt:
110 logging.debug('Aborted due to keyboard interrupt.')
Chris Sosa90c78502012-10-05 17:07:42 -0700111 return 1