# SPDX-License-Identifier: Apache-2.0
# Copyright Contributors to the Rez Project
"""
The main command-line entry point.
"""
from __future__ import print_function
import sys
import importlib
from argparse import _StoreTrueAction, SUPPRESS
from rez.cli._util import subcommands, LazyArgumentParser, _env_var_true
from rez.utils.logging_ import print_error
from rez.exceptions import RezError, RezSystemError, _NeverError
from rez import __version__, module_root_path
# true if command was like 'rez-env' rather than 'rez env'
_hyphened_command = False
[docs]def is_hyphened_command():
return _hyphened_command
[docs]class SetupRezSubParser(object):
"""Callback class for lazily setting up rez sub-parsers.
"""
def __init__(self, module_name):
self.module_name = module_name
def __call__(self, parser_name, parser):
mod = self.get_module()
error_msg = None
if not mod.__doc__:
error_msg = "command module %s must have a module-level " \
"docstring (used as the command help)" % self.module_name
if not hasattr(mod, 'command'):
error_msg = "command module %s must provide a command() " \
"function" % self.module_name
if not hasattr(mod, 'setup_parser'):
error_msg = "command module %s must provide a setup_parser() " \
"function" % self.module_name
if error_msg:
print(error_msg, file=sys.stderr)
return SUPPRESS
mod.setup_parser(parser)
parser.description = mod.__doc__
parser.set_defaults(func=mod.command, parser=parser)
# add the common args to the subparser
_add_common_args(parser)
# optionally, return the brief help line for this sub-parser
brief = mod.__doc__.strip('\n').split('\n')[0]
return brief
[docs] def get_module(self):
if self.module_name not in sys.modules:
importlib.import_module(self.module_name)
return sys.modules[self.module_name]
def _add_common_args(parser):
parser.add_argument("-v", "--verbose", action="count", default=0,
help="verbose mode, repeat for more verbosity")
parser.add_argument("--debug", dest="debug", action="store_true",
help=SUPPRESS)
parser.add_argument("--profile", dest="profile", type=str,
help=SUPPRESS)
[docs]class InfoAction(_StoreTrueAction):
def __call__(self, parser, args, values, option_string=None):
from rez.system import system
txt = system.get_summary_string()
print()
print(txt)
print()
sys.exit(0)
[docs]def setup_parser():
"""Create and setup parser for given rez command line interface.
Returns:
LazyArgumentParser: Argument parser for rez command.
"""
py = sys.version_info
parser = LazyArgumentParser("rez")
parser.add_argument("-i", "--info", action=InfoAction,
help="print information about rez and exit")
parser.add_argument("-V", "--version", action="version",
version="Rez %s from %s (python %d.%d)" % (
__version__, module_root_path, py.major, py.minor
))
# add args common to all subcommands... we add them both to the top parser,
# AND to the subparsers, for two reasons:
# 1) this allows us to do EITHER "rez --debug build" OR
# "rez build --debug"
# 2) this allows the flags to be used when using either "rez" or
# "rez-build" - ie, this will work: "rez-build --debug"
_add_common_args(parser)
# add lazy subparsers
subparser = parser.add_subparsers(dest='cmd', metavar='COMMAND')
for subcommand, data in subcommands.items():
module_name = data.get('module_name', 'rez.cli.%s' % subcommand)
subparser.add_parser(
subcommand,
help='', # required so that it can be setup later
setup_subparser=SetupRezSubParser(module_name))
return parser
[docs]def run(command=None):
global _hyphened_command
sys.dont_write_bytecode = True
# construct args list. Note that commands like 'rez-env foo' and
# 'rez env foo' are equivalent
#
if command:
# like 'rez-foo arg1 arg2'
args = [command] + sys.argv[1:]
_hyphened_command = True
elif len(sys.argv) > 1 and sys.argv[1] in subcommands:
# like 'rez foo arg1 arg2'
command = sys.argv[1]
args = sys.argv[1:]
else:
# like 'rez -i'
args = sys.argv[1:]
# parse args depending on subcommand behaviour
if command:
arg_mode = subcommands[command].get("arg_mode")
else:
arg_mode = None
parser = setup_parser()
if arg_mode == "grouped":
# args split into groups by '--'
arg_groups = [[]]
for arg in args:
if arg == '--':
arg_groups.append([])
continue
arg_groups[-1].append(arg)
opts = parser.parse_args(arg_groups[0])
extra_arg_groups = arg_groups[1:]
elif arg_mode == "passthrough":
# unknown args passed in first extra_arg_group
opts, extra_args = parser.parse_known_args(args)
extra_arg_groups = [extra_args]
else:
# native arg parsing
opts = parser.parse_args(args)
extra_arg_groups = []
if opts.debug or _env_var_true("REZ_DEBUG"):
exc_type = _NeverError
else:
exc_type = RezError
def run_cmd():
try:
# python3 will not automatically handle cases where no sub parser
# has been selected. In these cases func will not exist, and an
# AttributeError will be raised.
func = opts.func
except AttributeError:
parser.error("too few arguments.")
else:
return func(opts, opts.parser, extra_arg_groups)
if opts.profile:
import cProfile
cProfile.runctx("run_cmd()", globals(), locals(), filename=opts.profile)
returncode = 0
else:
try:
returncode = run_cmd()
except (NotImplementedError, RezSystemError):
raise
except exc_type as e:
print_error("%s: %s" % (e.__class__.__name__, str(e)))
sys.exit(1)
sys.exit(returncode or 0)
if __name__ == '__main__':
run()