Skip to content

Commit b719e15

Browse files
committed
scripts: west commands to support --domain
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 <[email protected]>
1 parent 4ec16a3 commit b719e15

File tree

5 files changed

+213
-14
lines changed

5 files changed

+213
-14
lines changed

scripts/west_commands/build.py

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from west import log
1313
from west.configuration import config
1414
from zcmake import DEFAULT_CMAKE_GENERATOR, run_cmake, run_build, CMakeCache
15-
from build_helpers import is_zephyr_build, find_build_dir, \
15+
from build_helpers import is_zephyr_build, find_build_dir, load_domains, \
1616
FIND_BUILD_DIR_DESCRIPTION
1717

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

202205
self._sanity_check()
203206
self._update_cache()
207+
self.domains = load_domains(self.build_dir)
204208

205-
self._run_build(args.target)
209+
self._run_build(args.target, args.domain)
206210

207211
def _find_board(self):
208212
board, origin = None, None
@@ -464,7 +468,7 @@ def _run_cmake(self, board, origin, cmake_opts):
464468
config_sysbuild = config_getboolean('sysbuild', False)
465469
if self.args.sysbuild or (config_sysbuild and not self.args.no_sysbuild):
466470
cmake_opts.extend(['-S{}'.format(SYSBUILD_PROJ_DIR),
467-
'-DAPP_DIR={}'.format(self.source_dir)])
471+
'-DAPP_DIR:PATH={}'.format(self.source_dir)])
468472
else:
469473
# self.args.no_sysbuild == True or config sysbuild False
470474
cmake_opts.extend(['-S{}'.format(self.source_dir)])
@@ -499,7 +503,7 @@ def _run_pristine(self):
499503
'-P', cache['ZEPHYR_BASE'] + '/cmake/pristine.cmake']
500504
run_cmake(cmake_args, cwd=self.build_dir, dry_run=self.args.dry_run)
501505

502-
def _run_build(self, target):
506+
def _run_build(self, target, domain):
503507
if target:
504508
_banner('running target {}'.format(target))
505509
elif self.run_cmake:
@@ -511,8 +515,23 @@ def _run_build(self, target):
511515
if self.args.verbose:
512516
self._append_verbose_args(extra_args,
513517
not bool(self.args.build_opt))
514-
run_build(self.build_dir, extra_args=extra_args,
515-
dry_run=self.args.dry_run)
518+
519+
domains = load_domains(self.build_dir)
520+
build_dir_list = []
521+
522+
if domain is None:
523+
# If no domain is specified, we just build top build dir as that
524+
# will build all domains.
525+
build_dir_list = [domains.get_top_build_dir()]
526+
else:
527+
_banner('building domain(s): {}'.format(' '.join(domain)))
528+
domain_list = domains.get_domains(domain)
529+
for d in domain_list:
530+
build_dir_list.append(d.build_dir)
531+
532+
for b in build_dir_list:
533+
run_build(b, extra_args=extra_args,
534+
dry_run=self.args.dry_run)
516535

517536
def _append_verbose_args(self, extra_args, add_dashes):
518537
# These hacks are only needed for CMake versions earlier than

scripts/west_commands/build_helpers.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from west import log
1717
from west.configuration import config
1818
from west.util import escapes_directory
19+
from domains import Domains
1920

2021
DEFAULT_BUILD_DIR = 'build'
2122
'''Name of the default Zephyr build directory.'''
@@ -133,3 +134,19 @@ def is_zephyr_build(path):
133134
log.dbg(f'{path} is NOT a valid zephyr build directory',
134135
level=log.VERBOSE_EXTREME)
135136
return False
137+
138+
139+
def load_domains(path):
140+
'''Load domains from a domains.yaml.
141+
142+
If domains.yaml is not found, then a single 'app' domain referring to the
143+
top-level build folder is created and returned.
144+
'''
145+
domains_file = Path(path) / 'domains.yaml'
146+
147+
if not domains_file.is_file():
148+
return Domains.from_data({'default': 'app',
149+
'build_dir': path,
150+
'domains': [{'name': 'app', 'build_dir': path}]})
151+
152+
return Domains.from_file(domains_file)

