scripts: remove west from scripts/

west will now be installed via pip in order for the bootstrapper to be
decoupled from the west runners.

Signed-off-by: Carles Cufi <carles.cufi@nordicsemi.no>
This commit is contained in:
Carles Cufi 2018-11-12 15:12:59 +01:00 committed by Carles Cufí
parent 33dae59a57
commit 9b4eb37f38
36 changed files with 0 additions and 5984 deletions

View File

@ -1,5 +0,0 @@
# Copyright 2018 Open Source Foundries Limited.
#
# SPDX-License-Identifier: Apache-2.0
# Empty file.

View File

@ -1,439 +0,0 @@
# Copyright 2018 Open Source Foundries Limited.
#
# SPDX-License-Identifier: Apache-2.0
'''West's bootstrap/wrapper script.
'''
import argparse
import configparser
import os
import platform
import pykwalify.core
import subprocess
import sys
import yaml
import west._bootstrap.version as version
if sys.version_info < (3,):
sys.exit('fatal error: you are running Python 2')
#
# Special files and directories in the west installation.
#
# These are given variable names for clarity, but they can't be
# changed without propagating the changes into west itself.
#
# Top-level west directory, containing west itself and the manifest.
WEST_DIR = 'west'
# Subdirectory to check out the west source repository into.
WEST = 'west'
# Default west repository URL.
WEST_URL_DEFAULT = 'https://github.com/zephyrproject-rtos/west'
# Default revision to check out of the west repository.
WEST_REV_DEFAULT = 'master'
# File inside of WEST_DIR which marks it as the top level of the
# Zephyr project installation.
#
# (The WEST_DIR name is not distinct enough to use when searching for
# the top level; other directories named "west" may exist elsewhere,
# e.g. zephyr/doc/west.)
WEST_MARKER = '.west_topdir'
# Manifest repository directory under WEST_DIR.
MANIFEST = 'manifest'
# Default manifest repository URL.
MANIFEST_URL_DEFAULT = 'https://github.com/zephyrproject-rtos/manifest'
# Default revision to check out of the manifest repository.
MANIFEST_REV_DEFAULT = 'master'
_SCHEMA_PATH = os.path.join(os.path.dirname(__file__), "west-schema.yml")
#
# Helpers shared between init and wrapper mode
#
class WestError(RuntimeError):
pass
class WestNotFound(WestError):
'''Neither the current directory nor any parent has a West installation.'''
def west_dir(start=None):
'''
Returns the path to the west/ directory, searching ``start`` and its
parents.
Raises WestNotFound if no west directory is found.
'''
return os.path.join(west_topdir(start), WEST_DIR)
def manifest_dir(start=None):
'''
Returns the path to the manifest/ directory, searching ``start`` and its
parents.
Raises WestNotFound if no west directory is found.
'''
return os.path.join(west_topdir(start), MANIFEST)
def west_topdir(start=None):
'''
Like west_dir(), but returns the path to the parent directory of the west/
directory instead, where project repositories are stored
'''
# If you change this function, make sure to update west.util.west_topdir().
cur_dir = start or os.getcwd()
while True:
if os.path.isfile(os.path.join(cur_dir, WEST_DIR, WEST_MARKER)):
return cur_dir
parent_dir = os.path.dirname(cur_dir)
if cur_dir == parent_dir:
# At the root
raise WestNotFound('Could not find a West installation '
'in this or any parent directory')
cur_dir = parent_dir
def clone(desc, url, rev, dest):
if os.path.exists(dest):
raise WestError('refusing to clone into existing location ' + dest)
print('=== Cloning {} from {}, rev. {} ==='.format(desc, url, rev))
subprocess.check_call(('git', 'clone', '-b', rev, '--', url, dest))
#
# west init
#
def init(argv):
'''Command line handler for ``west init`` invocations.
This exits the program with a nonzero exit code if fatal errors occur.'''
# Remember to update scripts/west-completion.bash if you add or remove
# flags
init_parser = argparse.ArgumentParser(
prog='west init',
formatter_class=argparse.RawDescriptionHelpFormatter,
description='''
Initializes a Zephyr installation. Use "west clone" afterwards to fetch the
sources.
In more detail, does the following:
1. Clones the manifest repository to west/manifest, and the west repository
to west/west
2. Creates a marker file west/{}
3. Creates an initial configuration file west/config
As an alternative to manually editing west/config, 'west init' can be rerun on
an already initialized West instance to update configuration settings. Only
explicitly passed configuration values (e.g. --mr MANIFEST_REVISION) are
updated.
Updating the manifest URL or revision via 'west init' automatically runs 'west
update --reset-manifest --reset-projects' afterwards to reset the manifest to
the new revision, and all projects to their new manifest revisions.
Updating the west URL or revision also runs 'west update --reset-west'.
To suppress the reset of the manifest, west, and projects, pass --no-reset.
With --no-reset, only the configuration file will be updated, and you will have
to handle any resetting yourself.
'''.format(WEST_MARKER))
init_parser.add_argument(
'-m', '--manifest-url',
help='Manifest repository URL (default: {})'
.format(MANIFEST_URL_DEFAULT))
init_parser.add_argument(
'--mr', '--manifest-rev', dest='manifest_rev',
help='Manifest revision to fetch (default: {})'
.format(MANIFEST_REV_DEFAULT))
init_parser.add_argument(
'--nr', '--no-reset', dest='reset', action='store_false',
help='''Suppress the automatic reset of the manifest, west, and project
repositories when re-running 'west init' in an existing
installation to update the manifest or west URL/revision''')
init_parser.add_argument(
'directory', nargs='?', default=None,
help='''Directory to initialize West in. Missing directories will be
created automatically. (default: current directory)''')
args = init_parser.parse_args(args=argv)
try:
reinit(os.path.join(west_dir(args.directory), 'config'), args)
except WestNotFound:
bootstrap(args)
def bootstrap(args):
'''Bootstrap a new manifest + West installation.'''
west_url = WEST_URL_DEFAULT
manifest_url = args.manifest_url or MANIFEST_URL_DEFAULT
west_rev = WEST_REV_DEFAULT
manifest_rev = args.manifest_rev or MANIFEST_REV_DEFAULT
directory = args.directory or os.getcwd()
if not os.path.isdir(directory):
try:
print('Initializing in new directory', directory)
os.makedirs(directory, exist_ok=False)
except PermissionError:
sys.exit('Cannot initialize in {}: permission denied'.format(
directory))
except FileExistsError:
sys.exit('Something else created {} concurrently; quitting'.format(
directory))
except Exception as e:
sys.exit("Can't create directory {}: {}".format(
directory, e.args))
else:
print('Initializing in', directory)
# Clone the west source code and the manifest into west/. Git will create
# the west/ directory if it does not exist.
clone('manifest repository', manifest_url, manifest_rev,
os.path.join(directory, WEST_DIR, MANIFEST))
# Parse the manifest and look for a section named "west"
manifest_file = os.path.join(directory, WEST_DIR, MANIFEST, 'default.yml')
with open(manifest_file, 'r') as f:
data = yaml.safe_load(f.read())
if 'west' in data:
wdata = data['west']
try:
pykwalify.core.Core(
source_data=wdata,
schema_files=[_SCHEMA_PATH]
).validate()
except pykwalify.errors.SchemaError as e:
sys.exit("Error: Failed to parse manifest file '{}': {}"
.format(manifest_file, e))
if 'url' in wdata:
west_url = wdata['url']
if 'revision' in wdata:
west_rev = wdata['revision']
print("cloning {} at revision {}".format(west_url, west_rev))
clone('west repository', west_url, west_rev,
os.path.join(directory, WEST_DIR, WEST))
# Create an initial configuration file
config_path = os.path.join(directory, WEST_DIR, 'config')
update_conf(config_path, manifest_url, manifest_rev)
print('=== Initial configuration written to {} ==='.format(config_path))
# Create a dotfile to mark the installation. Hide it on Windows.
with open(os.path.join(directory, WEST_DIR, WEST_MARKER), 'w') as f:
hide_file(f.name)
print('=== West initialized. Now run "west clone" in {}. ==='.
format(directory))
def reinit(config_path, args):
'''
Reinitialize an existing installation.
This updates the west/config configuration file, and optionally resets the
manifest, west, and project repositories to the new revision.
'''
manifest_url = args.manifest_url
if not (manifest_url or args.manifest_rev):
sys.exit('West already initialized. Please pass any settings you '
'want to change.')
update_conf(config_path, manifest_url, args.manifest_rev)
print('=== Updated configuration written to {} ==='.format(config_path))
if args.reset:
cmd = ['update', '--reset-manifest', '--reset-projects',
'--reset-west']
print("=== Running 'west {}' to update repositories ==="
.format(' '.join(cmd)))
wrap(cmd)
def update_conf(config_path, manifest_url, manifest_rev):
'''
Creates or updates the configuration file at 'config_path' with the
specified values. Values that are None/empty are ignored.
'''
config = configparser.ConfigParser()
# This is a no-op if the file doesn't exist, so no need to check
config.read(config_path)
update_key(config, 'manifest', 'remote', manifest_url)
update_key(config, 'manifest', 'revision', manifest_rev)
with open(config_path, 'w') as f:
config.write(f)
def update_key(config, section, key, value):
'''
Updates 'key' in section 'section' in ConfigParser 'config', creating
'section' if it does not exist.
If value is None/empty, 'key' is left as-is.
'''
if not value:
return
if section not in config:
config[section] = {}
config[section][key] = value
def hide_file(path):
'''Ensure path is a hidden file.
On Windows, this uses attrib to hide the file manually.
On UNIX systems, this just checks that the path's basename begins
with a period ('.'), for it to be hidden already. It's a fatal
error if it does not begin with a period in this case.
On other systems, this just prints a warning.
'''
system = platform.system()
if system == 'Windows':
subprocess.check_call(['attrib', '+H', path])
elif os.name == 'posix': # Try to check for all Unix, not just macOS/Linux
if not os.path.basename(path).startswith('.'):
sys.exit("internal error: {} can't be hidden on UNIX".format(path))
else:
print("warning: unknown platform {}; {} may not be hidden"
.format(system, path), file=sys.stderr)
#
# Wrap a West command
#
def append_to_pythonpath(directory):
pp = os.environ.get('PYTHONPATH')
os.environ['PYTHONPATH'] = ':'.join(([pp] if pp else []) + [directory])
def wrap(argv):
printing_version = False
printing_help_only = False
if argv:
if argv[0] in ('-V', '--version'):
print('West bootstrapper version: v{} ({})'.
format(version.__version__, os.path.dirname(__file__)))
printing_version = True
elif len(argv) == 1 and argv[0] in ('-h', '--help'):
# This only matters if we're called outside of an
# installation directory. We delegate to the main help if
# called from within one, because it includes a list of
# available commands, etc.
printing_help_only = True
start = os.getcwd()
try:
topdir = west_topdir(start)
except WestNotFound:
if printing_version:
sys.exit(0) # run outside of an installation directory
elif printing_help_only:
# We call print multiple times here and below instead of using
# \n to be newline agnostic.
print('To set up a Zephyr installation here, run "west init".')
print('Run "west init -h" for additional information.')
sys.exit(0)
else:
print('Error: "{}" is not a Zephyr installation directory.'.
format(start), file=sys.stderr)
print('Things to try:', file=sys.stderr)
print(' - Run "west init" to set up an installation here.',
file=sys.stderr)
print(' - Run "west init -h" for additional information.',
file=sys.stderr)
sys.exit(1)
west_git_repo = os.path.join(topdir, WEST_DIR, WEST)
if printing_version:
try:
git_describe = subprocess.check_output(
['git', 'describe', '--tags'],
stderr=subprocess.DEVNULL,
cwd=west_git_repo).decode(sys.getdefaultencoding()).strip()
print('West repository version: {} ({})'.format(git_describe,
west_git_repo))
except subprocess.CalledProcessError:
print('West repository version: unknown; no tags were found')
sys.exit(0)
# Import the west package from the installation and run its main
# function with the given command-line arguments.
#
# This can't be done as a subprocess: that would break the
# runners' debug handling for GDB, which needs to block the usual
# control-C signal handling. GDB uses Ctrl-C to halt the debug
# target. So we really do need to import west and delegate within
# this bootstrap process.
#
# Put this at position 1 to make sure it comes before random stuff
# that might be on a developer's PYTHONPATH in the import order.
sys.path.insert(1, os.path.join(west_git_repo, 'src'))
import west.main
west.main.main(argv)
#
# Main entry point
#
def main(wrap_argv=None):
'''Entry point to the wrapper script.'''
if wrap_argv is None:
wrap_argv = sys.argv[1:]
if not wrap_argv or wrap_argv[0] != 'init':
wrap(wrap_argv)
else:
init(wrap_argv[1:])
sys.exit(0)
if __name__ == '__main__':
main()

View File

@ -1,5 +0,0 @@
# Don't put anything else in here!
#
# This is the Python 3 version of option 3 in:
# https://packaging.python.org/guides/single-sourcing-package-version/#single-sourcing-the-version
__version__ = '0.4.1'

View File

@ -1,17 +0,0 @@
## A pykwalify schema for basic validation of the structure of a
## west YAML file. (Full validation would require additional work,
## e.g. to validate that remote URLs obey the URL format specified in
## rfc1738.)
##
# The top-level west yaml is a map. The only top-level element is
# 'west'. All other elements are contained within it. This allows
# us a bit of future-proofing.
type: map
mapping:
url:
required: false
type: str
revision:
required: false
type: str

View File

@ -1,42 +0,0 @@
# Copyright 2018 (c) Foundries.io.
#
# SPDX-License-Identifier: Apache-2.0
'''Common definitions for building Zephyr applications.
This provides some default settings and convenience wrappers for
building Zephyr applications needed by multiple commands.
See west.cmd.build for the build command itself.
'''
from west import cmake
from west import log
DEFAULT_BUILD_DIR = 'build'
'''Name of the default Zephyr build directory.'''
DEFAULT_CMAKE_GENERATOR = 'Ninja'
'''Name of the default CMake generator.'''
def is_zephyr_build(path):
'''Return true if and only if `path` appears to be a valid Zephyr
build directory.
"Valid" means the given path is a directory which contains a CMake
cache with a 'ZEPHYR_TOOLCHAIN_VARIANT' key.
'''
try:
cache = cmake.CMakeCache.from_build_dir(path)
except FileNotFoundError:
cache = {}
if 'ZEPHYR_TOOLCHAIN_VARIANT' in cache:
log.dbg('{} is a zephyr build directory'.format(path),
level=log.VERBOSE_EXTREME)
return True
else:
log.dbg('{} is NOT a valid zephyr build directory'.format(path),
level=log.VERBOSE_EXTREME)
return False

View File

