1# Copyright (c) 2018 Foundries.io 2# 3# SPDX-License-Identifier: Apache-2.0 4 5import argparse 6import os 7import pathlib 8import shlex 9import sys 10import yaml 11 12from west.commands import Verbosity 13from west.configuration import config 14from west.util import west_topdir 15from west.version import __version__ 16from zcmake import DEFAULT_CMAKE_GENERATOR, run_cmake, run_build, CMakeCache 17from build_helpers import is_zephyr_build, find_build_dir, load_domains, \ 18 FIND_BUILD_DIR_DESCRIPTION 19 20from zephyr_ext_common import Forceable 21 22_ARG_SEPARATOR = '--' 23 24SYSBUILD_PROJ_DIR = pathlib.Path(__file__).resolve().parent.parent.parent \ 25 / pathlib.Path('share/sysbuild') 26 27BUILD_INFO_LOG = 'build_info.yml' 28 29BUILD_USAGE = '''\ 30west build [-h] [-b BOARD[@REV]]] [-d BUILD_DIR] 31 [-S SNIPPET] [--shield SHIELD] 32 [-t TARGET] [-p {auto, always, never}] [-c] [--cmake-only] 33 [-n] [-o BUILD_OPT] [-f] 34 [--sysbuild | --no-sysbuild] [--domain DOMAIN] 35 [--extra-conf FILE.conf] 36 [--extra-dtc-overlay FILE.overlay] 37 [source_dir] -- [cmake_opt [cmake_opt ...]] 38''' 39 40BUILD_DESCRIPTION = f'''\ 41Convenience wrapper for building Zephyr applications. 42 43{FIND_BUILD_DIR_DESCRIPTION} 44 45positional arguments: 46 source_dir application source directory 47 cmake_opt extra options to pass to cmake; implies -c 48 (these must come after "--" as shown above) 49''' 50 51PRISTINE_DESCRIPTION = """\ 52A "pristine" build directory is empty. The -p option controls 53whether the build directory is made pristine before the build 54is done. A bare '--pristine' with no value is the same as 55--pristine=always. Setting --pristine=auto uses heuristics to 56guess if a pristine build may be necessary.""" 57 58def config_get(option, fallback): 59 return config.get('build', option, fallback=fallback) 60 61def config_getboolean(option, fallback): 62 return config.getboolean('build', option, fallback=fallback) 63 64class AlwaysIfMissing(argparse.Action): 65 66 def __call__(self, parser, namespace, values, option_string=None): 67 setattr(namespace, self.dest, values or 'always') 68 69class Build(Forceable): 70 71 def __init__(self): 72 super(Build, self).__init__( 73 'build', 74 # Keep this in sync with the string in west-commands.yml. 75 'compile a Zephyr application', 76 BUILD_DESCRIPTION, 77 accepts_unknown_args=True) 78 79 self.source_dir = None 80 '''Source directory for the build, or None on error.''' 81 82 self.build_dir = None 83 '''Final build directory used to run the build, or None on error.''' 84 85 self.created_build_dir = False 86 '''True if the build directory was created; False otherwise.''' 87 88 self.run_cmake = False 89 '''True if CMake was run; False otherwise. 90 91 Note: this only describes CMake runs done by this command. The 92 build system generated by CMake may also update itself due to 93 internal logic.''' 94 95 self.cmake_cache = None 96 '''Final parsed CMake cache for the build, or None on error.''' 97 98 def _banner(self, msg): 99 self.inf('-- west build: ' + msg, colorize=True) 100 101 def do_add_parser(self, parser_adder): 102 parser = parser_adder.add_parser( 103 self.name, 104 help=self.help, 105 formatter_class=argparse.RawDescriptionHelpFormatter, 106 description=self.description, 107 usage=BUILD_USAGE) 108 109 # Remember to update west-completion.bash if you add or remove 110 # flags 111 112 parser.add_argument('-b', '--board', 113 help='board to build for with optional board revision') 114 # Hidden option for backwards compatibility 115 parser.add_argument('-s', '--source-dir', help=argparse.SUPPRESS) 116 parser.add_argument('-d', '--build-dir', 117 help='build directory to create or use') 118 self.add_force_arg(parser) 119 120 group = parser.add_argument_group('cmake and build tool') 121 group.add_argument('-c', '--cmake', action='store_true', 122 help='force a cmake run') 123 group.add_argument('--cmake-only', action='store_true', 124 help="just run cmake; don't build (implies -c)") 125 group.add_argument('--domain', action='append', 126 help='''execute build tool (make or ninja) only for 127 given domain''') 128 group.add_argument('-t', '--target', 129 help='''run build system target TARGET 130 (try "-t usage")''') 131 group.add_argument('-T', '--test-item', 132 help='''Build based on test data in testcase.yaml 133 or sample.yaml. If source directory is not used 134 an argument has to be defined as 135 SOURCE_PATH/TEST_NAME. 136 E.g. samples/hello_world/sample.basic.helloworld. 137 If source directory is passed 138 then "TEST_NAME" is enough.''') 139 group.add_argument('-o', '--build-opt', default=[], action='append', 140 help='''options to pass to the build tool 141 (make or ninja); may be given more than once''') 142 group.add_argument('-n', '--just-print', '--dry-run', '--recon', 143 dest='dry_run', action='store_true', 144 help="just print build commands; don't run them") 145 group.add_argument('-S', '--snippet', dest='snippets', metavar='SNIPPET', 146 action='append', default=[], 147 help='''add the argument to SNIPPET; may be given 148 multiple times. Forces CMake to run again if given. 149 Do not use this option with manually specified 150 -DSNIPPET... cmake arguments: the results are 151 undefined''') 152 group.add_argument('--shield', dest='shields', metavar='SHIELD', 153 action='append', default=[], 154 help='''add the argument to SHIELD; may be given 155 multiple times. Forces CMake to run again if given. 156 Do not use this option with manually specified 157 -DSHIELD... cmake arguments: the results are 158 undefined''') 159 group.add_argument('--extra-conf', dest='extra_conf_files', metavar='EXTRA_CONF_FILE', 160 action='append', default=[], 161 help='''add the argument to EXTRA_CONF_FILE; may be given 162 multiple times. Forces CMake to run again if given. 163 Do not use this option with manually specified 164 -DEXTRA_CONF_FILE... cmake arguments: the results are 165 undefined''') 166 group.add_argument('--extra-dtc-overlay', dest='extra_dtc_overlay_files', 167 metavar='EXTRA_DTC_OVERLAY_FILE', action='append', default=[], 168 help='''add the argument to EXTRA_DTC_OVERLAY_FILE; may be given 169 multiple times. Forces CMake to run again if given. 170 Do not use this option with manually specified 171 -DEXTRA_DTC_OVERLAY_FILE... cmake arguments: the results are 172 undefined''') 173 174 group = parser.add_mutually_exclusive_group() 175 group.add_argument('--sysbuild', action='store_true', 176 help='''create multi domain build system''') 177 group.add_argument('--no-sysbuild', action='store_true', 178 help='''do not create multi domain build system 179 (default)''') 180 181 group = parser.add_argument_group('pristine builds', 182 PRISTINE_DESCRIPTION) 183 group.add_argument('-p', '--pristine', choices=['auto', 'always', 184 'never'], action=AlwaysIfMissing, nargs='?', 185 help='pristine build folder setting') 186 187 return parser 188 189 def do_run(self, args, remainder): 190 self.args = args # Avoid having to pass them around 191 self.config_board = config_get('board', None) 192 self.dbg('args: {} remainder: {}'.format(args, remainder), 193 level=Verbosity.DBG_EXTREME) 194 # Store legacy -s option locally 195 source_dir = self.args.source_dir 196 self._parse_remainder(remainder) 197 # Parse testcase.yaml or sample.yaml files for additional options. 198 if self.args.test_item: 199 # we get path + testitem 200 item = os.path.basename(self.args.test_item) 201 if self.args.source_dir: 202 test_path = self.args.source_dir 203 else: 204 test_path = os.path.dirname(self.args.test_item) 205 if test_path and os.path.exists(test_path): 206 self.args.source_dir = test_path 207 if not self._parse_test_item(item): 208 self.die("No test metadata found") 209 else: 210 self.die("test item path does not exist") 211 212 if source_dir: 213 if self.args.source_dir: 214 self.die("source directory specified twice:({} and {})".format( 215 source_dir, self.args.source_dir)) 216 self.args.source_dir = source_dir 217 self.dbg('source_dir: {} cmake_opts: {}'.format(self.args.source_dir, 218 self.args.cmake_opts), 219 level=Verbosity.DBG_EXTREME) 220 self._sanity_precheck() 221 self._setup_build_dir() 222 223 if args.pristine is not None: 224 pristine = args.pristine 225 else: 226 # Load the pristine={auto, always, never} configuration value 227 pristine = config_get('pristine', 'never') 228 if pristine not in ['auto', 'always', 'never']: 229 self.wrn( 230 'treating unknown build.pristine value "{}" as "never"'. 231 format(pristine)) 232 pristine = 'never' 233 self.auto_pristine = pristine == 'auto' 234 235 self.dbg('pristine: {} auto_pristine: {}'.format(pristine, 236 self.auto_pristine), 237 level=Verbosity.DBG_MORE) 238 if is_zephyr_build(self.build_dir): 239 if pristine == 'always': 240 self._run_pristine() 241 self.run_cmake = True 242 else: 243 self._update_cache() 244 if (self.args.cmake or self.args.cmake_opts or 245 self.args.cmake_only or self.args.snippets or 246 self.args.shields or self.args.extra_conf_files or 247 self.args.extra_dtc_overlay_files): 248 self.run_cmake = True 249 else: 250 self.run_cmake = True 251 252 self.source_dir = self._find_source_dir() 253 self._sanity_check() 254 255 build_info_path = self.build_dir 256 build_info_file = os.path.join(build_info_path, BUILD_INFO_LOG) 257 west_workspace = west_topdir(self.source_dir) 258 if not os.path.exists(build_info_path): 259 os.makedirs(build_info_path) 260 if not os.path.exists(build_info_file): 261 build_command = {'west': {'command': ' '.join(sys.argv[:]), 262 'topdir': str(west_workspace), 263 'version': str(__version__)}} 264 try: 265 with open(build_info_file, "w") as f: 266 yaml.dump(build_command, f, default_flow_style=False) 267 except Exception as e: 268 self.wrn(f'Failed to create info file: {build_info_file},', e) 269 270 board, origin = self._find_board() 271 self._run_cmake(board, origin, self.args.cmake_opts) 272 if args.cmake_only: 273 return 274 275 self._sanity_check() 276 self._update_cache() 277 self.domains = load_domains(self.build_dir) 278 279 self._run_build(args.target, args.domain) 280 281 def _find_board(self): 282 board, origin = None, None 283 if self.cmake_cache: 284 board, origin = (self.cmake_cache.get('CACHED_BOARD'), 285 'CMakeCache.txt') 286 287 # A malformed CMake cache may exist, but not have a board. 288 # This happens if there's a build error from a previous run. 289 if board is not None: 290 return (board, origin) 291 292 if self.args.board: 293 board, origin = self.args.board, 'command line' 294 elif 'BOARD' in os.environ: 295 board, origin = os.environ['BOARD'], 'env' 296 elif self.config_board is not None: 297 board, origin = self.config_board, 'configfile' 298 return board, origin 299 300 def _parse_remainder(self, remainder): 301 self.args.source_dir = None 302 self.args.cmake_opts = None 303 304 try: 305 # Only one source_dir is allowed, as the first positional arg 306 if remainder[0] != _ARG_SEPARATOR: 307 self.args.source_dir = remainder[0] 308 remainder = remainder[1:] 309 # Only the first argument separator is consumed, the rest are 310 # passed on to CMake 311 if remainder[0] == _ARG_SEPARATOR: 312 remainder = remainder[1:] 313 if remainder: 314 self.args.cmake_opts = remainder 315 except IndexError: 316 pass 317 318 def _parse_test_item(self, test_item): 319 found_test_metadata = False 320 for yp in ['sample.yaml', 'testcase.yaml']: 321 yf = os.path.join(self.args.source_dir, yp) 322 if not os.path.exists(yf): 323 continue 324 found_test_metadata = True 325 with open(yf, 'r') as stream: 326 try: 327 y = yaml.safe_load(stream) 328 except yaml.YAMLError as exc: 329 self.die(exc) 330 common = y.get('common') 331 tests = y.get('tests') 332 if not tests: 333 self.die(f"No tests found in {yf}") 334 if test_item not in tests: 335 self.die(f"Test item {test_item} not found in {yf}") 336 item = tests.get(test_item) 337 338 sysbuild = False 339 extra_dtc_overlay_files = [] 340 extra_overlay_confs = [] 341 extra_conf_files = [] 342 required_snippets = [] 343 for section in [common, item]: 344 if not section: 345 continue 346 sysbuild = section.get('sysbuild', sysbuild) 347 for data in [ 348 'extra_args', 349 'extra_configs', 350 'extra_conf_files', 351 'extra_overlay_confs', 352 'extra_dtc_overlay_files', 353 'required_snippets' 354 ]: 355 extra = section.get(data) 356 if not extra: 357 continue 358 if isinstance(extra, str): 359 arg_list = extra.split(" ") 360 else: 361 arg_list = extra 362 363 if data == 'extra_configs': 364 args = [] 365 for arg in arg_list: 366 equals = arg.find('=') 367 colon = arg.rfind(':', 0, equals) 368 if colon != -1: 369 # conditional configs (xxx:yyy:CONFIG_FOO=bar) 370 # are not supported by 'west build' 371 self.wrn('"west build" does not support ' 372 'conditional config "{}". Add "-D{}" ' 373 'to the supplied CMake arguments if ' 374 'desired.'.format(arg, arg[colon+1:])) 375 continue 376 args.append("-D{}".format(arg.replace('"', '\"'))) 377 elif data == 'extra_args': 378 # Retain quotes around config options 379 config_options = [arg for arg in arg_list if arg.startswith("CONFIG_")] 380 non_config_options = [arg for arg in arg_list if not arg.startswith("CONFIG_")] 381 args = ["-D{}".format(a.replace('"', '\"')) for a in config_options] 382 args.extend(["-D{}".format(arg.replace('"', '')) for arg in non_config_options]) 383 elif data == 'extra_conf_files': 384 extra_conf_files.extend(arg_list) 385 continue 386 elif data == 'extra_overlay_confs': 387 extra_overlay_confs.extend(arg_list) 388 continue 389 elif data == 'extra_dtc_overlay_files': 390 extra_dtc_overlay_files.extend(arg_list) 391 continue 392 elif data == 'required_snippets': 393 required_snippets.extend(arg_list) 394 continue 395 396 if self.args.cmake_opts: 397 self.args.cmake_opts.extend(args) 398 else: 399 self.args.cmake_opts = args 400 401 self.args.sysbuild = sysbuild 402 403 if found_test_metadata: 404 args = [] 405 if extra_conf_files: 406 args.append(f"CONF_FILE=\"{';'.join(extra_conf_files)}\"") 407 408 if extra_dtc_overlay_files: 409 args.append(f"DTC_OVERLAY_FILE=\"{';'.join(extra_dtc_overlay_files)}\"") 410 411 if extra_overlay_confs: 412 args.append(f"OVERLAY_CONFIG=\"{';'.join(extra_overlay_confs)}\"") 413 414 if required_snippets: 415 args.append(f"SNIPPET=\"{';'.join(required_snippets)}\"") 416 417 # Build the final argument list 418 args_expanded = ["-D{}".format(a.replace('"', '')) for a in args] 419 420 if self.args.cmake_opts: 421 self.args.cmake_opts.extend(args_expanded) 422 else: 423 self.args.cmake_opts = args_expanded 424 425 return found_test_metadata 426 427 def _sanity_precheck(self): 428 app = self.args.source_dir 429 if app: 430 self.check_force( 431 os.path.isdir(app), 432 'source directory {} does not exist'.format(app)) 433 self.check_force( 434 'CMakeLists.txt' in os.listdir(app), 435 "{} doesn't contain a CMakeLists.txt".format(app)) 436 437 def _update_cache(self): 438 try: 439 self.cmake_cache = CMakeCache.from_build_dir(self.build_dir) 440 except FileNotFoundError: 441 pass 442 443 def _setup_build_dir(self): 444 # Initialize build_dir and created_build_dir attributes. 445 # If we created the build directory, we must run CMake. 446 self.dbg('setting up build directory', level=Verbosity.DBG_EXTREME) 447 # The CMake Cache has not been loaded yet, so this is safe 448 board, _ = self._find_board() 449 source_dir = self._find_source_dir() 450 app = os.path.split(source_dir)[1] 451 build_dir = find_build_dir(self.args.build_dir, board=board, 452 source_dir=source_dir, app=app) 453 if not build_dir: 454 self.die('Unable to determine a default build folder. Check ' 455 'your build.dir-fmt configuration option') 456 457 if os.path.exists(build_dir): 458 if not os.path.isdir(build_dir): 459 self.die('build directory {} exists and is not a directory'. 460 format(build_dir)) 461 else: 462 os.makedirs(build_dir, exist_ok=False) 463 self.created_build_dir = True 464 self.run_cmake = True 465 466 self.build_dir = build_dir 467 468 def _find_source_dir(self): 469 # Initialize source_dir attribute, either from command line argument, 470 # implicitly from the build directory's CMake cache, or using the 471 # default (current working directory). 472 self.dbg('setting up source directory', level=Verbosity.DBG_EXTREME) 473 if self.args.source_dir: 474 source_dir = self.args.source_dir 475 elif self.cmake_cache: 476 source_dir = self.cmake_cache.get('APP_DIR') 477 478 if not source_dir: 479 source_dir = self.cmake_cache.get('APPLICATION_SOURCE_DIR') 480 481 if not source_dir: 482 source_dir = self.cmake_cache.get('CMAKE_HOME_DIRECTORY') 483 484 if not source_dir: 485 # This really ought to be there. The build directory 486 # must be corrupted somehow. Let's see what we can do. 487 self.die('build directory', self.build_dir, 488 'CMake cache has no CMAKE_HOME_DIRECTORY;', 489 'please give a source_dir') 490 else: 491 source_dir = os.getcwd() 492 return os.path.abspath(source_dir) 493 494 def _sanity_check_source_dir(self): 495 if self.source_dir == self.build_dir: 496 # There's no forcing this. 497 self.die('source and build directory {} cannot be the same; ' 498 'use --build-dir {} to specify a build directory'. 499 format(self.source_dir, self.build_dir)) 500 501 srcrel = os.path.relpath(self.source_dir) 502 self.check_force( 503 not is_zephyr_build(self.source_dir), 504 'it looks like {srcrel} is a build directory: ' 505 'did you mean --build-dir {srcrel} instead?'. 506 format(srcrel=srcrel)) 507 self.check_force( 508 'CMakeLists.txt' in os.listdir(self.source_dir), 509 'source directory "{srcrel}" does not contain ' 510 'a CMakeLists.txt; is this really what you ' 511 'want to build? (Use -s SOURCE_DIR to specify ' 512 'the application source directory)'. 513 format(srcrel=srcrel)) 514 515 def _sanity_check(self): 516 # Sanity check the build configuration. 517 # Side effect: may update cmake_cache attribute. 518 self.dbg('sanity checking the build', level=Verbosity.DBG_EXTREME) 519 self._sanity_check_source_dir() 520 521 if not self.cmake_cache: 522 return # That's all we can check without a cache. 523 524 if "CMAKE_PROJECT_NAME" not in self.cmake_cache: 525 # This happens sometimes when a build system is not 526 # completely generated due to an error during the 527 # CMake configuration phase. 528 self.run_cmake = True 529 530 cached_proj = self.cmake_cache.get('APPLICATION_SOURCE_DIR') 531 cached_app = self.cmake_cache.get('APP_DIR') 532 # if APP_DIR is None but APPLICATION_SOURCE_DIR is set, that indicates 533 # an older build folder, this still requires pristine. 534 if cached_app is None and cached_proj: 535 cached_app = cached_proj 536 537 self.dbg('APP_DIR:', cached_app, level=Verbosity.DBG_EXTREME) 538 source_abs = (os.path.abspath(self.args.source_dir) 539 if self.args.source_dir else None) 540 cached_abs = os.path.abspath(cached_app) if cached_app else None 541 542 self.dbg('pristine:', self.auto_pristine, level=Verbosity.DBG_EXTREME) 543 544 # If the build directory specifies a source app, make sure it's 545 # consistent with --source-dir. 546 apps_mismatched = (source_abs and cached_abs and 547 pathlib.Path(source_abs).resolve() != pathlib.Path(cached_abs).resolve()) 548 549 self.check_force( 550 not apps_mismatched or self.auto_pristine, 551 'Build directory "{}" is for application "{}", but source ' 552 'directory "{}" was specified; please clean it, use --pristine, ' 553 'or use --build-dir to set another build directory'. 554 format(self.build_dir, cached_abs, source_abs)) 555 556 if apps_mismatched: 557 self.run_cmake = True # If they insist, we need to re-run cmake. 558 559 # If CACHED_BOARD is not defined, we need some other way to 560 # find the board. 561 cached_board = self.cmake_cache.get('CACHED_BOARD') 562 self.dbg('CACHED_BOARD:', cached_board, level=Verbosity.DBG_EXTREME) 563 # If apps_mismatched and self.auto_pristine are true, we will 564 # run pristine on the build, invalidating the cached 565 # board. In that case, we need some way of getting the board. 566 self.check_force((cached_board and 567 not (apps_mismatched and self.auto_pristine)) 568 or self.args.board or self.config_board or 569 os.environ.get('BOARD'), 570 'Cached board not defined, please provide it ' 571 '(provide --board, set default with ' 572 '"west config build.board <BOARD>", or set ' 573 'BOARD in the environment)') 574 575 # Check consistency between cached board and --board. 576 boards_mismatched = (self.args.board and cached_board and 577 self.args.board != cached_board) 578 self.check_force( 579 not boards_mismatched or self.auto_pristine, 580 'Build directory {} targets board {}, but board {} was specified. ' 581 '(Clean the directory, use --pristine, or use --build-dir to ' 582 'specify a different one.)'. 583 format(self.build_dir, cached_board, self.args.board)) 584 585 if self.auto_pristine and (apps_mismatched or boards_mismatched): 586 self._run_pristine() 587 self.cmake_cache = None 588 self.dbg('run_cmake:', True, level=Verbosity.DBG_EXTREME) 589 self.run_cmake = True 590 591 # Tricky corner-case: The user has not specified a build folder but 592 # there was one in the CMake cache. Since this is going to be 593 # invalidated, reset to CWD and re-run the basic tests. 594 if ((boards_mismatched and not apps_mismatched) and 595 (not source_abs and cached_abs)): 596 self.source_dir = self._find_source_dir() 597 self._sanity_check_source_dir() 598 599 def _run_cmake(self, board, origin, cmake_opts): 600 if board is None and config_getboolean('board_warn', True): 601 self.wrn('This looks like a fresh build and BOARD is unknown;', 602 "so it probably won't work. To fix, use", 603 '--board=<your-board>.') 604 self.inf('Note: to silence the above message, run', 605 "'west config build.board_warn false'") 606 607 if not self.run_cmake: 608 return 609 610 self._banner('generating a build system') 611 612 if board is not None and origin != 'CMakeCache.txt': 613 cmake_opts = ['-DBOARD={}'.format(board)] 614 else: 615 cmake_opts = [] 616 if self.args.cmake_opts: 617 cmake_opts.extend(self.args.cmake_opts) 618 if self.args.snippets: 619 cmake_opts.append(f'-DSNIPPET={";".join(self.args.snippets)}') 620 if self.args.shields: 621 cmake_opts.append(f'-DSHIELD={";".join(self.args.shields)}') 622 if self.args.extra_conf_files: 623 cmake_opts.append(f'-DEXTRA_CONF_FILE={";".join(self.args.extra_conf_files)}') 624 if self.args.extra_dtc_overlay_files: 625 cmake_opts.append( 626 f'-DEXTRA_DTC_OVERLAY_FILE=' 627 f'{";".join(self.args.extra_dtc_overlay_files)}' 628 ) 629 630 user_args = config_get('cmake-args', None) 631 if user_args: 632 cmake_opts.extend(shlex.split(user_args)) 633 634 config_sysbuild = config_getboolean('sysbuild', False) 635 if self.args.sysbuild or (config_sysbuild and not self.args.no_sysbuild): 636 cmake_opts.extend(['-S{}'.format(SYSBUILD_PROJ_DIR), 637 '-DAPP_DIR:PATH={}'.format(self.source_dir)]) 638 else: 639 # self.args.no_sysbuild == True or config sysbuild False 640 cmake_opts.extend(['-S{}'.format(self.source_dir)]) 641 642 # Invoke CMake from the current working directory using the 643 # -S and -B options (officially introduced in CMake 3.13.0). 644 # This is important because users expect invocations like this 645 # to Just Work: 646 # 647 # west build -- -DOVERLAY_CONFIG=relative-path.conf 648 final_cmake_args = ['-DWEST_PYTHON={}'.format(pathlib.Path(sys.executable).as_posix()), 649 '-B{}'.format(self.build_dir), 650 '-G{}'.format(config_get('generator', 651 DEFAULT_CMAKE_GENERATOR))] 652 if cmake_opts: 653 final_cmake_args.extend(cmake_opts) 654 run_cmake(final_cmake_args, dry_run=self.args.dry_run) 655 656 def _run_pristine(self): 657 self._banner('making build dir {} pristine'.format(self.build_dir)) 658 if not is_zephyr_build(self.build_dir): 659 self.die('Refusing to run pristine on a folder that is not a ' 660 'Zephyr build system') 661 662 cache = CMakeCache.from_build_dir(self.build_dir) 663 664 app_src_dir = cache.get('APPLICATION_SOURCE_DIR') 665 app_bin_dir = cache.get('APPLICATION_BINARY_DIR') 666 667 cmake_args = [f'-DBINARY_DIR={app_bin_dir}', 668 f'-DSOURCE_DIR={app_src_dir}', 669 '-P', cache['ZEPHYR_BASE'] + '/cmake/pristine.cmake'] 670 run_cmake(cmake_args, cwd=self.build_dir, dry_run=self.args.dry_run) 671 672 def _run_build(self, target, domain): 673 if target: 674 self._banner('running target {}'.format(target)) 675 elif self.run_cmake: 676 self._banner('building application') 677 extra_args = ['--target', target] if target else [] 678 if self.args.build_opt: 679 extra_args.append('--') 680 extra_args.extend(self.args.build_opt) 681 if self.args.verbose: 682 self._append_verbose_args(extra_args, 683 not bool(self.args.build_opt)) 684 685 domains = load_domains(self.build_dir) 686 build_dir_list = [] 687 688 if domain is None: 689 # If no domain is specified, we just build top build dir as that 690 # will build all domains. 691 build_dir_list = [domains.get_top_build_dir()] 692 else: 693 self._banner('building domain(s): {}'.format(' '.join(domain))) 694 domain_list = domains.get_domains(domain) 695 for d in domain_list: 696 build_dir_list.append(d.build_dir) 697 698 for b in build_dir_list: 699 run_build(b, extra_args=extra_args, 700 dry_run=self.args.dry_run) 701 702 def _append_verbose_args(self, extra_args, add_dashes): 703 # These hacks are only needed for CMake versions earlier than 704 # 3.14. When Zephyr's minimum version is at least that, we can 705 # drop this nonsense and just run "cmake --build BUILD -v". 706 self._update_cache() 707 if not self.cmake_cache: 708 return 709 generator = self.cmake_cache.get('CMAKE_GENERATOR') 710 if not generator: 711 return 712 # Substring matching is for things like "Eclipse CDT4 - Ninja". 713 if 'Ninja' in generator: 714 if add_dashes: 715 extra_args.append('--') 716 extra_args.append('-v') 717 elif generator == 'Unix Makefiles': 718 if add_dashes: 719 extra_args.append('--') 720 extra_args.append('VERBOSE=1') 721