scripts/west_commands/domains.py

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# Copyright (c) 2022 Nordic Semiconductor ASA
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
'''Domain handling for west extension commands.
6+
7+
This provides parsing of domains yaml file and creation of objects of the
8+
Domain class.
9+
'''
10+
11+
import yaml
12+
import pykwalify.core
13+
from west import log
14+
15+
DOMAINS_SCHEMA = '''
16+
## A pykwalify schema for basic validation of the structure of a
17+
## domains YAML file.
18+
##
19+
# The domains.yaml file is a simple list of domains from a multi image build
20+
# along with the default domain to use.
21+
type: map
22+
mapping:
23+
default:
24+
required: true
25+
type: str
26+
build_dir:
27+
required: true
28+
type: str
29+
domains:
30+
required: false
31+
type: seq
32+
sequence:
33+
- type: map
34+
mapping:
35+
name:
36+
required: true
37+
type: str
38+
build_dir:
39+
required: true
40+
type: str
41+
'''
42+
43+
schema = yaml.safe_load(DOMAINS_SCHEMA)
44+
45+
46+
class Domains:
47+
48+
def __init__(self, data):
49+
self._domains = []
50+
self._domain_names = []
51+
self._domain_default = []
52+
53+
self._build_dir = data.get('build_dir')
54+
domain_list = data.get('domains')
55+
if not domain_list:
56+
log.wrn("no domains defined; this probably won't work")
57+
58+
for d in domain_list:
59+
domain = Domain(d['name'], d['build_dir'])
60+
self._domains.append(domain)
61+
self._domain_names.append(domain.name)
62+
if domain.name == data['default']:
63+
self._default_domain = domain
64+
65+
@staticmethod
66+
def from_file(domains_file):
67+
'''Load domains from domains.yaml.
68+
69+
Exception raised:
70+
- ``FileNotFoundError`` if the domains file is not found.
71+
'''
72+
try:
73+
with open(domains_file, 'r') as f:
74+
domains = yaml.safe_load(f.read())
75+
except FileNotFoundError:
76+
log.die(f'domains.yaml file not found: {domains_file}')
77+
78+
try:
79+
pykwalify.core.Core(source_data=domains, schema_data=schema)\
80+
.validate()
81+
except pykwalify.errors.SchemaError:
82+
log.die(f'ERROR: Malformed yaml in file: {domains_file}')
83+
84+
return Domains(domains)
85+
86+
@staticmethod
87+
def from_data(domains_data):
88+
'''Load domains from domains dictionary.
89+
'''
90+
return Domains(domains_data)
91+
92+
def get_domains(self, names=None):
93+
ret = []
94+
95+
if not names:
96+
return self._domains
97+
98+
for n in names:
99+
found = False
100+
for d in self._domains:
101+
if n == d.name:
102+
ret.append(d)
103+
found = True
104+
break
105+
# Getting here means the domain was not found.
106+
# Todo: throw an error.
107+
if not found:
108+
log.die(f'domain {n} not found, '
109+
f'valid domains are:', *self._domain_names)
110+
return ret
111+
112+
def get_default_domain(self):
113+
return self._default_domain
114+
115+
def get_top_build_dir(self):
116+
return self._build_dir
117+
118+
119+
class Domain:
120+
121+
def __init__(self, name, build_dir):
122+
self.name = name
123+
self.build_dir = build_dir
124+
125+
@property
126+
def name(self):
127+
return self._name
128+
129+
@name.setter
130+
def name(self, value):
131+
self._name = value
132+
133+
@property
134+
def build_dir(self):
135+
return self._build_dir
136+
137+
@build_dir.setter
138+
def build_dir(self, value):
139+
self._build_dir = value

scripts/west_commands/flash.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88

99
from west.commands import WestCommand
1010

11-
from run_common import add_parser_common, do_run_common
11+
from run_common import add_parser_common, do_run_common, get_build_dir
12+
from build_helpers import load_domains
1213

1314

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

2829
def do_run(self, my_args, runner_args):
29-
do_run_common(self, my_args, runner_args)
30+
build_dir = get_build_dir(my_args)
31+
domains = load_domains(build_dir).get_domains(my_args.domain)
32+
do_run_common(self, my_args, runner_args, domains=domains)

scripts/west_commands/run_common.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import traceback
1717

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

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

146148
return parser
147149

148-
def do_run_common(command, user_args, user_runner_args):
150+
def do_run_common(command, user_args, user_runner_args, domains=None):
149151
# This is the main routine for all the "west flash", "west debug",
150152
# etc. commands.
151153

152154
if user_args.context:
153155
dump_context(command, user_args, user_runner_args)
154156
return
155157

156-
command_name = command.name
157158
build_dir = get_build_dir(user_args)
158-
cache = load_cmake_cache(build_dir, user_args)
159-
board = cache['CACHED_BOARD']
160159
if not user_args.skip_rebuild:
161160
rebuild(command, build_dir, user_args)
162161

162+
if domains is None:
163+
if user_args.domain is None:
164+
# No domains are passed down and no domains specified by the user.
165+
# So default domain will be used.
166+
domains = [load_domains(build_dir).get_default_domain()]
167+
else:
168+
# No domains are passed down, but user has specified domains to use.
169+
# Get the user specified domains.
170+
domains = load_domains(build_dir).get_domains(user_args.domain)
171+
172+
for d in domains:
173+
do_run_common_image(command, user_args, user_runner_args, d.build_dir)
174+
175+
def do_run_common_image(command, user_args, user_runner_args, build_dir=None):
176+
command_name = command.name
177+
if build_dir is None:
178+
build_dir = get_build_dir(user_args)
179+
cache = load_cmake_cache(build_dir, user_args)
180+
board = cache['CACHED_BOARD']
181+
163182
# Load runners.yaml.
164183
yaml_path = runners_yaml_path(build_dir, board)
165184
runners_yaml = load_runners_yaml(yaml_path)
@@ -173,7 +192,9 @@ def do_run_common(command, user_args, user_runner_args):
173192
# Set up runner logging to delegate to west.log commands.
174193
logger = logging.getLogger('runners')
175194
logger.setLevel(LOG_LEVEL)
176-
logger.addHandler(WestLogHandler())
195+
if not logger.hasHandlers():
196+
# Only add a runners log handler if none has been added already.
197+
logger.addHandler(WestLogHandler())
177198

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

0 commit comments

Comments
 (0)