@ -1,220 +0,0 @@
# Copyright (c) 2018 Open Source Foundries Limited.
#
# SPDX-License-Identifier: Apache-2.0
'''Helpers for dealing with CMake'''
from collections import OrderedDict
import os.path
import re
import subprocess
import shutil
from west import log
from west.util import quote_sh_list
__all__ = ['run_cmake', 'run_build',
'make_c_identifier',
'CMakeCacheEntry', 'CMakeCache']
DEFAULT_CACHE = 'CMakeCache.txt'
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 run_build(build_directory, extra_args=(), quiet=False):
'''Run cmake in build tool mode in `build_directory`'''
run_cmake(['--build', build_directory] + list(extra_args), quiet=quiet)
def make_c_identifier(string):
'''Make a C identifier from a string in the same way CMake does.
'''
# The behavior of CMake's string(MAKE_C_IDENTIFIER ...) is not
# precisely documented. This behavior matches the test case
# that introduced the function:
#
# https://gitlab.kitware.com/cmake/cmake/commit/0ab50aea4c4d7099b339fb38b4459d0debbdbd85
ret = []
alpha_under = re.compile('[A-Za-z_]')
alpha_num_under = re.compile('[A-Za-z0-9_]')
if not alpha_under.match(string):
ret.append('_')
for c in string:
if alpha_num_under.match(c):
ret.append(c)
else:
ret.append('_')
return ''.join(ret)
class CMakeCacheEntry:
'''Represents a CMake cache entry.
This class understands the type system in a CMakeCache.txt, and
converts the following cache types to Python types:
Cache Type Python type
---------- -------------------------------------------
FILEPATH str
PATH str
STRING str OR list of str (if ';' is in the value)
BOOL bool
INTERNAL str OR list of str (if ';' is in the value)
---------- -------------------------------------------
'''
# Regular expression for a cache entry.
#
# CMake variable names can include escape characters, allowing a
# wider set of names than is easy to match with a regular
# expresion. To be permissive here, use a non-greedy match up to
# the first colon (':'). This breaks if the variable name has a
# colon inside, but it's good enough.
CACHE_ENTRY = re.compile(
r'''(?P<name>.*?) # name
:(?P<type>FILEPATH|PATH|STRING|BOOL|INTERNAL) # type
=(?P<value>.*) # value
''', re.X)
@classmethod
def _to_bool(cls, val):
# Convert a CMake BOOL string into a Python bool.
#
# "True if the constant is 1, ON, YES, TRUE, Y, or a
# non-zero number. False if the constant is 0, OFF, NO,
# FALSE, N, IGNORE, NOTFOUND, the empty string, or ends in
# the suffix -NOTFOUND. Named boolean constants are
# case-insensitive. If the argument is not one of these
# constants, it is treated as a variable."
#
# https://cmake.org/cmake/help/v3.0/command/if.html
val = val.upper()
if val in ('ON', 'YES', 'TRUE', 'Y'):
return True
elif val in ('OFF', 'NO', 'FALSE', 'N', 'IGNORE', 'NOTFOUND', ''):
return False
elif val.endswith('-NOTFOUND'):
return False
else:
try:
v = int(val)
return v != 0
except ValueError as exc:
raise ValueError('invalid bool {}'.format(val)) from exc
@classmethod
def from_line(cls, line, line_no):
# Comments can only occur at the beginning of a line.
# (The value of an entry could contain a comment character).
if line.startswith('//') or line.startswith('#'):
return None
# Whitespace-only lines do not contain cache entries.
if not line.strip():
return None
m = cls.CACHE_ENTRY.match(line)
if not m:
return None
name, type_, value = (m.group(g) for g in ('name', 'type', 'value'))
if type_ == 'BOOL':
try:
value = cls._to_bool(value)
except ValueError as exc:
args = exc.args + ('on line {}: {}'.format(line_no, line),)
raise ValueError(args) from exc
elif type_ == 'STRING' or type_ == 'INTERNAL':
# If the value is a CMake list (i.e. is a string which
# contains a ';'), convert to a Python list.
if ';' in value:
value = value.split(';')
return CMakeCacheEntry(name, value)
def __init__(self, name, value):
self.name = name
self.value = value
def __str__(self):
fmt = 'CMakeCacheEntry(name={}, value={})'
return fmt.format(self.name, self.value)
class CMakeCache:
'''Parses and represents a CMake cache file.'''
@staticmethod
def from_build_dir(build_dir):
return CMakeCache(os.path.join(build_dir, DEFAULT_CACHE))
def __init__(self, cache_file):
self.cache_file = cache_file
self.load(cache_file)
def load(self, cache_file):
entries = []
with open(cache_file, 'r') as cache:
for line_no, line in enumerate(cache):
entry = CMakeCacheEntry.from_line(line, line_no)
if entry:
entries.append(entry)
self._entries = OrderedDict((e.name, e) for e in entries)
def get(self, name, default=None):
entry = self._entries.get(name)
if entry is not None:
return entry.value
else:
return default
def get_list(self, name, default=None):
if default is None:
default = []
entry = self._entries.get(name)
if entry is not None:
value = entry.value
if isinstance(value, list):
return value
elif isinstance(value, str):
return [value] if value else []
else:
msg = 'invalid value {} type {}'
raise RuntimeError(msg.format(value, type(value)))
else:
return default
def __contains__(self, name):
return name in self._entries
def __getitem__(self, name):
return self._entries[name].value
def __setitem__(self, name, entry):
if not isinstance(entry, CMakeCacheEntry):
msg = 'improper type {} for value {}, expecting CMakeCacheEntry'
raise TypeError(msg.format(type(entry), entry))
self._entries[name] = entry
def __delitem__(self, name):
del self._entries[name]
def __iter__(self):
return iter(self._entries.values())

View File

@ -1,74 +0,0 @@
# Copyright 2018 Open Source Foundries Limited.
#
# SPDX-License-Identifier: Apache-2.0
'''West's commands subpackage.
All commands should be implemented within modules in this package.
'''
from abc import ABC, abstractmethod
__all__ = ['CommandContextError', 'WestCommand']
class CommandContextError(RuntimeError):
'''Indicates that a context-dependent command could not be run.'''
class WestCommand(ABC):
'''Abstract superclass for a west command.
All top-level commands supported by west implement this interface.'''
def __init__(self, name, description, accepts_unknown_args=False):
'''Create a command instance.
`name`: the command's name, as entered by the user.
`description`: one-line command description to show to the user.
`accepts_unknown_args`: if true, the command can handle
arbitrary unknown command line arguments in its run()
method. Otherwise, passing unknown arguments will cause
UnknownArgumentsError to be raised.
'''
self.name = name
self.description = description
self._accept_unknown = accepts_unknown_args
def run(self, args, unknown):
'''Run the command.
`args`: known arguments parsed via `register_arguments()`
`unknown`: unknown arguments present on the command line
'''
if unknown and not self._accept_unknown:
self.parser.error('unexpected arguments: {}'.format(unknown))
self.do_run(args, unknown)
def add_parser(self, parser_adder):
'''Registers a parser for this command, and returns it.
'''
self.parser = self.do_add_parser(parser_adder)
return self.parser
#
# Mandatory subclass hooks
#
@abstractmethod
def do_add_parser(self, parser_adder):
'''Subclass method for registering command line arguments.
`parser_adder` is an argparse argument subparsers adder.'''
@abstractmethod
def do_run(self, args, unknown):
'''Subclasses must implement; called when the command is run.
`args` is the namespace of parsed known arguments.
If `accepts_unknown_args` was False when constructing this
object, `unknown` will be empty. Otherwise, it is an iterable
containing all unknown arguments present on the command line.
'''

View File

@ -1,288 +0,0 @@
# Copyright (c) 2018 Foundries.io
#
# SPDX-License-Identifier: Apache-2.0
import argparse
import os
from west import log
from west import cmake
from west.build import DEFAULT_BUILD_DIR, DEFAULT_CMAKE_GENERATOR, \
is_zephyr_build
from west.commands import WestCommand
BUILD_HELP = '''\
Convenience wrapper for building Zephyr applications.
This command attempts to do what you mean when run from a Zephyr
application source or a pre-existing build directory:
- When "west build" is run from a Zephyr build directory, the source
directory is obtained from the CMake cache, and that build directory
is re-compiled.
- Otherwise, the source directory defaults to the current working
directory, so running "west build" from a Zephyr application's
source directory compiles it.
The source and build directories can be explicitly set with the
--source-dir and --build-dir options. The build directory defaults to
'build' if it is not auto-detected. The build directory is always
created if it does not exist.
This command runs CMake to generate a build system if one is not
present in the build directory, then builds the application.
Subsequent builds try to avoid re-running CMake; you can force it
to run by setting --cmake.
To pass additional options to CMake, give them as extra arguments
after a '--' For example, "west build -- -DOVERLAY_CONFIG=some.conf" sets
an overlay config file. (Doing this forces a CMake run.)'''
class Build(WestCommand):
def __init__(self):
super(Build, self).__init__(
'build',
BUILD_HELP,
accepts_unknown_args=False)
self.source_dir = None
'''Source directory for the build, or None on error.'''
self.build_dir = None
'''Final build directory used to run the build, or None on error.'''
self.created_build_dir = False
'''True if the build directory was created; False otherwise.'''
self.run_cmake = False
'''True if CMake was run; False otherwise.
Note: this only describes CMake runs done by this command. The
build system generated by CMake may also update itself due to
internal logic.'''
self.cmake_cache = None
'''Final parsed CMake cache for the build, or None on error.'''
def do_add_parser(self, parser_adder):
parser = parser_adder.add_parser(
self.name,
formatter_class=argparse.RawDescriptionHelpFormatter,
description=self.description)
# Remember to update scripts/west-completion.bash if you add or remove
# flags
parser.add_argument('-b', '--board',
help='''Board to build for (must be given for the
first build, can be omitted later)''')
parser.add_argument('-s', '--source-dir',
help='''Explicitly set the source directory.
If not given and rebuilding an existing Zephyr
build directory, this is taken from the CMake
cache. Otherwise, the current directory is
assumed.''')
parser.add_argument('-d', '--build-dir',
help='''Explicitly sets the build directory.
If not given and the current directory is a Zephyr
build directory, it will be used; otherwise, "{}"
is assumed. The directory will be created if
it doesn't exist.'''.format(DEFAULT_BUILD_DIR))
parser.add_argument('-t', '--target',
help='''Override the build system target (e.g.
'clean', 'pristine', etc.)''')
parser.add_argument('-c', '--cmake', action='store_true',
help='Force CMake to run')
parser.add_argument('-f', '--force', action='store_true',
help='Ignore any errors and try to build anyway')
parser.add_argument('cmake_opts', nargs='*', metavar='cmake_opt',
help='Extra option to pass to CMake; implies -c')
return parser
def do_run(self, args, ignored):
self.args = args # Avoid having to pass them around
log.dbg('args:', args, level=log.VERBOSE_EXTREME)
self._sanity_precheck()
self._setup_build_dir()
if is_zephyr_build(self.build_dir):
self._update_cache()
if self.args.cmake or self.args.cmake_opts:
self.run_cmake = True
else:
self.run_cmake = True
self._setup_source_dir()
self._sanity_check()
log.inf('source directory: {}'.format(self.source_dir), colorize=True)
log.inf('build directory: {}{}'.
format(self.build_dir,
(' (created)' if self.created_build_dir
else '')),
colorize=True)
if self.cmake_cache:
board = self.cmake_cache.get('CACHED_BOARD')
elif self.args.board:
board = self.args.board
else:
board = 'UNKNOWN' # shouldn't happen
log.inf('BOARD:', board, colorize=True)
self._run_cmake(self.args.cmake_opts)
self._sanity_check()
self._update_cache()
extra_args = ['--target', args.target] if args.target else []
cmake.run_build(self.build_dir, extra_args=extra_args)
def _sanity_precheck(self):
app = self.args.source_dir
if app:
if not os.path.isdir(app):
self._check_force('source directory {} does not exist'.
format(app))
elif 'CMakeLists.txt' not in os.listdir(app):
self._check_force("{} doesn't contain a CMakeLists.txt".
format(app))
def _update_cache(self):
try:
self.cmake_cache = cmake.CMakeCache.from_build_dir(self.build_dir)
except FileNotFoundError:
pass
def _setup_build_dir(self):
# Initialize build_dir and created_build_dir attributes.
log.dbg('setting up build directory', level=log.VERBOSE_EXTREME)
if self.args.build_dir:
build_dir = self.args.build_dir
else:
cwd = os.getcwd()
if is_zephyr_build(cwd):
build_dir = cwd
else:
build_dir = DEFAULT_BUILD_DIR
build_dir = os.path.abspath(build_dir)
if os.path.exists(build_dir):
if not os.path.isdir(build_dir):
log.die('build directory {} exists and is not a directory'.
format(build_dir))
else:
os.makedirs(build_dir, exist_ok=False)
self.created_build_dir = True
self.run_cmake = True
self.build_dir = build_dir
def _setup_source_dir(self):
# Initialize source_dir attribute, either from command line argument,
# implicitly from the build directory's CMake cache, or using the
# default (current working directory).
log.dbg('setting up source directory', level=log.VERBOSE_EXTREME)
if self.args.source_dir:
source_dir = self.args.source_dir
elif self.cmake_cache:
source_dir = self.cmake_cache.get('APPLICATION_SOURCE_DIR')
if not source_dir:
# Maybe Zephyr changed the key? Give the user a way
# to retry, at least.
log.die("can't determine application from build directory "
"{}, please specify an application to build".
format(self.build_dir))
else:
source_dir = os.getcwd()
self.source_dir = os.path.abspath(source_dir)
def _sanity_check(self):
# Sanity check the build configuration.
# Side effect: may update cmake_cache attribute.
log.dbg('sanity checking the build', level=log.VERBOSE_EXTREME)
if self.source_dir == self.build_dir:
# There's no forcing this.
log.die('source and build directory {} cannot be the same; '
'use --build-dir {} to specify a build directory'.
format(self.source_dir, self.build_dir))
srcrel = os.path.relpath(self.source_dir)
if is_zephyr_build(self.source_dir):
self._check_force('it looks like {srcrel} is a build directory: '
'did you mean -build-dir {srcrel} instead?'.
format(srcrel=srcrel))
elif 'CMakeLists.txt' not in os.listdir(self.source_dir):
self._check_force('source directory "{srcrel}" does not contain '
'a CMakeLists.txt; is that really what you '
'want to build? (Use -s SOURCE_DIR to specify '
'the application source directory)'.
format(srcrel=srcrel))
if not is_zephyr_build(self.build_dir) and not self.args.board:
self._check_force('this looks like a new or clean build, '
'please provide --board')
if not self.cmake_cache:
return # That's all we can check without a cache.
cached_app = self.cmake_cache.get('APPLICATION_SOURCE_DIR')
log.dbg('APPLICATION_SOURCE_DIR:', cached_app,
level=log.VERBOSE_EXTREME)
source_abs = (os.path.abspath(self.args.source_dir)
if self.args.source_dir else None)
cached_abs = os.path.abspath(cached_app) if cached_app else None
if cached_abs and source_abs and source_abs != cached_abs:
self._check_force('build directory "{}" is for application "{}", '
'but source directory "{}" was specified; '
'please clean it or use --build-dir to set '
'another build directory'.
format(self.build_dir, cached_abs,
source_abs))
self.run_cmake = True # If they insist, we need to re-run cmake.
cached_board = self.cmake_cache.get('CACHED_BOARD')
log.dbg('CACHED_BOARD:', cached_board, level=log.VERBOSE_EXTREME)
if not cached_board and not self.args.board:
if self.created_build_dir:
self._check_force(
'Building for the first time: you must provide --board')
else:
self._check_force(
'Board is missing or unknown, please provide --board')
if self.args.board and cached_board and \
self.args.board != cached_board:
self._check_force('Build directory {} targets board {}, '
'but board {} was specified. (Clean that '
'directory or use --build-dir to specify '
'a different one.)'.
format(self.build_dir, cached_board,
self.args.board))
def _check_force(self, msg):
if not self.args.force:
log.err(msg)
log.die('refusing to proceed without --force due to above error')
def _run_cmake(self, cmake_opts):
if not self.run_cmake:
log.dbg('not running cmake; build system is present')
return
# It's unfortunate to have to use the undocumented -B and -H
# options to set the source and binary directories.
#
# However, it's the only known way to set that directory and
# run CMake from the current working directory. This is
# important because users expect invocations like this to Just
# Work:
#
# west build -- -DOVERLAY_CONFIG=relative-path.conf
final_cmake_args = ['-B{}'.format(self.build_dir),
'-H{}'.format(self.source_dir),
'-G{}'.format(DEFAULT_CMAKE_GENERATOR)]
if self.args.board:
final_cmake_args.append('-DBOARD={}'.format(self.args.board))
if cmake_opts:
final_cmake_args.extend(cmake_opts)
cmake.run_cmake(final_cmake_args)

View File

@ -1,70 +0,0 @@
# Copyright (c) 2018 Open Source Foundries Limited.
#
# SPDX-License-Identifier: Apache-2.0
'''west "debug" and "debugserver" commands.'''
from textwrap import dedent
from west.commands.run_common import desc_common, add_parser_common, \
do_run_common
from west.commands import WestCommand
class Debug(WestCommand):
def __init__(self):
super(Debug, self).__init__(
'debug',
dedent('''
Connect to the board, program the flash, and start a
debugging session.\n\n''') +
desc_common('debug'),
accepts_unknown_args=True)
def do_add_parser(self, parser_adder):
return add_parser_common(parser_adder, self)
def do_run(self, my_args, runner_args):
do_run_common(self, my_args, runner_args,
'ZEPHYR_BOARD_DEBUG_RUNNER')
class DebugServer(WestCommand):
def __init__(self):
super(DebugServer, self).__init__(
'debugserver',
dedent('''
Connect to the board and accept debug networking connections.
The debug server binds to a known port, and allows client software
started elsewhere to connect to it and debug the running
Zephyr image.\n\n''') +
desc_common('debugserver'),
accepts_unknown_args=True)
def do_add_parser(self, parser_adder):
return add_parser_common(parser_adder, self)
def do_run(self, my_args, runner_args):
do_run_common(self, my_args, runner_args,
'ZEPHYR_BOARD_DEBUG_RUNNER')
class Attach(WestCommand):
def __init__(self):
super(Attach, self).__init__(
'attach',
dedent('''
Connect to the board without programming the flash, and
start a debugging session.\n\n''') +
desc_common('attach'),
accepts_unknown_args=True)
def do_add_parser(self, parser_adder):
return add_parser_common(parser_adder, self)
def do_run(self, my_args, runner_args):
do_run_common(self, my_args, runner_args,
'ZEPHYR_BOARD_DEBUG_RUNNER')

View File

@ -1,26 +0,0 @@
# Copyright (c) 2018 Open Source Foundries Limited.
#
# SPDX-License-Identifier: Apache-2.0
'''west "flash" command'''
from west.commands.run_common import desc_common, add_parser_common, \
do_run_common
from west.commands import WestCommand
class Flash(WestCommand):
def __init__(self):
super(Flash, self).__init__(
'flash',
'Flash and run a binary on a board.\n\n' +
desc_common('flash'),
accepts_unknown_args=True)
def do_add_parser(self, parser_adder):
return add_parser_common(parser_adder, self)
def do_run(self, my_args, runner_args):
do_run_common(self, my_args, runner_args,
'ZEPHYR_BOARD_FLASH_RUNNER')

File diff suppressed because it is too large Load Diff

