Skip to content

Commit

Permalink
scripts: west commands to support --domain
Browse files Browse the repository at this point in the history
This commit extends the west commands build, flash, and debug to support
--domain when having multiple domains (images) defined in a domains.yaml
build file.

The domains.yaml uses the following yaml format to specify the
build directory of each domain in the multi image build:
> default: <domain-n>
> domains:
>   <domain-1>:
>     build_dir: <build_dir-domain-1>
>   <domain-2>:
>     build_dir: <build_dir-domain-2>
>   ...

`west <build|flash|debug>` has been extended to support
`--domain <domain>`.

`west build` calls CMake to create the build system, and if `--domain`
is given, then the build tool will be invoked afterwards for the
specified domain.

`west flash` will default flash all domains, but `--domain <domain>`
argument can be used to select a specific domain to flash, for example:
> west flash --domain mcuboot

`west debug` only a single domain can be debugged at any given time.
If `--domain` is not specified, then the default domain specified in the
domains.yml file will be used.
Users can still select a different domain, for example with:
> west debug --domain mcuboot

Signed-off-by: Torsten Rasmussen <Torsten.Rasmussen@nordicsemi.no>
  • Loading branch information
tejlmand authored and carlescufi committed Aug 3, 2022
1 parent 5fe5d6b commit 8408af6
Show file tree
Hide file tree
Showing 5 changed files with 213 additions and 14 deletions.
31 changes: 25 additions & 6 deletions scripts/west_commands/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from west import log
from west.configuration import config
from zcmake import DEFAULT_CMAKE_GENERATOR, run_cmake, run_build, CMakeCache
from build_helpers import is_zephyr_build, find_build_dir, \
from build_helpers import is_zephyr_build, find_build_dir, load_domains, \
FIND_BUILD_DIR_DESCRIPTION

from zephyr_ext_common import Forceable
Expand Down Expand Up @@ -115,6 +115,9 @@ def do_add_parser(self, parser_adder):
help='force a cmake run')
group.add_argument('--cmake-only', action='store_true',
help="just run cmake; don't build (implies -c)")
group.add_argument('--domain', action='append',
help='''execute build tool (make or ninja) only for
given domain''')
group.add_argument('-t', '--target',
help='''run build system target TARGET
(try "-t usage")''')
Expand Down Expand Up @@ -201,8 +204,9 @@ def do_run(self, args, remainder):

self._sanity_check()
self._update_cache()
self.domains = load_domains(self.build_dir)

self._run_build(args.target)
self._run_build(args.target, args.domain)

def _find_board(self):
board, origin = None, None
Expand Down Expand Up @@ -464,7 +468,7 @@ def _run_cmake(self, board, origin, cmake_opts):
config_sysbuild = config_getboolean('sysbuild', False)
if self.args.sysbuild or (config_sysbuild and not self.args.no_sysbuild):
cmake_opts.extend(['-S{}'.format(SYSBUILD_PROJ_DIR),
'-DAPP_DIR={}'.format(self.source_dir)])
'-DAPP_DIR:PATH={}'.format(self.source_dir)])
else:
# self.args.no_sysbuild == True or config sysbuild False
cmake_opts.extend(['-S{}'.format(self.source_dir)])
Expand Down Expand Up @@ -499,7 +503,7 @@ def _run_pristine(self):
'-P', cache['ZEPHYR_BASE'] + '/cmake/pristine.cmake']
run_cmake(cmake_args, cwd=self.build_dir, dry_run=self.args.dry_run)

def _run_build(self, target):
def _run_build(self, target, domain):
if target:
_banner('running target {}'.format(target))
elif self.run_cmake:
Expand All @@ -511,8 +515,23 @@ def _run_build(self, target):
if self.args.verbose:
self._append_verbose_args(extra_args,
not bool(self.args.build_opt))
run_build(self.build_dir, extra_args=extra_args,
dry_run=self.args.dry_run)

domains = load_domains(self.build_dir)
build_dir_list = []

if domain is None:
# If no domain is specified, we just build top build dir as that
# will build all domains.
build_dir_list = [domains.get_top_build_dir()]
else:
_banner('building domain(s): {}'.format(' '.join(domain)))
domain_list = domains.get_domains(domain)
for d in domain_list:
build_dir_list.append(d.build_dir)

for b in build_dir_list:
run_build(b, extra_args=extra_args,
dry_run=self.args.dry_run)

def _append_verbose_args(self, extra_args, add_dashes):
# These hacks are only needed for CMake versions earlier than
Expand Down
17 changes: 17 additions & 0 deletions scripts/west_commands/build_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from west import log
from west.configuration import config
from west.util import escapes_directory
from domains import Domains

DEFAULT_BUILD_DIR = 'build'
'''Name of the default Zephyr build directory.'''
Expand Down Expand Up @@ -133,3 +134,19 @@ def is_zephyr_build(path):
log.dbg(f'{path} is NOT a valid zephyr build directory',
level=log.VERBOSE_EXTREME)
return False


def load_domains(path):
'''Load domains from a domains.yaml.
If domains.yaml is not found, then a single 'app' domain referring to the
top-level build folder is created and returned.
'''
domains_file = Path(path) / 'domains.yaml'

if not domains_file.is_file():
return Domains.from_data({'default': 'app',
'build_dir': path,
'domains': [{'name': 'app', 'build_dir': path}]})

return Domains.from_file(domains_file)
139 changes: 139 additions & 0 deletions scripts/west_commands/domains.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Copyright (c) 2022 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: Apache-2.0

'''Domain handling for west extension commands.
This provides parsing of domains yaml file and creation of objects of the
Domain class.
'''

import yaml
import pykwalify.core
from west import log

