blob: 6797ff1c48d0ed8d5f1f0d6d59fe9c122c0f97f9 [file] [log] [blame]
Marti Bolivarab822642019-01-23 08:31:06 -07001# Copyright (c) 2018 Foundries.io
2#
3# SPDX-License-Identifier: Apache-2.0
4
5import argparse
6import os
Mikkel Jakobsen98eb3162020-03-04 15:14:43 +01007import pathlib
Martí Bolívar0186ead2019-12-09 11:05:11 -08008import shlex
Torsten Rasmussenef3c5e52020-06-08 21:09:15 +02009import sys
Anas Nashif070cf6a2021-05-29 07:53:41 -040010import yaml
Marti Bolivarab822642019-01-23 08:31:06 -070011
12from west import log
Carles Cufib7c75912019-04-14 21:52:45 +020013from west.configuration import config
Jan Van Winkel4d975db2019-05-04 14:44:56 +020014from zcmake import DEFAULT_CMAKE_GENERATOR, run_cmake, run_build, CMakeCache
Torsten Rasmussen8408af62021-11-22 10:29:56 +010015from build_helpers import is_zephyr_build, find_build_dir, load_domains, \
Carles Cufi98980c62019-06-05 16:04:29 +020016 FIND_BUILD_DIR_DESCRIPTION
Marti Bolivar8c447992019-01-30 13:53:40 -070017
Carles Cufi31bdad52019-04-26 21:53:02 +020018from zephyr_ext_common import Forceable
Marti Bolivar8c447992019-01-30 13:53:40 -070019
Carles Cufib7101772019-02-21 15:58:19 +010020_ARG_SEPARATOR = '--'
Marti Bolivarab822642019-01-23 08:31:06 -070021
Torsten Rasmussen5fe5d6b2021-11-22 13:35:06 +010022SYSBUILD_PROJ_DIR = pathlib.Path(__file__).resolve().parent.parent.parent \
23 / pathlib.Path('share/sysbuild')
24
Marti Bolivar1f5e6d82019-05-01 16:48:04 -060025BUILD_USAGE = '''\
Gregers Gram Rygg46801802022-02-23 13:27:14 +010026west build [-h] [-b BOARD[@REV]]] [-d BUILD_DIR]
Marti Bolivar1f5e6d82019-05-01 16:48:04 -060027 [-t TARGET] [-p {auto, always, never}] [-c] [--cmake-only]
Marti Bolivar3a486a82019-05-04 13:32:36 -060028 [-n] [-o BUILD_OPT] [-f]
Torsten Rasmussen69f4fa62022-08-04 10:45:38 +020029 [--sysbuild | --no-sysbuild] [--domain DOMAIN]
Marti Bolivar3a486a82019-05-04 13:32:36 -060030 [source_dir] -- [cmake_opt [cmake_opt ...]]
Marti Bolivar1f5e6d82019-05-01 16:48:04 -060031'''
32
Martí Bolívar6e4c2b92020-06-25 12:58:58 -070033BUILD_DESCRIPTION = f'''\
Marti Bolivarab822642019-01-23 08:31:06 -070034Convenience wrapper for building Zephyr applications.
35
Martí Bolívar6e4c2b92020-06-25 12:58:58 -070036{FIND_BUILD_DIR_DESCRIPTION}
37
Carles Cufib7101772019-02-21 15:58:19 +010038positional arguments:
Martí Bolívar6e4c2b92020-06-25 12:58:58 -070039 source_dir application source directory
40 cmake_opt extra options to pass to cmake; implies -c
41 (these must come after "--" as shown above)
Carles Cufib7101772019-02-21 15:58:19 +010042'''
Marti Bolivarab822642019-01-23 08:31:06 -070043
Martí Bolívar6e4c2b92020-06-25 12:58:58 -070044PRISTINE_DESCRIPTION = """\
45A "pristine" build directory is empty. The -p option controls
46whether the build directory is made pristine before the build
47is done. A bare '--pristine' with no value is the same as
48--pristine=always. Setting --pristine=auto uses heuristics to
49guess if a pristine build may be necessary."""
50
Marti Bolivare6873b82019-06-02 22:48:52 -060051def _banner(msg):
52 log.inf('-- west build: ' + msg, colorize=True)
53
Marti Bolivard1595032019-05-04 13:14:28 -060054def config_get(option, fallback):
55 return config.get('build', option, fallback=fallback)
56
57def config_getboolean(option, fallback):
58 return config.getboolean('build', option, fallback=fallback)
59
Carles Cufib7c75912019-04-14 21:52:45 +020060class AlwaysIfMissing(argparse.Action):
61
Marti Bolivar69099e32019-05-04 13:16:15 -060062 def __call__(self, parser, namespace, values, option_string=None):
63 setattr(namespace, self.dest, values or 'always')
Marti Bolivarab822642019-01-23 08:31:06 -070064
Marti Bolivar8c447992019-01-30 13:53:40 -070065class Build(Forceable):
Marti Bolivarab822642019-01-23 08:31:06 -070066
67 def __init__(self):
68 super(Build, self).__init__(
69 'build',
Marti Bolivar808028b2019-01-28 10:45:02 -070070 # Keep this in sync with the string in west-commands.yml.
Marti Bolivarab822642019-01-23 08:31:06 -070071 'compile a Zephyr application',
72 BUILD_DESCRIPTION,
Carles Cufib7101772019-02-21 15:58:19 +010073 accepts_unknown_args=True)
Marti Bolivarab822642019-01-23 08:31:06 -070074
75 self.source_dir = None
76 '''Source directory for the build, or None on error.'''
77
78 self.build_dir = None
79 '''Final build directory used to run the build, or None on error.'''
80
81 self.created_build_dir = False
82 '''True if the build directory was created; False otherwise.'''
83
84 self.run_cmake = False
85 '''True if CMake was run; False otherwise.
86
87 Note: this only describes CMake runs done by this command. The
88 build system generated by CMake may also update itself due to
89 internal logic.'''
90
91 self.cmake_cache = None
92 '''Final parsed CMake cache for the build, or None on error.'''
93
94 def do_add_parser(self, parser_adder):
95 parser = parser_adder.add_parser(
96 self.name,
97 help=self.help,
98 formatter_class=argparse.RawDescriptionHelpFormatter,
Carles Cufib7101772019-02-21 15:58:19 +010099 description=self.description,
Marti Bolivar1f5e6d82019-05-01 16:48:04 -0600100 usage=BUILD_USAGE)
Marti Bolivarab822642019-01-23 08:31:06 -0700101
Martí Bolívar0d5e6c12020-12-09 15:53:00 -0800102 # Remember to update west-completion.bash if you add or remove
Marti Bolivarab822642019-01-23 08:31:06 -0700103 # flags
104
Gregers Gram Rygg46801802022-02-23 13:27:14 +0100105 parser.add_argument('-b', '--board',
106 help='board to build for with optional board revision')
Carles Cufib7101772019-02-21 15:58:19 +0100107 # Hidden option for backwards compatibility
108 parser.add_argument('-s', '--source-dir', help=argparse.SUPPRESS)
Marti Bolivarab822642019-01-23 08:31:06 -0700109 parser.add_argument('-d', '--build-dir',
Martí Bolívar6e4c2b92020-06-25 12:58:58 -0700110 help='build directory to create or use')
Marti Bolivar8c447992019-01-30 13:53:40 -0700111 self.add_force_arg(parser)
Martí Bolívar6e4c2b92020-06-25 12:58:58 -0700112
113 group = parser.add_argument_group('cmake and build tool')
114 group.add_argument('-c', '--cmake', action='store_true',
115 help='force a cmake run')
116 group.add_argument('--cmake-only', action='store_true',
117 help="just run cmake; don't build (implies -c)")
Torsten Rasmussen8408af62021-11-22 10:29:56 +0100118 group.add_argument('--domain', action='append',
119 help='''execute build tool (make or ninja) only for
120 given domain''')
Martí Bolívar6e4c2b92020-06-25 12:58:58 -0700121 group.add_argument('-t', '--target',
Martí Bolívar0d5e6c12020-12-09 15:53:00 -0800122 help='''run build system target TARGET
123 (try "-t usage")''')
Anas Nashif070cf6a2021-05-29 07:53:41 -0400124 group.add_argument('-T', '--test-item',
125 help='''Build based on test data in testcase.yaml
126 or sample.yaml''')
Martí Bolívar6e4c2b92020-06-25 12:58:58 -0700127 group.add_argument('-o', '--build-opt', default=[], action='append',
128 help='''options to pass to the build tool
129 (make or ninja); may be given more than once''')
130 group.add_argument('-n', '--just-print', '--dry-run', '--recon',
131 dest='dry_run', action='store_true',
132 help="just print build commands; don't run them")
Marti Bolivar5880fee2023-01-09 16:17:51 -0800133 group.add_argument('-S', '--snippet', dest='snippets',
134 action='append', default=[],
135 help='''add the argument to SNIPPET; may be given
136 multiple times. Forces CMake to run again if given.
137 Do not use this option with manually specified
138 -DSNIPPET... cmake arguments: the results are
139 undefined''')
Martí Bolívar6e4c2b92020-06-25 12:58:58 -0700140
Torsten Rasmussen5fe5d6b2021-11-22 13:35:06 +0100141 group = parser.add_mutually_exclusive_group()
142 group.add_argument('--sysbuild', action='store_true',
143 help='''create multi domain build system''')
144 group.add_argument('--no-sysbuild', action='store_true',
145 help='''do not create multi domain build system
146 (default)''')
147
Martí Bolívar6e4c2b92020-06-25 12:58:58 -0700148 group = parser.add_argument_group('pristine builds',
149 PRISTINE_DESCRIPTION)
150 group.add_argument('-p', '--pristine', choices=['auto', 'always',
151 'never'], action=AlwaysIfMissing, nargs='?',
152 help='pristine build folder setting')
153
Marti Bolivarab822642019-01-23 08:31:06 -0700154 return parser
155
Carles Cufib7101772019-02-21 15:58:19 +0100156 def do_run(self, args, remainder):
Marti Bolivarab822642019-01-23 08:31:06 -0700157 self.args = args # Avoid having to pass them around
Marti Bolivar8b9b7e72019-05-23 09:31:31 -0600158 self.config_board = config_get('board', None)
Marti Bolivara1ef6962019-05-01 17:06:15 -0600159 log.dbg('args: {} remainder: {}'.format(args, remainder),
160 level=log.VERBOSE_EXTREME)
Carles Cufib7101772019-02-21 15:58:19 +0100161 # Store legacy -s option locally
162 source_dir = self.args.source_dir
163 self._parse_remainder(remainder)
Anas Nashif070cf6a2021-05-29 07:53:41 -0400164 # Parse testcase.yaml or sample.yaml files for additional options.
165 if self.args.test_item:
Anas Nashif79b955c2023-01-27 11:37:19 +0000166 # we get path + testitem
167 item = os.path.basename(self.args.test_item)
168 test_path = os.path.dirname(self.args.test_item)
169 if test_path:
170 self.args.source_dir = test_path
171 if not self._parse_test_item(item):
Anas Nashif10081242023-01-27 11:35:36 +0000172 log.die("No test metadata found")
Carles Cufib7101772019-02-21 15:58:19 +0100173 if source_dir:
174 if self.args.source_dir:
175 log.die("source directory specified twice:({} and {})".format(
176 source_dir, self.args.source_dir))
177 self.args.source_dir = source_dir
178 log.dbg('source_dir: {} cmake_opts: {}'.format(self.args.source_dir,
Marti Bolivara1ef6962019-05-01 17:06:15 -0600179 self.args.cmake_opts),
180 level=log.VERBOSE_EXTREME)
Marti Bolivarab822642019-01-23 08:31:06 -0700181 self._sanity_precheck()
182 self._setup_build_dir()
Carles Cufib7c75912019-04-14 21:52:45 +0200183
184 if args.pristine is not None:
185 pristine = args.pristine
186 else:
187 # Load the pristine={auto, always, never} configuration value
Martí Bolívarc19c6fb2021-01-15 09:17:01 -0800188 pristine = config_get('pristine', 'never')
Carles Cufib7c75912019-04-14 21:52:45 +0200189 if pristine not in ['auto', 'always', 'never']:
Marti Bolivar69099e32019-05-04 13:16:15 -0600190 log.wrn(
191 'treating unknown build.pristine value "{}" as "never"'.
192 format(pristine))
Carles Cufib7c75912019-04-14 21:52:45 +0200193 pristine = 'never'
Martí Bolívar1f9ab852023-03-09 13:10:28 -0800194 self.auto_pristine = pristine == 'auto'
Carles Cufib7c75912019-04-14 21:52:45 +0200195
196 log.dbg('pristine: {} auto_pristine: {}'.format(pristine,
Marti Bolivara1ef6962019-05-01 17:06:15 -0600197 self.auto_pristine),
198 level=log.VERBOSE_VERY)
Marti Bolivarab822642019-01-23 08:31:06 -0700199 if is_zephyr_build(self.build_dir):
Carles Cufib7c75912019-04-14 21:52:45 +0200200 if pristine == 'always':
Carles Cufi3a88dce2019-04-18 16:46:12 +0200201 self._run_pristine()
Marti Bolivarab822642019-01-23 08:31:06 -0700202 self.run_cmake = True
Carles Cufib7c75912019-04-14 21:52:45 +0200203 else:
204 self._update_cache()
Marti Bolivar1f5e6d82019-05-01 16:48:04 -0600205 if (self.args.cmake or self.args.cmake_opts or
Marti Bolivar5880fee2023-01-09 16:17:51 -0800206 self.args.cmake_only or self.args.snippets):
Carles Cufib7c75912019-04-14 21:52:45 +0200207 self.run_cmake = True
Marti Bolivarab822642019-01-23 08:31:06 -0700208 else:
209 self.run_cmake = True
Carles Cufi98980c62019-06-05 16:04:29 +0200210 self.source_dir = self._find_source_dir()
Marti Bolivarab822642019-01-23 08:31:06 -0700211 self._sanity_check()
212
Marti Bolivar88fb8ba2019-05-04 13:14:57 -0600213 board, origin = self._find_board()
214 self._run_cmake(board, origin, self.args.cmake_opts)
Marti Bolivar1f5e6d82019-05-01 16:48:04 -0600215 if args.cmake_only:
216 return
217
Marti Bolivarab822642019-01-23 08:31:06 -0700218 self._sanity_check()
219 self._update_cache()
Torsten Rasmussen8408af62021-11-22 10:29:56 +0100220 self.domains = load_domains(self.build_dir)
Marti Bolivarab822642019-01-23 08:31:06 -0700221
Torsten Rasmussen8408af62021-11-22 10:29:56 +0100222 self._run_build(args.target, args.domain)
Marti Bolivarab822642019-01-23 08:31:06 -0700223
Marti Bolivar88fb8ba2019-05-04 13:14:57 -0600224 def _find_board(self):
225 board, origin = None, None
Marti Bolivar88fb8ba2019-05-04 13:14:57 -0600226 if self.cmake_cache:
227 board, origin = (self.cmake_cache.get('CACHED_BOARD'),
228 'CMakeCache.txt')
Martí Bolívaraf5a79f2021-02-02 09:22:44 -0800229
230 # A malformed CMake cache may exist, but not have a board.
231 # This happens if there's a build error from a previous run.
232 if board is not None:
233 return (board, origin)
234
235 if self.args.board:
Marti Bolivar88fb8ba2019-05-04 13:14:57 -0600236 board, origin = self.args.board, 'command line'
237 elif 'BOARD' in os.environ:
238 board, origin = os.environ['BOARD'], 'env'
Marti Bolivar8b9b7e72019-05-23 09:31:31 -0600239 elif self.config_board is not None:
240 board, origin = self.config_board, 'configfile'
Marti Bolivar88fb8ba2019-05-04 13:14:57 -0600241 return board, origin
242
Carles Cufib7101772019-02-21 15:58:19 +0100243 def _parse_remainder(self, remainder):
244 self.args.source_dir = None
245 self.args.cmake_opts = None
Anas Nashif070cf6a2021-05-29 07:53:41 -0400246
Carles Cufib7101772019-02-21 15:58:19 +0100247 try:
248 # Only one source_dir is allowed, as the first positional arg
249 if remainder[0] != _ARG_SEPARATOR:
250 self.args.source_dir = remainder[0]
251 remainder = remainder[1:]
252 # Only the first argument separator is consumed, the rest are
253 # passed on to CMake
254 if remainder[0] == _ARG_SEPARATOR:
255 remainder = remainder[1:]
Ulf Magnusson5d307c92019-09-02 15:35:56 +0200256 if remainder:
Carles Cufib7101772019-02-21 15:58:19 +0100257 self.args.cmake_opts = remainder
258 except IndexError:
259 return
260
Anas Nashif79b955c2023-01-27 11:37:19 +0000261 def _parse_test_item(self, test_item):
Anas Nashif10081242023-01-27 11:35:36 +0000262 found_test_metadata = False
Anas Nashif070cf6a2021-05-29 07:53:41 -0400263 for yp in ['sample.yaml', 'testcase.yaml']:
264 yf = os.path.join(self.args.source_dir, yp)
265 if not os.path.exists(yf):
266 continue
Anas Nashif10081242023-01-27 11:35:36 +0000267 found_test_metadata = True
Anas Nashif070cf6a2021-05-29 07:53:41 -0400268 with open(yf, 'r') as stream:
269 try:
270 y = yaml.safe_load(stream)
271 except yaml.YAMLError as exc:
272 log.die(exc)
273 tests = y.get('tests')
274 if not tests:
Anas Nashif10081242023-01-27 11:35:36 +0000275 log.die(f"No tests found in {yf}")
Anas Nashif79b955c2023-01-27 11:37:19 +0000276 item = tests.get(test_item)
Anas Nashif070cf6a2021-05-29 07:53:41 -0400277 if not item:
Anas Nashif79b955c2023-01-27 11:37:19 +0000278 log.die(f"Test item {test_item} not found in {yf}")
Anas Nashif070cf6a2021-05-29 07:53:41 -0400279
280 for data in ['extra_args', 'extra_configs']:
281 extra = item.get(data)
282 if not extra:
283 continue
284 if isinstance(extra, str):
285 arg_list = extra.split(" ")
286 else:
287 arg_list = extra
288 args = ["-D{}".format(arg.replace('"', '')) for arg in arg_list]
289 if self.args.cmake_opts:
290 self.args.cmake_opts.extend(args)
291 else:
292 self.args.cmake_opts = args
Anas Nashif10081242023-01-27 11:35:36 +0000293 return found_test_metadata
Anas Nashif070cf6a2021-05-29 07:53:41 -0400294
Marti Bolivarab822642019-01-23 08:31:06 -0700295 def _sanity_precheck(self):
296 app = self.args.source_dir
297 if app:
Marti Bolivar8c447992019-01-30 13:53:40 -0700298 self.check_force(
299 os.path.isdir(app),
300 'source directory {} does not exist'.format(app))
301 self.check_force(
302 'CMakeLists.txt' in os.listdir(app),
303 "{} doesn't contain a CMakeLists.txt".format(app))
Marti Bolivarab822642019-01-23 08:31:06 -0700304
305 def _update_cache(self):
306 try:
Carles Cufi31bdad52019-04-26 21:53:02 +0200307 self.cmake_cache = CMakeCache.from_build_dir(self.build_dir)
Marti Bolivarab822642019-01-23 08:31:06 -0700308 except FileNotFoundError:
309 pass
310
311 def _setup_build_dir(self):
312 # Initialize build_dir and created_build_dir attributes.
Marti Bolivar8c447992019-01-30 13:53:40 -0700313 # If we created the build directory, we must run CMake.
Marti Bolivarab822642019-01-23 08:31:06 -0700314 log.dbg('setting up build directory', level=log.VERBOSE_EXTREME)
Carles Cufi98980c62019-06-05 16:04:29 +0200315 # The CMake Cache has not been loaded yet, so this is safe
Ulf Magnusson89efaed2019-09-05 13:44:09 +0200316 board, _ = self._find_board()
Carles Cufi98980c62019-06-05 16:04:29 +0200317 source_dir = self._find_source_dir()
318 app = os.path.split(source_dir)[1]
319 build_dir = find_build_dir(self.args.build_dir, board=board,
320 source_dir=source_dir, app=app)
321 if not build_dir:
322 log.die('Unable to determine a default build folder. Check '
323 'your build.dir-fmt configuration option')
Marti Bolivarab822642019-01-23 08:31:06 -0700324
325 if os.path.exists(build_dir):
326 if not os.path.isdir(build_dir):
327 log.die('build directory {} exists and is not a directory'.
328 format(build_dir))
329 else:
330 os.makedirs(build_dir, exist_ok=False)
331 self.created_build_dir = True
332 self.run_cmake = True
333
334 self.build_dir = build_dir
335
Carles Cufi98980c62019-06-05 16:04:29 +0200336 def _find_source_dir(self):
Marti Bolivarab822642019-01-23 08:31:06 -0700337 # Initialize source_dir attribute, either from command line argument,
338 # implicitly from the build directory's CMake cache, or using the
339 # default (current working directory).
340 log.dbg('setting up source directory', level=log.VERBOSE_EXTREME)
341 if self.args.source_dir:
342 source_dir = self.args.source_dir
343 elif self.cmake_cache:
Marti Bolivaracda2572019-04-10 17:09:17 -0600344 source_dir = self.cmake_cache.get('CMAKE_HOME_DIRECTORY')
Marti Bolivarab822642019-01-23 08:31:06 -0700345 if not source_dir:
Marti Bolivaracda2572019-04-10 17:09:17 -0600346 # This really ought to be there. The build directory
347 # must be corrupted somehow. Let's see what we can do.
348 log.die('build directory', self.build_dir,
349 'CMake cache has no CMAKE_HOME_DIRECTORY;',
350 'please give a source_dir')
Marti Bolivarab822642019-01-23 08:31:06 -0700351 else:
352 source_dir = os.getcwd()
Carles Cufi98980c62019-06-05 16:04:29 +0200353 return os.path.abspath(source_dir)
Marti Bolivarab822642019-01-23 08:31:06 -0700354
Carles Cufib7c75912019-04-14 21:52:45 +0200355 def _sanity_check_source_dir(self):
Marti Bolivarab822642019-01-23 08:31:06 -0700356 if self.source_dir == self.build_dir:
357 # There's no forcing this.
358 log.die('source and build directory {} cannot be the same; '
359 'use --build-dir {} to specify a build directory'.
360 format(self.source_dir, self.build_dir))
361
362 srcrel = os.path.relpath(self.source_dir)
Marti Bolivar8c447992019-01-30 13:53:40 -0700363 self.check_force(
364 not is_zephyr_build(self.source_dir),
365 'it looks like {srcrel} is a build directory: '
366 'did you mean --build-dir {srcrel} instead?'.
367 format(srcrel=srcrel))
368 self.check_force(
369 'CMakeLists.txt' in os.listdir(self.source_dir),
370 'source directory "{srcrel}" does not contain '
371 'a CMakeLists.txt; is this really what you '
372 'want to build? (Use -s SOURCE_DIR to specify '
373 'the application source directory)'.
374 format(srcrel=srcrel))
Carles Cufib7c75912019-04-14 21:52:45 +0200375
376 def _sanity_check(self):
377 # Sanity check the build configuration.
378 # Side effect: may update cmake_cache attribute.
379 log.dbg('sanity checking the build', level=log.VERBOSE_EXTREME)
380 self._sanity_check_source_dir()
381
Marti Bolivarab822642019-01-23 08:31:06 -0700382 if not self.cmake_cache:
383 return # That's all we can check without a cache.
384
Martí Bolívar877fc592020-04-10 15:39:55 -0700385 if "CMAKE_PROJECT_NAME" not in self.cmake_cache:
386 # This happens sometimes when a build system is not
387 # completely generated due to an error during the
388 # CMake configuration phase.
389 self.run_cmake = True
390
Torsten Rasmussen5fe5d6b2021-11-22 13:35:06 +0100391 cached_proj = self.cmake_cache.get('APPLICATION_SOURCE_DIR')
392 cached_app = self.cmake_cache.get('APP_DIR')
393 # if APP_DIR is None but APPLICATION_SOURCE_DIR is set, that indicates
394 # an older build folder, this still requires pristine.
395 if cached_app is None and cached_proj:
396 cached_app = cached_proj
397
398 log.dbg('APP_DIR:', cached_app, level=log.VERBOSE_EXTREME)
Marti Bolivarab822642019-01-23 08:31:06 -0700399 source_abs = (os.path.abspath(self.args.source_dir)
400 if self.args.source_dir else None)
401 cached_abs = os.path.abspath(cached_app) if cached_app else None
Marti Bolivar8c447992019-01-30 13:53:40 -0700402
Carles Cufib7c75912019-04-14 21:52:45 +0200403 log.dbg('pristine:', self.auto_pristine, level=log.VERBOSE_EXTREME)
Mikkel Jakobsen98eb3162020-03-04 15:14:43 +0100404
Marti Bolivar8c447992019-01-30 13:53:40 -0700405 # If the build directory specifies a source app, make sure it's
406 # consistent with --source-dir.
407 apps_mismatched = (source_abs and cached_abs and
Marc Herbertc2524a02021-11-02 10:30:27 -0700408 pathlib.Path(source_abs).resolve() != pathlib.Path(cached_abs).resolve())
Mikkel Jakobsen98eb3162020-03-04 15:14:43 +0100409
Marti Bolivar8c447992019-01-30 13:53:40 -0700410 self.check_force(
Carles Cufib7c75912019-04-14 21:52:45 +0200411 not apps_mismatched or self.auto_pristine,
Marti Bolivar8c447992019-01-30 13:53:40 -0700412 'Build directory "{}" is for application "{}", but source '
Carles Cufib7c75912019-04-14 21:52:45 +0200413 'directory "{}" was specified; please clean it, use --pristine, '
414 'or use --build-dir to set another build directory'.
Marti Bolivar8c447992019-01-30 13:53:40 -0700415 format(self.build_dir, cached_abs, source_abs))
Mikkel Jakobsen98eb3162020-03-04 15:14:43 +0100416
Marti Bolivar8c447992019-01-30 13:53:40 -0700417 if apps_mismatched:
Marti Bolivarab822642019-01-23 08:31:06 -0700418 self.run_cmake = True # If they insist, we need to re-run cmake.
419
Marti Bolivar8b9b7e72019-05-23 09:31:31 -0600420 # If CACHED_BOARD is not defined, we need some other way to
421 # find the board.
Marti Bolivarab822642019-01-23 08:31:06 -0700422 cached_board = self.cmake_cache.get('CACHED_BOARD')
423 log.dbg('CACHED_BOARD:', cached_board, level=log.VERBOSE_EXTREME)
Marti Bolivar8b9b7e72019-05-23 09:31:31 -0600424 # If apps_mismatched and self.auto_pristine are true, we will
425 # run pristine on the build, invalidating the cached
426 # board. In that case, we need some way of getting the board.
Carles Cufib7c75912019-04-14 21:52:45 +0200427 self.check_force((cached_board and
428 not (apps_mismatched and self.auto_pristine))
Marti Bolivar8b9b7e72019-05-23 09:31:31 -0600429 or self.args.board or self.config_board or
430 os.environ.get('BOARD'),
431 'Cached board not defined, please provide it '
432 '(provide --board, set default with '
433 '"west config build.board <BOARD>", or set '
434 'BOARD in the environment)')
Marti Bolivarab822642019-01-23 08:31:06 -0700435
Marti Bolivar8c447992019-01-30 13:53:40 -0700436 # Check consistency between cached board and --board.
437 boards_mismatched = (self.args.board and cached_board and
438 self.args.board != cached_board)
439 self.check_force(
Carles Cufib7c75912019-04-14 21:52:45 +0200440 not boards_mismatched or self.auto_pristine,
Marti Bolivar8c447992019-01-30 13:53:40 -0700441 'Build directory {} targets board {}, but board {} was specified. '
Carles Cufib7c75912019-04-14 21:52:45 +0200442 '(Clean the directory, use --pristine, or use --build-dir to '
443 'specify a different one.)'.
Marti Bolivar8c447992019-01-30 13:53:40 -0700444 format(self.build_dir, cached_board, self.args.board))
Marti Bolivarab822642019-01-23 08:31:06 -0700445
Carles Cufib7c75912019-04-14 21:52:45 +0200446 if self.auto_pristine and (apps_mismatched or boards_mismatched):
Carles Cufi3a88dce2019-04-18 16:46:12 +0200447 self._run_pristine()
Carles Cufib7c75912019-04-14 21:52:45 +0200448 self.cmake_cache = None
449 log.dbg('run_cmake:', True, level=log.VERBOSE_EXTREME)
450 self.run_cmake = True
451
452 # Tricky corner-case: The user has not specified a build folder but
453 # there was one in the CMake cache. Since this is going to be
454 # invalidated, reset to CWD and re-run the basic tests.
455 if ((boards_mismatched and not apps_mismatched) and
Marti Bolivar69099e32019-05-04 13:16:15 -0600456 (not source_abs and cached_abs)):
Carles Cufi98980c62019-06-05 16:04:29 +0200457 self.source_dir = self._find_source_dir()
Carles Cufib7c75912019-04-14 21:52:45 +0200458 self._sanity_check_source_dir()
459
Marti Bolivar88fb8ba2019-05-04 13:14:57 -0600460 def _run_cmake(self, board, origin, cmake_opts):
Marti Bolivar88fb8ba2019-05-04 13:14:57 -0600461 if board is None and config_getboolean('board_warn', True):
462 log.wrn('This looks like a fresh build and BOARD is unknown;',
463 "so it probably won't work. To fix, use",
464 '--board=<your-board>.')
465 log.inf('Note: to silence the above message, run',
466 "'west config build.board_warn false'")
467
Marti Bolivarab822642019-01-23 08:31:06 -0700468 if not self.run_cmake:
Marti Bolivarab822642019-01-23 08:31:06 -0700469 return
470
Marti Bolivare6873b82019-06-02 22:48:52 -0600471 _banner('generating a build system')
472
Marti Bolivar88fb8ba2019-05-04 13:14:57 -0600473 if board is not None and origin != 'CMakeCache.txt':
474 cmake_opts = ['-DBOARD={}'.format(board)]
475 else:
476 cmake_opts = []
477 if self.args.cmake_opts:
478 cmake_opts.extend(self.args.cmake_opts)
Marti Bolivar5880fee2023-01-09 16:17:51 -0800479 if self.args.snippets:
480 cmake_opts.append(f'-DSNIPPET={";".join(self.args.snippets)}')
Marti Bolivar88fb8ba2019-05-04 13:14:57 -0600481
Martí Bolívar0186ead2019-12-09 11:05:11 -0800482 user_args = config_get('cmake-args', None)
483 if user_args:
484 cmake_opts.extend(shlex.split(user_args))
485
Torsten Rasmussen5fe5d6b2021-11-22 13:35:06 +0100486 config_sysbuild = config_getboolean('sysbuild', False)
487 if self.args.sysbuild or (config_sysbuild and not self.args.no_sysbuild):
488 cmake_opts.extend(['-S{}'.format(SYSBUILD_PROJ_DIR),
Torsten Rasmussen8408af62021-11-22 10:29:56 +0100489 '-DAPP_DIR:PATH={}'.format(self.source_dir)])
Torsten Rasmussen5fe5d6b2021-11-22 13:35:06 +0100490 else:
491 # self.args.no_sysbuild == True or config sysbuild False
492 cmake_opts.extend(['-S{}'.format(self.source_dir)])
493
Carles Cufi3c6584d2019-04-14 21:51:16 +0200494 # Invoke CMake from the current working directory using the
495 # -S and -B options (officially introduced in CMake 3.13.0).
496 # This is important because users expect invocations like this
497 # to Just Work:
Marti Bolivarab822642019-01-23 08:31:06 -0700498 #
499 # west build -- -DOVERLAY_CONFIG=relative-path.conf
Torsten Rasmussenef3c5e52020-06-08 21:09:15 +0200500 final_cmake_args = ['-DWEST_PYTHON={}'.format(sys.executable),
501 '-B{}'.format(self.build_dir),
Marti Bolivarbbe890a2019-05-01 16:53:32 -0600502 '-G{}'.format(config_get('generator',
503 DEFAULT_CMAKE_GENERATOR))]
Marti Bolivarab822642019-01-23 08:31:06 -0700504 if cmake_opts:
505 final_cmake_args.extend(cmake_opts)
Marti Bolivar8465cf22019-05-01 17:24:23 -0600506 run_cmake(final_cmake_args, dry_run=self.args.dry_run)
Carles Cufib7c75912019-04-14 21:52:45 +0200507
Carles Cufi3a88dce2019-04-18 16:46:12 +0200508 def _run_pristine(self):
Marti Bolivare6873b82019-06-02 22:48:52 -0600509 _banner('making build dir {} pristine'.format(self.build_dir))
Carles Cufi3a88dce2019-04-18 16:46:12 +0200510 if not is_zephyr_build(self.build_dir):
Marti Bolivar69099e32019-05-04 13:16:15 -0600511 log.die('Refusing to run pristine on a folder that is not a '
512 'Zephyr build system')
Carles Cufi3a88dce2019-04-18 16:46:12 +0200513
Torsten Rasmussene819fa42020-03-10 14:52:35 +0100514 cache = CMakeCache.from_build_dir(self.build_dir)
Torsten Rasmussen774103d2021-01-18 10:56:25 +0100515
516 app_src_dir = cache.get('APPLICATION_SOURCE_DIR')
517 app_bin_dir = cache.get('APPLICATION_BINARY_DIR')
518
519 cmake_args = [f'-DBINARY_DIR={app_bin_dir}',
520 f'-DSOURCE_DIR={app_src_dir}',
521 '-P', cache['ZEPHYR_BASE'] + '/cmake/pristine.cmake']
Marti Bolivar8465cf22019-05-01 17:24:23 -0600522 run_cmake(cmake_args, cwd=self.build_dir, dry_run=self.args.dry_run)
Carles Cufi3a88dce2019-04-18 16:46:12 +0200523
Torsten Rasmussen8408af62021-11-22 10:29:56 +0100524 def _run_build(self, target, domain):
Marti Bolivare6873b82019-06-02 22:48:52 -0600525 if target:
526 _banner('running target {}'.format(target))
Martí Bolívar34a59162020-03-05 14:20:18 -0800527 elif self.run_cmake:
Marti Bolivare6873b82019-06-02 22:48:52 -0600528 _banner('building application')
Carles Cufib7c75912019-04-14 21:52:45 +0200529 extra_args = ['--target', target] if target else []
Marti Bolivar3a486a82019-05-04 13:32:36 -0600530 if self.args.build_opt:
531 extra_args.append('--')
532 extra_args.extend(self.args.build_opt)
Marti Bolivar0396b6e2019-05-06 18:10:35 -0600533 if self.args.verbose:
534 self._append_verbose_args(extra_args,
535 not bool(self.args.build_opt))
Torsten Rasmussen8408af62021-11-22 10:29:56 +0100536
537 domains = load_domains(self.build_dir)
538 build_dir_list = []
539
540 if domain is None:
541 # If no domain is specified, we just build top build dir as that
542 # will build all domains.
543 build_dir_list = [domains.get_top_build_dir()]
544 else:
545 _banner('building domain(s): {}'.format(' '.join(domain)))
546 domain_list = domains.get_domains(domain)
547 for d in domain_list:
548 build_dir_list.append(d.build_dir)
549
550 for b in build_dir_list:
551 run_build(b, extra_args=extra_args,
552 dry_run=self.args.dry_run)
Marti Bolivar0396b6e2019-05-06 18:10:35 -0600553
554 def _append_verbose_args(self, extra_args, add_dashes):
555 # These hacks are only needed for CMake versions earlier than
556 # 3.14. When Zephyr's minimum version is at least that, we can
557 # drop this nonsense and just run "cmake --build BUILD -v".
558 self._update_cache()
559 if not self.cmake_cache:
560 return
561 generator = self.cmake_cache.get('CMAKE_GENERATOR')
562 if not generator:
563 return
564 # Substring matching is for things like "Eclipse CDT4 - Ninja".
565 if 'Ninja' in generator:
566 if add_dashes:
567 extra_args.append('--')
568 extra_args.append('-v')
569 elif generator == 'Unix Makefiles':
570 if add_dashes:
571 extra_args.append('--')
572 extra_args.append('VERBOSE=1')