View File

@ -1,452 +0,0 @@
# Copyright (c) 2018 Open Source Foundries Limited.
#
# SPDX-License-Identifier: Apache-2.0
'''Common code used by commands which execute runners.
'''
import argparse
from os import getcwd, path
from subprocess import CalledProcessError
import textwrap
from west import cmake
from west import log
from west import util
from west.build import DEFAULT_BUILD_DIR, is_zephyr_build
from west.runners import get_runner_cls, ZephyrBinaryRunner
from west.runners.core import RunnerConfig
from west.commands import CommandContextError
# Context-sensitive help indentation.
# Don't change this, or output from argparse won't match up.
INDENT = ' ' * 2
def add_parser_common(parser_adder, command):
parser = parser_adder.add_parser(
command.name,
formatter_class=argparse.RawDescriptionHelpFormatter,
description=command.description)
# Remember to update scripts/west-completion.bash if you add or remove
# flags
parser.add_argument('-H', '--context', action='store_true',
help='''Rebuild application and print context-sensitive
help; this may be combined with --runner to restrict
output to a given runner.''')
group = parser.add_argument_group(title='General Options')
group.add_argument('-d', '--build-dir',
help='''Build directory to obtain runner information
from. If not given, this command tries to use build/
and then the current working directory, in that
order.''')
group.add_argument('-c', '--cmake-cache',
help='''Path to CMake cache file containing runner
configuration (this is generated by the Zephyr
build system when compiling binaries);
default: {}.
If this is a relative path, it is assumed relative to
the build directory. An absolute path can also be
given instead.'''.format(cmake.DEFAULT_CACHE))
group.add_argument('-r', '--runner',
help='''If given, overrides any cached {}
runner.'''.format(command.name))
group.add_argument('--skip-rebuild', action='store_true',
help='''If given, do not rebuild the application
before running {} commands.'''.format(command.name))
group = parser.add_argument_group(
title='Configuration overrides',
description=textwrap.dedent('''\
These values usually come from the Zephyr build system itself
as stored in the CMake cache; providing these options
overrides those settings.'''))
# Important:
#
# 1. The destination variables of these options must match
# the RunnerConfig slots.
# 2. The default values for all of these must be None.
#
# This is how we detect if the user provided them or not when
# overriding values from the cached configuration.
command_verb = "flash" if command == "flash" else "debug"
group.add_argument('--board-dir',
help='Zephyr board directory')
group.add_argument('--elf-file',
help='Path to elf file to {0}'.format(command_verb))
group.add_argument('--hex-file',
help='Path to hex file to {0}'.format(command_verb))
group.add_argument('--bin-file',
help='Path to binary file to {0}'.format(command_verb))
group.add_argument('--gdb',
help='Path to GDB, if applicable')
group.add_argument('--openocd',
help='Path to OpenOCD, if applicable')
group.add_argument(
'--openocd-search',
help='Path to add to OpenOCD search path, if applicable')
return parser
def desc_common(command_name):
return textwrap.dedent('''\
Any options not recognized by this command are passed to the
back-end {command} runner (run "west {command} --context"
for help on available runner-specific options).
If you need to pass an option to a runner which has the
same name as one recognized by this command, you can
end argument parsing with a '--', like so:
west {command} --{command}-arg=value -- --runner-arg=value2
'''.format(**{'command': command_name}))
def cached_runner_config(build_dir, cache):
'''Parse the RunnerConfig from a build directory and CMake Cache.'''
board_dir = cache['ZEPHYR_RUNNER_CONFIG_BOARD_DIR']
elf_file = cache.get('ZEPHYR_RUNNER_CONFIG_ELF_FILE',
cache['ZEPHYR_RUNNER_CONFIG_KERNEL_ELF'])
hex_file = cache.get('ZEPHYR_RUNNER_CONFIG_HEX_FILE',
cache['ZEPHYR_RUNNER_CONFIG_KERNEL_HEX'])
bin_file = cache.get('ZEPHYR_RUNNER_CONFIG_BIN_FILE',
cache['ZEPHYR_RUNNER_CONFIG_KERNEL_BIN'])
gdb = cache.get('ZEPHYR_RUNNER_CONFIG_GDB')
openocd = cache.get('ZEPHYR_RUNNER_CONFIG_OPENOCD')
openocd_search = cache.get('ZEPHYR_RUNNER_CONFIG_OPENOCD_SEARCH')
return RunnerConfig(build_dir, board_dir,
elf_file, hex_file, bin_file,
gdb=gdb, openocd=openocd,
openocd_search=openocd_search)
def _override_config_from_namespace(cfg, namespace):
'''Override a RunnerConfig's contents with command-line values.'''
for var in cfg.__slots__:
if var in namespace:
val = getattr(namespace, var)
if val is not None:
setattr(cfg, var, val)
def _build_dir(args, die_if_none=True):
# Get the build directory for the given argument list and environment.
if args.build_dir:
return args.build_dir
cwd = getcwd()
default = path.join(cwd, DEFAULT_BUILD_DIR)
if is_zephyr_build(default):
return default
elif is_zephyr_build(cwd):
return cwd
elif die_if_none:
log.die('--build-dir was not given, and neither {} '
'nor {} are zephyr build directories.'.
format(default, cwd))
else:
return None
def do_run_common(command, args, runner_args, cached_runner_var):
if args.context:
_dump_context(command, args, runner_args, cached_runner_var)
return
command_name = command.name
build_dir = _build_dir(args)
if not args.skip_rebuild:
try:
cmake.run_build(build_dir)
except CalledProcessError:
if args.build_dir:
log.die('cannot run {}, build in {} failed'.format(
command_name, args.build_dir))
else:
log.die('cannot run {}; no --build-dir given and build in '
'current directory {} failed'.format(command_name,
build_dir))
# Runner creation, phase 1.
#
# Get the default runner name from the cache, allowing a command
# line override. Get the ZephyrBinaryRunner class by name, and
# make sure it supports the command.
cache_file = path.join(build_dir, args.cmake_cache or cmake.DEFAULT_CACHE)
cache = cmake.CMakeCache(cache_file)
board = cache['CACHED_BOARD']
available = cache.get_list('ZEPHYR_RUNNERS')
if not available:
log.wrn('No cached runners are available in', cache_file)
runner = args.runner or cache.get(cached_runner_var)
if runner is None:
raise CommandContextError(textwrap.dedent("""
No {} runner available for {}. Please either specify one
manually, or check your board's documentation for
alternative instructions.""".format(command_name, board)))
log.inf('Using runner:', runner)
if runner not in available:
log.wrn('Runner {} is not configured for use with {}, '
'this may not work'.format(runner, board))
runner_cls = get_runner_cls(runner)
if command_name not in runner_cls.capabilities().commands:
log.die('Runner {} does not support command {}'.format(
runner, command_name))
# Runner creation, phase 2.
#
# At this point, the common options above are already parsed in
# 'args', and unrecognized arguments are in 'runner_args'.
#
# - Pull the RunnerConfig out of the cache
# - Override cached values with applicable command-line options
cfg = cached_runner_config(build_dir, cache)
_override_config_from_namespace(cfg, args)
# Runner creation, phase 3.
#
# - Pull out cached runner arguments, and append command-line
# values (which should override the cache)
# - Construct a runner-specific argument parser to handle cached
# values plus overrides given in runner_args
# - Parse arguments and create runner instance from final
# RunnerConfig and parsed arguments.
cached_runner_args = cache.get_list(
'ZEPHYR_RUNNER_ARGS_{}'.format(cmake.make_c_identifier(runner)))
assert isinstance(runner_args, list), runner_args
# If the user passed -- to force the parent argument parser to stop
# parsing, it will show up here, and needs to be filtered out.
runner_args = [arg for arg in runner_args if arg != '--']
final_runner_args = cached_runner_args + runner_args
parser = argparse.ArgumentParser(prog=runner)
runner_cls.add_parser(parser)
parsed_args, unknown = parser.parse_known_args(args=final_runner_args)
if unknown:
raise CommandContextError('Runner', runner,
'received unknown arguments', unknown)
runner = runner_cls.create(cfg, parsed_args)
runner.run(command_name)
#
# Context-specific help
#
def _dump_context(command, args, runner_args, cached_runner_var):
build_dir = _build_dir(args, die_if_none=False)
# Try to figure out the CMake cache file based on the build
# directory or an explicit argument.
if build_dir is not None:
cache_file = path.abspath(
path.join(build_dir, args.cmake_cache or cmake.DEFAULT_CACHE))
elif args.cmake_cache:
cache_file = path.abspath(args.cmake_cache)
else:
cache_file = None
# Load the cache itself, if possible.
if cache_file is None:
log.wrn('No build directory (--build-dir) or CMake cache '
'(--cache-file) given or found; output will be limited')
cache = None
else:
try:
cache = cmake.CMakeCache(cache_file)
except Exception:
log.die('Cannot load cache {}.'.format(cache_file))
# If we have a build directory, try to ensure build artifacts are
# up to date. If that doesn't work, still try to print information
# on a best-effort basis.
if build_dir and not args.skip_rebuild:
try:
cmake.run_build(build_dir)
except CalledProcessError:
msg = 'Failed re-building application; cannot load context. '
if args.build_dir:
msg += 'Is {} the right --build-dir?'.format(args.build_dir)
else:
msg += textwrap.dedent('''\
Use --build-dir (-d) to specify a build directory; the one
used was {}.'''.format(build_dir))
log.die('\n'.join(textwrap.wrap(msg, initial_indent='',
subsequent_indent=INDENT,
break_on_hyphens=False)))
if cache is None:
_dump_no_context_info(command, args)
if not args.runner:
return
if args.runner:
# Just information on one runner was requested.
_dump_one_runner_info(cache, args, build_dir, INDENT)
return
board = cache['CACHED_BOARD']
all_cls = {cls.name(): cls for cls in ZephyrBinaryRunner.get_runners() if
command.name in cls.capabilities().commands}
available = [r for r in cache.get_list('ZEPHYR_RUNNERS') if r in all_cls]
available_cls = {r: all_cls[r] for r in available if r in all_cls}
default_runner = cache.get(cached_runner_var)
cfg = cached_runner_config(build_dir, cache)
log.inf('All Zephyr runners which support {}:'.format(command.name),
colorize=True)
for line in util.wrap(', '.join(all_cls.keys()), INDENT):
log.inf(line)
log.inf('(Not all may work with this build, see available runners below.)',
colorize=True)
if cache is None:
log.warn('Missing or invalid CMake cache {}; there is no context.',
'Use --build-dir to specify the build directory.')
return
log.inf('Build directory:', colorize=True)
log.inf(INDENT + build_dir)
log.inf('Board:', colorize=True)
log.inf(INDENT + board)
log.inf('CMake cache:', colorize=True)
log.inf(INDENT + cache_file)
if not available:
# Bail with a message if no runners are available.
msg = ('No runners available for {}. '
'Consult the documentation for instructions on how to run '
'binaries on this target.').format(board)
for line in util.wrap(msg, ''):
log.inf(line, colorize=True)
return
log.inf('Available {} runners:'.format(command.name), colorize=True)
log.inf(INDENT + ', '.join(available))
log.inf('Additional options for available', command.name, 'runners:',
colorize=True)
for runner in available:
_dump_runner_opt_help(runner, all_cls[runner])
log.inf('Default {} runner:'.format(command.name), colorize=True)
log.inf(INDENT + default_runner)
_dump_runner_config(cfg, '', INDENT)
log.inf('Runner-specific information:', colorize=True)
for runner in available:
log.inf('{}{}:'.format(INDENT, runner), colorize=True)
_dump_runner_cached_opts(cache, runner, INDENT * 2, INDENT * 3)
_dump_runner_caps(available_cls[runner], INDENT * 2)
if len(available) > 1:
log.inf('(Add -r RUNNER to just print information about one runner.)',
colorize=True)
def _dump_no_context_info(command, args):
all_cls = {cls.name(): cls for cls in ZephyrBinaryRunner.get_runners() if
command.name in cls.capabilities().commands}
log.inf('All Zephyr runners which support {}:'.format(command.name),
colorize=True)
for line in util.wrap(', '.join(all_cls.keys()), INDENT):
log.inf(line)
if not args.runner:
log.inf('Add -r RUNNER to print more information about any runner.',
colorize=True)
def _dump_one_runner_info(cache, args, build_dir, indent):
runner = args.runner
cls = get_runner_cls(runner)
if cache is None:
_dump_runner_opt_help(runner, cls)
_dump_runner_caps(cls, '')
return
available = runner in cache.get_list('ZEPHYR_RUNNERS')
cfg = cached_runner_config(build_dir, cache)
log.inf('Build directory:', colorize=True)
log.inf(INDENT + build_dir)
log.inf('Board:', colorize=True)
log.inf(INDENT + cache['CACHED_BOARD'])
log.inf('CMake cache:', colorize=True)
log.inf(INDENT + cache.cache_file)
log.inf(runner, 'is available:', 'yes' if available else 'no',
colorize=True)
_dump_runner_opt_help(runner, cls)
_dump_runner_config(cfg, '', indent)
if available:
_dump_runner_cached_opts(cache, runner, '', indent)
_dump_runner_caps(cls, '')
if not available:
log.wrn('Runner', runner, 'is not configured in this build.')
def _dump_runner_caps(cls, base_indent):
log.inf('{}Capabilities:'.format(base_indent), colorize=True)
log.inf('{}{}'.format(base_indent + INDENT, cls.capabilities()))
def _dump_runner_opt_help(runner, cls):
# Construct and print the usage text
dummy_parser = argparse.ArgumentParser(prog='', add_help=False)
cls.add_parser(dummy_parser)
formatter = dummy_parser._get_formatter()
for group in dummy_parser._action_groups:
# Break the abstraction to filter out the 'flash', 'debug', etc.
# TODO: come up with something cleaner (may require changes
# in the runner core).
actions = group._group_actions
if len(actions) == 1 and actions[0].dest == 'command':
# This is the lone positional argument. Skip it.
continue
formatter.start_section('REMOVE ME')
formatter.add_text(group.description)
formatter.add_arguments(actions)
formatter.end_section()
# Get the runner help, with the "REMOVE ME" string gone
runner_help = '\n'.join(formatter.format_help().splitlines()[1:])
log.inf('{} options:'.format(runner), colorize=True)
log.inf(runner_help)
def _dump_runner_config(cfg, initial_indent, subsequent_indent):
log.inf('{}Cached common runner configuration:'.format(initial_indent),
colorize=True)
for var in cfg.__slots__:
log.inf('{}--{}={}'.format(subsequent_indent, var, getattr(cfg, var)))
def _dump_runner_cached_opts(cache, runner, initial_indent, subsequent_indent):
runner_args = _get_runner_args(cache, runner)
if not runner_args:
return
log.inf('{}Cached runner-specific options:'.format(initial_indent),
colorize=True)
for arg in runner_args:
log.inf('{}{}'.format(subsequent_indent, arg))
def _get_runner_args(cache, runner):
runner_ident = cmake.make_c_identifier(runner)
args_var = 'ZEPHYR_RUNNER_ARGS_{}'.format(runner_ident)
return cache.get_list(args_var)

View File

@ -1,95 +0,0 @@
# Copyright (c) 2018, Nordic Semiconductor ASA
#
# SPDX-License-Identifier: Apache-2.0
'''
Configuration file handling, using the standard configparser module.
'''
import configparser
import os
import platform
from west.util import west_dir
# Configuration values.
#
# Initially empty, populated in read_config(). Always having this available is
# nice in case something checks configuration values before the configuration
# file has been read (e.g. the log.py functions, to check color settings, and
# tests).
config = configparser.ConfigParser()
def read_config():
'''
Reads all configuration files, making the configuration values available as
a configparser.ConfigParser object in config.config. This object works
similarly to a dictionary: config.config['foo']['bar'] gets the value for
key 'bar' in section 'foo'.
Git conventions for configuration file locations are used. See the FILES
section in the git-config(1) man page.
The following configuration files are read.
System-wide:
Linux: /etc/westconfig
Mac OS: /usr/local/etc/westconfig
Windows: %PROGRAMDATA%\\west\\config
User-specific:
$XDG_CONFIG_HOME/west/config (on Linux)
and
~/.westconfig
($XDG_CONFIG_DIR defaults to ~/.config/ if unset.)
Instance-specific:
<West base directory>/west/config
Configuration values from later configuration files override configuration
from earlier ones. Instance-specific configuration values have the highest
precedence, and system-wide the lowest.
'''
# Gather (potential) configuration file paths
# System-wide and user-specific
if platform.system() == 'Linux':
# Probably wouldn't hurt to check $XDG_CONFIG_HOME (defaults to
# ~/.config) on all systems. It's listed in git-config(1). People were
# iffy about it as of writing though.
files = ['/etc/westconfig',
os.path.join(os.environ.get('XDG_CONFIG_HOME',
os.path.expanduser('~/.config')),
'west', 'config')]
elif platform.system() == 'Darwin': # Mac OS
# This was seen on a local machine ($(prefix) = /usr/local)
files = ['/usr/local/etc/westconfig']
elif platform.system() == 'Windows':
# Seen on a local machine
files = [os.path.expandvars('%PROGRAMDATA%\\west\\config')]
files.append(os.path.expanduser('~/.westconfig'))
# Repository-specific
files.append(os.path.join(west_dir(), 'config'))
#
# Parse all existing configuration files
#
config.read(files, encoding='utf-8')
def use_colors():
# Convenience function for reading the color.ui setting
return config.getboolean('color', 'ui', fallback=True)

