blob: f7a605f4d7fc8a289279985dc8d3a42bc6b17e3f [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
Maciej Perkowskifb9a6af2023-08-29 16:51:58 +0200126 or sample.yaml. If source directory is not used
127 an argument has to be defined as
128 SOURCE_PATH/TEST_NAME.
129 E.g. samples/hello_world/sample.basic.helloworld.
130 If source directory is passed
131 then "TEST_NAME" is enough.''')
Martí Bolívar6e4c2b92020-06-25 12:58:58 -0700132 group.add_argument('-o', '--build-opt', default=[], action='append',
133 help='''options to pass to the build tool
134 (make or ninja); may be given more than once''')
135 group.add_argument('-n', '--just-print', '--dry-run', '--recon',
136 dest='dry_run', action='store_true',
137 help="just print build commands; don't run them")
Marti Bolivar5880fee2023-01-09 16:17:51 -0800138 group.add_argument('-S', '--snippet', dest='snippets',
139 action='append', default=[],
140 help='''add the argument to SNIPPET; may be given
141 multiple times. Forces CMake to run again if given.
142 Do not use this option with manually specified
143 -DSNIPPET... cmake arguments: the results are
144 undefined''')
Martí Bolívar6e4c2b92020-06-25 12:58:58 -0700145
Torsten Rasmussen5fe5d6b2021-11-22 13:35:06 +0100146 group = parser.add_mutually_exclusive_group()
147 group.add_argument('--sysbuild', action='store_true',
148 help='''create multi domain build system''')
149 group.add_argument('--no-sysbuild', action='store_true',
150 help='''do not create multi domain build system
151 (default)''')
152
Martí Bolívar6e4c2b92020-06-25 12:58:58 -0700153 group = parser.add_argument_group('pristine builds',
154 PRISTINE_DESCRIPTION)
155 group.add_argument('-p', '--pristine', choices=['auto', 'always',
156 'never'], action=AlwaysIfMissing, nargs='?',
157 help='pristine build folder setting')
158
Marti Bolivarab822642019-01-23 08:31:06 -0700159 return parser
160
Carles Cufib7101772019-02-21 15:58:19 +0100161 def do_run(self, args, remainder):
Marti Bolivarab822642019-01-23 08:31:06 -0700162 self.args = args # Avoid having to pass them around
Marti Bolivar8b9b7e72019-05-23 09:31:31 -0600163 self.config_board = config_get('board', None)
Marti Bolivara1ef6962019-05-01 17:06:15 -0600164 log.dbg('args: {} remainder: {}'.format(args, remainder),
165 level=log.VERBOSE_EXTREME)
Carles Cufib7101772019-02-21 15:58:19 +0100166 # Store legacy -s option locally
167 source_dir = self.args.source_dir
168 self._parse_remainder(remainder)
Anas Nashif070cf6a2021-05-29 07:53:41 -0400169 # Parse testcase.yaml or sample.yaml files for additional options.
170 if self.args.test_item:
Anas Nashif79b955c2023-01-27 11:37:19 +0000171 # we get path + testitem
172 item = os.path.basename(self.args.test_item)
Maciej Perkowskifb9a6af2023-08-29 16:51:58 +0200173 if self.args.source_dir:
174 test_path = self.args.source_dir
175 else:
176 test_path = os.path.dirname(self.args.test_item)
Anas Nashifd4b0c482023-08-23 10:28:49 +0000177 if test_path and os.path.exists(test_path):
Anas Nashif79b955c2023-01-27 11:37:19 +0000178 self.args.source_dir = test_path
Anas Nashifd4b0c482023-08-23 10:28:49 +0000179 if not self._parse_test_item(item):
180 log.die("No test metadata found")
181 else:
182 log.die("test item path does not exist")
183
Carles Cufib7101772019-02-21 15:58:19 +0100184 if source_dir:
185 if self.args.source_dir:
186 log.die("source directory specified twice:({} and {})".format(
187 source_dir, self.args.source_dir))
188 self.args.source_dir = source_dir
189 log.dbg('source_dir: {} cmake_opts: {}'.format(self.args.source_dir,
Marti Bolivara1ef6962019-05-01 17:06:15 -0600190 self.args.cmake_opts),
191 level=log.VERBOSE_EXTREME)
Marti Bolivarab822642019-01-23 08:31:06 -0700192 self._sanity_precheck()
193 self._setup_build_dir()
Carles Cufib7c75912019-04-14 21:52:45 +0200194
195 if args.pristine is not None:
196 pristine = args.pristine
197 else:
198 # Load the pristine={auto, always, never} configuration value
Martí Bolívarc19c6fb2021-01-15 09:17:01 -0800199 pristine = config_get('pristine', 'never')
Carles Cufib7c75912019-04-14 21:52:45 +0200200 if pristine not in ['auto', 'always', 'never']:
Marti Bolivar69099e32019-05-04 13:16:15 -0600201 log.wrn(
202 'treating unknown build.pristine value "{}" as "never"'.
203 format(pristine))
Carles Cufib7c75912019-04-14 21:52:45 +0200204 pristine = 'never'
Martí Bolívar1f9ab852023-03-09 13:10:28 -0800205 self.auto_pristine = pristine == 'auto'
Carles Cufib7c75912019-04-14 21:52:45 +0200206
207 log.dbg('pristine: {} auto_pristine: {}'.format(pristine,
Marti Bolivara1ef6962019-05-01 17:06:15 -0600208 self.auto_pristine),
209 level=log.VERBOSE_VERY)
Marti Bolivarab822642019-01-23 08:31:06 -0700210 if is_zephyr_build(self.build_dir):
Carles Cufib7c75912019-04-14 21:52:45 +0200211 if pristine == 'always':
Carles Cufi3a88dce2019-04-18 16:46:12 +0200212 self._run_pristine()
Marti Bolivarab822642019-01-23 08:31:06 -0700213 self.run_cmake = True
Carles Cufib7c75912019-04-14 21:52:45 +0200214 else:
215 self._update_cache()
Marti Bolivar1f5e6d82019-05-01 16:48:04 -0600216 if (self.args.cmake or self.args.cmake_opts or
Marti Bolivar5880fee2023-01-09 16:17:51 -0800217 self.args.cmake_only or self.args.snippets):
Carles Cufib7c75912019-04-14 21:52:45 +0200218 self.run_cmake = True
Marti Bolivarab822642019-01-23 08:31:06 -0700219 else:
220 self.run_cmake = True
Carles Cufi98980c62019-06-05 16:04:29 +0200221 self.source_dir = self._find_source_dir()
Marti Bolivarab822642019-01-23 08:31:06 -0700222 self._sanity_check()
223
Marti Bolivar88fb8ba2019-05-04 13:14:57 -0600224 board, origin = self._find_board()
225 self._run_cmake(board, origin, self.args.cmake_opts)
Marti Bolivar1f5e6d82019-05-01 16:48:04 -0600226 if args.cmake_only:
227 return
228
Marti Bolivarab822642019-01-23 08:31:06 -0700229 self._sanity_check()
230 self._update_cache()
Torsten Rasmussen8408af62021-11-22 10:29:56 +0100231 self.domains = load_domains(self.build_dir)
Marti Bolivarab822642019-01-23 08:31:06 -0700232
Torsten Rasmussen8408af62021-11-22 10:29:56 +0100233 self._run_build(args.target, args.domain)
Marti Bolivarab822642019-01-23 08:31:06 -0700234
Marti Bolivar88fb8ba2019-05-04 13:14:57 -0600235 def _find_board(self):
236 board, origin = None, None
Marti Bolivar88fb8ba2019-05-04 13:14:57 -0600237 if self.cmake_cache:
238 board, origin = (self.cmake_cache.get('CACHED_BOARD'),
239 'CMakeCache.txt')
Martí Bolívaraf5a79f2021-02-02 09:22:44 -0800240
241 # A malformed CMake cache may exist, but not have a board.
242 # This happens if there's a build error from a previous run.
243 if board is not None:
244 return (board, origin)
245
246 if self.args.board:
Marti Bolivar88fb8ba2019-05-04 13:14:57 -0600247 board, origin = self.args.board, 'command line'
248 elif 'BOARD' in os.environ:
249 board, origin = os.environ['BOARD'], 'env'
Marti Bolivar8b9b7e72019-05-23 09:31:31 -0600250 elif self.config_board is not None:
251 board, origin = self.config_board, 'configfile'
Marti Bolivar88fb8ba2019-05-04 13:14:57 -0600252 return board, origin
253
Carles Cufib7101772019-02-21 15:58:19 +0100254 def _parse_remainder(self, remainder):
255 self.args.source_dir = None
256 self.args.cmake_opts = None
Anas Nashif070cf6a2021-05-29 07:53:41 -0400257
Carles Cufib7101772019-02-21 15:58:19 +0100258 try:
259 # Only one source_dir is allowed, as the first positional arg
260 if remainder[0] != _ARG_SEPARATOR:
261 self.args.source_dir = remainder[0]
262 remainder = remainder[1:]
263 # Only the first argument separator is consumed, the rest are
264 # passed on to CMake
265 if remainder[0] == _ARG_SEPARATOR:
266 remainder = remainder[1:]
Ulf Magnusson5d307c92019-09-02 15:35:56 +0200267 if remainder:
Carles Cufib7101772019-02-21 15:58:19 +0100268 self.args.cmake_opts = remainder
269 except IndexError:
270 return
271
Anas Nashif79b955c2023-01-27 11:37:19 +0000272 def _parse_test_item(self, test_item):
Anas Nashif10081242023-01-27 11:35:36 +0000273 found_test_metadata = False
Anas Nashif070cf6a2021-05-29 07:53:41 -0400274 for yp in ['sample.yaml', 'testcase.yaml']:
275 yf = os.path.join(self.args.source_dir, yp)
276 if not os.path.exists(yf):
277 continue
Anas Nashif10081242023-01-27 11:35:36 +0000278 found_test_metadata = True
Anas Nashif070cf6a2021-05-29 07:53:41 -0400279 with open(yf, 'r') as stream:
280 try:
281 y = yaml.safe_load(stream)
282 except yaml.YAMLError as exc:
283 log.die(exc)
Anas Nashif6fdfc912023-07-19 02:01:52 +0000284 common = y.get('common')
Anas Nashif070cf6a2021-05-29 07:53:41 -0400285 tests = y.get('tests')
286 if not tests:
Anas Nashif10081242023-01-27 11:35:36 +0000287 log.die(f"No tests found in {yf}")
Anas Nashif79b955c2023-01-27 11:37:19 +0000288 item = tests.get(test_item)
Anas Nashif070cf6a2021-05-29 07:53:41 -0400289 if not item:
Anas Nashif79b955c2023-01-27 11:37:19 +0000290 log.die(f"Test item {test_item} not found in {yf}")
Anas Nashif070cf6a2021-05-29 07:53:41 -0400291
Anas Nashif6fdfc912023-07-19 02:01:52 +0000292 sysbuild = False
Anas Nashif47102de2023-07-21 12:03:58 +0000293 extra_dtc_overlay_files = []
294 extra_overlay_confs = []
295 extra_conf_files = []
Jamie McCraebc97d8f2023-08-03 10:56:41 +0100296 required_snippets = []
Anas Nashif6fdfc912023-07-19 02:01:52 +0000297 for section in [common, item]:
298 if not section:
Anas Nashif070cf6a2021-05-29 07:53:41 -0400299 continue
Anas Nashif6fdfc912023-07-19 02:01:52 +0000300 sysbuild = section.get('sysbuild', sysbuild)
Anas Nashif47102de2023-07-21 12:03:58 +0000301 for data in [
302 'extra_args',
303 'extra_configs',
304 'extra_conf_files',
305 'extra_overlay_confs',
Jamie McCraebc97d8f2023-08-03 10:56:41 +0100306 'extra_dtc_overlay_files',
307 'required_snippets'
Anas Nashif47102de2023-07-21 12:03:58 +0000308 ]:
Anas Nashif6fdfc912023-07-19 02:01:52 +0000309 extra = section.get(data)
310 if not extra:
311 continue
312 if isinstance(extra, str):
313 arg_list = extra.split(" ")
314 else:
315 arg_list = extra
Anas Nashif47102de2023-07-21 12:03:58 +0000316
Anas Nashif6fdfc912023-07-19 02:01:52 +0000317 if data == 'extra_configs':
318 args = ["-D{}".format(arg.replace('"', '\"')) for arg in arg_list]
319 elif data == 'extra_args':
Chaitanya Tata6b05af62023-09-05 01:47:48 +0530320 # Retain quotes around config options
321 config_options = [arg for arg in arg_list if arg.startswith("CONFIG_")]
322 non_config_options = [arg for arg in arg_list if not arg.startswith("CONFIG_")]
323 args = ["-D{}".format(a.replace('"', '\"')) for a in config_options]
324 args.extend(["-D{}".format(arg.replace('"', '')) for arg in non_config_options])
Anas Nashif47102de2023-07-21 12:03:58 +0000325 elif data == 'extra_conf_files':
326 extra_conf_files.extend(arg_list)
327 continue
328 elif data == 'extra_overlay_confs':
329 extra_overlay_confs.extend(arg_list)
330 continue
331 elif data == 'extra_dtc_overlay_files':
332 extra_dtc_overlay_files.extend(arg_list)
333 continue
Jamie McCraebc97d8f2023-08-03 10:56:41 +0100334 elif data == 'required_snippets':
335 required_snippets.extend(arg_list)
336 continue
Anas Nashif47102de2023-07-21 12:03:58 +0000337
Anas Nashif6fdfc912023-07-19 02:01:52 +0000338 if self.args.cmake_opts:
339 self.args.cmake_opts.extend(args)
340 else:
341 self.args.cmake_opts = args
342
343 self.args.sysbuild = sysbuild
Anas Nashif47102de2023-07-21 12:03:58 +0000344
Anas Nashifd4b0c482023-08-23 10:28:49 +0000345 if found_test_metadata:
346 args = []
347 if extra_conf_files:
348 args.append(f"CONF_FILE=\"{';'.join(extra_conf_files)}\"")
Anas Nashif47102de2023-07-21 12:03:58 +0000349
Anas Nashifd4b0c482023-08-23 10:28:49 +0000350 if extra_dtc_overlay_files:
351 args.append(f"DTC_OVERLAY_FILE=\"{';'.join(extra_dtc_overlay_files)}\"")
Anas Nashif47102de2023-07-21 12:03:58 +0000352
Anas Nashifd4b0c482023-08-23 10:28:49 +0000353 if extra_overlay_confs:
354 args.append(f"OVERLAY_CONFIG=\"{';'.join(extra_overlay_confs)}\"")
Jamie McCraebc97d8f2023-08-03 10:56:41 +0100355
356 if required_snippets:
357 args.append(f"SNIPPET=\"{';'.join(required_snippets)}\"")
358
Anas Nashifd4b0c482023-08-23 10:28:49 +0000359 # Build the final argument list
360 args_expanded = ["-D{}".format(a.replace('"', '')) for a in args]
Anas Nashif47102de2023-07-21 12:03:58 +0000361
Anas Nashifd4b0c482023-08-23 10:28:49 +0000362 if self.args.cmake_opts:
363 self.args.cmake_opts.extend(args_expanded)
364 else:
365 self.args.cmake_opts = args_expanded
Anas Nashif47102de2023-07-21 12:03:58 +0000366
Anas Nashif10081242023-01-27 11:35:36 +0000367 return found_test_metadata
Anas Nashif070cf6a2021-05-29 07:53:41 -0400368
Marti Bolivarab822642019-01-23 08:31:06 -0700369 def _sanity_precheck(self):
370 app = self.args.source_dir
371 if app:
Marti Bolivar8c447992019-01-30 13:53:40 -0700372 self.check_force(
373 os.path.isdir(app),
374 'source directory {} does not exist'.format(app))
375 self.check_force(
376 'CMakeLists.txt' in os.listdir(app),
377 "{} doesn't contain a CMakeLists.txt".format(app))
Marti Bolivarab822642019-01-23 08:31:06 -0700378
379 def _update_cache(self):
380 try:
Carles Cufi31bdad52019-04-26 21:53:02 +0200381 self.cmake_cache = CMakeCache.from_build_dir(self.build_dir)
Marti Bolivarab822642019-01-23 08:31:06 -0700382 except FileNotFoundError:
383 pass
384
385 def _setup_build_dir(self):
386 # Initialize build_dir and created_build_dir attributes.
Marti Bolivar8c447992019-01-30 13:53:40 -0700387 # If we created the build directory, we must run CMake.
Marti Bolivarab822642019-01-23 08:31:06 -0700388 log.dbg('setting up build directory', level=log.VERBOSE_EXTREME)
Carles Cufi98980c62019-06-05 16:04:29 +0200389 # The CMake Cache has not been loaded yet, so this is safe
Ulf Magnusson89efaed2019-09-05 13:44:09 +0200390 board, _ = self._find_board()
Carles Cufi98980c62019-06-05 16:04:29 +0200391 source_dir = self._find_source_dir()
392 app = os.path.split(source_dir)[1]
393 build_dir = find_build_dir(self.args.build_dir, board=board,
394 source_dir=source_dir, app=app)
395 if not build_dir:
396 log.die('Unable to determine a default build folder. Check '
397 'your build.dir-fmt configuration option')
Marti Bolivarab822642019-01-23 08:31:06 -0700398
399 if os.path.exists(build_dir):
400 if not os.path.isdir(build_dir):
401 log.die('build directory {} exists and is not a directory'.
402 format(build_dir))
403 else:
404 os.makedirs(build_dir, exist_ok=False)
405 self.created_build_dir = True
406 self.run_cmake = True
407
408 self.build_dir = build_dir
409
Carles Cufi98980c62019-06-05 16:04:29 +0200410 def _find_source_dir(self):
Marti Bolivarab822642019-01-23 08:31:06 -0700411 # Initialize source_dir attribute, either from command line argument,
412 # implicitly from the build directory's CMake cache, or using the
413 # default (current working directory).
414 log.dbg('setting up source directory', level=log.VERBOSE_EXTREME)
415 if self.args.source_dir:
416 source_dir = self.args.source_dir
417 elif self.cmake_cache:
Marti Bolivaracda2572019-04-10 17:09:17 -0600418 source_dir = self.cmake_cache.get('CMAKE_HOME_DIRECTORY')
Marti Bolivarab822642019-01-23 08:31:06 -0700419 if not source_dir:
Marti Bolivaracda2572019-04-10 17:09:17 -0600420 # This really ought to be there. The build directory
421 # must be corrupted somehow. Let's see what we can do.
422 log.die('build directory', self.build_dir,
423 'CMake cache has no CMAKE_HOME_DIRECTORY;',
424 'please give a source_dir')
Marti Bolivarab822642019-01-23 08:31:06 -0700425 else:
426 source_dir = os.getcwd()
Carles Cufi98980c62019-06-05 16:04:29 +0200427 return os.path.abspath(source_dir)
Marti Bolivarab822642019-01-23 08:31:06 -0700428
Carles Cufib7c75912019-04-14 21:52:45 +0200429 def _sanity_check_source_dir(self):
Marti Bolivarab822642019-01-23 08:31:06 -0700430 if self.source_dir == self.build_dir:
431 # There's no forcing this.
432 log.die('source and build directory {} cannot be the same; '
433 'use --build-dir {} to specify a build directory'.
434 format(self.source_dir, self.build_dir))
435
436 srcrel = os.path.relpath(self.source_dir)
Marti Bolivar8c447992019-01-30 13:53:40 -0700437 self.check_force(
438 not is_zephyr_build(self.source_dir),
439 'it looks like {srcrel} is a build directory: '
440 'did you mean --build-dir {srcrel} instead?'.
441 format(srcrel=srcrel))
442 self.check_force(
443 'CMakeLists.txt' in os.listdir(self.source_dir),
444 'source directory "{srcrel}" does not contain '
445 'a CMakeLists.txt; is this really what you '
446 'want to build? (Use -s SOURCE_DIR to specify '
447 'the application source directory)'.
448 format(srcrel=srcrel))
Carles Cufib7c75912019-04-14 21:52:45 +0200449
450 def _sanity_check(self):
451 # Sanity check the build configuration.
452 # Side effect: may update cmake_cache attribute.
453 log.dbg('sanity checking the build', level=log.VERBOSE_EXTREME)
454 self._sanity_check_source_dir()
455
Marti Bolivarab822642019-01-23 08:31:06 -0700456 if not self.cmake_cache:
457 return # That's all we can check without a cache.
458
Martí Bolívar877fc592020-04-10 15:39:55 -0700459 if "CMAKE_PROJECT_NAME" not in self.cmake_cache:
460 # This happens sometimes when a build system is not
461 # completely generated due to an error during the
462 # CMake configuration phase.
463 self.run_cmake = True
464
Torsten Rasmussen5fe5d6b2021-11-22 13:35:06 +0100465 cached_proj = self.cmake_cache.get('APPLICATION_SOURCE_DIR')
466 cached_app = self.cmake_cache.get('APP_DIR')
467 # if APP_DIR is None but APPLICATION_SOURCE_DIR is set, that indicates
468 # an older build folder, this still requires pristine.
469 if cached_app is None and cached_proj:
470 cached_app = cached_proj
471
472 log.dbg('APP_DIR:', cached_app, level=log.VERBOSE_EXTREME)
Marti Bolivarab822642019-01-23 08:31:06 -0700473 source_abs = (os.path.abspath(self.args.source_dir)
474 if self.args.source_dir else None)
475 cached_abs = os.path.abspath(cached_app) if cached_app else None
Marti Bolivar8c447992019-01-30 13:53:40 -0700476
Carles Cufib7c75912019-04-14 21:52:45 +0200477 log.dbg('pristine:', self.auto_pristine, level=log.VERBOSE_EXTREME)
Mikkel Jakobsen98eb3162020-03-04 15:14:43 +0100478
Marti Bolivar8c447992019-01-30 13:53:40 -0700479 # If the build directory specifies a source app, make sure it's
480 # consistent with --source-dir.
481 apps_mismatched = (source_abs and cached_abs and
Marc Herbertc2524a02021-11-02 10:30:27 -0700482 pathlib.Path(source_abs).resolve() != pathlib.Path(cached_abs).resolve())
Mikkel Jakobsen98eb3162020-03-04 15:14:43 +0100483
Marti Bolivar8c447992019-01-30 13:53:40 -0700484 self.check_force(
Carles Cufib7c75912019-04-14 21:52:45 +0200485 not apps_mismatched or self.auto_pristine,
Marti Bolivar8c447992019-01-30 13:53:40 -0700486 'Build directory "{}" is for application "{}", but source '
Carles Cufib7c75912019-04-14 21:52:45 +0200487 'directory "{}" was specified; please clean it, use --pristine, '
488 'or use --build-dir to set another build directory'.
Marti Bolivar8c447992019-01-30 13:53:40 -0700489 format(self.build_dir, cached_abs, source_abs))
Mikkel Jakobsen98eb3162020-03-04 15:14:43 +0100490
Marti Bolivar8c447992019-01-30 13:53:40 -0700491 if apps_mismatched:
Marti Bolivarab822642019-01-23 08:31:06 -0700492 self.run_cmake = True # If they insist, we need to re-run cmake.
493
Marti Bolivar8b9b7e72019-05-23 09:31:31 -0600494 # If CACHED_BOARD is not defined, we need some other way to
495 # find the board.
Marti Bolivarab822642019-01-23 08:31:06 -0700496 cached_board = self.cmake_cache.get('CACHED_BOARD')
497 log.dbg('CACHED_BOARD:', cached_board, level=log.VERBOSE_EXTREME)
Marti Bolivar8b9b7e72019-05-23 09:31:31 -0600498 # If apps_mismatched and self.auto_pristine are true, we will
499 # run pristine on the build, invalidating the cached
500 # board. In that case, we need some way of getting the board.
Carles Cufib7c75912019-04-14 21:52:45 +0200501 self.check_force((cached_board and
502 not (apps_mismatched and self.auto_pristine))
Marti Bolivar8b9b7e72019-05-23 09:31:31 -0600503 or self.args.board or self.config_board or
504 os.environ.get('BOARD'),
505 'Cached board not defined, please provide it '
506 '(provide --board, set default with '
507 '"west config build.board <BOARD>", or set '
508 'BOARD in the environment)')
Marti Bolivarab822642019-01-23 08:31:06 -0700509
Marti Bolivar8c447992019-01-30 13:53:40 -0700510 # Check consistency between cached board and --board.
511 boards_mismatched = (self.args.board and cached_board and
512 self.args.board != cached_board)
513 self.check_force(
Carles Cufib7c75912019-04-14 21:52:45 +0200514 not boards_mismatched or self.auto_pristine,
Marti Bolivar8c447992019-01-30 13:53:40 -0700515 'Build directory {} targets board {}, but board {} was specified. '
Carles Cufib7c75912019-04-14 21:52:45 +0200516 '(Clean the directory, use --pristine, or use --build-dir to '
517 'specify a different one.)'.
Marti Bolivar8c447992019-01-30 13:53:40 -0700518 format(self.build_dir, cached_board, self.args.board))
Marti Bolivarab822642019-01-23 08:31:06 -0700519
Carles Cufib7c75912019-04-14 21:52:45 +0200520 if self.auto_pristine and (apps_mismatched or boards_mismatched):
Carles Cufi3a88dce2019-04-18 16:46:12 +0200521 self._run_pristine()
Carles Cufib7c75912019-04-14 21:52:45 +0200522 self.cmake_cache = None
523 log.dbg('run_cmake:', True, level=log.VERBOSE_EXTREME)
524 self.run_cmake = True
525
526 # Tricky corner-case: The user has not specified a build folder but
527 # there was one in the CMake cache. Since this is going to be
528 # invalidated, reset to CWD and re-run the basic tests.
529 if ((boards_mismatched and not apps_mismatched) and
Marti Bolivar69099e32019-05-04 13:16:15 -0600530 (not source_abs and cached_abs)):
Carles Cufi98980c62019-06-05 16:04:29 +0200531 self.source_dir = self._find_source_dir()
Carles Cufib7c75912019-04-14 21:52:45 +0200532 self._sanity_check_source_dir()
533
Marti Bolivar88fb8ba2019-05-04 13:14:57 -0600534 def _run_cmake(self, board, origin, cmake_opts):
Marti Bolivar88fb8ba2019-05-04 13:14:57 -0600535 if board is None and config_getboolean('board_warn', True):
536 log.wrn('This looks like a fresh build and BOARD is unknown;',
537 "so it probably won't work. To fix, use",
538 '--board=<your-board>.')
539 log.inf('Note: to silence the above message, run',
540 "'west config build.board_warn false'")
541
Marti Bolivarab822642019-01-23 08:31:06 -0700542 if not self.run_cmake:
Marti Bolivarab822642019-01-23 08:31:06 -0700543 return
544
Marti Bolivare6873b82019-06-02 22:48:52 -0600545 _banner('generating a build system')
546
Marti Bolivar88fb8ba2019-05-04 13:14:57 -0600547 if board is not None and origin != 'CMakeCache.txt':
548 cmake_opts = ['-DBOARD={}'.format(board)]
549 else:
550 cmake_opts = []
551 if self.args.cmake_opts:
552 cmake_opts.extend(self.args.cmake_opts)
Marti Bolivar5880fee2023-01-09 16:17:51 -0800553 if self.args.snippets:
554 cmake_opts.append(f'-DSNIPPET={";".join(self.args.snippets)}')
Marti Bolivar88fb8ba2019-05-04 13:14:57 -0600555
Martí Bolívar0186ead2019-12-09 11:05:11 -0800556 user_args = config_get('cmake-args', None)
557 if user_args:
558 cmake_opts.extend(shlex.split(user_args))
559
Torsten Rasmussen5fe5d6b2021-11-22 13:35:06 +0100560 config_sysbuild = config_getboolean('sysbuild', False)
561 if self.args.sysbuild or (config_sysbuild and not self.args.no_sysbuild):
562 cmake_opts.extend(['-S{}'.format(SYSBUILD_PROJ_DIR),
Torsten Rasmussen8408af62021-11-22 10:29:56 +0100563 '-DAPP_DIR:PATH={}'.format(self.source_dir)])
Torsten Rasmussen5fe5d6b2021-11-22 13:35:06 +0100564 else:
565 # self.args.no_sysbuild == True or config sysbuild False
566 cmake_opts.extend(['-S{}'.format(self.source_dir)])
567
Carles Cufi3c6584d2019-04-14 21:51:16 +0200568 # Invoke CMake from the current working directory using the
569 # -S and -B options (officially introduced in CMake 3.13.0).
570 # This is important because users expect invocations like this
571 # to Just Work:
Marti Bolivarab822642019-01-23 08:31:06 -0700572 #
573 # west build -- -DOVERLAY_CONFIG=relative-path.conf
Torsten Rasmussenc03e1902023-05-15 11:59:52 +0200574 final_cmake_args = ['-DWEST_PYTHON={}'.format(pathlib.Path(sys.executable).as_posix()),
Torsten Rasmussenef3c5e52020-06-08 21:09:15 +0200575 '-B{}'.format(self.build_dir),
Marti Bolivarbbe890a2019-05-01 16:53:32 -0600576 '-G{}'.format(config_get('generator',
577 DEFAULT_CMAKE_GENERATOR))]
Marti Bolivarab822642019-01-23 08:31:06 -0700578 if cmake_opts:
579 final_cmake_args.extend(cmake_opts)
Marti Bolivar8465cf22019-05-01 17:24:23 -0600580 run_cmake(final_cmake_args, dry_run=self.args.dry_run)
Carles Cufib7c75912019-04-14 21:52:45 +0200581
Carles Cufi3a88dce2019-04-18 16:46:12 +0200582 def _run_pristine(self):
Marti Bolivare6873b82019-06-02 22:48:52 -0600583 _banner('making build dir {} pristine'.format(self.build_dir))
Carles Cufi3a88dce2019-04-18 16:46:12 +0200584 if not is_zephyr_build(self.build_dir):
Marti Bolivar69099e32019-05-04 13:16:15 -0600585 log.die('Refusing to run pristine on a folder that is not a '
586 'Zephyr build system')
Carles Cufi3a88dce2019-04-18 16:46:12 +0200587
Torsten Rasmussene819fa42020-03-10 14:52:35 +0100588 cache = CMakeCache.from_build_dir(self.build_dir)
Torsten Rasmussen774103d2021-01-18 10:56:25 +0100589
590 app_src_dir = cache.get('APPLICATION_SOURCE_DIR')
591 app_bin_dir = cache.get('APPLICATION_BINARY_DIR')
592
593 cmake_args = [f'-DBINARY_DIR={app_bin_dir}',
594 f'-DSOURCE_DIR={app_src_dir}',
595 '-P', cache['ZEPHYR_BASE'] + '/cmake/pristine.cmake']
Marti Bolivar8465cf22019-05-01 17:24:23 -0600596 run_cmake(cmake_args, cwd=self.build_dir, dry_run=self.args.dry_run)
Carles Cufi3a88dce2019-04-18 16:46:12 +0200597
Torsten Rasmussen8408af62021-11-22 10:29:56 +0100598 def _run_build(self, target, domain):
Marti Bolivare6873b82019-06-02 22:48:52 -0600599 if target:
600 _banner('running target {}'.format(target))
Martí Bolívar34a59162020-03-05 14:20:18 -0800601 elif self.run_cmake:
Marti Bolivare6873b82019-06-02 22:48:52 -0600602 _banner('building application')
Carles Cufib7c75912019-04-14 21:52:45 +0200603 extra_args = ['--target', target] if target else []
Marti Bolivar3a486a82019-05-04 13:32:36 -0600604 if self.args.build_opt:
605 extra_args.append('--')
606 extra_args.extend(self.args.build_opt)
Marti Bolivar0396b6e2019-05-06 18:10:35 -0600607 if self.args.verbose:
608 self._append_verbose_args(extra_args,
609 not bool(self.args.build_opt))
Torsten Rasmussen8408af62021-11-22 10:29:56 +0100610
611 domains = load_domains(self.build_dir)
612 build_dir_list = []
613
614 if domain is None:
615 # If no domain is specified, we just build top build dir as that
616 # will build all domains.
617 build_dir_list = [domains.get_top_build_dir()]
618 else:
619 _banner('building domain(s): {}'.format(' '.join(domain)))
620 domain_list = domains.get_domains(domain)
621 for d in domain_list:
622 build_dir_list.append(d.build_dir)
623
624 for b in build_dir_list:
625 run_build(b, extra_args=extra_args,
626 dry_run=self.args.dry_run)
Marti Bolivar0396b6e2019-05-06 18:10:35 -0600627
628 def _append_verbose_args(self, extra_args, add_dashes):
629 # These hacks are only needed for CMake versions earlier than
630 # 3.14. When Zephyr's minimum version is at least that, we can
631 # drop this nonsense and just run "cmake --build BUILD -v".
632 self._update_cache()
633 if not self.cmake_cache:
634 return
635 generator = self.cmake_cache.get('CMAKE_GENERATOR')
636 if not generator:
637 return
638 # Substring matching is for things like "Eclipse CDT4 - Ninja".
639 if 'Ninja' in generator:
640 if add_dashes:
641 extra_args.append('--')
642 extra_args.append('-v')
643 elif generator == 'Unix Makefiles':
644 if add_dashes:
645 extra_args.append('--')
646 extra_args.append('VERBOSE=1')