def sign(self, command, build_dir, bcfg, formats): if not formats: return args = command.args b = pathlib.Path(build_dir) cache = CMakeCache.from_build_dir(build_dir) tool_path = self.find_imgtool(command, args) # The vector table offset is set in Kconfig: vtoff = self.get_cfg(command, bcfg, 'CONFIG_TEXT_SECTION_OFFSET') # Flash device write alignment and the partition's slot size # come from devicetree: flash = self.edt_flash_node(b, cache) align, addr, size = self.edt_flash_params(flash) runner_config = cached_runner_config(build_dir, cache) if 'bin' in formats: in_bin = runner_config.bin_file if not in_bin: log.die("can't find unsigned .bin to sign") else: in_bin = None if 'hex' in formats: in_hex = runner_config.hex_file if not in_hex: log.die("can't find unsigned .hex to sign") else: in_hex = None log.banner('image configuration:') log.inf('partition offset: {0} (0x{0:x})'.format(addr)) log.inf('partition size: {0} (0x{0:x})'.format(size)) log.inf('text section offset: {0} (0x{0:x})'.format(vtoff)) # Base sign command. # # We provide a default --version in case the user is just # messing around and doesn't want to set one. It will be # overridden if there is a --version in args.tool_args. sign_base = [tool_path, 'sign', '--version', '0.0.0+0', '--align', str(align), '--header-size', str(vtoff), '--slot-size', str(size)] sign_base.extend(args.tool_args) log.banner('signed binaries:') if in_bin: out_bin = args.sbin or str(b / 'zephyr' / 'zephyr.signed.bin') sign_bin = sign_base + [in_bin, out_bin] log.inf('bin: {}'.format(out_bin)) log.dbg(quote_sh_list(sign_bin)) subprocess.check_call(sign_bin) if in_hex: out_hex = args.shex or str(b / 'zephyr' / 'zephyr.signed.hex') sign_hex = sign_base + [in_hex, out_hex] log.inf('hex: {}'.format(out_hex)) log.dbg(quote_sh_list(sign_hex)) subprocess.check_call(sign_hex)
def main(argv=None): # Makes ANSI color escapes work on Windows, and strips them when # stdout/stderr isn't a terminal colorama.init() # See if we're in an installation. try: topdir = west_topdir() except WestNotFound: topdir = None # Read the configuration files before looking for extensions. # We need this to find the manifest path in order to load extensions. config.read_config() # Load any extension command specs if we're in an installation. if topdir: try: extensions = get_extension_commands() except (MalformedConfig, FileNotFoundError): extensions = {} else: extensions = {} if argv is None: argv = sys.argv[1:] args, unknown = parse_args(argv, extensions, topdir) for_stack_trace = 'run as "west -v {}" for a stack trace'.format( quote_sh_list(argv)) try: args.handler(args, unknown) 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: traceback.print_exc() else: log.inf(for_stack_trace) sys.exit(cpe.returncode) except ExtensionCommandError as ece: log.err( 'extension command', args.command, 'was improperly defined and could not be run{}'.format( ': ' + ece.hint if ece.hint else '')) if args.verbose: traceback.print_exc() else: log.inf(for_stack_trace) sys.exit(ece.returncode) except CommandContextError as cce: log.err('command', args.command, 'cannot be run in this context:', *cce.args) sys.exit(cce.returncode) except CommandError as ce: sys.exit(ce.returncode)
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) # 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] + 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)
def _ensure_min_version(cmake, dry_run): cmd = [cmake, '--version'] if dry_run: log.inf('Dry run:', quote_sh_list(cmd)) return try: version_out = subprocess.check_output(cmd, stderr=subprocess.DEVNULL) except subprocess.CalledProcessError as cpe: log.die('cannot get cmake version:', str(cpe)) decoded = version_out.decode('utf-8') lines = decoded.splitlines() if not lines: log.die('can\'t get cmake version: ' + 'unexpected "cmake --version" output:\n{}\n'.format(decoded) + 'Please install CMake ' + _MIN_CMAKE_VERSION_STR + ' or higher (https://cmake.org/download/).') version = lines[0].split()[2] if '-' in version: # Handle semver cases like "3.19.20210206-g1e50ab6" # which Kitware uses for prerelease versions. version = version.split('-', 1)[0] if packaging.version.parse(version) < _MIN_CMAKE_VERSION: log.die( 'cmake version', version, 'is less than minimum version {};'.format(_MIN_CMAKE_VERSION_STR), 'please update your CMake (https://cmake.org/download/).') else: log.dbg('cmake version', version, 'is OK; minimum version is', _MIN_CMAKE_VERSION_STR)
def check_call(self, args, cwd=None): cmd_str = util.quote_sh_list(args) log.dbg("running '{}'".format(cmd_str), 'in', cwd or os.getcwd(), level=log.VERBOSE_VERY) subprocess.check_call(args, cwd=cwd)
def run_cmake(args, cwd=None, capture_output=False): '''Run cmake to (re)generate a build system. If capture_output is set to True, returns the output of the command instead of displaying it on stdout/stderr..''' cmake = shutil.which('cmake') if cmake is None: log.die('CMake is not installed or cannot be found; cannot build.') cmd = [cmake] + args kwargs = dict() if capture_output: kwargs['stdout'] = subprocess.PIPE # CMake sends the output of message() to stderr unless it's STATUS kwargs['stderr'] = subprocess.STDOUT if cwd: kwargs['cwd'] = cwd log.dbg('Running CMake:', quote_sh_list(cmd), level=log.VERBOSE_NORMAL) p = subprocess.Popen(cmd, **kwargs) out, err = p.communicate() if p.returncode == 0: if out: return out.decode(sys.getdefaultencoding()).splitlines() else: return None else: # A real error occurred, raise an exception raise subprocess.CalledProcessError(p.returncode, p.args)
def sign(self, command, build_dir, bcfg, formats): args = command.args if args.tool_path: command.check_force( shutil.which(args.tool_path), '--tool-path {}: not an executable'.format(args.tool_path)) tool_path = args.tool_path else: tool_path = shutil.which('rimage') if not tool_path: log.die('rimage not found; either install it', 'or provide --tool-path') b = pathlib.Path(build_dir) cache = cmake.CMakeCache.from_build_dir(build_dir) board = cache['CACHED_BOARD'] if board != 'up_squared_adsp': log.die('Supported only for up_squared_adsp board') log.inf('Signing with tool {}'.format(tool_path)) bootloader = str(b / 'zephyr' / 'bootloader.elf.mod') kernel = str(b / 'zephyr' / 'zephyr.elf.mod') out_bin = str(b / 'zephyr' / 'zephyr.ri') sign_base = ([tool_path] + args.tool_args + ['-o', out_bin, '-m', 'apl', '-i', '3'] + [bootloader, kernel]) log.inf(quote_sh_list(sign_base)) subprocess.check_call(sign_base)
def check_output(args, cwd=None): '''Runs subprocess.check_output(args, cwd=cwd) after logging the call at VERBOSE_VERY level.''' cmd_str = quote_sh_list(args) log.dbg(f"running '{cmd_str}' in {cwd or os.getcwd()}", level=log.VERBOSE_VERY) return subprocess.check_output(args, cwd=cwd)
def sign(self, command, build_dir, bcfg, formats): args = command.args if args.tool_path: command.check_force( shutil.which(args.tool_path), '--tool-path {}: not an executable'.format(args.tool_path)) tool_path = args.tool_path else: tool_path = shutil.which('rimage') if not tool_path: log.die('rimage not found; either install it', 'or provide --tool-path') b = pathlib.Path(build_dir) cache = CMakeCache.from_build_dir(build_dir) board = cache['CACHED_BOARD'] log.inf('Signing for board ' + board) target = self.edt_get_rimage_target(board) conf = target + '.toml' log.inf('Signing for SOC target ' + target + ' using ' + conf) if not args.quiet: log.inf('Signing with tool {}'.format(tool_path)) bootloader = str(b / 'zephyr' / 'bootloader.elf.mod') kernel = str(b / 'zephyr' / 'zephyr.elf.mod') out_bin = str(b / 'zephyr' / 'zephyr.ri') out_xman = str(b / 'zephyr' / 'zephyr.ri.xman') out_tmp = str(b / 'zephyr' / 'zephyr.rix') conf_path_cmd = [] if cache.get('RIMAGE_CONFIG_PATH') and not args.tool_data: rimage_conf = pathlib.Path(cache['RIMAGE_CONFIG_PATH']) conf_path = str(rimage_conf / conf) conf_path_cmd = ['-c', conf_path] elif args.tool_data: conf_dir = pathlib.Path(args.tool_data) conf_path = str(conf_dir / conf) conf_path_cmd = ['-c', conf_path] else: log.die('Configuration not found') sign_base = ([tool_path] + args.tool_args + ['-o', out_bin] + conf_path_cmd + ['-i', '3', '-e'] + [bootloader, kernel]) if not args.quiet: log.inf(quote_sh_list(sign_base)) subprocess.check_call(sign_base) filenames = [out_xman, out_bin] with open(out_tmp, 'wb') as outfile: for fname in filenames: with open(fname, 'rb') as infile: outfile.write(infile.read()) os.remove(out_bin) os.rename(out_tmp, out_bin)
def git(self, cmd, extra_args=(), capture_stdout=False, capture_stderr=False, check=True, cwd=None): '''Helper for running a git command using metadata from a Project instance. :param cmd: git command as a string (or list of strings); all strings are formatted using self.format() before use. :param extra_args: sequence of additional arguments to pass to the git command (useful mostly if cmd is a string). :param capture_stdout: True if stdout should be captured into the returned object instead of being printed. :param capture_stderr: Like capture_stdout, but for stderr. Use with caution as it prevents error messages from being shown to the user. :param check: True if a subprocess.CalledProcessError should be raised if the git command finishes with a non-zero return code. :param cwd: directory to run command in (default: self.abspath) Returns a CompletedProcess (which is back-ported for Python 3.4).''' _warn_once_if_no_git() if isinstance(cmd, str): cmd_list = shlex.split(cmd) else: cmd_list = list(cmd) extra_args = list(extra_args) if cwd is None: cwd = self.abspath args = ['git'] + [self.format(arg) for arg in cmd_list] + extra_args cmd_str = util.quote_sh_list(args) log.dbg("running '{}'".format(cmd_str), 'in', cwd, level=log.VERBOSE_VERY) popen = subprocess.Popen( args, cwd=cwd, stdout=subprocess.PIPE if capture_stdout else None, stderr=subprocess.PIPE if capture_stderr else None) stdout, stderr = popen.communicate() dbg_msg = "'{}' in {} finished with exit status {}".format( cmd_str, cwd, popen.returncode) if capture_stdout: dbg_msg += " and wrote {} to stdout".format(stdout) if capture_stderr: dbg_msg += " and wrote {} to stderr".format(stderr) log.dbg(dbg_msg, level=log.VERBOSE_VERY) if check and popen.returncode: raise subprocess.CalledProcessError(popen.returncode, cmd_list, output=stdout, stderr=stderr) else: return CompletedProcess(popen.args, popen.returncode, stdout, stderr)
def sign(self, args): cache = cmake.CMakeCache.from_build_dir(args.build_dir) runner_config = cached_runner_config(args.build_dir, cache) bcfg = BuildConfiguration(args.build_dir) # Build a signed .bin if args.gen_bin and runner_config.bin_file: sign_bin = self.sign_cmd(args, bcfg, runner_config.bin_file, args.sbin) log.dbg(quote_sh_list(sign_bin)) subprocess.check_call(sign_bin) # Build a signed .hex if args.gen_hex and runner_config.hex_file: sign_hex = self.sign_cmd(args, bcfg, runner_config.hex_file, args.shex) log.dbg(quote_sh_list(sign_hex)) subprocess.check_call(sign_hex)
def run_cmake(args, cwd=None, capture_output=False, dry_run=False): '''Run cmake to (re)generate a build system, a script, etc. :param args: arguments to pass to CMake :param cwd: directory to run CMake in, cwd is default :param capture_output: if True, the output is returned instead of being displayed (None is returned by default, or if dry_run is also True) :param dry_run: don't actually execute the command, just print what would have been run If capture_output is set to True, returns the output of the command instead of displaying it on stdout/stderr..''' cmake = shutil.which('cmake') if cmake is None and not dry_run: log.die('CMake is not installed or cannot be found; cannot build.') _ensure_min_version(cmake, dry_run) cmd = [cmake] + args kwargs = dict() if capture_output: kwargs['stdout'] = subprocess.PIPE # CMake sends the output of message() to stderr unless it's STATUS kwargs['stderr'] = subprocess.STDOUT if cwd: kwargs['cwd'] = cwd if dry_run: in_cwd = ' (in {})'.format(cwd) if cwd else '' log.inf('Dry run{}:'.format(in_cwd), quote_sh_list(cmd)) return None log.dbg('Running CMake:', quote_sh_list(cmd), level=log.VERBOSE_NORMAL) p = subprocess.Popen(cmd, **kwargs) out, _ = p.communicate() if p.returncode == 0: if out: return out.decode(sys.getdefaultencoding()).splitlines() else: return None else: # A real error occurred, raise an exception raise subprocess.CalledProcessError(p.returncode, p.args)
def main(argv=None): # Makes ANSI color escapes work on Windows, and strips them when # stdout/stderr isn't a terminal colorama.init() # See if we're in an installation. try: topdir = west_topdir() except WestNotFound: topdir = None # Read the configuration files before looking for extensions. # We need this to find the manifest path in order to load extensions. config.read_config() # Load any extension command specs if we're in an installation. if topdir: try: extensions = get_extension_commands() except (MalformedConfig, FileNotFoundError): extensions = {} else: extensions = {} if argv is None: argv = sys.argv[1:] args, unknown = parse_args(argv, extensions, topdir) try: args.handler(args, unknown) except KeyboardInterrupt: sys.exit(0) except CalledProcessError as cpe: log.err('command exited with status {}: {}'.format( cpe.returncode, quote_sh_list(cpe.cmd))) if args.verbose: traceback.print_exc() sys.exit(cpe.returncode) except ExtensionCommandError as ece: msg = 'extension command "{}" could not be run{}.'.format( args.command, ': ' + ece.hint if ece.hint else '') if args.verbose: log.err(msg) traceback.print_exc() else: log.err(msg, 'See {} for a traceback.'.format(dump_traceback())) sys.exit(ece.returncode) except CommandContextError as cce: log.err('command', args.command, 'cannot be run in this context:', *cce.args) log.err('see {} for a traceback.'.format(dump_traceback())) sys.exit(cce.returncode) except CommandError as ce: # No need to dump_traceback() here. The command is responsible # for logging its own errors. sys.exit(ce.returncode)
def run_cmake(args, quiet=False): '''Run cmake to (re)generate a build system''' cmake = shutil.which('cmake') if cmake is None: log.die('CMake is not installed or cannot be found; cannot build.') cmd = [cmake] + args kwargs = dict() if quiet: kwargs['stdout'] = subprocess.DEVNULL kwargs['stderr'] = subprocess.STDOUT log.dbg('Running CMake:', cmd, level=log.VERBOSE_VERY) log.dbg('As command:', quote_sh_list(cmd), level=log.VERBOSE_VERY) subprocess.check_call(cmd, **kwargs)
def call(self, cmd): '''Subclass subprocess.call() wrapper. Subclasses should use this method to run command in a subprocess and get its return code, rather than using subprocess directly, to keep accurate debug logs. ''' quoted = quote_sh_list(cmd) if JUST_PRINT: log.inf(quoted) return 0 log.dbg(quoted) return subprocess.call(cmd)
def check_output(self, cmd): '''Subclass subprocess.check_output() wrapper. Subclasses should use this method to run command in a subprocess and check that it executed correctly, rather than using subprocess directly, to keep accurate debug logs. ''' quoted = quote_sh_list(cmd) if JUST_PRINT: log.inf(quoted) return b'' log.dbg(quoted) try: return subprocess.check_output(cmd) except subprocess.CalledProcessError: raise
def main(argv=None): # Makes ANSI color escapes work on Windows, and strips them when # stdout/stderr isn't a terminal colorama.init() # Read the configuration files config.read_config() # Load any external command specs. If the config file isn't # fully set up yet, ignore the error. This allows west init to # work properly. try: externals = get_external_commands() except MalformedConfig: externals = {} if argv is None: argv = sys.argv[1:] args, unknown = parse_args(argv, externals) 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.err('command', args.command, 'cannot be run in this context:', *cce.args) sys.exit(cce.returncode) except CommandError as ce: sys.exit(ce.returncode)
def popen_ignore_int(self, cmd): '''Spawn a child command, ensuring it ignores SIGINT. The returned subprocess.Popen object must be manually terminated.''' cflags = 0 preexec = None system = platform.system() quoted = quote_sh_list(cmd) if system == 'Windows': cflags |= subprocess.CREATE_NEW_PROCESS_GROUP elif system in {'Linux', 'Darwin'}: preexec = os.setsid if JUST_PRINT: log.inf(quoted) return _DebugDummyPopen() log.dbg(quoted) return subprocess.Popen(cmd, creationflags=cflags, preexec_fn=preexec)
def sign(self, command, build_dir, bcfg, formats): args = command.args if args.tool_path: command.check_force( shutil.which(args.tool_path), '--tool-path {}: not an executable'.format(args.tool_path)) tool_path = args.tool_path else: tool_path = shutil.which('rimage') if not tool_path: log.die('rimage not found; either install it', 'or provide --tool-path') b = pathlib.Path(build_dir) cache = CMakeCache.from_build_dir(build_dir) board = cache['CACHED_BOARD'] log.inf('Signing for board ' + board) target = self.edt_get_rimage_target(board) log.inf('Signing for SOC target ' + target) if not args.quiet: log.inf('Signing with tool {}'.format(tool_path)) bootloader = str(b / 'zephyr' / 'bootloader.elf.mod') kernel = str(b / 'zephyr' / 'zephyr.elf.mod') out_bin = str(b / 'zephyr' / 'zephyr.ri') sign_base = ([tool_path] + args.tool_args + ['-o', out_bin, '-m', target, '-i', '3'] + [bootloader, kernel]) if not args.quiet: log.inf(quote_sh_list(sign_base)) subprocess.check_call(sign_base)
def sign(self, command, build_dir, build_conf, formats): if not formats: return args = command.args b = pathlib.Path(build_dir) imgtool = self.find_imgtool(command, args) # The vector table offset is set in Kconfig: vtoff = self.get_cfg(command, build_conf, 'CONFIG_ROM_START_OFFSET') # Flash device write alignment and the partition's slot size # come from devicetree: flash = self.edt_flash_node(b, args.quiet) align, addr, size = self.edt_flash_params(flash) if build_conf.getboolean('CONFIG_BOOTLOADER_MCUBOOT'): log.wrn("CONFIG_BOOTLOADER_MCUBOOT is not set to y in " f"{build_conf.path}; this probably won't work") kernel = build_conf.get('CONFIG_KERNEL_BIN_NAME', 'zephyr') if 'bin' in formats: in_bin = b / 'zephyr' / f'{kernel}.bin' if not in_bin.is_file(): log.die(f"no unsigned .bin found at {in_bin}") in_bin = os.fspath(in_bin) else: in_bin = None if 'hex' in formats: in_hex = b / 'zephyr' / f'{kernel}.hex' if not in_hex.is_file(): log.die(f"no unsigned .hex found at {in_hex}") in_hex = os.fspath(in_hex) else: in_hex = None if not args.quiet: log.banner('image configuration:') log.inf('partition offset: {0} (0x{0:x})'.format(addr)) log.inf('partition size: {0} (0x{0:x})'.format(size)) log.inf('rom start offset: {0} (0x{0:x})'.format(vtoff)) # Base sign command. # # We provide a default --version in case the user is just # messing around and doesn't want to set one. It will be # overridden if there is a --version in args.tool_args. sign_base = imgtool + [ 'sign', '--version', '0.0.0+0', '--align', str(align), '--header-size', str(vtoff), '--slot-size', str(size) ] sign_base.extend(args.tool_args) if not args.quiet: log.banner('signing binaries') if in_bin: out_bin = args.sbin or str(b / 'zephyr' / 'zephyr.signed.bin') sign_bin = sign_base + [in_bin, out_bin] if not args.quiet: log.inf(f'unsigned bin: {in_bin}') log.inf(f'signed bin: {out_bin}') log.dbg(quote_sh_list(sign_bin)) subprocess.check_call(sign_bin) if in_hex: out_hex = args.shex or str(b / 'zephyr' / 'zephyr.signed.hex') sign_hex = sign_base + [in_hex, out_hex] if not args.quiet: log.inf(f'unsigned hex: {in_hex}') log.inf(f'signed hex: {out_hex}') log.dbg(quote_sh_list(sign_hex)) subprocess.check_call(sign_hex)
def git(self, cmd, extra_args=(), capture_stdout=False, capture_stderr=False, check=True, cwd=None): '''Run a git command in the project repository. Returns a ``subprocess.CompletedProcess`` (an equivalent object is back-ported for Python 3.4). :param cmd: git command as a string (or list of strings); all strings are formatted using `format` before use. :param extra_args: sequence of additional arguments to pass to the git command (useful mostly if *cmd* is a string). :param capture_stdout: if True, git's standard output is captured in the ``CompletedProcess`` instead of being printed. :param capture_stderr: Like *capture_stdout*, but for standard error. Use with caution: this may prevent error messages from being shown to the user. :param check: if given, ``subprocess.CalledProcessError`` is raised if git finishes with a non-zero return code :param cwd: directory to run git in (default: ``self.abspath``) ''' _warn_once_if_no_git() if isinstance(cmd, str): cmd_list = shlex.split(cmd) else: cmd_list = list(cmd) extra_args = list(extra_args) if cwd is None: if self.abspath is not None: cwd = self.abspath else: raise ValueError('no abspath; cwd must be given') args = ['git'] + [self.format(arg) for arg in cmd_list] + extra_args cmd_str = util.quote_sh_list(args) log.dbg("running '{}'".format(cmd_str), 'in', cwd, level=log.VERBOSE_VERY) popen = subprocess.Popen( args, cwd=cwd, stdout=subprocess.PIPE if capture_stdout else None, stderr=subprocess.PIPE if capture_stderr else None) stdout, stderr = popen.communicate() dbg_msg = "'{}' in {} finished with exit status {}".format( cmd_str, cwd, popen.returncode) if capture_stdout: dbg_msg += " and wrote {} to stdout".format(stdout) if capture_stderr: dbg_msg += " and wrote {} to stderr".format(stderr) log.dbg(dbg_msg, level=log.VERBOSE_VERY) if check and popen.returncode: raise subprocess.CalledProcessError(popen.returncode, cmd_list, output=stdout, stderr=stderr) else: return CompletedProcess(popen.args, popen.returncode, stdout, stderr)
def main(argv=None): # Silence validation errors from pykwalify, which are logged at # logging.ERROR level. We want to handle those ourselves as # needed. logging.getLogger('pykwalify').setLevel(logging.CRITICAL) # Makes ANSI color escapes work on Windows, and strips them when # stdout/stderr isn't a terminal colorama.init() # See if we're in an installation. try: topdir = west_topdir() except WestNotFound: topdir = None # Read the configuration files before looking for extensions. # We need this to find the manifest path in order to load extensions. config.read_config(topdir=topdir) # Parse the manifest and create extension command thunks. We'll # pass the saved manifest around so it doesn't have to be # re-parsed. mve = None if topdir: try: manifest = Manifest.from_file(topdir=topdir) extensions = get_extension_commands(manifest) except (MalformedManifest, MalformedConfig, FileNotFoundError, ManifestVersionError) as e: manifest = None extensions = None if isinstance(e, ManifestVersionError): mve = e else: manifest = None extensions = {} # Create the initial set of parsers. We'll need to re-create these # if we're running an extension command. Register extensions with # the parser. if argv is None: argv = sys.argv[1:] west_parser, subparser_gen = _make_parsers() west_parser.west_extensions = extensions west_parser.mve = mve # Cache the parser in the global Help instance. Dirty, but it # needs this data as its parser attribute is not the parent # parser, but the return value of a subparser_gen. BUILTIN_COMMANDS['help'].west_parser = west_parser # Add sub-parsers for the built-in commands. for command in BUILTIN_COMMANDS.values(): command.add_parser(subparser_gen) # Add stub parsers for extensions. # # These just reserve the names of each extension. The real parser # for each extension can't be added until we import the # extension's code, which we won't do unless parse_known_args() # says to run that extension. extensions_by_name = {} if extensions: for path, specs in extensions.items(): for spec in specs: subparser_gen.add_parser(spec.name, add_help=False) extensions_by_name[spec.name] = spec # Parse arguments for the first time. We'll need to do this again # if we're running an extension. args, unknown = west_parser.parse_known_args(args=argv) # Set up logging verbosity before running the command, so # e.g. verbose messages related to argument handling errors work # properly. This works even for extension commands that haven't # been instantiated yet, because --verbose is an option to the top # level parser, and the command run() method doesn't get called # until later. log.set_verbosity(args.verbose) log.dbg('args namespace:', args, level=log.VERBOSE_EXTREME) # Try to set ZEPHYR_BASE. It would be nice to get rid of this # someday and just have extensions that need it set this variable. if args.command and args.command not in ['init', 'help'] and not args.help: set_zephyr_base(args) # If we were run as 'west -h ...' or 'west --help ...', # monkeypatch the args namespace so we end up running Help. The # user might have also provided a command. If so, print help about # that command. if args.help or args.command is None: args.command_name = args.command args.command = 'help' # Finally, run the command. try: if args.command in extensions_by_name: # Check a program invariant. We should never get here # unless we were able to parse the manifest. That's where # information about extensions is gained. assert mve is None, \ 'internal error: running extension "{}" ' \ 'but got ManifestVersionError'.format(args.command) # This does not return. get_extension_commands() ensures # that extensions do not shadow built-in command names, so # checking this first is safe. run_extension(extensions_by_name[args.command], topdir, argv, manifest) else: if mve: if args.command == 'help': log.wrn( _mve_msg(mve, suggest_ugprade=False) + '\n Cannot get extension command help, ' + "and most commands won't run." + '\n To silence this warning, upgrade west.') elif args.command in ['config', 'topdir']: # config and topdir are safe to run, but let's # warn the user that most other commands won't be. log.wrn( _mve_msg(mve, suggest_ugprade=False) + "\n This should work, but most commands won't." + '\n To silence this warning, upgrade west.') elif args.command != 'init': log.die(_mve_msg(mve)) cmd = BUILTIN_COMMANDS.get(args.command, BUILTIN_COMMANDS['help']) cmd.run(args, unknown, topdir, manifest=manifest) except KeyboardInterrupt: sys.exit(0) except BrokenPipeError: sys.exit(0) except CalledProcessError as cpe: log.err('command exited with status {}: {}'.format( cpe.returncode, quote_sh_list(cpe.cmd))) if args.verbose: traceback.print_exc() sys.exit(cpe.returncode) except ExtensionCommandError as ece: msg = 'extension command "{}" could not be run{}.'.format( args.command, ': ' + ece.hint if ece.hint else '') if args.verbose: log.err(msg) traceback.print_exc() else: log.err(msg, 'See {} for a traceback.'.format(dump_traceback())) sys.exit(ece.returncode) except CommandContextError as cce: log.err('command', args.command, 'cannot be run in this context:', *cce.args) log.err('see {} for a traceback.'.format(dump_traceback())) sys.exit(cce.returncode) except CommandError as ce: # No need to dump_traceback() here. The command is responsible # for logging its own errors. sys.exit(ce.returncode) except (MalformedManifest, MalformedConfig) as malformed: log.die('\n '.join(["can't load west manifest"] + list(malformed.args)))
def main(argv=None): # Makes ANSI color escapes work on Windows, and strips them when # stdout/stderr isn't a terminal colorama.init() # Read the configuration files config.read_config() # Load any extension command specs. If the config file isn't # fully set up yet or the west.yml cannot be found, ignore the error. # This allows west init to work properly. try: extensions = get_extension_commands() except (MalformedConfig, FileNotFoundError): extensions = {} if argv is None: argv = sys.argv[1:] args, unknown = parse_args(argv, extensions) for_stack_trace = 'run as "west -v {}" for a stack trace'.format( quote_sh_list(argv)) 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. # Treat the Python script as an executable. This works because on # Unix the script created by pip has a shebang and on Windows it is # actually a binary executable log.dbg("sys.argv[0]:\"{}\" argv:\"{}\"".format(sys.argv[0], argv)) # Use Popen + exit instead of execv due to the asynchronous nature of # execv on Windows, where it creates a new process with a different # pid that executes in parallel to the original one instead of # replacing it as it does on UNIX # https://bugs.python.org/issue9148 # https://bugs.python.org/issue19124 try: proc = Popen([sys.argv[0]] + argv) proc.communicate() except KeyboardInterrupt: sys.exit(0) log.dbg('proc.returncode: {}'.format(proc.returncode)) sys.exit(errno.EIO if proc.returncode is None else proc.returncode) 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: traceback.print_exc() else: log.inf(for_stack_trace) sys.exit(cpe.returncode) except ExtensionCommandError as ece: log.err( 'extension command', args.command, 'was improperly defined and could not be run{}'.format( ': ' + ece.hint if ece.hint else '')) if args.verbose: traceback.print_exc() else: log.inf(for_stack_trace) sys.exit(ece.returncode) except CommandContextError as cce: log.err('command', args.command, 'cannot be run in this context:', *cce.args) sys.exit(cce.returncode) except CommandError as ce: sys.exit(ce.returncode)
def main(argv=None): # Silence validation errors from pykwalify, which are logged at # logging.ERROR level. We want to handle those ourselves as # needed. logging.getLogger('pykwalify').setLevel(logging.CRITICAL) # Makes ANSI color escapes work on Windows, and strips them when # stdout/stderr isn't a terminal colorama.init() # See if we're in an installation. try: topdir = west_topdir() except WestNotFound: topdir = None # Read the configuration files before looking for extensions. # We need this to find the manifest path in order to load extensions. config.read_config() # Parse the manifest and create extension command thunks. We'll # pass the saved manifest around so it doesn't have to be # re-parsed. if topdir: try: manifest = Manifest.from_file() extensions = get_extension_commands(manifest) except (MalformedManifest, MalformedConfig, FileNotFoundError): manifest = None extensions = None else: manifest = None extensions = {} if argv is None: argv = sys.argv[1:] args, unknown = parse_args(argv, extensions, topdir, manifest) try: args.handler(args, unknown) except KeyboardInterrupt: sys.exit(0) except CalledProcessError as cpe: log.err('command exited with status {}: {}'.format( cpe.returncode, quote_sh_list(cpe.cmd))) if args.verbose: traceback.print_exc() sys.exit(cpe.returncode) except ExtensionCommandError as ece: msg = 'extension command "{}" could not be run{}.'.format( args.command, ': ' + ece.hint if ece.hint else '') if args.verbose: log.err(msg) traceback.print_exc() else: log.err(msg, 'See {} for a traceback.'.format(dump_traceback())) sys.exit(ece.returncode) except CommandContextError as cce: log.err('command', args.command, 'cannot be run in this context:', *cce.args) log.err('see {} for a traceback.'.format(dump_traceback())) sys.exit(cce.returncode) except CommandError as ce: # No need to dump_traceback() here. The command is responsible # for logging its own errors. sys.exit(ce.returncode) except (MalformedManifest, MalformedConfig) as malformed: log.die("can't load west manifest:", malformed)
def _git_helper(project, cmd, extra_args, cwd, capture_stdout, check): # Runs a git command. # # project: # The Project instance for the project, derived from the manifest file. # # cmd: # String with git arguments. Supports some "(foo)" shorthands. See below. # # extra_args: # List of additional arguments to pass to the git command (e.g. from the # user). # # cwd: # Directory to switch to first (None = current directory) # # capture_stdout: # True if stdout should be captured into the returned # subprocess.CompletedProcess instance instead of being printed. # # We never capture stderr, to prevent error messages from being eaten. # # check: # True if an error should be raised if the git command finishes with a # non-zero return code. # # Returns a subprocess.CompletedProcess instance. # TODO: Run once somewhere? if shutil.which('git') is None: log.die('Git is not installed or cannot be found') args = (('git', ) + tuple(_expand_shorthands(project, arg) for arg in cmd.split()) + tuple(extra_args)) cmd_str = util.quote_sh_list(args) log.dbg("running '{}'".format(cmd_str), 'in', cwd, level=log.VERBOSE_VERY) popen = subprocess.Popen( args, stdout=subprocess.PIPE if capture_stdout else None, cwd=cwd) stdout, _ = popen.communicate() dbg_msg = "'{}' in {} finished with exit status {}" \ .format(cmd_str, cwd, popen.returncode) if capture_stdout: dbg_msg += " and wrote {} to stdout".format(stdout) log.dbg(dbg_msg, level=log.VERBOSE_VERY) if check and popen.returncode: msg = "Command '{}' failed for {{name_and_path}}".format(cmd_str) if _error_context_msg: msg += _error_context_msg.replace('\n', ' ') _die(project, msg) if capture_stdout: # Manual UTF-8 decoding and universal newlines. Before Python 3.6, # Popen doesn't seem to allow using universal newlines mode (which # enables decoding) with a specific encoding (because the encoding= # parameter is missing). # # Also strip all trailing newlines as convenience. The splitlines() # already means we lose a final '\n' anyway. stdout = "\n".join(stdout.decode('utf-8').splitlines()).rstrip("\n") return CompletedProcess(popen.args, popen.returncode, stdout)
def sign(self, command, build_dir, build_conf, formats): args = command.args if args.tool_path: command.check_force(shutil.which(args.tool_path), '--tool-path {}: not an executable'. format(args.tool_path)) tool_path = args.tool_path else: tool_path = shutil.which('rimage') if not tool_path: log.die('rimage not found; either install it', 'or provide --tool-path') #### -c sof/rimage/config/signing_schema.toml #### b = pathlib.Path(build_dir) cache = CMakeCache.from_build_dir(build_dir) # warning: RIMAGE_TARGET is a duplicate of CONFIG_RIMAGE_SIGNING_SCHEMA target = cache.get('RIMAGE_TARGET') if not target: log.die('rimage target not defined') cmake_toml = target + '.toml' if not args.quiet: log.inf('Signing with tool {}'.format(tool_path)) if target in ('imx8', 'imx8m'): kernel = str(b / 'zephyr' / 'zephyr.elf') out_bin = str(b / 'zephyr' / 'zephyr.ri') out_xman = str(b / 'zephyr' / 'zephyr.ri.xman') out_tmp = str(b / 'zephyr' / 'zephyr.rix') else: bootloader = str(b / 'zephyr' / 'boot.mod') kernel = str(b / 'zephyr' / 'main.mod') out_bin = str(b / 'zephyr' / 'zephyr.ri') out_xman = str(b / 'zephyr' / 'zephyr.ri.xman') out_tmp = str(b / 'zephyr' / 'zephyr.rix') conf_path_cmd = [] if '-c' in args.tool_args: # Precedence to the -- rimage command line conf_path_cmd = [] if args.tool_data: log.wrn('--tool-data ' + args.tool_data + ' ignored, overridden by -c') # For logging only conf_path = args.tool_args[args.tool_args.index('-c') + 1] elif args.tool_data: conf_dir = pathlib.Path(args.tool_data) conf_path = str(conf_dir / cmake_toml) conf_path_cmd = ['-c', conf_path] elif cache.get('RIMAGE_CONFIG_PATH'): rimage_conf = pathlib.Path(cache['RIMAGE_CONFIG_PATH']) conf_path = str(rimage_conf / cmake_toml) conf_path_cmd = ['-c', conf_path] else: log.die('-c configuration not found') log.inf('Signing for SOC target ' + target + ' using ' + conf_path) if '--no-manifest' in args.tool_args: no_manifest = True args.tool_args.remove('--no-manifest') else: no_manifest = False if no_manifest: extra_ri_args = ['-i', '3'] else: extra_ri_args = ['-i', '3', '-e'] sign_base = [tool_path] # Sub-command arg '-q' takes precedence over west '-v' if not args.quiet and args.verbose: sign_base += ['-v'] * args.verbose components = [ ] if (target in ('imx8', 'imx8m')) else [ bootloader ] components += [ kernel ] sign_base += (args.tool_args + ['-o', out_bin] + conf_path_cmd + extra_ri_args + components) if not args.quiet: log.inf(quote_sh_list(sign_base)) subprocess.check_call(sign_base) if no_manifest: filenames = [out_bin] else: filenames = [out_xman, out_bin] if not args.quiet: log.inf('Prefixing ' + out_bin + ' with manifest ' + out_xman) with open(out_tmp, 'wb') as outfile: for fname in filenames: with open(fname, 'rb') as infile: outfile.write(infile.read()) os.remove(out_bin) os.rename(out_tmp, out_bin)
def sign(self, command, build_dir, bcfg, formats): args = command.args if args.tool_path: command.check_force( shutil.which(args.tool_path), '--tool-path {}: not an executable'.format(args.tool_path)) tool_path = args.tool_path else: tool_path = shutil.which('imgtool') if not tool_path: log.die( 'imgtool not found; either install it', '(e.g. "pip3 install imgtool") or provide --tool-path') align, vtoff, slot_size = [ self.get_cfg(command, bcfg, x) for x in ('DT_FLASH_WRITE_BLOCK_SIZE', 'CONFIG_TEXT_SECTION_OFFSET', 'DT_FLASH_AREA_IMAGE_0_SIZE') ] log.dbg('build config: --align={}, --header-size={}, --slot-size={}'. format(align, vtoff, slot_size)) # Base sign command. # # We provide a default --version in case the user is just # messing around and doesn't want to set one. It will be # overridden if there is a --version in args.tool_args. sign_base = [tool_path, 'sign', '--version', '0.0.0+0'] if align: sign_base.extend(['--align', str(align)]) else: log.wrn('expected nonzero flash alignment, but ' 'DT_FLASH_WRITE_BLOCK_SIZE={} ' "in build directory's ({}) device tree".format( align, build_dir)) if vtoff: sign_base.extend(['--header-size', str(vtoff)]) else: log.wrn('expected nonzero header size, but ' 'CONFIG_TEXT_SECTION_OFFSET={} ' "in build directory's ({}) .config".format( vtoff, build_dir)) if slot_size: sign_base.extend(['--slot-size', str(slot_size)]) else: log.wrn('expected nonzero slot size, but ' 'DT_FLASH_AREA_IMAGE_0_SIZE={} ' "in build directory's ({}) device tree".format( slot_size, build_dir)) b = pathlib.Path(build_dir) cache = cmake.CMakeCache.from_build_dir(build_dir) runner_config = cached_runner_config(build_dir, cache) # Build a signed .bin if 'bin' in formats and runner_config.bin_file: out_bin = args.sbin or str(b / 'zephyr' / 'zephyr.signed.bin') log.inf('Generating:', out_bin) sign_bin = (sign_base + args.tool_args + [runner_config.bin_file, out_bin]) log.dbg(quote_sh_list(sign_bin)) subprocess.check_call(sign_bin) # Build a signed .hex if 'hex' in formats and runner_config.hex_file: out_hex = args.shex or str(b / 'zephyr' / 'zephyr.signed.hex') log.inf('Generating:', out_hex) sign_hex = (sign_base + args.tool_args + [runner_config.hex_file, out_hex]) log.dbg(quote_sh_list(sign_hex)) subprocess.check_call(sign_hex)
def check_call(self, args, cwd=None): cmd_str = util.quote_sh_list(args) log.dbg(f"running '{cmd_str}' in {cwd or os.getcwd()}", level=log.VERBOSE_VERY) subprocess.check_call(args, cwd=cwd)
def sign(self, command, build_dir, build_conf, formats): args = command.args if args.tool_path: command.check_force( shutil.which(args.tool_path), '--tool-path {}: not an executable'.format(args.tool_path)) tool_path = args.tool_path else: tool_path = shutil.which('rimage') if not tool_path: log.die('rimage not found; either install it', 'or provide --tool-path') b = pathlib.Path(build_dir) cache = CMakeCache.from_build_dir(build_dir) target = cache.get('RIMAGE_TARGET') if not target: log.die('rimage target not defined') conf = target + '.toml' log.inf('Signing for SOC target ' + target + ' using ' + conf) if not args.quiet: log.inf('Signing with tool {}'.format(tool_path)) if target in ('imx8', 'imx8m'): kernel = str(b / 'zephyr' / 'zephyr.elf') out_bin = str(b / 'zephyr' / 'zephyr.ri') out_xman = str(b / 'zephyr' / 'zephyr.ri.xman') out_tmp = str(b / 'zephyr' / 'zephyr.rix') else: bootloader = str(b / 'zephyr' / 'boot.mod') kernel = str(b / 'zephyr' / 'main.mod') out_bin = str(b / 'zephyr' / 'zephyr.ri') out_xman = str(b / 'zephyr' / 'zephyr.ri.xman') out_tmp = str(b / 'zephyr' / 'zephyr.rix') conf_path_cmd = [] if cache.get('RIMAGE_CONFIG_PATH') and not args.tool_data: rimage_conf = pathlib.Path(cache['RIMAGE_CONFIG_PATH']) conf_path = str(rimage_conf / conf) conf_path_cmd = ['-c', conf_path] elif args.tool_data: conf_dir = pathlib.Path(args.tool_data) conf_path = str(conf_dir / conf) conf_path_cmd = ['-c', conf_path] else: log.die('Configuration not found') if '--no-manifest' in args.tool_args: no_manifest = True args.tool_args.remove('--no-manifest') else: no_manifest = False if no_manifest: extra_ri_args = ['-i', '3'] else: extra_ri_args = ['-i', '3', '-e'] components = [] if (target in ('imx8', 'imx8m')) else [bootloader] components += [kernel] sign_base = ([tool_path] + args.tool_args + ['-o', out_bin] + conf_path_cmd + extra_ri_args + components) if not args.quiet: log.inf(quote_sh_list(sign_base)) subprocess.check_call(sign_base) if no_manifest: filenames = [out_bin] else: filenames = [out_xman, out_bin] with open(out_tmp, 'wb') as outfile: for fname in filenames: with open(fname, 'rb') as infile: outfile.write(infile.read()) os.remove(out_bin) os.rename(out_tmp, out_bin)