View File

@ -1,105 +0,0 @@
# Copyright 2018 Open Source Foundries Limited.
#
# SPDX-License-Identifier: Apache-2.0
'''Logging module for west
Provides common methods for logging messages to display to the user.'''
from west import config
import colorama
import sys
VERBOSE_NONE = 0
'''Base verbosity level (zero), no verbose messages printed.'''
VERBOSE_NORMAL = 1
'''Base verbosity level, some verbose messages printed.'''
VERBOSE_VERY = 2
'''Very verbose output messages will be printed.'''
VERBOSE_EXTREME = 3
'''Extremely verbose output messages will be printed.'''
VERBOSE = VERBOSE_NONE
'''Global verbosity level. VERBOSE_NONE is the default.'''
def set_verbosity(value):
'''Set the logging verbosity level.'''
global VERBOSE
VERBOSE = int(value)
def dbg(*args, level=VERBOSE_NORMAL):
'''Print a verbose debug logging message.
The message is only printed if level is at least the current
verbosity level.'''
if level > VERBOSE:
return
print(*args)
def inf(*args, colorize=False):
'''Print an informational message.
colorize (default: False):
If True, the message is printed in bright green if stdout is a terminal.
'''
if not config.use_colors():
colorize = False
# This approach colorizes any sep= and end= text too, as expected.
#
# colorama automatically strips the ANSI escapes when stdout isn't a
# terminal (by wrapping sys.stdout).
if colorize:
print(colorama.Fore.LIGHTGREEN_EX, end='')
print(*args)
if colorize:
_reset_colors(sys.stdout)
def wrn(*args):
'''Print a warning.'''
if config.use_colors():
print(colorama.Fore.LIGHTRED_EX, end='', file=sys.stderr)
print('WARNING: ', end='', file=sys.stderr)
print(*args, file=sys.stderr)
if config.use_colors():
_reset_colors(sys.stderr)
def err(*args, fatal=False):
'''Print an error.'''
if config.use_colors():
print(colorama.Fore.LIGHTRED_EX, end='', file=sys.stderr)
print('FATAL ERROR: ' if fatal else 'ERROR: ', end='', file=sys.stderr)
print(*args, file=sys.stderr)
if config.use_colors():
_reset_colors(sys.stderr)
def die(*args, exit_code=1):
'''Print a fatal error, and abort with the given exit code.'''
err(*args, fatal=True)
sys.exit(exit_code)
def _reset_colors(file):
# The flush=True avoids issues with unrelated output from commands (usually
# Git) becoming colorized, due to the final attribute reset ANSI escape
# getting line-buffered
print(colorama.Style.RESET_ALL, end='', file=file, flush=True)

View File

@ -1,241 +0,0 @@
#!/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()

View File

@ -1,132 +0,0 @@
## A pykwalify schema for basic validation of the structure of a
## manifest YAML file. (Full validation would require additional work,
## e.g. to validate that remote URLs obey the URL format specified in
## rfc1738.)
##
## This schema has similar semantics to the repo XML format:
##
## https://gerrit.googlesource.com/git-repo/+/master/docs/manifest-format.txt
##
## However, the features don't map 1:1.
# The top-level manifest is a map. There may be multiple sections in the
# manifest file. Each section can be validated by their own schema.
# This schema validates the 'manifest' section.
type: map
mapping:
# The "defaults" key specifies some default values used in the
# rest of the manifest.
#
# The value is a map with the following keys:
#
# - remote: if given, this is the default remote in each project
# - revision: if given, this is the default revision to check
# out of each project
#
# See below for more information about remotes and projects.
#
# Examples:
#
# default:
# remote: zephyrproject-rtos
# revision: master
defaults:
required: false
type: map
mapping:
remote:
required: false
type: str
revision:
required: false
type: str
# The "remotes" key specifies a sequence of remotes, each of
# which has a name and a fetch URL.
#
# These work like repo remotes, in that they specify a URL
# prefix which remote-specific Git repositories hang off of.
# (This saves typing and makes it easier to move things around
# when most repositories are on the same server or GitHub
# organization.)
#
# Example:
#
# remotes:
# - name: zephyrproject-rtos
# url-base: https://github.com/zephyrproject-rtos
# - name: developer-fork
# url-base: https://github.com/a-developer
remotes:
required: true
type: seq
sequence:
- type: map
mapping:
name:
required: true
type: str
url-base:
required: true
type: str
# The "projects" key specifies a sequence of "projects",
# i.e. Git repositories. These work like repo projects, in that
# each project has a name, a remote, and optional additional
# metadata.
#
# Each project is a map with the following keys:
#
# - name: Mandatory, the name of the git repository. The clone
# URL is formed by remote url-base + '/' + name. The name cannot
# be one of the reserved values "west" and "manifest".
# - remote: Optional, the name of the remote to pull it from.
# If the remote is missing, the remote'key in the top-level
# defaults key is used instead. If both are missing, it's an error.
# - revision: Optional, the name of the revision to check out.
# If not given, the value from the default element will be used.
# If both are missing, then the default is 'master'.
# - path: Where to clone the repository locally. If missing,
# it's cloned at top level in a directory given by its name.
# - clone-depth: if given, it is a number which creates a shallow
# history in the cloned repository limited to the given number
# of commits.
#
# Example, using default and non-default remotes:
#
# projects:
# # Uses default remote (zephyrproject-rtos), so clone URL is:
# # https://github.com/zephyrproject-rtos/zephyr
# - name: zephyr
# # Manually specified remote; clone URL is:
# # https://github.com/a-developer/west
# - name: west
# remote: developer-fork
# # Manually specified remote, clone URL is:
# # https://github.com/zephyrproject-rtos/some-vendor-hal
# # Local clone path (relative to installation root) is:
# # ext/hal/some-vendor
# - name: some-vendor-hal
# remote: zephyrproject-rtos
# path: ext/hal/some-vendor
projects:
required: true
type: seq
sequence:
- type: map
mapping:
name:
required: true
type: str
remote:
required: false
type: str
revision:
required: false
type: text # SHAs could be only numbers
path:
required: false
type: str
clone-depth:
required: false
type: int

View File

@ -1,400 +0,0 @@
# Copyright (c) 2018, Nordic Semiconductor ASA
# Copyright 2018, Foundries.io Ltd
#
# SPDX-License-Identifier: Apache-2.0
'''Parser and abstract data types for west manifests.
The main class is Manifest. The recommended method for creating a
Manifest instance is via its from_file() or from_data() helper
methods.
There are additionally Defaults, Remote, and Project types defined,
which represent the values by the same names in a west
manifest. (I.e. "Remote" represents one of the elements in the
"remote" sequence in the manifest, and so on.) Some Default values,
such as the default project revision, may be supplied by this module
if they are not present in the manifest data.'''
import os
import pykwalify.core
import yaml
from west import util, log
# Todo: take from _bootstrap?
# Default west repository URL.
WEST_URL_DEFAULT = 'https://github.com/zephyrproject-rtos/west'
# Default revision to check out of the west repository.
WEST_REV_DEFAULT = 'master'
META_NAMES = ['west', 'manifest']
'''Names of the special "meta-projects", which are reserved and cannot
be used to name a project in the manifest file.'''
MANIFEST_SECTIONS = ['manifest', 'west']
'''Sections in the manifest file'''
def default_path():
'''Return the path to the default manifest in the west directory.
Raises WestNotFound if called from outside of a west working directory.'''
return os.path.join(util.west_dir(), 'manifest', 'default.yml')
class Manifest:
'''Represents the contents of a West manifest file.
The most convenient way to construct an instance is using the
from_file and from_data helper methods.'''
@staticmethod
def from_file(source_file=None, sections=MANIFEST_SECTIONS):
'''Create and return a new Manifest object given a source YAML file.
:param source_file: Path to a YAML file containing the manifest.
:param sections: Only parse specified sections from YAML file,
default: all sections are parsed.
If source_file is None, the value returned by default_path()
is used.
Raises MalformedManifest in case of validation errors.'''
if source_file is None:
source_file = default_path()
return Manifest(source_file=source_file, sections=sections)
@staticmethod
def from_data(source_data, sections=MANIFEST_SECTIONS):
'''Create and return a new Manifest object given parsed YAML data.
:param source_data: Parsed YAML data as a Python object.
:param sections: Only parse specified sections from YAML data,
default: all sections are parsed.
Raises MalformedManifest in case of validation errors.'''
return Manifest(source_data=source_data, sections=sections)
def __init__(self, source_file=None, source_data=None,
sections=MANIFEST_SECTIONS):
'''Create a new Manifest object.
:param source_file: Path to a YAML file containing the manifest.
:param source_data: Parsed YAML data as a Python object.
:param sections: Only parse specified sections from YAML file,
default: all sections are parsed.
Normally, it is more convenient to use the `from_file` and
`from_data` convenience factories than calling the constructor
directly.
Exactly one of the source_file and source_data parameters must
be given.
Raises MalformedManifest in case of validation errors.'''
if source_file and source_data:
raise ValueError('both source_file and source_data were given')
if source_file:
with open(source_file, 'r') as f:
self._data = yaml.safe_load(f.read())
path = source_file
else:
self._data = source_data
path = None
self.path = path
'''Path to the file containing the manifest, or None if created
from data rather than the file system.'''
if not self._data:
self._malformed('manifest contains no data')
if 'manifest' not in self._data:
self._malformed('manifest contains no manifest element')
for key in self._data:
if key in sections:
try:
pykwalify.core.Core(
source_data=self._data[key],
schema_files=[_SCHEMA_PATH[key]]
).validate()
except pykwalify.errors.SchemaError as e:
self._malformed(e, key)
self.defaults = None
'''west.manifest.Defaults object representing default values
in the manifest, either as specified by the user or west itself.'''
self.remotes = None
'''Sequence of west.manifest.Remote objects representing manifest
remotes.'''
self.projects = None
'''Sequence of west.manifest.Project objects representing manifest
projects.
Each element's values are fully initialized; there is no need
to consult the defaults field to supply missing values.'''
self.west_project = None
'''west.manifest.SpecialProject object representing the west meta
project.'''
# Set up the public attributes documented above, as well as
# any internal attributes needed to implement the public API.
self._load(self._data, sections)
def get_remote(self, name):
'''Get a manifest Remote, given its name.'''
return self._remotes_dict[name]
def _malformed(self, complaint, section='manifest'):
context = (' file {} '.format(self.path) if self.path
else ' data:\n{}\n'.format(self._data))
raise MalformedManifest('Malformed manifest{}(schema: {}):\n{}'
.format(context, _SCHEMA_PATH[section],
complaint))
def _load(self, data, sections):
# Initialize this instance's fields from values given in the
# manifest data, which must be validated according to the schema.
if 'west' in sections:
west = data.get('west', {})
url = west.get('url') or WEST_URL_DEFAULT
revision = west.get('revision') or WEST_REV_DEFAULT
self.west_project = SpecialProject('west',
url=url,
revision=revision,
path=os.path.join('west',
'west'))
# Next is the manifest section
if 'manifest' not in sections:
return
projects = []
project_abspaths = set()
manifest = data.get('manifest')
# Map from each remote's name onto that remote's data in the manifest.
remotes = tuple(Remote(r['name'], r['url-base']) for r in
manifest['remotes'])
remotes_dict = {r.name: r for r in remotes}
# Get any defaults out of the manifest.
#
# md = manifest defaults (dictionary with values parsed from
# the manifest)
md = manifest.get('defaults', dict())
mdrem = md.get('remote')
if mdrem:
# The default remote name, if provided, must refer to a
# well-defined remote.
if mdrem not in remotes_dict:
self._malformed('default remote {} is not defined'.
format(mdrem))
default_remote = remotes_dict[mdrem]
default_remote_name = mdrem
else:
default_remote = None
default_remote_name = None
defaults = Defaults(remote=default_remote, revision=md.get('revision'))
# mp = manifest project (dictionary with values parsed from
# the manifest)
for mp in manifest['projects']:
# Validate the project name.
name = mp['name']
if name in META_NAMES:
self._malformed('the name "{}" is reserved and cannot '.
format(name) +
'be used to name a manifest project')
# Validate the project remote.
remote_name = mp.get('remote', default_remote_name)
if remote_name is None:
self._malformed('project {} does not specify a remote'.
format(name))
if remote_name not in remotes_dict:
self._malformed('project {} remote {} is not defined'.
format(name, remote_name))
project = Project(name,
remotes_dict[remote_name],
defaults,
path=mp.get('path'),
clone_depth=mp.get('clone-depth'),
revision=mp.get('revision'))
# Two projects cannot have the same path. We use absolute
# paths to check for collisions to ensure paths are
# normalized (e.g. for case-insensitive file systems or
# in cases like on Windows where / or \ may serve as a
# path component separator).
if project.abspath in project_abspaths:
self._malformed('project {} path {} is already in use'.
format(project.name, project.path))
project_abspaths.add(project.abspath)
projects.append(project)
self.defaults = defaults
self.remotes = remotes
self._remotes_dict = remotes_dict
self.projects = tuple(projects)
class MalformedManifest(Exception):
'''Exception indicating that west manifest parsing failed due to a
malformed value.'''
# Definitions for Manifest attribute types.
class Defaults:
'''Represents default values in a manifest, either specified by the
user or by west itself.
Defaults are neither comparable nor hashable.'''
__slots__ = 'remote revision'.split()
def __init__(self, remote=None, revision=None):
'''Initialize a defaults value from manifest data.
:param remote: Remote instance corresponding to the default remote,
or None (an actual Remote object, not the name of
a remote as a string).
:param revision: Default Git revision; 'master' if not given.'''
if remote is not None:
_wrn_if_not_remote(remote)
if revision is None:
revision = 'master'
self.remote = remote
self.revision = revision
def __eq__(self, other):
return NotImplemented
def __repr__(self):
return 'Defaults(remote={}, revision={})'.format(repr(self.remote),
repr(self.revision))
class Remote:
'''Represents a remote defined in a west manifest.
Remotes may be compared for equality, but are not hashable.'''
__slots__ = 'name url_base'.split()
def __init__(self, name, url_base):
'''Initialize a remote from manifest data.
:param name: remote's name
:param url_base: remote's URL base.'''
if url_base.endswith('/'):
log.wrn('Remote', name, 'URL base', url_base,
'ends with a slash ("/"); these are automatically',
'appended by West')
self.name = name
self.url_base = url_base
def __eq__(self, other):
return self.name == other.name and self.url_base == other.url_base
def __repr__(self):
return 'Remote(name={}, url_base={})'.format(repr(self.name),
repr(self.url_base))
class Project:
'''Represents a project defined in a west manifest.
Projects are neither comparable nor hashable.'''
__slots__ = 'name remote url path abspath clone_depth revision'.split()
def __init__(self, name, remote, defaults, path=None, clone_depth=None,
revision=None):
'''Specify a Project by name, Remote, and optional information.
:param name: Project's user-defined name in the manifest.
:param remote: Remote instance corresponding to this Project as
specified in the manifest. This is used to build
the project's URL, and is also stored as an attribute.
:param defaults: If the revision parameter is not given, the project's
revision is set to defaults.revision.
:param path: Relative path to the project in the west
installation, if present in the manifest. If not given,
the project's ``name`` is used.
:param clone_depth: Nonnegative integer clone depth if present in
the manifest.
:param revision: Project revision as given in the manifest, if present.
If not given, defaults.revision is used instead.
'''
_wrn_if_not_remote(remote)
self.name = name
self.remote = remote
self.url = remote.url_base + '/' + name
self.path = os.path.normpath(path or name)
self.abspath = os.path.realpath(os.path.join(util.west_topdir(),
self.path))
self.clone_depth = clone_depth
self.revision = revision or defaults.revision
def __eq__(self, other):
return NotImplemented
def __repr__(self):
reprs = [repr(x) for x in
(self.name, self.remote, self.url, self.path,
self.abspath, self.clone_depth, self.revision)]
return ('Project(name={}, remote={}, url={}, path={}, abspath={}, '
'clone_depth={}, revision={})').format(*reprs)
class SpecialProject(Project):
'''Represents a special project, e.g. the west or manifest project.
Projects are neither comparable nor hashable.'''
def __init__(self, name, path=None, revision=None, url=None):
'''Specify a Special Project by name, and url, and optional information.
:param name: Special Project's user-defined name in the manifest
:param path: Relative path to the project in the west
installation, if present in the manifest. If None,
the project's ``name`` is used.
:param revision: Project revision as given in the manifest, if present.
:param url: Complete URL for special project.
'''
self.name = name
self.url = url
self.path = path or name
self.abspath = os.path.realpath(os.path.join(util.west_topdir(),
self.path))
self.revision = revision
self.remote = None
self.clone_depth = None
def _wrn_if_not_remote(remote):
if not isinstance(remote, Remote):
log.wrn('Remote', remote, 'is not a Remote instance')
_SCHEMA_PATH = {'manifest': os.path.join(os.path.dirname(__file__),
"manifest-schema.yml"),
'west': os.path.join(os.path.dirname(__file__),
"_bootstrap",
"west-schema.yml")}

