#!/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 import log from commands import CommandContextError from commands.build import Build from commands.flash import Flash from commands.debug import Debug, DebugServer, Attach from commands.project import ListProjects, Fetch, Pull, Rebase, Branch, \ Checkout, Diff, Status, Update, ForAll, \ WestUpdated from util import quote_sh_list, in_multirepo_install IN_MULTIREPO_INSTALL = in_multirepo_install(__file__) BUILD_FLASH_COMMANDS = [ Build(), Flash(), Debug(), DebugServer(), Attach(), ] PROJECT_COMMANDS = [ ListProjects(), 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 validate_context(args, unknown): '''Validate the run-time context expected by west.''' if args.zephyr_base: os.environ['ZEPHYR_BASE'] = args.zephyr_base else: if 'ZEPHYR_BASE' not in os.environ: log.wrn('--zephyr-base missing and no ZEPHYR_BASE', 'in the environment') else: args.zephyr_base = os.environ['ZEPHYR_BASE'] 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 as e: 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 -h" for help on each command.') west_parser.add_argument('-z', '--zephyr-base', default=None, help='''Path to the Zephyr base directory. If not given, ZEPHYR_BASE must be defined in the environment, and will be used instead.''') 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) try: validate_context(args, unknown) except InvalidWestContext as iwc: log.err(*iwc.args, fatal=True) west_parser.print_usage(file=sys.stderr) sys.exit(1) if 'handler' not in args: log.err('you must specify a command', fatal=True) west_parser.print_usage(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) 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] + sys.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) except Exception as exc: log.err(*exc.args, fatal=True) if args.verbose: raise else: log.inf(for_stack_trace) if __name__ == "__main__": main()