DOMAINS_SCHEMA = '''
## A pykwalify schema for basic validation of the structure of a
## domains YAML file.
##
# The domains.yaml file is a simple list of domains from a multi image build
# along with the default domain to use.
type: map
mapping:
default:
required: true
type: str
build_dir:
required: true
type: str
domains:
required: false
type: seq
sequence:
- type: map
mapping:
name:
required: true
type: str
build_dir:
required: true
type: str
'''

schema = yaml.safe_load(DOMAINS_SCHEMA)


class Domains:

def __init__(self, data):
self._domains = []
self._domain_names = []
self._domain_default = []

self._build_dir = data.get('build_dir')
domain_list = data.get('domains')
if not domain_list:
log.wrn("no domains defined; this probably won't work")

for d in domain_list:
domain = Domain(d['name'], d['build_dir'])
self._domains.append(domain)
self._domain_names.append(domain.name)
if domain.name == data['default']:
self._default_domain = domain

@staticmethod
def from_file(domains_file):
'''Load domains from domains.yaml.
Exception raised:
- ``FileNotFoundError`` if the domains file is not found.
'''
try:
with open(domains_file, 'r') as f:
domains = yaml.safe_load(f.read())
except FileNotFoundError:
log.die(f'domains.yaml file not found: {domains_file}')

try:
pykwalify.core.Core(source_data=domains, schema_data=schema)\
.validate()
except pykwalify.errors.SchemaError:
log.die(f'ERROR: Malformed yaml in file: {domains_file}')

return Domains(domains)

@staticmethod
def from_data(domains_data):
'''Load domains from domains dictionary.
'''
return Domains(domains_data)

def get_domains(self, names=None):
ret = []

if not names:
return self._domains

for n in names:
found = False
for d in self._domains:
if n == d.name:
ret.append(d)
found = True
break
# Getting here means the domain was not found.
# Todo: throw an error.
if not found:
log.die(f'domain {n} not found, '
f'valid domains are:', *self._domain_names)
return ret

def get_default_domain(self):
return self._default_domain

def get_top_build_dir(self):
return self._build_dir


class Domain:

def __init__(self, name, build_dir):
self.name = name
self.build_dir = build_dir

@property
def name(self):
return self._name

@name.setter
def name(self, value):
self._name = value

@property
def build_dir(self):
return self._build_dir

@build_dir.setter
def build_dir(self, value):
self._build_dir = value
7 changes: 5 additions & 2 deletions scripts/west_commands/flash.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@

from west.commands import WestCommand

from run_common import add_parser_common, do_run_common
from run_common import add_parser_common, do_run_common, get_build_dir
from build_helpers import load_domains


class Flash(WestCommand):
Expand All @@ -26,4 +27,6 @@ def do_add_parser(self, parser_adder):
return add_parser_common(self, parser_adder)

def do_run(self, my_args, runner_args):
do_run_common(self, my_args, runner_args)
build_dir = get_build_dir(my_args)
domains = load_domains(build_dir).get_domains(my_args.domain)
do_run_common(self, my_args, runner_args, domains=domains)
33 changes: 27 additions & 6 deletions scripts/west_commands/run_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import traceback

from west import log
from build_helpers import find_build_dir, is_zephyr_build, \
from build_helpers import find_build_dir, is_zephyr_build, load_domains, \
FIND_BUILD_DIR_DESCRIPTION
from west.commands import CommandError
from west.configuration import config
Expand Down Expand Up @@ -104,6 +104,8 @@ def add_parser_common(command, parser_adder=None, parser=None):
help='override default runner from --build-dir')
group.add_argument('--skip-rebuild', action='store_true',
help='do not refresh cmake dependencies first')
group.add_argument('--domain', action='append',
help='execute runner only for given domain')

group = parser.add_argument_group(
'runner configuration',
Expand Down Expand Up @@ -145,21 +147,38 @@ def add_parser_common(command, parser_adder=None, parser=None):

return parser

def do_run_common(command, user_args, user_runner_args):
def do_run_common(command, user_args, user_runner_args, domains=None):
# This is the main routine for all the "west flash", "west debug",
# etc. commands.

if user_args.context:
dump_context(command, user_args, user_runner_args)
return

command_name = command.name
build_dir = get_build_dir(user_args)
cache = load_cmake_cache(build_dir, user_args)
board = cache['CACHED_BOARD']
if not user_args.skip_rebuild:
rebuild(command, build_dir, user_args)

if domains is None:
if user_args.domain is None:
# No domains are passed down and no domains specified by the user.
# So default domain will be used.
domains = [load_domains(build_dir).get_default_domain()]
else:
# No domains are passed down, but user has specified domains to use.
# Get the user specified domains.
domains = load_domains(build_dir).get_domains(user_args.domain)

for d in domains:
do_run_common_image(command, user_args, user_runner_args, d.build_dir)

def do_run_common_image(command, user_args, user_runner_args, build_dir=None):
command_name = command.name
if build_dir is None:
build_dir = get_build_dir(user_args)
cache = load_cmake_cache(build_dir, user_args)
board = cache['CACHED_BOARD']

# Load runners.yaml.
yaml_path = runners_yaml_path(build_dir, board)
runners_yaml = load_runners_yaml(yaml_path)
Expand All @@ -173,7 +192,9 @@ def do_run_common(command, user_args, user_runner_args):
# Set up runner logging to delegate to west.log commands.
logger = logging.getLogger('runners')
logger.setLevel(LOG_LEVEL)
logger.addHandler(WestLogHandler())
if not logger.hasHandlers():
# Only add a runners log handler if none has been added already.
logger.addHandler(WestLogHandler())

# If the user passed -- to force the parent argument parser to stop
# parsing, it will show up here, and needs to be filtered out.
Expand Down

0 comments on commit 8408af6

Please sign in to comment.