View File

@ -1,35 +0,0 @@
# Copyright (c) 2017 Linaro Limited.
#
# SPDX-License-Identifier: Apache-2.0
from west.runners.core import ZephyrBinaryRunner
# We import these here to ensure the ZephyrBinaryRunner subclasses are
# defined; otherwise, ZephyrBinaryRunner.create_for_shell_script()
# won't work.
# Explicitly silence the unused import warning.
# flake8: noqa: F401
from west.runners import arc
from west.runners import bossac
from west.runners import dfu
from west.runners import esp32
from west.runners import jlink
from west.runners import nios2
from west.runners import nrfjprog
from west.runners import nsim
from west.runners import openocd
from west.runners import pyocd
from west.runners import qemu
from west.runners import xtensa
from west.runners import intel_s1000
from west.runners import blackmagicprobe
def get_runner_cls(runner):
'''Get a runner's class object, given its name.'''
for cls in ZephyrBinaryRunner.get_runners():
if cls.name() == runner:
return cls
raise ValueError('unknown runner "{}"'.format(runner))
__all__ = ['ZephyrBinaryRunner', 'get_runner_cls']

View File

@ -1,107 +0,0 @@
# Copyright (c) 2017 Linaro Limited.
# Copyright (c) 2017 Open Source Foundries Limited.
#
# SPDX-License-Identifier: Apache-2.0
'''ARC architecture-specific runners.'''
from os import path
from west.runners.core import ZephyrBinaryRunner
DEFAULT_ARC_TCL_PORT = 6333
DEFAULT_ARC_TELNET_PORT = 4444
DEFAULT_ARC_GDB_PORT = 3333
class EmStarterKitBinaryRunner(ZephyrBinaryRunner):
'''Runner front-end for the EM Starterkit board, using openocd.'''
# This unusual 'flash' implementation matches the original shell script.
#
# It works by starting a GDB server in a separate session, connecting a
# client to it to load the program, and running 'continue' within the
# client to execute the application.
#
def __init__(self, cfg,
tui=False, tcl_port=DEFAULT_ARC_TCL_PORT,
telnet_port=DEFAULT_ARC_TELNET_PORT,
gdb_port=DEFAULT_ARC_GDB_PORT):
super(EmStarterKitBinaryRunner, self).__init__(cfg)
self.gdb_cmd = [cfg.gdb] + (['-tui'] if tui else [])
search_args = []
if cfg.openocd_search is not None:
search_args = ['-s', cfg.openocd_search]
self.openocd_cmd = [cfg.openocd or 'openocd'] + search_args
self.tcl_port = tcl_port
self.telnet_port = telnet_port
self.gdb_port = gdb_port
@classmethod
def name(cls):
return 'em-starterkit'
@classmethod
def do_add_parser(cls, parser):
parser.add_argument('--tui', default=False, action='store_true',
help='if given, GDB uses -tui')
parser.add_argument('--tcl-port', default=DEFAULT_ARC_TCL_PORT,
help='openocd TCL port, defaults to 6333')
parser.add_argument('--telnet-port', default=DEFAULT_ARC_TELNET_PORT,
help='openocd telnet port, defaults to 4444')
parser.add_argument('--gdb-port', default=DEFAULT_ARC_GDB_PORT,
help='openocd gdb port, defaults to 3333')
@classmethod
def create(cls, cfg, args):
if cfg.gdb is None:
raise ValueError('--gdb not provided at command line')
return EmStarterKitBinaryRunner(
cfg,
tui=args.tui, tcl_port=args.tcl_port, telnet_port=args.telnet_port,
gdb_port=args.gdb_port)
def do_run(self, command, **kwargs):
kwargs['openocd-cfg'] = path.join(self.cfg.board_dir, 'support',
'openocd.cfg')
if command in {'flash', 'debug'}:
self.flash_debug(command, **kwargs)
else:
self.debugserver(**kwargs)
def flash_debug(self, command, **kwargs):
config = kwargs['openocd-cfg']
server_cmd = (self.openocd_cmd +
['-f', config] +
['-c', 'tcl_port {}'.format(self.tcl_port),
'-c', 'telnet_port {}'.format(self.telnet_port),
'-c', 'gdb_port {}'.format(self.gdb_port),
'-c', 'init',
'-c', 'targets',
'-c', 'halt'])
continue_arg = []
if command == 'flash':
continue_arg = ['-ex', 'set confirm off', '-ex', 'monitor resume',
'-ex', 'quit']
gdb_cmd = (self.gdb_cmd +
['-ex', 'target remote :{}'.format(self.gdb_port),
'-ex', 'load'] +
continue_arg +
[self.cfg.elf_file])
self.run_server_and_client(server_cmd, gdb_cmd)
def debugserver(self, **kwargs):
config = kwargs['openocd-cfg']
cmd = (self.openocd_cmd +
['-f', config,
'-c', 'init',
'-c', 'targets',
'-c', 'reset halt'])
self.check_call(cmd)

View File

@ -1,96 +0,0 @@
# Copyright (c) 2018 Roman Tataurov <diytronic@yandex.ru>
# Modified 2018 Tavish Naruka <tavishnaruka@gmail.com>
#
# SPDX-License-Identifier: Apache-2.0
'''Runner for flashing with Black Magic Probe.'''
# https://github.com/blacksphere/blackmagic/wiki
from west.runners.core import ZephyrBinaryRunner, RunnerCaps
class BlackMagicProbeRunner(ZephyrBinaryRunner):
'''Runner front-end for Black Magic probe.'''
def __init__(self, cfg, gdb_serial):
super(BlackMagicProbeRunner, self).__init__(cfg)
self.gdb = [cfg.gdb] if cfg.gdb else None
self.elf_file = cfg.elf_file
self.gdb_serial = gdb_serial
@classmethod
def name(cls):
return 'blackmagicprobe'
@classmethod
def capabilities(cls):
return RunnerCaps(commands={'flash', 'debug', 'attach'})
@classmethod
def create(cls, cfg, args):
return BlackMagicProbeRunner(cfg, args.gdb_serial)
@classmethod
def do_add_parser(cls, parser):
parser.add_argument('--gdb-serial', default='/dev/ttyACM0',
help='GDB serial port')
def bmp_flash(self, command, **kwargs):
if self.gdb is None:
raise ValueError('Cannot flash; gdb is missing')
if self.elf_file is None:
raise ValueError('Cannot debug; elf file is missing')
command = (self.gdb +
['-ex', "set confirm off",
'-ex', "target extended-remote {}".format(self.gdb_serial),
'-ex', "monitor swdp_scan",
'-ex', "attach 1",
'-ex', "load {}".format(self.elf_file),
'-ex', "kill",
'-ex', "quit",
'-silent'])
self.check_call(command)
def bmp_attach(self, command, **kwargs):
if self.gdb is None:
raise ValueError('Cannot attach; gdb is missing')
if self.elf_file is None:
command = (self.gdb +
['-ex', "set confirm off",
'-ex', "target extended-remote {}".format(
self.gdb_serial),
'-ex', "monitor swdp_scan",
'-ex', "attach 1"])
else:
command = (self.gdb +
['-ex', "set confirm off",
'-ex', "target extended-remote {}".format(
self.gdb_serial),
'-ex', "monitor swdp_scan",
'-ex', "attach 1",
'-ex', "file {}".format(self.elf_file)])
self.check_call(command)
def bmp_debug(self, command, **kwargs):
if self.gdb is None:
raise ValueError('Cannot debug; gdb is missing')
if self.elf_file is None:
raise ValueError('Cannot debug; elf file is missing')
command = (self.gdb +
['-ex', "set confirm off",
'-ex', "target extended-remote {}".format(self.gdb_serial),
'-ex', "monitor swdp_scan",
'-ex', "attach 1",
'-ex', "file {}".format(self.elf_file),
'-ex', "load {}".format(self.elf_file)])
self.check_call(command)
def do_run(self, command, **kwargs):
if command == 'flash':
self.bmp_flash(command, **kwargs)
elif command == 'debug':
self.bmp_debug(command, **kwargs)
elif command == 'attach':
self.bmp_attach(command, **kwargs)
else:
self.bmp_flash(command, **kwargs)

View File

@ -1,54 +0,0 @@
# Copyright (c) 2017 Linaro Limited.
#
# SPDX-License-Identifier: Apache-2.0
'''bossac-specific runner (flash only) for Atmel SAM microcontrollers.'''
import platform
from west.runners.core import ZephyrBinaryRunner, RunnerCaps
DEFAULT_BOSSAC_PORT = '/dev/ttyACM0'
class BossacBinaryRunner(ZephyrBinaryRunner):
'''Runner front-end for bossac.'''
def __init__(self, cfg, bossac='bossac', port=DEFAULT_BOSSAC_PORT):
super(BossacBinaryRunner, self).__init__(cfg)
self.bossac = bossac
self.port = port
@classmethod
def name(cls):
return 'bossac'
@classmethod
def capabilities(cls):
return RunnerCaps(commands={'flash'})
@classmethod
def do_add_parser(cls, parser):
parser.add_argument('--bossac', default='bossac',
help='path to bossac, default is bossac')
parser.add_argument('--bossac-port', default='/dev/ttyACM0',
help='serial port to use, default is /dev/ttyACM0')
@classmethod
def create(cls, cfg, args):
return BossacBinaryRunner(cfg, bossac=args.bossac,
port=args.bossac_port)
def do_run(self, command, **kwargs):
if platform.system() != 'Linux':
msg = 'CAUTION: No flash tool for your host system found!'
raise NotImplementedError(msg)
cmd_stty = ['stty', '-F', self.port, 'raw', 'ispeed', '1200',
'ospeed', '1200', 'cs8', '-cstopb', 'ignpar', 'eol', '255',
'eof', '255']
cmd_flash = [self.bossac, '-p', self.port, '-R', '-e', '-w', '-v',
'-b', self.cfg.bin_file]
self.check_call(cmd_stty)
self.check_call(cmd_flash)

View File

