Mike Frysinger | e58c0e2 | 2017-10-04 15:43:30 -0400 | [diff] [blame] | 1 | # -*- coding: utf-8 -*- |
Chris Sosa | 90c7850 | 2012-10-05 17:07:42 -0700 | [diff] [blame] | 2 | # 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 Pursell | e19f83b | 2015-07-01 12:57:07 -0700 | [diff] [blame] | 6 | """This implements the entry point for the `cros` CLI toolset. |
Don Garrett | 25f309a | 2014-03-19 14:02:12 -0700 | [diff] [blame] | 7 | |
David Pursell | e19f83b | 2015-07-01 12:57:07 -0700 | [diff] [blame] | 8 | This script is invoked by chromite/bin/cros, which sets up the |
David Pursell | ffb9004 | 2015-03-23 09:21:41 -0700 | [diff] [blame] | 9 | proper execution environment and calls this module's main() function. |
Don Garrett | 25f309a | 2014-03-19 14:02:12 -0700 | [diff] [blame] | 10 | |
| 11 | 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] | 12 | example, `cros lint` will use the cli/cros/cros_lint.py subcommand. |
Don Garrett | 25f309a | 2014-03-19 14:02:12 -0700 | [diff] [blame] | 13 | |
David Pursell | ffb9004 | 2015-03-23 09:21:41 -0700 | [diff] [blame] | 14 | See cli/ for actual command implementations. |
Don Garrett | 25f309a | 2014-03-19 14:02:12 -0700 | [diff] [blame] | 15 | """ |
| 16 | |
Mike Frysinger | 383367e | 2014-09-16 15:06:17 -0400 | [diff] [blame] | 17 | from __future__ import print_function |
| 18 | |
Mike Frysinger | f0b5342 | 2020-02-21 02:49:38 -0500 | [diff] [blame] | 19 | import sys |
| 20 | |
David Pursell | f1c27c1 | 2015-03-18 09:51:38 -0700 | [diff] [blame] | 21 | from chromite.cli import command |
Chris Sosa | b445f79 | 2012-10-11 15:26:39 -0700 | [diff] [blame] | 22 | from chromite.lib import commandline |
Ralph Nathan | 91874ca | 2015-03-19 13:29:41 -0700 | [diff] [blame] | 23 | from chromite.lib import cros_logging as logging |
Chris Sosa | 90c7850 | 2012-10-05 17:07:42 -0700 | [diff] [blame] | 24 | |
| 25 | |
Mike Frysinger | f0b5342 | 2020-02-21 02:49:38 -0500 | [diff] [blame] | 26 | assert sys.version_info >= (3, 6), 'This module requires Python 3.6+' |
| 27 | |
| 28 | |
Mike Frysinger | 1a47081 | 2019-11-07 01:19:17 -0500 | [diff] [blame] | 29 | def GetOptions(cmd_name=None): |
David Pursell | 643ed34 | 2015-07-07 09:24:32 -0700 | [diff] [blame] | 30 | """Returns the parser to use for commandline parsing. |
Chris Sosa | 90c7850 | 2012-10-05 17:07:42 -0700 | [diff] [blame] | 31 | |
David Pursell | 643ed34 | 2015-07-07 09:24:32 -0700 | [diff] [blame] | 32 | Args: |
Mike Frysinger | 1a47081 | 2019-11-07 01:19:17 -0500 | [diff] [blame] | 33 | cmd_name: The subcommand to import & add. |
David Pursell | 643ed34 | 2015-07-07 09:24:32 -0700 | [diff] [blame] | 34 | |
| 35 | Returns: |
| 36 | A commandline.ArgumentParser object. |
| 37 | """ |
Pi-Hsun Shih | fd5a91e | 2019-11-28 15:06:06 +0800 | [diff] [blame] | 38 | parser = commandline.ArgumentParser(caching=True, default_log_level='notice') |
David Pursell | 643ed34 | 2015-07-07 09:24:32 -0700 | [diff] [blame] | 39 | |
Mike Frysinger | 1a47081 | 2019-11-07 01:19:17 -0500 | [diff] [blame] | 40 | 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 Pursell | 643ed34 | 2015-07-07 09:24:32 -0700 | [diff] [blame] | 48 | 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 Frysinger | 1a47081 | 2019-11-07 01:19:17 -0500 | [diff] [blame] | 54 | else: |
| 55 | subparsers.add_parser(subcommand, add_help=False) |
Chris Sosa | 90c7850 | 2012-10-05 17:07:42 -0700 | [diff] [blame] | 56 | |
| 57 | return parser |
| 58 | |
| 59 | |
Ryan Cui | 47f80e4 | 2013-04-01 19:01:54 -0700 | [diff] [blame] | 60 | def _RunSubCommand(subcommand): |
| 61 | """Helper function for testing purposes.""" |
Ryan Cui | de21f48 | 2013-08-06 11:50:59 -0700 | [diff] [blame] | 62 | return subcommand.Run() |
Ryan Cui | 47f80e4 | 2013-04-01 19:01:54 -0700 | [diff] [blame] | 63 | |
| 64 | |
Mike Frysinger | 9ad5fab | 2013-05-30 13:37:26 -0400 | [diff] [blame] | 65 | def main(argv): |
David Pursell | dfbfbc8 | 2015-03-05 10:59:16 -0800 | [diff] [blame] | 66 | try: |
Mike Frysinger | 1a47081 | 2019-11-07 01:19:17 -0500 | [diff] [blame] | 67 | # 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 Pursell | dfbfbc8 | 2015-03-05 10:59:16 -0800 | [diff] [blame] | 73 | if not argv: |
| 74 | parser.print_help() |
| 75 | return 1 |
| 76 | |
Mike Frysinger | 1a47081 | 2019-11-07 01:19:17 -0500 | [diff] [blame] | 77 | 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 Pursell | dfbfbc8 | 2015-03-05 10:59:16 -0800 | [diff] [blame] | 81 | namespace = parser.parse_args(argv) |
David Pursell | ffb9004 | 2015-03-23 09:21:41 -0700 | [diff] [blame] | 82 | subcommand = namespace.command_class(namespace) |
Aviv Keshet | 01a82e9 | 2017-03-02 17:39:59 -0800 | [diff] [blame] | 83 | 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 Frysinger | 1a47081 | 2019-11-07 01:19:17 -0500 | [diff] [blame] | 90 | logging.error('cros %s failed before completing.', namespace.subcommand) |
Aviv Keshet | 01a82e9 | 2017-03-02 17:39:59 -0800 | [diff] [blame] | 91 | if namespace.debug: |
Mike Frysinger | 684d36e | 2015-06-03 05:03:34 -0400 | [diff] [blame] | 92 | raise |
Aviv Keshet | 01a82e9 | 2017-03-02 17:39:59 -0800 | [diff] [blame] | 93 | else: |
| 94 | logging.error(e) |
Mike Frysinger | a46f57d | 2018-07-19 12:18:22 -0400 | [diff] [blame] | 95 | logging.error('(Re-run with --debug for more details.)') |
Mike Frysinger | 684d36e | 2015-06-03 05:03:34 -0400 | [diff] [blame] | 96 | |
Aviv Keshet | 01a82e9 | 2017-03-02 17:39:59 -0800 | [diff] [blame] | 97 | if code is not None: |
| 98 | return code |
David Pursell | dfbfbc8 | 2015-03-05 10:59:16 -0800 | [diff] [blame] | 99 | |
| 100 | return 0 |
| 101 | except KeyboardInterrupt: |
| 102 | logging.debug('Aborted due to keyboard interrupt.') |
Chris Sosa | 90c7850 | 2012-10-05 17:07:42 -0700 | [diff] [blame] | 103 | return 1 |