blob: f8c46ae3243a5f97ec0f27c1f131dcde542754a7 [file] [log] [blame]
Mike Frysingere58c0e22017-10-04 15:43:30 -04001# -*- coding: utf-8 -*-
Chris Sosa90c78502012-10-05 17:07:42 -07002# 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 Purselle19f83b2015-07-01 12:57:07 -07006"""This implements the entry point for the `cros` CLI toolset.
Don Garrett25f309a2014-03-19 14:02:12 -07007
David Purselle19f83b2015-07-01 12:57:07 -07008This script is invoked by chromite/bin/cros, which sets up the
David Pursellffb90042015-03-23 09:21:41 -07009proper execution environment and calls this module's main() function.
Don Garrett25f309a2014-03-19 14:02:12 -070010
11In turn, this script looks for a subcommand based on how it was invoked. For
David Pursellffb90042015-03-23 09:21:41 -070012example, `cros lint` will use the cli/cros/cros_lint.py subcommand.
Don Garrett25f309a2014-03-19 14:02:12 -070013
David Pursellffb90042015-03-23 09:21:41 -070014See cli/ for actual command implementations.
Don Garrett25f309a2014-03-19 14:02:12 -070015"""
16
Mike Frysinger383367e2014-09-16 15:06:17 -040017from __future__ import print_function
18
Mike Frysingerf0b53422020-02-21 02:49:38 -050019import sys
20
David Pursellf1c27c12015-03-18 09:51:38 -070021from chromite.cli import command
Chris Sosab445f792012-10-11 15:26:39 -070022from chromite.lib import commandline
Ralph Nathan91874ca2015-03-19 13:29:41 -070023from chromite.lib import cros_logging as logging
Chris Sosa90c78502012-10-05 17:07:42 -070024
25
Mike Frysingerf0b53422020-02-21 02:49:38 -050026assert sys.version_info >= (3, 6), 'This module requires Python 3.6+'
27
28
Mike Frysinger1a470812019-11-07 01:19:17 -050029def GetOptions(cmd_name=None):
David Pursell643ed342015-07-07 09:24:32 -070030 """Returns the parser to use for commandline parsing.
Chris Sosa90c78502012-10-05 17:07:42 -070031
David Pursell643ed342015-07-07 09:24:32 -070032 Args:
Mike Frysinger1a470812019-11-07 01:19:17 -050033 cmd_name: The subcommand to import & add.
David Pursell643ed342015-07-07 09:24:32 -070034
35 Returns:
36 A commandline.ArgumentParser object.
37 """
Pi-Hsun Shihfd5a91e2019-11-28 15:06:06 +080038 parser = commandline.ArgumentParser(caching=True, default_log_level='notice')
David Pursell643ed342015-07-07 09:24:32 -070039
Mike Frysinger1a470812019-11-07 01:19:17 -050040 subparsers = parser.add_subparsers(title='Subcommands', dest='subcommand')
41 subparsers.required = True
42
43 # We add all the commands so `cros --help ...` looks reasonable.
44 # We add them in order also so the --help output is stable for users.
45 for subcommand in sorted(command.ListCommands()):
46 if subcommand == cmd_name:
47 class_def = command.ImportCommand(cmd_name)
David Pursell643ed342015-07-07 09:24:32 -070048 epilog = getattr(class_def, 'EPILOG', None)
49 sub_parser = subparsers.add_parser(
50 cmd_name, description=class_def.__doc__, epilog=epilog,
51 caching=class_def.use_caching_options,
52 formatter_class=commandline.argparse.RawDescriptionHelpFormatter)
53 class_def.AddParser(sub_parser)
Mike Frysinger1a470812019-11-07 01:19:17 -050054 else:
55 subparsers.add_parser(subcommand, add_help=False)
Chris Sosa90c78502012-10-05 17:07:42 -070056
57 return parser
58
59
Ryan Cui47f80e42013-04-01 19:01:54 -070060def _RunSubCommand(subcommand):
61 """Helper function for testing purposes."""
Ryan Cuide21f482013-08-06 11:50:59 -070062 return subcommand.Run()
Ryan Cui47f80e42013-04-01 19:01:54 -070063
64
Mike Frysinger9ad5fab2013-05-30 13:37:26 -040065def main(argv):
David Purselldfbfbc82015-03-05 10:59:16 -080066 try:
Mike Frysinger1a470812019-11-07 01:19:17 -050067 # The first time we parse the commandline is only to figure out what
68 # subcommand the user wants to run. This allows us to avoid importing
69 # all subcommands which can be quite slow. This works because there is
70 # no way in Python to list all subcommands and their help output in a
71 # single run.
72 parser = GetOptions()
David Purselldfbfbc82015-03-05 10:59:16 -080073 if not argv:
74 parser.print_help()
75 return 1
76
Mike Frysinger1a470812019-11-07 01:19:17 -050077 namespace, _ = parser.parse_known_args(argv)
78 # The user has selected a subcommand now, so get the full parser after we
79 # import the single subcommand.
80 parser = GetOptions(namespace.subcommand)
David Purselldfbfbc82015-03-05 10:59:16 -080081 namespace = parser.parse_args(argv)
David Pursellffb90042015-03-23 09:21:41 -070082 subcommand = namespace.command_class(namespace)
Aviv Keshet01a82e92017-03-02 17:39:59 -080083 try:
84 code = _RunSubCommand(subcommand)
85 except (commandline.ChrootRequiredError, commandline.ExecRequiredError):
86 # The higher levels want these passed back, so oblige.
87 raise
88 except Exception as e:
89 code = 1
Mike Frysinger1a470812019-11-07 01:19:17 -050090 logging.error('cros %s failed before completing.', namespace.subcommand)
Aviv Keshet01a82e92017-03-02 17:39:59 -080091 if namespace.debug:
Mike Frysinger684d36e2015-06-03 05:03:34 -040092 raise
Aviv Keshet01a82e92017-03-02 17:39:59 -080093 else:
94 logging.error(e)
Mike Frysingera46f57d2018-07-19 12:18:22 -040095 logging.error('(Re-run with --debug for more details.)')
Mike Frysinger684d36e2015-06-03 05:03:34 -040096
Aviv Keshet01a82e92017-03-02 17:39:59 -080097 if code is not None:
98 return code
David Purselldfbfbc82015-03-05 10:59:16 -080099
100 return 0
101 except KeyboardInterrupt:
102 logging.debug('Aborted due to keyboard interrupt.')
Chris Sosa90c78502012-10-05 17:07:42 -0700103 return 1