@ -1,508 +0,0 @@
#! /usr/bin/env python3
# Copyright (c) 2017 Linaro Limited.
# Copyright (c) 2017 Open Source Foundries Limited.
#
# SPDX-License-Identifier: Apache-2.0
"""Zephyr binary runner core interfaces
This provides the core ZephyrBinaryRunner class meant for public use,
as well as some other helpers for concrete runner classes.
"""
import abc
import argparse
import os
import platform
import signal
import subprocess
from west import log
from west.util import quote_sh_list
# Turn on to enable just printing the commands that would be run,
# without actually running them. This can break runners that are expecting
# output or if one command depends on another, so it's just for debugging.
JUST_PRINT = False
class _DebugDummyPopen:
def terminate(self):
pass
def wait(self):
pass
MAX_PORT = 49151
class NetworkPortHelper:
'''Helper class for dealing with local IP network ports.'''
def get_unused_ports(self, starting_from):
'''Find unused network ports, starting at given values.
starting_from is an iterable of ports the caller would like to use.
The return value is an iterable of ports, in the same order, using
the given values if they were unused, or the next sequentially
available unused port otherwise.
Ports may be bound between this call's check and actual usage, so
callers still need to handle errors involving returned ports.'''
start = list(starting_from)
used = self._used_now()
ret = []
for desired in start:
port = desired
while port in used:
port += 1
if port > MAX_PORT:
msg = "ports above {} are in use"
raise ValueError(msg.format(desired))
used.add(port)
ret.append(port)
return ret
def _used_now(self):
handlers = {
'Windows': self._used_now_windows,
'Linux': self._used_now_linux,
'Darwin': self._used_now_darwin,
}
handler = handlers[platform.system()]
return handler()
def _used_now_windows(self):
cmd = ['netstat', '-a', '-n', '-p', 'tcp']
return self._parser_windows(cmd)
def _used_now_linux(self):
cmd = ['ss', '-a', '-n', '-t']
return self._parser_linux(cmd)
def _used_now_darwin(self):
cmd = ['netstat', '-a', '-n', '-p', 'tcp']
return self._parser_darwin(cmd)
def _parser_windows(self, cmd):
out = subprocess.check_output(cmd).split(b'\r\n')
used_bytes = [x.split()[1].rsplit(b':', 1)[1] for x in out
if x.startswith(b' TCP')]
return {int(b) for b in used_bytes}
def _parser_linux(self, cmd):
out = subprocess.check_output(cmd).splitlines()[1:]
used_bytes = [s.split()[3].rsplit(b':', 1)[1] for s in out]
return {int(b) for b in used_bytes}
def _parser_darwin(self, cmd):
out = subprocess.check_output(cmd).split(b'\n')
used_bytes = [x.split()[3].rsplit(b':', 1)[1] for x in out
if x.startswith(b'tcp')]
return {int(b) for b in used_bytes}
class BuildConfiguration:
'''This helper class provides access to build-time configuration.
Configuration options can be read as if the object were a dict,
either object['CONFIG_FOO'] or object.get('CONFIG_FOO').
Configuration values in .config and generated_dts_board.conf are
available.'''
def __init__(self, build_dir):
self.build_dir = build_dir
self.options = {}
self._init()
def __getitem__(self, item):
return self.options[item]
def get(self, option, *args):
return self.options.get(option, *args)
def _init(self):
build_z = os.path.join(self.build_dir, 'zephyr')
generated = os.path.join(build_z, 'include', 'generated')
files = [os.path.join(build_z, '.config'),
os.path.join(generated, 'generated_dts_board.conf')]
for f in files:
self._parse(f)
def _parse(self, filename):
with open(filename, 'r') as f:
for line in f:
line = line.strip()
if not line or line.startswith('#'):
continue
option, value = line.split('=', 1)
self.options[option] = self._parse_value(value)
def _parse_value(self, value):
if value.startswith('"') or value.startswith("'"):
return value.split()
try:
return int(value, 0)
except ValueError:
return value
class RunnerCaps:
'''This class represents a runner class's capabilities.
Each capability is represented as an attribute with the same
name. Flag attributes are True or False.
Available capabilities:
- commands: set of supported commands; default is {'flash',
'debug', 'debugserver', 'attach'}.
- flash_addr: whether the runner supports flashing to an
arbitrary address. Default is False. If true, the runner
must honor the --dt-flash option.
'''
def __init__(self,
commands={'flash', 'debug', 'debugserver', 'attach'},
flash_addr=False):
self.commands = commands
self.flash_addr = bool(flash_addr)
def __str__(self):
return 'RunnerCaps(commands={}, flash_addr={})'.format(
self.commands, self.flash_addr)
class RunnerConfig:
'''Runner execution-time configuration.
This is a common object shared by all runners. Individual runners
can register specific configuration options using their
do_add_parser() hooks.
This class's __slots__ contains exactly the configuration variables.
'''
__slots__ = ['build_dir', 'board_dir', 'elf_file', 'hex_file',
'bin_file', 'gdb', 'openocd', 'openocd_search']
# TODO: revisit whether we can get rid of some of these. Having
# tool-specific configuration options here is a layering
# violation, but it's very convenient to have a single place to
# store the locations of tools (like gdb and openocd) that are
# needed by multiple ZephyrBinaryRunner subclasses.
def __init__(self, build_dir, board_dir,
elf_file, hex_file, bin_file,
gdb=None, openocd=None, openocd_search=None):
self.build_dir = build_dir
'''Zephyr application build directory'''
self.board_dir = board_dir
'''Zephyr board directory'''
self.elf_file = elf_file
'''Path to the elf file that the runner should operate on'''
self.hex_file = hex_file
'''Path to the hex file that the runner should operate on'''
self.bin_file = bin_file
'''Path to the bin file that the runner should operate on'''
self.gdb = gdb
''''Path to GDB compatible with the target, may be None.'''
self.openocd = openocd
'''Path to OpenOCD to use for this target, may be None.'''
self.openocd_search = openocd_search
'''directory to add to OpenOCD search path, may be None.'''
_YN_CHOICES = ['Y', 'y', 'N', 'n', 'yes', 'no', 'YES', 'NO']
class _DTFlashAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
if values.lower().startswith('y'):
namespace.dt_flash = True
else:
namespace.dt_flash = False
class ZephyrBinaryRunner(abc.ABC):
'''Abstract superclass for binary runners (flashers, debuggers).
**Note**: these APIs are still evolving, and will change!
With some exceptions, boards supported by Zephyr must provide
generic means to be flashed (have a Zephyr firmware binary
permanently installed on the device for running) and debugged
(have a breakpoint debugger and program loader on a host
workstation attached to a running target).
This is supported by three top-level commands managed by the
Zephyr build system:
- 'flash': flash a previously configured binary to the board,
start execution on the target, then return.
- 'debug': connect to the board via a debugging protocol, program
the flash, then drop the user into a debugger interface with
symbol tables loaded from the current binary, and block until it
exits.
- 'debugserver': connect via a board-specific debugging protocol,
then reset and halt the target. Ensure the user is now able to
connect to a debug server with symbol tables loaded from the
binary.
- 'attach': connect to the board via a debugging protocol, then drop
the user into a debugger interface with symbol tables loaded from
the current binary, and block until it exits. Unlike 'debug', this
command does not program the flash.
This class provides an API for these commands. Every runner has a
name (like 'pyocd'), and declares commands it can handle (like
'flash'). Zephyr boards (like 'nrf52_pca10040') declare compatible
runner(s) by name to the build system, which makes concrete runner
instances to execute commands via this class.
If your board can use an existing runner, all you have to do is
give its name to the build system. How to do that is out of the
scope of this documentation, but use the existing boards as a
starting point.
If you want to define and use your own runner:
1. Define a ZephyrBinaryRunner subclass, and implement its
abstract methods. You may need to override capabilities().
2. Make sure the Python module defining your runner class is
imported, e.g. by editing this package's __init__.py (otherwise,
get_runners() won't work).
3. Give your runner's name to the Zephyr build system in your
board's build files.
For command-line invocation from the Zephyr build system, runners
define their own argparse-based interface through the common
add_parser() (and runner-specific do_add_parser() it delegates
to), and provide a way to create instances of themselves from
a RunnerConfig and parsed runner-specific arguments via create().
Runners use a variety of target-specific tools and configuration
values, the user interface to which is abstracted by this
class. Each runner subclass should take any values it needs to
execute one of these commands in its constructor. The actual
command execution is handled in the run() method.'''
def __init__(self, cfg):
'''Initialize core runner state.
`cfg` is a RunnerConfig instance.'''
self.cfg = cfg
@staticmethod
def get_runners():
'''Get a list of all currently defined runner classes.'''
return ZephyrBinaryRunner.__subclasses__()
@classmethod
@abc.abstractmethod
def name(cls):
'''Return this runner's user-visible name.
When choosing a name, pick something short and lowercase,
based on the name of the tool (like openocd, jlink, etc.) or
the target architecture/board (like xtensa, em-starterkit,
etc.).'''
@classmethod
def capabilities(cls):
'''Returns a RunnerCaps representing this runner's capabilities.
This implementation returns the default capabilities.
Subclasses should override appropriately if needed.'''
return RunnerCaps()
@classmethod
def add_parser(cls, parser):
'''Adds a sub-command parser for this runner.
The given object, parser, is a sub-command parser from the
argparse module. For more details, refer to the documentation
for argparse.ArgumentParser.add_subparsers().
The lone common optional argument is:
* --dt-flash (if the runner capabilities includes flash_addr)
Runner-specific options are added through the do_add_parser()
hook.'''
# Common options that depend on runner capabilities.
if cls.capabilities().flash_addr:
parser.add_argument('--dt-flash', default='n', choices=_YN_CHOICES,
action=_DTFlashAction,
help='''If 'yes', use configuration generated
by device tree (DT) to compute flash
addresses.''')
# Runner-specific options.
cls.do_add_parser(parser)
@classmethod
@abc.abstractmethod
def do_add_parser(cls, parser):
'''Hook for adding runner-specific options.'''
@classmethod
@abc.abstractmethod
def create(cls, cfg, args):
'''Create an instance from command-line arguments.
- `cfg`: RunnerConfig instance (pass to superclass __init__)
- `args`: runner-specific argument namespace parsed from
execution environment, as specified by `add_parser()`.'''
@classmethod
def get_flash_address(cls, args, build_conf, default=0x0):
'''Helper method for extracting a flash address.
If args.dt_flash is true, get the address from the
BoardConfiguration, build_conf. (If
CONFIG_HAS_FLASH_LOAD_OFFSET is n in that configuration, it
returns CONFIG_FLASH_BASE_ADDRESS. Otherwise, it returns
CONFIG_FLASH_BASE_ADDRESS + CONFIG_FLASH_LOAD_OFFSET.)
Otherwise (when args.dt_flash is False), the default value is
returned.'''
if args.dt_flash:
if build_conf['CONFIG_HAS_FLASH_LOAD_OFFSET']:
return (build_conf['CONFIG_FLASH_BASE_ADDRESS'] +
build_conf['CONFIG_FLASH_LOAD_OFFSET'])
else:
return build_conf['CONFIG_FLASH_BASE_ADDRESS']
else:
return default
def run(self, command, **kwargs):
'''Runs command ('flash', 'debug', 'debugserver', 'attach').
This is the main entry point to this runner.'''
caps = self.capabilities()
if command not in caps.commands:
raise ValueError('runner {} does not implement command {}'.format(
self.name(), command))
self.do_run(command, **kwargs)
@abc.abstractmethod
def do_run(self, command, **kwargs):
'''Concrete runner; run() delegates to this. Implement in subclasses.
In case of an unsupported command, raise a ValueError.'''
def run_server_and_client(self, server, client):
'''Run a server that ignores SIGINT, and a client that handles it.
This routine portably:
- creates a Popen object for the ``server`` command which ignores
SIGINT
- runs ``client`` in a subprocess while temporarily ignoring SIGINT
- cleans up the server after the client exits.
It's useful to e.g. open a GDB server and client.'''
server_proc = self.popen_ignore_int(server)
previous = signal.signal(signal.SIGINT, signal.SIG_IGN)
try:
self.check_call(client)
finally:
signal.signal(signal.SIGINT, previous)
server_proc.terminate()
server_proc.wait()
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_call(self, cmd):
'''Subclass subprocess.check_call() 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
log.dbg(quoted)
try:
subprocess.check_call(cmd)
except subprocess.CalledProcessError:
raise
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 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)

View File

@ -1,121 +0,0 @@
# Copyright (c) 2017 Linaro Limited.
#
# SPDX-License-Identifier: Apache-2.0
'''Runner for flashing with dfu-util.'''
from collections import namedtuple
import sys
import time
from west import log
from west.runners.core import ZephyrBinaryRunner, RunnerCaps, \
BuildConfiguration
DfuSeConfig = namedtuple('DfuSeConfig', ['address', 'options'])
class DfuUtilBinaryRunner(ZephyrBinaryRunner):
'''Runner front-end for dfu-util.'''
def __init__(self, cfg, pid, alt, img, exe='dfu-util',
dfuse_config=None):
super(DfuUtilBinaryRunner, self).__init__(cfg)
self.alt = alt
self.img = img
self.cmd = [exe, '-d,{}'.format(pid)]
try:
self.list_pattern = ', alt={},'.format(int(self.alt))
except ValueError:
self.list_pattern = ', name="{}",'.format(self.alt)
if dfuse_config is None:
self.dfuse = False
else:
self.dfuse = True
self.dfuse_config = dfuse_config
@classmethod
def name(cls):
return 'dfu-util'
@classmethod
def capabilities(cls):
return RunnerCaps(commands={'flash'}, flash_addr=True)
@classmethod
def do_add_parser(cls, parser):
# Required:
parser.add_argument("--pid", required=True,
help="USB VID:PID of the board")
parser.add_argument("--alt", required=True,
help="interface alternate setting number or name")
# Optional:
parser.add_argument("--img",
help="binary to flash, default is --bin-file")
parser.add_argument("--dfuse", default=False, action='store_true',
help='''set if target is a DfuSe device;
implies --dt-flash.''')
parser.add_argument("--dfuse-modifiers", default='leave',
help='''colon-separated list of DfuSe modifiers
(default is "leave", which starts execution
immediately); --dfuse must also be given for this
option to take effect.''')
parser.add_argument('--dfu-util', default='dfu-util',
help='dfu-util executable; defaults to "dfu-util"')
@classmethod
def create(cls, cfg, args):
if args.img is None:
args.img = cfg.bin_file
if args.dfuse:
args.dt_flash = True # --dfuse implies --dt-flash.
build_conf = BuildConfiguration(cfg.build_dir)
dcfg = DfuSeConfig(address=cls.get_flash_address(args, build_conf),
options=args.dfuse_modifiers)
else:
dcfg = None
return DfuUtilBinaryRunner(cfg, args.pid, args.alt, args.img,
exe=args.dfu_util, dfuse_config=dcfg)
def find_device(self):
cmd = list(self.cmd) + ['-l']
output = self.check_output(cmd)
output = output.decode(sys.getdefaultencoding())
return self.list_pattern in output
def do_run(self, command, **kwargs):
reset = False
if not self.find_device():
reset = True
log.dbg('Device not found, waiting for it',
level=log.VERBOSE_EXTREME)
# Use of print() here is advised. We don't want to lose
# this information in a separate log -- this is
# interactive and requires a terminal.
print('Please reset your board to switch to DFU mode...')
while not self.find_device():
time.sleep(0.1)
cmd = list(self.cmd)
if self.dfuse:
# http://dfu-util.sourceforge.net/dfuse.html
dcfg = self.dfuse_config
addr_opts = hex(dcfg.address) + ':' + dcfg.options
cmd.extend(['-s', addr_opts])
cmd.extend(['-a', self.alt, '-D', self.img])
self.check_call(cmd)
if self.dfuse and 'leave' in dcfg.options.split(':'):
# Normal DFU devices generally need to be reset to switch
# back to the flashed program.
#
# DfuSe targets do as well, except when 'leave' is given
# as an option.
reset = False
if reset:
print('Now reset your board again to switch back to runtime mode.')

View File

@ -1,100 +0,0 @@
# Copyright (c) 2017 Linaro Limited.
#
# SPDX-License-Identifier: Apache-2.0
'''Runner for flashing ESP32 devices with esptool/espidf.'''
from os import path
from west import log
from west.runners.core import ZephyrBinaryRunner, RunnerCaps
class Esp32BinaryRunner(ZephyrBinaryRunner):
'''Runner front-end for espidf.'''
def __init__(self, cfg, device, baud=921600, flash_size='detect',
flash_freq='40m', flash_mode='dio', espidf='espidf',
bootloader_bin=None, partition_table_bin=None):
super(Esp32BinaryRunner, self).__init__(cfg)
self.elf = cfg.elf_file
self.device = device
self.baud = baud
self.flash_size = flash_size
self.flash_freq = flash_freq
self.flash_mode = flash_mode
self.espidf = espidf
self.bootloader_bin = bootloader_bin
self.partition_table_bin = partition_table_bin
@classmethod
def name(cls):
return 'esp32'
@classmethod
def capabilities(cls):
return RunnerCaps(commands={'flash'})
@classmethod
def do_add_parser(cls, parser):
# Required
parser.add_argument('--esp-idf-path', required=True,
help='path to ESP-IDF')
# Optional
parser.add_argument('--esp-device', default='/dev/ttyUSB0',
help='serial port to flash, default /dev/ttyUSB0')
parser.add_argument('--esp-baud-rate', default='921600',
help='serial baud rate, default 921600')
parser.add_argument('--esp-flash-size', default='detect',
help='flash size, default "detect"')
parser.add_argument('--esp-flash-freq', default='40m',
help='flash frequency, default "40m"')
parser.add_argument('--esp-flash-mode', default='dio',
help='flash mode, default "dio"')
parser.add_argument(
'--esp-tool',
help='''if given, complete path to espidf. default is to search for
it in [ESP_IDF_PATH]/components/esptool_py/esptool/esptool.py''')
parser.add_argument('--esp-flash-bootloader',
help='Bootloader image to flash')
parser.add_argument('--esp-flash-partition_table',
help='Partition table to flash')
@classmethod
def create(cls, cfg, args):
if args.esp_tool:
espidf = args.esp_tool
else:
espidf = path.join(args.esp_idf_path, 'components', 'esptool_py',
'esptool', 'esptool.py')
return Esp32BinaryRunner(
cfg, args.esp_device, baud=args.esp_baud_rate,
flash_size=args.esp_flash_size, flash_freq=args.esp_flash_freq,
flash_mode=args.esp_flash_mode, espidf=espidf,
bootloader_bin=args.esp_flash_bootloader,
partition_table_bin=args.esp_flash_partition_table)
def do_run(self, command, **kwargs):
bin_name = path.splitext(self.elf)[0] + path.extsep + 'bin'
cmd_convert = [self.espidf, '--chip', 'esp32', 'elf2image', self.elf]
cmd_flash = [self.espidf, '--chip', 'esp32', '--port', self.device,
'--baud', self.baud, '--before', 'default_reset',
'--after', 'hard_reset', 'write_flash', '-u',
'--flash_mode', self.flash_mode,
'--flash_freq', self.flash_freq,
'--flash_size', self.flash_size]
if self.bootloader_bin:
cmd_flash.extend(['0x1000', self.bootloader_bin])
cmd_flash.extend(['0x8000', self.partition_table_bin])
cmd_flash.extend(['0x10000', bin_name])
else:
cmd_flash.extend(['0x1000', bin_name])
log.inf("Converting ELF to BIN")
self.check_call(cmd_convert)
log.inf("Flashing ESP32 on {} ({}bps)".format(self.device, self.baud))
self.check_call(cmd_flash)

View File

@ -1,166 +0,0 @@
# Copyright (c) 2018 Intel Corporation.
# Copyright 2018 Open Source Foundries Limited.
#
# SPDX-License-Identifier: Apache-2.0
'''Runner for debugging and flashing Intel S1000 CRB'''
from os import path
import time
import signal
from west import log
from west.runners.core import ZephyrBinaryRunner
DEFAULT_XT_GDB_PORT = 20000
class IntelS1000BinaryRunner(ZephyrBinaryRunner):
'''Runner front-end for Intel S1000.'''
def __init__(self, cfg, xt_ocd_dir,
ocd_topology, ocd_jtag_instr, gdb_flash_file,
gdb_port=DEFAULT_XT_GDB_PORT):
super(IntelS1000BinaryRunner, self).__init__(cfg)
self.board_dir = cfg.board_dir
self.elf_name = cfg.elf_file
self.gdb_cmd = cfg.gdb
self.xt_ocd_dir = xt_ocd_dir
self.ocd_topology = ocd_topology
self.ocd_jtag_instr = ocd_jtag_instr
self.gdb_flash_file = gdb_flash_file
self.gdb_port = gdb_port
@classmethod
def name(cls):
return 'intel_s1000'
@classmethod
def do_add_parser(cls, parser):
# Optional
parser.add_argument(
'--xt-ocd-dir', default='/opt/tensilica/xocd-12.0.4/xt-ocd',
help='ocd-dir, defaults to /opt/tensilica/xocd-12.0.4/xt-ocd')
parser.add_argument(
'--ocd-topology', default='topology_dsp0_flyswatter2.xml',
help='ocd-topology, defaults to topology_dsp0_flyswatter2.xml')
parser.add_argument(
'--ocd-jtag-instr', default='dsp0_gdb.txt',
help='ocd-jtag-instr, defaults to dsp0_gdb.txt')
parser.add_argument(
'--gdb-flash-file', default='load_elf.txt',
help='gdb-flash-file, defaults to load_elf.txt')
parser.add_argument(
'--gdb-port', default=DEFAULT_XT_GDB_PORT,
help='xt-gdb port, defaults to 20000')
@classmethod
def create(cls, cfg, args):
return IntelS1000BinaryRunner(
cfg, args.xt_ocd_dir,
args.ocd_topology, args.ocd_jtag_instr, args.gdb_flash_file,
gdb_port=args.gdb_port)
def do_run(self, command, **kwargs):
kwargs['ocd-topology'] = path.join(self.board_dir, 'support',
self.ocd_topology)
kwargs['ocd-jtag-instr'] = path.join(self.board_dir, 'support',
self.ocd_jtag_instr)
kwargs['gdb-flash-file'] = path.join(self.board_dir, 'support',
self.gdb_flash_file)
if command == 'flash':
self.flash(**kwargs)
elif command == 'debugserver':
self.debugserver(**kwargs)
else:
self.do_debug(**kwargs)
def flash(self, **kwargs):
topology_file = kwargs['ocd-topology']
jtag_instr_file = kwargs['ocd-jtag-instr']
gdb_flash_file = kwargs['gdb-flash-file']
self.print_gdbserver_message(self.gdb_port)
server_cmd = [self.xt_ocd_dir,
'-c', topology_file,
'-I', jtag_instr_file]
# Start the server
# Note that XTOCD takes a few seconds to execute and always fails the
# first time. It has to be relaunched the second time to work.
server_proc = self.popen_ignore_int(server_cmd)
time.sleep(6)
server_proc.terminate()
server_proc = self.popen_ignore_int(server_cmd)
time.sleep(6)
# Start the client
gdb_cmd = [self.gdb_cmd, '-x', gdb_flash_file]
client_proc = self.popen_ignore_int(gdb_cmd)
# Wait for 3 seconds (waiting for XTGDB to finish loading the image)
time.sleep(3)
# At this point, the ELF image is loaded and the program is in
# execution. Now we can quit the client (xt-gdb) and the server
# (xt-ocd) as they are not needed anymore. The loaded program
# (ELF) will continue to run though.
client_proc.terminate()
server_proc.terminate()
def do_debug(self, **kwargs):
if self.elf_name is None:
raise ValueError('Cannot debug; elf is missing')
if self.gdb_cmd is None:
raise ValueError('Cannot debug; no gdb specified')
topology_file = kwargs['ocd-topology']
jtag_instr_file = kwargs['ocd-jtag-instr']
self.print_gdbserver_message(self.gdb_port)
server_cmd = [self.xt_ocd_dir,
'-c', topology_file,
'-I', jtag_instr_file]
# Start the server
# Note that XTOCD takes a few seconds to execute and always fails the
# first time. It has to be relaunched the second time to work.
server_proc = self.popen_ignore_int(server_cmd)
time.sleep(6)
server_proc.terminate()
server_proc = self.popen_ignore_int(server_cmd)
time.sleep(6)
gdb_cmd = [self.gdb_cmd,
'-ex', 'target remote :{}'.format(self.gdb_port),
self.elf_name]
# Start the client
# The below statement will consume the "^C" keypress ensuring
# the python main application doesn't exit. This is important
# since ^C in gdb means a "halt" operation.
previous = signal.signal(signal.SIGINT, signal.SIG_IGN)
try:
self.check_call(gdb_cmd)
finally:
signal.signal(signal.SIGINT, previous)
server_proc.terminate()
server_proc.wait()
def print_gdbserver_message(self, gdb_port):
log.inf('Intel S1000 GDB server running on port {}'.format(gdb_port))
def debugserver(self, **kwargs):
topology_file = kwargs['ocd-topology']
jtag_instr_file = kwargs['ocd-jtag-instr']
self.print_gdbserver_message(self.gdb_port)
server_cmd = [self.xt_ocd_dir,
'-c', topology_file,
'-I', jtag_instr_file]
# Note that XTOCD takes a few seconds to execute and always fails the
# first time. It has to be relaunched the second time to work.
server_proc = self.popen_ignore_int(server_cmd)
time.sleep(6)
server_proc.terminate()
self.check_call(server_cmd)

View File

@ -1,150 +0,0 @@
# Copyright (c) 2017 Linaro Limited.
#
# SPDX-License-Identifier: Apache-2.0
'''Runner for debugging with J-Link.'''
import os
import tempfile
import sys
from west import log
from west.runners.core import ZephyrBinaryRunner, RunnerCaps, \
BuildConfiguration
DEFAULT_JLINK_EXE = 'JLink.exe' if sys.platform == 'win32' else 'JLinkExe'
DEFAULT_JLINK_GDB_PORT = 2331
class JLinkBinaryRunner(ZephyrBinaryRunner):
'''Runner front-end for the J-Link GDB server.'''
def __init__(self, cfg, device,
commander=DEFAULT_JLINK_EXE,
flash_addr=0x0, erase=True,
iface='swd', speed='auto',
gdbserver='JLinkGDBServer', gdb_port=DEFAULT_JLINK_GDB_PORT,
tui=False):
super(JLinkBinaryRunner, self).__init__(cfg)
self.bin_name = cfg.bin_file
self.elf_name = cfg.elf_file
self.gdb_cmd = [cfg.gdb] if cfg.gdb else None
self.device = device
self.commander = commander
self.flash_addr = flash_addr
self.erase = erase
self.gdbserver_cmd = [gdbserver]
self.iface = iface
self.speed = speed
self.gdb_port = gdb_port
self.tui_arg = ['-tui'] if tui else []
@classmethod
def name(cls):
return 'jlink'
@classmethod
def capabilities(cls):
return RunnerCaps(commands={'flash', 'debug', 'debugserver', 'attach'},
flash_addr=True)
@classmethod
def do_add_parser(cls, parser):
# Required:
parser.add_argument('--device', required=True, help='device name')
# Optional:
parser.add_argument('--iface', default='swd',
help='interface to use, default is swd')
parser.add_argument('--speed', default='auto',
help='interface speed, default is autodetect')
parser.add_argument('--tui', default=False, action='store_true',
help='if given, GDB uses -tui')
parser.add_argument('--gdbserver', default='JLinkGDBServer',
help='GDB server, default is JLinkGDBServer')
parser.add_argument('--gdb-port', default=DEFAULT_JLINK_GDB_PORT,
help='pyocd gdb port, defaults to {}'.format(
DEFAULT_JLINK_GDB_PORT))
parser.add_argument('--commander', default=DEFAULT_JLINK_EXE,
help='J-Link Commander, default is JLinkExe')
parser.add_argument('--erase', default=False, action='store_true',
help='if given, mass erase flash before loading')
@classmethod
def create(cls, cfg, args):
build_conf = BuildConfiguration(cfg.build_dir)
flash_addr = cls.get_flash_address(args, build_conf)
return JLinkBinaryRunner(cfg, args.device,
commander=args.commander,
flash_addr=flash_addr, erase=args.erase,
iface=args.iface, speed=args.speed,
gdbserver=args.gdbserver,
gdb_port=args.gdb_port,
tui=args.tui)
def print_gdbserver_message(self):
log.inf('J-Link GDB server running on port {}'.format(self.gdb_port))
def do_run(self, command, **kwargs):
server_cmd = (self.gdbserver_cmd +
['-select', 'usb', # only USB connections supported
'-port', str(self.gdb_port),
'-if', self.iface,
'-speed', self.speed,
'-device', self.device,
'-silent',
'-singlerun'])
if command == 'flash':
self.flash(**kwargs)
elif command == 'debugserver':
self.print_gdbserver_message()
self.check_call(server_cmd)
else:
if self.gdb_cmd is None:
raise ValueError('Cannot debug; gdb is missing')
if self.elf_name is None:
raise ValueError('Cannot debug; elf is missing')
client_cmd = (self.gdb_cmd +
self.tui_arg +
[self.elf_name] +
['-ex', 'target remote :{}'.format(self.gdb_port)])
if command == 'debug':
client_cmd += ['-ex', 'monitor halt',
'-ex', 'monitor reset',
'-ex', 'load']
self.print_gdbserver_message()
self.run_server_and_client(server_cmd, client_cmd)
def flash(self, **kwargs):
if self.bin_name is None:
raise ValueError('Cannot flash; bin_name is missing')
lines = ['r'] # Reset and halt the target
if self.erase:
lines.append('erase') # Erase all flash sectors
lines.append('loadfile {} 0x{:x}'.format(self.bin_name,
self.flash_addr))
lines.append('g') # Start the CPU
lines.append('q') # Close the connection and quit
log.dbg('JLink commander script:')
log.dbg('\n'.join(lines))
# Don't use NamedTemporaryFile: the resulting file can't be
# opened again on Windows.
with tempfile.TemporaryDirectory(suffix='jlink') as d:
fname = os.path.join(d, 'runner.jlink')
with open(fname, 'wb') as f:
f.writelines(bytes(line + '\n', 'utf-8') for line in lines)
cmd = ([self.commander] +
['-if', self.iface,
'-speed', self.speed,
'-device', self.device,
'-CommanderScript', fname])
log.inf('Flashing Target Device')
self.check_call(cmd)

View File

@ -1,99 +0,0 @@
# Copyright (c) 2017 Linaro Limited.
#
# SPDX-License-Identifier: Apache-2.0
'''Runner for NIOS II, based on quartus-flash.py and GDB.'''
from west import log
from west.runners.core import ZephyrBinaryRunner, NetworkPortHelper
class Nios2BinaryRunner(ZephyrBinaryRunner):
'''Runner front-end for NIOS II.'''
# From the original shell script:
#
# "XXX [flash] only support[s] cases where the .elf is sent
# over the JTAG and the CPU directly boots from __start. CONFIG_XIP
# and CONFIG_INCLUDE_RESET_VECTOR must be disabled."
def __init__(self, cfg, quartus_py=None, cpu_sof=None, tui=False):
super(Nios2BinaryRunner, self).__init__(cfg)
self.hex_name = cfg.hex_file
self.elf_name = cfg.elf_file
self.cpu_sof = cpu_sof
self.quartus_py = quartus_py
self.gdb_cmd = [cfg.gdb] if cfg.gdb else None
self.tui_arg = ['-tui'] if tui else []
@classmethod
def name(cls):
return 'nios2'
@classmethod
def do_add_parser(cls, parser):
# TODO merge quartus-flash.py script into this file.
parser.add_argument('--quartus-flash', required=True)
parser.add_argument('--cpu-sof', required=True,
help='path to the the CPU .sof data')
parser.add_argument('--tui', default=False, action='store_true',
help='if given, GDB uses -tui')
@classmethod
def create(cls, cfg, args):
return Nios2BinaryRunner(cfg,
quartus_py=args.quartus_flash,
cpu_sof=args.cpu_sof,
tui=args.tui)
def do_run(self, command, **kwargs):
if command == 'flash':
self.flash(**kwargs)
else:
self.debug_debugserver(command, **kwargs)
def flash(self, **kwargs):
if self.quartus_py is None:
raise ValueError('Cannot flash; --quartus-flash not given.')
if self.cpu_sof is None:
raise ValueError('Cannot flash; --cpu-sof not given.')
cmd = [self.quartus_py,
'--sof', self.cpu_sof,
'--kernel', self.hex_name]
self.check_call(cmd)
def print_gdbserver_message(self, gdb_port):
log.inf('Nios II GDB server running on port {}'.format(gdb_port))
def debug_debugserver(self, command, **kwargs):
# Per comments in the shell script, the NIOSII GDB server
# doesn't exit gracefully, so it's better to explicitly search
# for an unused port. The script picks a random value in
# between 1024 and 49151, but we'll start with the
# "traditional" 3333 choice.
gdb_start = 3333
nh = NetworkPortHelper()
gdb_port = nh.get_unused_ports([gdb_start])[0]
server_cmd = (['nios2-gdb-server',
'--tcpport', str(gdb_port),
'--stop', '--reset-target'])
if command == 'debugserver':
self.print_gdbserver_message(gdb_port)
self.check_call(server_cmd)
else:
if self.elf_name is None:
raise ValueError('Cannot debug; elf is missing')
if self.gdb_cmd is None:
raise ValueError('Cannot debug; no gdb specified')
gdb_cmd = (self.gdb_cmd +
self.tui_arg +
[self.elf_name,
'-ex', 'target remote :{}'.format(gdb_port)])
self.print_gdbserver_message(gdb_port)
self.run_server_and_client(server_cmd, gdb_cmd)

View File

@ -1,129 +0,0 @@
# Copyright (c) 2017 Linaro Limited.
#
# SPDX-License-Identifier: Apache-2.0
'''Runner for flashing with nrfjprog.'''
import sys
from west import log
from west.runners.core import ZephyrBinaryRunner, RunnerCaps
class NrfJprogBinaryRunner(ZephyrBinaryRunner):
'''Runner front-end for nrfjprog.'''
def __init__(self, cfg, family, softreset, snr, erase=False):
super(NrfJprogBinaryRunner, self).__init__(cfg)
self.hex_ = cfg.hex_file
self.family = family
self.softreset = softreset
self.snr = snr
self.erase = erase
@classmethod
def name(cls):
return 'nrfjprog'
@classmethod
def capabilities(cls):
return RunnerCaps(commands={'flash'})
@classmethod
def do_add_parser(cls, parser):
parser.add_argument('--nrf-family', required=True,
choices=['NRF51', 'NRF52'],
help='family of nRF MCU')
parser.add_argument('--softreset', required=False,
action='store_true',
help='use reset instead of pinreset')
parser.add_argument('--erase', action='store_true',
help='if given, mass erase flash before loading')
parser.add_argument('--snr', required=False,
help='serial number of board to use')
@classmethod
def create(cls, cfg, args):
return NrfJprogBinaryRunner(cfg, args.nrf_family, args.softreset,
args.snr, erase=args.erase)
def get_board_snr_from_user(self):
snrs = self.check_output(['nrfjprog', '--ids'])
snrs = snrs.decode(sys.getdefaultencoding()).strip().splitlines()
if len(snrs) == 0:
raise RuntimeError('"nrfjprog --ids" did not find a board; '
'is the board connected?')
elif len(snrs) == 1:
board_snr = snrs[0]
if board_snr == '0':
raise RuntimeError('"nrfjprog --ids" returned 0; '
'is a debugger already connected?')
return board_snr
log.dbg("Refusing the temptation to guess a board",
level=log.VERBOSE_EXTREME)
# Use of print() here is advised. We don't want to lose
# this information in a separate log -- this is
# interactive and requires a terminal.
print('There are multiple boards connected.')
for i, snr in enumerate(snrs, 1):
print('{}. {}'.format(i, snr))
p = 'Please select one with desired serial number (1-{}): '.format(
len(snrs))
while True:
value = input(p)
try:
value = int(value)
except ValueError:
continue
if 1 <= value <= len(snrs):
break
return snrs[value - 1]
def do_run(self, command, **kwargs):
commands = []
if (self.snr is None):
board_snr = self.get_board_snr_from_user()
else:
board_snr = self.snr.lstrip("0")
program_cmd = ['nrfjprog', '--program', self.hex_, '-f', self.family,
'--snr', board_snr]
print('Flashing file: {}'.format(self.hex_))
if self.erase:
commands.extend([
['nrfjprog',
'--eraseall',
'-f', self.family,
'--snr', board_snr],
program_cmd
])
else:
if self.family == 'NRF51':
commands.append(program_cmd + ['--sectorerase'])
else:
commands.append(program_cmd + ['--sectoranduicrerase'])
if self.family == 'NRF52' and not self.softreset:
commands.extend([
# Enable pin reset
['nrfjprog', '--pinresetenable', '-f', self.family,
'--snr', board_snr],
])
if self.softreset:
commands.append(['nrfjprog', '--reset', '-f', self.family,
'--snr', board_snr])
else:
commands.append(['nrfjprog', '--pinreset', '-f', self.family,
'--snr', board_snr])
for cmd in commands:
self.check_call(cmd)
log.inf('Board with serial number {} flashed successfully.'.format(
board_snr))

View File

@ -1,94 +0,0 @@
# Copyright (c) 2018 Synopsys Inc.
# Copyright (c) 2017 Open Source Foundries Limited.
#
# SPDX-License-Identifier: Apache-2.0
'''ARC architecture-specific runners.'''
from os import path
from west.runners.core import ZephyrBinaryRunner
DEFAULT_ARC_GDB_PORT = 3333
DEFAULT_PROPS_FILE = 'nsim.props'
class NsimBinaryRunner(ZephyrBinaryRunner):
'''Runner front-end for the ARC si.'''
# This unusual 'flash' implementation matches the original shell script.
#
# It works by starting a GDB server in a separate session, connecting a
# client to it to load the program, and running 'continue' within the
# client to execute the application.
#
def __init__(self, cfg,
tui=False,
gdb_port=DEFAULT_ARC_GDB_PORT,
props=DEFAULT_PROPS_FILE):
super(NsimBinaryRunner, self).__init__(cfg)
self.gdb_cmd = [cfg.gdb] + (['-tui'] if tui else [])
self.nsim_cmd = ['nsimdrv']
self.gdb_port = gdb_port
self.props = props
@classmethod
def name(cls):
return 'arc-nsim'
@classmethod
def do_add_parser(cls, parser):
parser.add_argument('--gdb-port', default=DEFAULT_ARC_GDB_PORT,
help='nsim gdb port, defaults to 3333')
parser.add_argument('--props', default=DEFAULT_PROPS_FILE,
help='nsim props file, defaults to nsim.props')
@classmethod
def create(cls, cfg, args):
if cfg.gdb is None:
raise ValueError('--gdb not provided at command line')
return NsimBinaryRunner(
cfg,
gdb_port=args.gdb_port,
props=args.props)
def do_run(self, command, **kwargs):
kwargs['nsim-cfg'] = path.join(self.cfg.board_dir, 'support',
self.props)
if command == 'flash':
self.do_flash(**kwargs)
elif command == 'debug':
self.do_debug(**kwargs)
else:
self.debugserver(**kwargs)
def do_flash(self, **kwargs):
config = kwargs['nsim-cfg']
cmd = (self.nsim_cmd + ['-propsfile', config, self.cfg.elf_file])
self.check_call(cmd)
def do_debug(self, **kwargs):
config = kwargs['nsim-cfg']
server_cmd = (self.nsim_cmd + ['-gdb',
'-port={}'.format(self.gdb_port),
'-propsfile', config])
gdb_cmd = (self.gdb_cmd +
['-ex', 'target remote :{}'.format(self.gdb_port),
'-ex', 'load', self.cfg.elf_file])
self.run_server_and_client(server_cmd, gdb_cmd)
def debugserver(self, **kwargs):
config = kwargs['nsim-cfg']
cmd = (self.nsim_cmd +
['-gdb', '-port={}'.format(self.gdb_port),
'-propsfile', config])
self.check_call(cmd)

View File

@ -1,145 +0,0 @@
# Copyright (c) 2017 Linaro Limited.
#
# SPDX-License-Identifier: Apache-2.0
'''Runner for openocd.'''
from os import path
from west.runners.core import ZephyrBinaryRunner
DEFAULT_OPENOCD_TCL_PORT = 6333
DEFAULT_OPENOCD_TELNET_PORT = 4444
DEFAULT_OPENOCD_GDB_PORT = 3333
class OpenOcdBinaryRunner(ZephyrBinaryRunner):
'''Runner front-end for openocd.'''
def __init__(self, cfg,
pre_cmd=None, load_cmd=None, verify_cmd=None, post_cmd=None,
tui=None,
tcl_port=DEFAULT_OPENOCD_TCL_PORT,
telnet_port=DEFAULT_OPENOCD_TELNET_PORT,
gdb_port=DEFAULT_OPENOCD_GDB_PORT):
super(OpenOcdBinaryRunner, self).__init__(cfg)
self.openocd_config = path.join(cfg.board_dir, 'support',
'openocd.cfg')
search_args = []
if cfg.openocd_search is not None:
search_args = ['-s', cfg.openocd_search]
self.openocd_cmd = [cfg.openocd] + search_args
self.elf_name = cfg.elf_file
self.load_cmd = load_cmd
self.verify_cmd = verify_cmd
self.pre_cmd = pre_cmd
self.post_cmd = post_cmd
self.tcl_port = tcl_port
self.telnet_port = telnet_port
self.gdb_port = gdb_port
self.gdb_cmd = [cfg.gdb] if cfg.gdb else None
self.tui_arg = ['-tui'] if tui else []
@classmethod
def name(cls):
return 'openocd'
@classmethod
def do_add_parser(cls, parser):
# Options for flashing:
parser.add_argument('--cmd-pre-load',
help='Command to run before flashing')
parser.add_argument('--cmd-load',
help='''Command to load/flash binary
(required when flashing)''')
parser.add_argument('--cmd-verify',
help='''Command to verify flashed binary''')
parser.add_argument('--cmd-post-verify',
help='Command to run after verification')
# Options for debugging:
parser.add_argument('--tui', default=False, action='store_true',
help='if given, GDB uses -tui')
parser.add_argument('--tcl-port', default=DEFAULT_OPENOCD_TCL_PORT,
help='openocd TCL port, defaults to 6333')
parser.add_argument('--telnet-port',
default=DEFAULT_OPENOCD_TELNET_PORT,
help='openocd telnet port, defaults to 4444')
parser.add_argument('--gdb-port', default=DEFAULT_OPENOCD_GDB_PORT,
help='openocd gdb port, defaults to 3333')
@classmethod
def create(cls, cfg, args):
return OpenOcdBinaryRunner(
cfg,
pre_cmd=args.cmd_pre_load, load_cmd=args.cmd_load,
verify_cmd=args.cmd_verify, post_cmd=args.cmd_post_verify,
tui=args.tui,
tcl_port=args.tcl_port, telnet_port=args.telnet_port,
gdb_port=args.gdb_port)
def do_run(self, command, **kwargs):
if command == 'flash':
self.do_flash(**kwargs)
elif command == 'debug':
self.do_debug(**kwargs)
else:
self.do_debugserver(**kwargs)
def do_flash(self, **kwargs):
if self.load_cmd is None:
raise ValueError('Cannot flash; load command is missing')
if self.verify_cmd is None:
raise ValueError('Cannot flash; verify command is missing')
pre_cmd = []
if self.pre_cmd is not None:
pre_cmd = ['-c', self.pre_cmd]
post_cmd = []
if self.post_cmd is not None:
post_cmd = ['-c', self.post_cmd]
cmd = (self.openocd_cmd +
['-f', self.openocd_config,
'-c', 'init',
'-c', 'targets'] +
pre_cmd +
['-c', 'reset halt',
'-c', self.load_cmd,
'-c', 'reset halt',
'-c', self.verify_cmd] +
post_cmd +
['-c', 'reset run',
'-c', 'shutdown'])
self.check_call(cmd)
def do_debug(self, **kwargs):
if self.gdb_cmd is None:
raise ValueError('Cannot debug; no gdb specified')
if self.elf_name is None:
raise ValueError('Cannot debug; no .elf specified')
server_cmd = (self.openocd_cmd +
['-f', self.openocd_config,
'-c', 'tcl_port {}'.format(self.tcl_port),
'-c', 'telnet_port {}'.format(self.telnet_port),
'-c', 'gdb_port {}'.format(self.gdb_port),
'-c', 'init',
'-c', 'targets',
'-c', 'halt'])
gdb_cmd = (self.gdb_cmd + self.tui_arg +
['-ex', 'target remote :{}'.format(self.gdb_port),
self.elf_name])
self.run_server_and_client(server_cmd, gdb_cmd)
def do_debugserver(self, **kwargs):
cmd = (self.openocd_cmd +
['-f', self.openocd_config,
'-c', 'init',
'-c', 'targets',
'-c', 'reset halt'])
self.check_call(cmd)

View File

@ -1,169 +0,0 @@
# Copyright (c) 2017 Linaro Limited.
#
# SPDX-License-Identifier: Apache-2.0
'''Runner for pyOCD .'''
import os
from west.runners.core import ZephyrBinaryRunner, RunnerCaps, \
BuildConfiguration
from west import log
DEFAULT_PYOCD_GDB_PORT = 3333
class PyOcdBinaryRunner(ZephyrBinaryRunner):
'''Runner front-end for pyOCD.'''
def __init__(self, cfg, target,
flashtool='pyocd-flashtool', flash_addr=0x0,
flashtool_opts=None,
gdbserver='pyocd-gdbserver',
gdb_port=DEFAULT_PYOCD_GDB_PORT, tui=False,
board_id=None, daparg=None, frequency=None):
super(PyOcdBinaryRunner, self).__init__(cfg)
self.target_args = ['-t', target]
self.flashtool = flashtool
self.flash_addr_args = ['-a', hex(flash_addr)] if flash_addr else []
self.gdb_cmd = [cfg.gdb] if cfg.gdb is not None else None
self.gdbserver = gdbserver
self.gdb_port = gdb_port
self.tui_args = ['-tui'] if tui else []
self.hex_name = cfg.hex_file
self.bin_name = cfg.bin_file
self.elf_name = cfg.elf_file
board_args = []
if board_id is not None:
board_args = ['-b', board_id]
self.board_args = board_args
daparg_args = []
if daparg is not None:
daparg_args = ['-da', daparg]
self.daparg_args = daparg_args
frequency_args = []
if frequency is not None:
frequency_args = ['-f', frequency]
self.frequency_args = frequency_args
self.flashtool_extra = flashtool_opts if flashtool_opts else []
@classmethod
def name(cls):
return 'pyocd'
@classmethod
def capabilities(cls):
return RunnerCaps(commands={'flash', 'debug', 'debugserver', 'attach'},
flash_addr=True)
@classmethod
def do_add_parser(cls, parser):
parser.add_argument('--target', required=True,
help='target override')
parser.add_argument('--daparg',
help='Additional -da arguments to pyocd tool')
parser.add_argument('--flashtool', default='pyocd-flashtool',
help='flash tool path, default is pyocd-flashtool')
parser.add_argument('--flashtool-opt', default=[], action='append',
help='''Additional options for pyocd-flashtool,
e.g. -ce to chip erase''')
parser.add_argument('--frequency',
help='SWD clock frequency in Hz')
parser.add_argument('--gdbserver', default='pyocd-gdbserver',
help='GDB server, default is pyocd-gdbserver')
parser.add_argument('--gdb-port', default=DEFAULT_PYOCD_GDB_PORT,
help='pyocd gdb port, defaults to {}'.format(
DEFAULT_PYOCD_GDB_PORT))
parser.add_argument('--tui', default=False, action='store_true',
help='if given, GDB uses -tui')
parser.add_argument('--board-id',
help='ID of board to flash, default is to prompt')
@classmethod
def create(cls, cfg, args):
daparg = os.environ.get('PYOCD_DAPARG')
if daparg:
log.wrn('Setting PYOCD_DAPARG in the environment is',
'deprecated; use the --daparg option instead.')
if args.daparg is None:
log.dbg('Missing --daparg set to {} from environment'.format(
daparg), level=log.VERBOSE_VERY)
args.daparg = daparg
build_conf = BuildConfiguration(cfg.build_dir)
flash_addr = cls.get_flash_address(args, build_conf)
return PyOcdBinaryRunner(
cfg, args.target, flashtool=args.flashtool,
flash_addr=flash_addr, flashtool_opts=args.flashtool_opt,
gdbserver=args.gdbserver, gdb_port=args.gdb_port, tui=args.tui,
board_id=args.board_id, daparg=args.daparg,
frequency=args.frequency)
def port_args(self):
return ['-p', str(self.gdb_port)]
def do_run(self, command, **kwargs):
if command == 'flash':
self.flash(**kwargs)
else:
self.debug_debugserver(command, **kwargs)
def flash(self, **kwargs):
if os.path.isfile(self.hex_name):
fname = self.hex_name
elif os.path.isfile(self.bin_name):
fname = self.bin_name
else:
raise ValueError(
'Cannot flash; no hex ({}) or bin ({}) files'.format(
self.hex_name, self.bin_name))
cmd = ([self.flashtool] +
self.flash_addr_args +
self.daparg_args +
self.target_args +
self.board_args +
self.frequency_args +
self.flashtool_extra +
[fname])
log.inf('Flashing Target Device')
self.check_call(cmd)
def print_gdbserver_message(self):
log.inf('pyOCD GDB server running on port {}'.format(self.gdb_port))
def debug_debugserver(self, command, **kwargs):
server_cmd = ([self.gdbserver] +
self.daparg_args +
self.port_args() +
self.target_args +
self.board_args +
self.frequency_args)
if command == 'debugserver':
self.print_gdbserver_message()
self.check_call(server_cmd)
else:
if self.gdb_cmd is None:
raise ValueError('Cannot debug; gdb is missing')
if self.elf_name is None:
raise ValueError('Cannot debug; elf is missing')
client_cmd = (self.gdb_cmd +
self.tui_args +
[self.elf_name] +
['-ex', 'target remote :{}'.format(self.gdb_port)])
if command == 'debug':
client_cmd += ['-ex', 'monitor halt',
'-ex', 'monitor reset',
'-ex', 'load']
self.print_gdbserver_message()
self.run_server_and_client(server_cmd, client_cmd)

View File

@ -1,34 +0,0 @@
# Copyright (c) 2017 Linaro Limited.
#
# SPDX-License-Identifier: Apache-2.0
'''Runner stub for QEMU.'''
from west.runners.core import ZephyrBinaryRunner, RunnerCaps
class QemuBinaryRunner(ZephyrBinaryRunner):
'''Place-holder for QEMU runner customizations.'''
def __init__(self, cfg):
super(QemuBinaryRunner, self).__init__(cfg)
@classmethod
def name(cls):
return 'qemu'
@classmethod
def capabilities(cls):
# This is a stub.
return RunnerCaps(commands=set())
@classmethod
def do_add_parser(cls, parser):
pass # Nothing to do.
@classmethod
def create(cls, cfg, args):
return QemuBinaryRunner(cfg)
def do_run(self, command, **kwargs):
pass

View File

@ -1,40 +0,0 @@
# Copyright (c) 2017 Linaro Limited.
#
# SPDX-License-Identifier: Apache-2.0
'''Runner for debugging with xt-gdb.'''
from os import path
from west.runners.core import ZephyrBinaryRunner, RunnerCaps
class XtensaBinaryRunner(ZephyrBinaryRunner):
'''Runner front-end for xt-gdb.'''
def __init__(self, cfg):
super(XtensaBinaryRunner, self).__init__(cfg)
@classmethod
def name(cls):
return 'xtensa'
@classmethod
def capabilities(cls):
return RunnerCaps(commands={'debug'})
@classmethod
def do_add_parser(cls, parser):
parser.add_argument('--xcc-tools', required=True,
help='path to XTensa tools')
@classmethod
def create(cls, cfg, args):
# Override any GDB with the one provided by the XTensa tools.
cfg.gdb = path.join(args.xcc_tools, 'bin', 'xt-gdb')
return XtensaBinaryRunner(cfg)
def do_run(self, command, **kwargs):
gdb_cmd = [self.cfg.gdb, self.cfg.elf_file]
self.check_call(gdb_cmd)

View File

@ -1,80 +0,0 @@
# Copyright 2018 Open Source Foundries Limited.
#
# SPDX-License-Identifier: Apache-2.0
'''Miscellaneous utilities used by west
'''
import os
import shlex
import textwrap
def quote_sh_list(cmd):
'''Transform a command from list into shell string form.'''
fmt = ' '.join('{}' for _ in cmd)
args = [shlex.quote(s) for s in cmd]
return fmt.format(*args)
def wrap(text, indent):
'''Convenience routine for wrapping text to a consistent indent.'''
return textwrap.wrap(text, initial_indent=indent,
subsequent_indent=indent)
class WestNotFound(RuntimeError):
'''Neither the current directory nor any parent has a West installation.'''
def west_dir(start=None):
'''Returns the absolute path of the west/ top level directory.
Starts the search from the start directory, and goes to its
parents. If the start directory is not specified, the current
directory is used.
Raises WestNotFound if no west top-level directory is found.
'''
return os.path.join(west_topdir(start), 'west')
def west_topdir(start=None):
'''
Like west_dir(), but returns the path to the parent directory of the west/
directory instead, where project repositories are stored
'''
# If you change this function, make sure to update the bootstrap
# script's find_west_topdir().
if start is None:
cur_dir = os.getcwd()
else:
cur_dir = start
while True:
if os.path.isfile(os.path.join(cur_dir, 'west', '.west_topdir')):
return cur_dir
parent_dir = os.path.dirname(cur_dir)
if cur_dir == parent_dir:
# At the root
raise WestNotFound('Could not find a West installation '
'in this or any parent directory')
cur_dir = parent_dir
def in_multirepo_install(start=None):
'''Returns True iff the path ``start`` is in a multi-repo installation.
If start is not given, it defaults to the current working directory.
This is equivalent to checking if west_dir() raises an exception
when given the same start kwarg.
'''
try:
west_topdir(start)
result = True
except WestNotFound:
result = False
return result

View File

@ -1,92 +0,0 @@
#!/usr/bin/env python3
# Zephyr launcher which is interoperable with:
#
# 1. "mono-repo" Zephyr installations that have 'make flash'
# etc. supplied by a copy of some west code in scripts/meta.
#
# 2. "multi-repo" Zephyr installations where west is provided in a
# separate Git repository elsewhere.
#
# This is basically a copy of the "wrapper" functionality in the west
# bootstrap script for the multi-repo case, plus a fallback onto the
# copy in scripts/meta/west for mono-repo installs.
import os
import sys
import colorama
if sys.version_info < (3,):
sys.exit('fatal error: you are running Python 2')
# Top-level west directory, containing west itself and the manifest.
WEST_DIR = 'west'
# Subdirectory to check out the west source repository into.
WEST = 'west'
# File inside of WEST_DIR which marks it as the top level of the
# Zephyr project installation.
#
# (The WEST_DIR name is not distinct enough to use when searching for
# the top level; other directories named "west" may exist elsewhere,
# e.g. zephyr/doc/west.)
WEST_MARKER = '.west_topdir'
class WestNotFound(RuntimeError):
'''Neither the current directory nor any parent has a West installation.'''
def find_west_topdir(start):
'''Find the top-level installation directory, starting at ``start``.
If none is found, raises WestNotFound.'''
cur_dir = start
while True:
if os.path.isfile(os.path.join(cur_dir, WEST_DIR, WEST_MARKER)):
return cur_dir
parent_dir = os.path.dirname(cur_dir)
if cur_dir == parent_dir:
# At the root
raise WestNotFound()
cur_dir = parent_dir
def wrap(west_dir, argv):
# Pull in the west main module, after adding the directory
# containing the package to sys.path.
sys.path.append(west_dir)
import west.main
# Invoke west's main with our arguments. It needs to be run from
# this process for 'west debug' to work properly, so don't change
# this code to running main in a subprocess.
west.main.main(sys.argv[1:])
def main():
# Figure out which west to run. If we're in a multirepo
# installation, prefer the standalone west. Otherwise, we're in a
# monorepo installation, so we need to fall back on the copy of
# west in the Zephyr repository's scripts/meta directory.
try:
topdir = find_west_topdir(__file__)
west_dir = os.path.join(topdir, 'west', 'west', 'src')
except WestNotFound:
west_dir = os.path.join(os.environ['ZEPHYR_BASE'], 'scripts', 'meta')
try:
wrap(west_dir, sys.argv[1:])
finally:
print(colorama.Fore.LIGHTRED_EX, end='')
print('NOTE: you just ran a copy of west from {};'.
format(os.path.dirname(__file__)),
'this will be removed from the Zephyr repository in the future.',
'West is now developed separately.')
print(colorama.Style.RESET_ALL, end='', flush=True)
if __name__ == '__main__':
main()

View File

@ -4,14 +4,3 @@ set ZEPHYR_BASE=%~dp0
if exist "%userprofile%\zephyrrc.cmd" (
call "%userprofile%\zephyrrc.cmd"
)
rem Zephyr meta-tool (west) launcher alias, which keeps monorepo
rem Zephyr installations' 'make flash' etc. working. See
rem https://www.python.org/dev/peps/pep-0486/ for details on the
rem virtualenv-related pieces. (We need to implement this manually
rem because Zephyr's minimum supported Python version is 3.4.)
if defined VIRTUAL_ENV (
doskey west=python %ZEPHYR_BASE%\scripts\west $*
) else (
doskey west=py -3 %ZEPHYR_BASE%\scripts\west $*
)