zephyr/scripts/meta/west/main.py

242 lines
8.0 KiB
Python
Executable File

#!/usr/bin/env python3
# Copyright 2018 Open Source Foundries Limited.
#
# SPDX-License-Identifier: Apache-2.0
'''Zephyr RTOS meta-tool (west) main module
'''
import argparse
import colorama
from functools import partial
import os
import sys
from subprocess import CalledProcessError, check_output, DEVNULL
from west import log
from west import config
from west.commands import CommandContextError
from west.commands.build import Build
from west.commands.flash import Flash
from west.commands.debug import Debug, DebugServer, Attach
from west.commands.project import List, Clone, Fetch, Pull, Rebase, Branch, \
Checkout, Diff, Status, Update, ForAll, \
WestUpdated
from west.manifest import Manifest
from west.util import quote_sh_list, in_multirepo_install, west_dir
IN_MULTIREPO_INSTALL = in_multirepo_install(os.path.dirname(__file__))
BUILD_FLASH_COMMANDS = [
Build(),
Flash(),
Debug(),
DebugServer(),
Attach(),
]
PROJECT_COMMANDS = [
List(),
Clone(),
Fetch(),
Pull(),
Rebase(),
Branch(),
Checkout(),
Diff(),
Status(),
Update(),
ForAll(),
]
# Built-in commands in this West. For compatibility with monorepo
# installations of West within the Zephyr tree, we only expose the
# project commands if this is a multirepo installation.
COMMANDS = BUILD_FLASH_COMMANDS
if IN_MULTIREPO_INSTALL:
COMMANDS += PROJECT_COMMANDS
class InvalidWestContext(RuntimeError):
pass
def command_handler(command, known_args, unknown_args):
command.run(known_args, unknown_args)
def set_zephyr_base(args):
'''Ensure ZEPHYR_BASE is set, emitting warnings if that's not
possible, or if the user is pointing it somewhere different than
what the manifest expects.'''
zb_env = os.environ.get('ZEPHYR_BASE')
if args.zephyr_base:
# The command line --zephyr-base takes precedence over
# everything else.
zb = os.path.abspath(args.zephyr_base)
zb_origin = 'command line'
else:
# If the user doesn't specify it concretely, use the project
# with path 'zephyr' if that exists, or the ZEPHYR_BASE value
# in the calling environment.
#
# At some point, we need a more flexible way to set environment
# variables based on manifest contents, but this is good enough
# to get started with and to ask for wider testing.
manifest = Manifest.from_file()
for project in manifest.projects:
if project.path == 'zephyr':
zb = project.abspath
zb_origin = 'manifest file {}'.format(manifest.path)
break
else:
if zb_env is None:
log.wrn('no --zephyr-base given, ZEPHYR_BASE is unset,',
'and no manifest project has path "zephyr"')
zb = None
zb_origin = None
else:
zb = zb_env
zb_origin = 'environment'
if zb_env and os.path.abspath(zb) != os.path.abspath(zb_env):
# The environment ZEPHYR_BASE takes precedence over either the
# command line or the manifest, but in normal multi-repo
# operation we shouldn't expect to need to set ZEPHYR_BASE to
# point to some random place. In practice, this is probably
# happening because zephyr-env.sh/cmd was run in some other
# zephyr installation, and the user forgot about that.
log.wrn('ZEPHYR_BASE={}'.format(zb_env),
'in the calling environment, but has been set to',
zb, 'instead by the', zb_origin)
os.environ['ZEPHYR_BASE'] = zb
log.dbg('ZEPHYR_BASE={} (origin: {})'.format(zb, zb_origin))
def print_version_info():
# The bootstrapper will print its own version, as well as that of
# the west repository itself, then exit. So if this file is being
# asked to print the version, it's because it's being run
# directly, and not via the bootstrapper.
#
# Rather than play tricks like invoking "pip show west" (which
# assumes the bootstrapper was installed via pip, the common but
# not universal case), refuse the temptation to make guesses and
# print an honest answer.
log.inf('West bootstrapper version: N/A, not run via bootstrapper')
# The running west installation.
if IN_MULTIREPO_INSTALL:
try:
desc = check_output(['git', 'describe', '--tags'],
stderr=DEVNULL,
cwd=os.path.dirname(__file__))
west_version = desc.decode(sys.getdefaultencoding()).strip()
except CalledProcessError:
west_version = 'unknown'
else:
west_version = 'N/A, monorepo installation'
west_src_west = os.path.dirname(__file__)
print('West repository version: {} ({})'.
format(west_version,
os.path.dirname(os.path.dirname(west_src_west))))
def parse_args(argv):
# The prog='west' override avoids the absolute path of the main.py script
# showing up when West is run via the wrapper
west_parser = argparse.ArgumentParser(
prog='west', description='The Zephyr RTOS meta-tool.',
epilog='Run "west <command> -h" for help on each command.')
# Remember to update scripts/west-completion.bash if you add or remove
# flags
west_parser.add_argument('-z', '--zephyr-base', default=None,
help='''Override the Zephyr base directory. The
default is the manifest project with path
"zephyr".''')
west_parser.add_argument('-v', '--verbose', default=0, action='count',
help='''Display verbose output. May be given
multiple times to increase verbosity.''')
west_parser.add_argument('-V', '--version', action='store_true')
subparser_gen = west_parser.add_subparsers(title='commands',
dest='command')
for command in COMMANDS:
parser = command.add_parser(subparser_gen)
parser.set_defaults(handler=partial(command_handler, command))
args, unknown = west_parser.parse_known_args(args=argv)
if args.version:
print_version_info()
sys.exit(0)
# Set up logging verbosity before doing anything else, so
# e.g. verbose messages related to argument handling errors
# work properly.
log.set_verbosity(args.verbose)
if IN_MULTIREPO_INSTALL:
set_zephyr_base(args)
if 'handler' not in args:
if IN_MULTIREPO_INSTALL:
log.err('west installation found (in {}), but no command given'.
format(west_dir()))
else:
log.err('no west command given')
west_parser.print_help(file=sys.stderr)
sys.exit(1)
return args, unknown
def main(argv=None):
# Makes ANSI color escapes work on Windows, and strips them when
# stdout/stderr isn't a terminal
colorama.init()
if argv is None:
argv = sys.argv[1:]
args, unknown = parse_args(argv)
if IN_MULTIREPO_INSTALL:
# Read the configuration files
config.read_config()
for_stack_trace = 'run as "west -v ... {} ..." for a stack trace'.format(
args.command)
try:
args.handler(args, unknown)
except WestUpdated:
# West has been automatically updated. Restart ourselves to run the
# latest version, with the same arguments that we were given.
os.execv(sys.executable, [sys.executable] + argv)
except KeyboardInterrupt:
sys.exit(0)
except CalledProcessError as cpe:
log.err('command exited with status {}: {}'.format(
cpe.args[0], quote_sh_list(cpe.args[1])))
if args.verbose:
raise
else:
log.inf(for_stack_trace)
except CommandContextError as cce:
log.die('command', args.command, 'cannot be run in this context:',
*cce.args)
if __name__ == "__main__":
main()