You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
712 lines
31 KiB
712 lines
31 KiB
# Copyright (c) 2018 Foundries.io |
|
# |
|
# SPDX-License-Identifier: Apache-2.0 |
|
|
|
import argparse |
|
import contextlib |
|
import os |
|
import pathlib |
|
import shlex |
|
import sys |
|
|
|
import yaml |
|
from build_helpers import FIND_BUILD_DIR_DESCRIPTION, find_build_dir, is_zephyr_build, load_domains |
|
from west.commands import Verbosity |
|
from west.configuration import config |
|
from west.util import west_topdir |
|
from west.version import __version__ |
|
from zcmake import DEFAULT_CMAKE_GENERATOR, CMakeCache, run_build, run_cmake |
|
from zephyr_ext_common import Forceable |
|
|
|
_ARG_SEPARATOR = '--' |
|
|
|
SYSBUILD_PROJ_DIR = pathlib.Path(__file__).resolve().parent.parent.parent \ |
|
/ pathlib.Path('share/sysbuild') |
|
|
|
BUILD_INFO_LOG = 'build_info.yml' |
|
|
|
BUILD_USAGE = '''\ |
|
west build [-h] [-b BOARD[@REV]]] [-d BUILD_DIR] |
|
[-S SNIPPET] [--shield SHIELD] |
|
[-t TARGET] [-p {auto, always, never}] [-c] [--cmake-only] |
|
[-n] [-o BUILD_OPT] [-f] |
|
[--sysbuild | --no-sysbuild] [--domain DOMAIN] |
|
[--extra-conf FILE.conf] |
|
[--extra-dtc-overlay FILE.overlay] |
|
[source_dir] -- [cmake_opt [cmake_opt ...]] |
|
''' |
|
|
|
BUILD_DESCRIPTION = f'''\ |
|
Convenience wrapper for building Zephyr applications. |
|
|
|
{FIND_BUILD_DIR_DESCRIPTION} |
|
|
|
positional arguments: |
|
source_dir application source directory |
|
cmake_opt extra options to pass to cmake; implies -c |
|
(these must come after "--" as shown above) |
|
''' |
|
|
|
PRISTINE_DESCRIPTION = """\ |
|
A "pristine" build directory is empty. The -p option controls |
|
whether the build directory is made pristine before the build |
|
is done. A bare '--pristine' with no value is the same as |
|
--pristine=always. Setting --pristine=auto uses heuristics to |
|
guess if a pristine build may be necessary.""" |
|
|
|
def config_get(option, fallback): |
|
return config.get('build', option, fallback=fallback) |
|
|
|
def config_getboolean(option, fallback): |
|
return config.getboolean('build', option, fallback=fallback) |
|
|
|
class AlwaysIfMissing(argparse.Action): |
|
|
|
def __call__(self, parser, namespace, values, option_string=None): |
|
setattr(namespace, self.dest, values or 'always') |
|
|
|
class Build(Forceable): |
|
|
|
def __init__(self): |
|
super().__init__( |
|
'build', |
|
# Keep this in sync with the string in west-commands.yml. |
|
'compile a Zephyr application', |
|
BUILD_DESCRIPTION, |
|
accepts_unknown_args=True) |
|
|
|
self.source_dir = None |
|
'''Source directory for the build, or None on error.''' |
|
|
|
self.build_dir = None |
|
'''Final build directory used to run the build, or None on error.''' |
|
|
|
self.created_build_dir = False |
|
'''True if the build directory was created; False otherwise.''' |
|
|
|
self.run_cmake = False |
|
'''True if CMake was run; False otherwise. |
|
|
|
Note: this only describes CMake runs done by this command. The |
|
build system generated by CMake may also update itself due to |
|
internal logic.''' |
|
|
|
self.cmake_cache = None |
|
'''Final parsed CMake cache for the build, or None on error.''' |
|
|
|
def _banner(self, msg): |
|
self.inf('-- west build: ' + msg, colorize=True) |
|
|
|
def do_add_parser(self, parser_adder): |
|
parser = parser_adder.add_parser( |
|
self.name, |
|
help=self.help, |
|
formatter_class=argparse.RawDescriptionHelpFormatter, |
|
description=self.description, |
|
usage=BUILD_USAGE) |
|
|
|
# Remember to update west-completion.bash if you add or remove |
|
# flags |
|
|
|
parser.add_argument('-b', '--board', |
|
help='board to build for with optional board revision') |
|
# Hidden option for backwards compatibility |
|
parser.add_argument('-s', '--source-dir', help=argparse.SUPPRESS) |
|
parser.add_argument('-d', '--build-dir', |
|
help='build directory to create or use') |
|
self.add_force_arg(parser) |
|
|
|
group = parser.add_argument_group('cmake and build tool') |
|
group.add_argument('-c', '--cmake', action='store_true', |
|
help='force a cmake run') |
|
group.add_argument('--cmake-only', action='store_true', |
|
help="just run cmake; don't build (implies -c)") |
|
group.add_argument('--domain', action='append', |
|
help='''execute build tool (make or ninja) only for |
|
given domain''') |
|
group.add_argument('-t', '--target', |
|
help='''run build system target TARGET |
|
(try "-t usage")''') |
|
group.add_argument('-T', '--test-item', |
|
help='''Build based on test data in testcase.yaml |
|
or sample.yaml. If source directory is not used |
|
an argument has to be defined as |
|
SOURCE_PATH/TEST_NAME. |
|
E.g. samples/hello_world/sample.basic.helloworld. |
|
If source directory is passed |
|
then "TEST_NAME" is enough.''') |
|
group.add_argument('-o', '--build-opt', default=[], action='append', |
|
help='''options to pass to the build tool |
|
(make or ninja); may be given more than once''') |
|
group.add_argument('-n', '--just-print', '--dry-run', '--recon', |
|
dest='dry_run', action='store_true', |
|
help="just print build commands; don't run them") |
|
group.add_argument('-S', '--snippet', dest='snippets', metavar='SNIPPET', |
|
action='append', default=[], |
|
help='''add the argument to SNIPPET; may be given |
|
multiple times. Forces CMake to run again if given. |
|
Do not use this option with manually specified |
|
-DSNIPPET... cmake arguments: the results are |
|
undefined''') |
|
group.add_argument('--shield', dest='shields', metavar='SHIELD', |
|
action='append', default=[], |
|
help='''add the argument to SHIELD; may be given |
|
multiple times. Forces CMake to run again if given. |
|
Do not use this option with manually specified |
|
-DSHIELD... cmake arguments: the results are |
|
undefined''') |
|
group.add_argument('--extra-conf', dest='extra_conf_files', metavar='EXTRA_CONF_FILE', |
|
action='append', default=[], |
|
help='''add the argument to EXTRA_CONF_FILE; may be given |
|
multiple times. Forces CMake to run again if given. |
|
Do not use this option with manually specified |
|
-DEXTRA_CONF_FILE... cmake arguments: the results are |
|
undefined''') |
|
group.add_argument('--extra-dtc-overlay', dest='extra_dtc_overlay_files', |
|
metavar='EXTRA_DTC_OVERLAY_FILE', action='append', default=[], |
|
help='''add the argument to EXTRA_DTC_OVERLAY_FILE; may be given |
|
multiple times. Forces CMake to run again if given. |
|
Do not use this option with manually specified |
|
-DEXTRA_DTC_OVERLAY_FILE... cmake arguments: the results are |
|
undefined''') |
|
|
|
group = parser.add_mutually_exclusive_group() |
|
group.add_argument('--sysbuild', action='store_true', |
|
help='''create multi domain build system''') |
|
group.add_argument('--no-sysbuild', action='store_true', |
|
help='''do not create multi domain build system |
|
(default)''') |
|
|
|
group = parser.add_argument_group('pristine builds', |
|
PRISTINE_DESCRIPTION) |
|
group.add_argument('-p', '--pristine', choices=['auto', 'always', |
|
'never'], action=AlwaysIfMissing, nargs='?', |
|
help='pristine build folder setting') |
|
|
|
return parser |
|
|
|
def do_run(self, args, remainder): |
|
self.args = args # Avoid having to pass them around |
|
self.config_board = config_get('board', None) |
|
self.dbg(f'args: {args} remainder: {remainder}', |
|
level=Verbosity.DBG_EXTREME) |
|
# Store legacy -s option locally |
|
source_dir = self.args.source_dir |
|
self._parse_remainder(remainder) |
|
# Parse testcase.yaml or sample.yaml files for additional options. |
|
if self.args.test_item: |
|
# we get path + testitem |
|
item = os.path.basename(self.args.test_item) |
|
if self.args.source_dir: |
|
test_path = self.args.source_dir |
|
else: |
|
test_path = os.path.dirname(self.args.test_item) |
|
if test_path and os.path.exists(test_path): |
|
self.args.source_dir = test_path |
|
if not self._parse_test_item(item): |
|
self.die("No test metadata found") |
|
else: |
|
self.die("test item path does not exist") |
|
|
|
if source_dir: |
|
if self.args.source_dir: |
|
self.die( |
|
f"source directory specified twice:({source_dir} and {self.args.source_dir})") |
|
self.args.source_dir = source_dir |
|
self.dbg(f'source_dir: {self.args.source_dir} cmake_opts: {self.args.cmake_opts}', |
|
level=Verbosity.DBG_EXTREME) |
|
self._sanity_precheck() |
|
self._setup_build_dir() |
|
|
|
if args.pristine is not None: |
|
pristine = args.pristine |
|
else: |
|
# Load the pristine={auto, always, never} configuration value |
|
pristine = config_get('pristine', 'never') |
|
if pristine not in ['auto', 'always', 'never']: |
|
self.wrn( |
|
f'treating unknown build.pristine value "{pristine}" as "never"') |
|
pristine = 'never' |
|
self.auto_pristine = pristine == 'auto' |
|
|
|
self.dbg(f'pristine: {pristine} auto_pristine: {self.auto_pristine}', |
|
level=Verbosity.DBG_MORE) |
|
if is_zephyr_build(self.build_dir): |
|
if pristine == 'always': |
|
self._run_pristine() |
|
self.run_cmake = True |
|
else: |
|
self._update_cache() |
|
if (self.args.cmake or self.args.cmake_opts or |
|
self.args.cmake_only or self.args.snippets or |
|
self.args.shields or self.args.extra_conf_files or |
|
self.args.extra_dtc_overlay_files): |
|
self.run_cmake = True |
|
else: |
|
self.run_cmake = True |
|
|
|
self.source_dir = self._find_source_dir() |
|
self._sanity_check() |
|
|
|
build_info_path = self.build_dir |
|
build_info_file = os.path.join(build_info_path, BUILD_INFO_LOG) |
|
west_workspace = west_topdir(self.source_dir) |
|
if not os.path.exists(build_info_path): |
|
os.makedirs(build_info_path) |
|
if not os.path.exists(build_info_file): |
|
build_command = {'west': {'command': ' '.join(sys.argv[:]), |
|
'topdir': str(west_workspace), |
|
'version': str(__version__)}} |
|
try: |
|
with open(build_info_file, "w") as f: |
|
yaml.dump(build_command, f, default_flow_style=False) |
|
except Exception as e: |
|
self.wrn(f'Failed to create info file: {build_info_file},', e) |
|
|
|
board, origin = self._find_board() |
|
self._run_cmake(board, origin, self.args.cmake_opts) |
|
if args.cmake_only: |
|
return |
|
|
|
self._sanity_check() |
|
self._update_cache() |
|
self.domains = load_domains(self.build_dir) |
|
|
|
self._run_build(args.target, args.domain) |
|
|
|
def _find_board(self): |
|
board, origin = None, None |
|
if self.cmake_cache: |
|
board, origin = (self.cmake_cache.get('CACHED_BOARD'), |
|
'CMakeCache.txt') |
|
|
|
# A malformed CMake cache may exist, but not have a board. |
|
# This happens if there's a build error from a previous run. |
|
if board is not None: |
|
return (board, origin) |
|
|
|
if self.args.board: |
|
board, origin = self.args.board, 'command line' |
|
elif 'BOARD' in os.environ: |
|
board, origin = os.environ['BOARD'], 'env' |
|
elif self.config_board is not None: |
|
board, origin = self.config_board, 'configfile' |
|
return board, origin |
|
|
|
def _parse_remainder(self, remainder): |
|
self.args.source_dir = None |
|
self.args.cmake_opts = None |
|
|
|
try: |
|
# Only one source_dir is allowed, as the first positional arg |
|
if remainder[0] != _ARG_SEPARATOR: |
|
self.args.source_dir = remainder[0] |
|
remainder = remainder[1:] |
|
# Only the first argument separator is consumed, the rest are |
|
# passed on to CMake |
|
if remainder[0] == _ARG_SEPARATOR: |
|
remainder = remainder[1:] |
|
if remainder: |
|
self.args.cmake_opts = remainder |
|
except IndexError: |
|
pass |
|
|
|
def _parse_test_item(self, test_item): |
|
found_test_metadata = False |
|
for yp in ['sample.yaml', 'testcase.yaml']: |
|
yf = os.path.join(self.args.source_dir, yp) |
|
if not os.path.exists(yf): |
|
continue |
|
found_test_metadata = True |
|
with open(yf) as stream: |
|
try: |
|
y = yaml.safe_load(stream) |
|
except yaml.YAMLError as exc: |
|
self.die(exc) |
|
common = y.get('common') |
|
tests = y.get('tests') |
|
if not tests: |
|
self.die(f"No tests found in {yf}") |
|
if test_item not in tests: |
|
self.die(f"Test item {test_item} not found in {yf}") |
|
item = tests.get(test_item) |
|
|
|
sysbuild = False |
|
extra_dtc_overlay_files = [] |
|
extra_overlay_confs = [] |
|
extra_conf_files = [] |
|
required_snippets = [] |
|
for section in [common, item]: |
|
if not section: |
|
continue |
|
sysbuild = section.get('sysbuild', sysbuild) |
|
for data in [ |
|
'extra_args', |
|
'extra_configs', |
|
'extra_conf_files', |
|
'extra_overlay_confs', |
|
'extra_dtc_overlay_files', |
|
'required_snippets' |
|
]: |
|
extra = section.get(data) |
|
if not extra: |
|
continue |
|
if isinstance(extra, str): |
|
arg_list = extra.split(" ") |
|
else: |
|
arg_list = extra |
|
|
|
if data == 'extra_configs': |
|
args = [] |
|
for arg in arg_list: |
|
equals = arg.find('=') |
|
colon = arg.rfind(':', 0, equals) |
|
if colon != -1: |
|
# conditional configs (xxx:yyy:CONFIG_FOO=bar) |
|
# are not supported by 'west build' |
|
self.wrn('"west build" does not support ' |
|
f'conditional config "{arg}". Add "-D{arg[colon+1:]}" ' |
|
'to the supplied CMake arguments if ' |
|
'desired.') |
|
continue |
|
args.append("-D{}".format(arg.replace('"', '\"'))) |
|
elif data == 'extra_args': |
|
# Retain quotes around config options |
|
config_options = [arg for arg in arg_list if arg.startswith("CONFIG_")] |
|
non_config_options = [ |
|
arg for arg in arg_list if not arg.startswith("CONFIG_") |
|
] |
|
args = ["-D{}".format(a.replace('"', '\"')) for a in config_options] |
|
args.extend([ |
|
"-D{}".format(arg.replace('"', '')) for arg in non_config_options |
|
]) |
|
elif data == 'extra_conf_files': |
|
extra_conf_files.extend(arg_list) |
|
continue |
|
elif data == 'extra_overlay_confs': |
|
extra_overlay_confs.extend(arg_list) |
|
continue |
|
elif data == 'extra_dtc_overlay_files': |
|
extra_dtc_overlay_files.extend(arg_list) |
|
continue |
|
elif data == 'required_snippets': |
|
required_snippets.extend(arg_list) |
|
continue |
|
|
|
if self.args.cmake_opts: |
|
self.args.cmake_opts.extend(args) |
|
else: |
|
self.args.cmake_opts = args |
|
|
|
self.args.sysbuild = sysbuild |
|
|
|
if found_test_metadata: |
|
args = [] |
|
if extra_conf_files: |
|
args.append(f"CONF_FILE=\"{';'.join(extra_conf_files)}\"") |
|
|
|
if extra_dtc_overlay_files: |
|
args.append(f"DTC_OVERLAY_FILE=\"{';'.join(extra_dtc_overlay_files)}\"") |
|
|
|
if extra_overlay_confs: |
|
args.append(f"OVERLAY_CONFIG=\"{';'.join(extra_overlay_confs)}\"") |
|
|
|
if required_snippets: |
|
args.append(f"SNIPPET=\"{';'.join(required_snippets)}\"") |
|
|
|
# Build the final argument list |
|
args_expanded = ["-D{}".format(a.replace('"', '')) for a in args] |
|
|
|
if self.args.cmake_opts: |
|
self.args.cmake_opts.extend(args_expanded) |
|
else: |
|
self.args.cmake_opts = args_expanded |
|
|
|
return found_test_metadata |
|
|
|
def _sanity_precheck(self): |
|
app = self.args.source_dir |
|
if app: |
|
self.check_force( |
|
os.path.isdir(app), |
|
f'source directory {app} does not exist') |
|
self.check_force( |
|
'CMakeLists.txt' in os.listdir(app), |
|
f"{app} doesn't contain a CMakeLists.txt") |
|
|
|
def _update_cache(self): |
|
with contextlib.suppress(FileNotFoundError): |
|
self.cmake_cache = CMakeCache.from_build_dir(self.build_dir) |
|
|
|
def _setup_build_dir(self): |
|
# Initialize build_dir and created_build_dir attributes. |
|
# If we created the build directory, we must run CMake. |
|
self.dbg('setting up build directory', level=Verbosity.DBG_EXTREME) |
|
# The CMake Cache has not been loaded yet, so this is safe |
|
board, _ = self._find_board() |
|
source_dir = self._find_source_dir() |
|
app = os.path.split(source_dir)[1] |
|
build_dir = find_build_dir(self.args.build_dir, board=board, |
|
source_dir=source_dir, app=app) |
|
if not build_dir: |
|
self.die('Unable to determine a default build folder. Check ' |
|
'your build.dir-fmt configuration option') |
|
|
|
if os.path.exists(build_dir): |
|
if not os.path.isdir(build_dir): |
|
self.die(f'build directory {build_dir} exists and is not a directory') |
|
else: |
|
os.makedirs(build_dir, exist_ok=False) |
|
self.created_build_dir = True |
|
self.run_cmake = True |
|
|
|
self.build_dir = build_dir |
|
|
|
def _find_source_dir(self): |
|
# Initialize source_dir attribute, either from command line argument, |
|
# implicitly from the build directory's CMake cache, or using the |
|
# default (current working directory). |
|
self.dbg('setting up source directory', level=Verbosity.DBG_EXTREME) |
|
if self.args.source_dir: |
|
source_dir = self.args.source_dir |
|
elif self.cmake_cache: |
|
source_dir = self.cmake_cache.get('APP_DIR') |
|
|
|
if not source_dir: |
|
source_dir = self.cmake_cache.get('APPLICATION_SOURCE_DIR') |
|
|
|
if not source_dir: |
|
source_dir = self.cmake_cache.get('CMAKE_HOME_DIRECTORY') |
|
|
|
if not source_dir: |
|
# This really ought to be there. The build directory |
|
# must be corrupted somehow. Let's see what we can do. |
|
self.die('build directory', self.build_dir, |
|
'CMake cache has no CMAKE_HOME_DIRECTORY;', |
|
'please give a source_dir') |
|
else: |
|
source_dir = os.getcwd() |
|
return os.path.abspath(source_dir) |
|
|
|
def _sanity_check_source_dir(self): |
|
if self.source_dir == self.build_dir: |
|
# There's no forcing this. |
|
self.die(f'source and build directory {self.source_dir} cannot be the same; ' |
|
f'use --build-dir {self.build_dir} to specify a build directory') |
|
|
|
srcrel = os.path.relpath(self.source_dir) |
|
self.check_force( |
|
not is_zephyr_build(self.source_dir), |
|
f'it looks like {srcrel} is a build directory: ' |
|
f'did you mean --build-dir {srcrel} instead?') |
|
self.check_force( |
|
'CMakeLists.txt' in os.listdir(self.source_dir), |
|
f'source directory "{srcrel}" does not contain ' |
|
'a CMakeLists.txt; is this really what you ' |
|
'want to build? (Use -s SOURCE_DIR to specify ' |
|
'the application source directory)') |
|
|
|
def _sanity_check(self): |
|
# Sanity check the build configuration. |
|
# Side effect: may update cmake_cache attribute. |
|
self.dbg('sanity checking the build', level=Verbosity.DBG_EXTREME) |
|
self._sanity_check_source_dir() |
|
|
|
if not self.cmake_cache: |
|
return # That's all we can check without a cache. |
|
|
|
if "CMAKE_PROJECT_NAME" not in self.cmake_cache: |
|
# This happens sometimes when a build system is not |
|
# completely generated due to an error during the |
|
# CMake configuration phase. |
|
self.run_cmake = True |
|
|
|
cached_proj = self.cmake_cache.get('APPLICATION_SOURCE_DIR') |
|
cached_app = self.cmake_cache.get('APP_DIR') |
|
# if APP_DIR is None but APPLICATION_SOURCE_DIR is set, that indicates |
|
# an older build folder, this still requires pristine. |
|
if cached_app is None and cached_proj: |
|
cached_app = cached_proj |
|
|
|
self.dbg('APP_DIR:', cached_app, level=Verbosity.DBG_EXTREME) |
|
source_abs = (os.path.abspath(self.args.source_dir) |
|
if self.args.source_dir else None) |
|
cached_abs = os.path.abspath(cached_app) if cached_app else None |
|
|
|
self.dbg('pristine:', self.auto_pristine, level=Verbosity.DBG_EXTREME) |
|
|
|
# If the build directory specifies a source app, make sure it's |
|
# consistent with --source-dir. |
|
apps_mismatched = (source_abs and cached_abs and |
|
pathlib.Path(source_abs).resolve() != pathlib.Path(cached_abs).resolve()) |
|
|
|
self.check_force( |
|
not apps_mismatched or self.auto_pristine, |
|
f'Build directory "{self.build_dir}" is for application "{cached_abs}", but source ' |
|
f'directory "{source_abs}" was specified; please clean it, use --pristine, ' |
|
'or use --build-dir to set another build directory') |
|
|
|
if apps_mismatched: |
|
self.run_cmake = True # If they insist, we need to re-run cmake. |
|
|
|
# If CACHED_BOARD is not defined, we need some other way to |
|
# find the board. |
|
cached_board = self.cmake_cache.get('CACHED_BOARD') |
|
self.dbg('CACHED_BOARD:', cached_board, level=Verbosity.DBG_EXTREME) |
|
# If apps_mismatched and self.auto_pristine are true, we will |
|
# run pristine on the build, invalidating the cached |
|
# board. In that case, we need some way of getting the board. |
|
self.check_force((cached_board and |
|
not (apps_mismatched and self.auto_pristine)) |
|
or self.args.board or self.config_board or |
|
os.environ.get('BOARD'), |
|
'Cached board not defined, please provide it ' |
|
'(provide --board, set default with ' |
|
'"west config build.board <BOARD>", or set ' |
|
'BOARD in the environment)') |
|
|
|
# Check consistency between cached board and --board. |
|
boards_mismatched = (self.args.board and cached_board and |
|
self.args.board != cached_board) |
|
self.check_force( |
|
not boards_mismatched or self.auto_pristine, |
|
f'Build directory {self.build_dir} targets board {cached_board}, ' |
|
'but board {self.args.board} was specified. ' |
|
'(Clean the directory, use --pristine, or use --build-dir to ' |
|
'specify a different one.)') |
|
|
|
if self.auto_pristine and (apps_mismatched or boards_mismatched): |
|
self._run_pristine() |
|
self.cmake_cache = None |
|
self.dbg('run_cmake:', True, level=Verbosity.DBG_EXTREME) |
|
self.run_cmake = True |
|
|
|
# Tricky corner-case: The user has not specified a build folder but |
|
# there was one in the CMake cache. Since this is going to be |
|
# invalidated, reset to CWD and re-run the basic tests. |
|
if ((boards_mismatched and not apps_mismatched) and |
|
(not source_abs and cached_abs)): |
|
self.source_dir = self._find_source_dir() |
|
self._sanity_check_source_dir() |
|
|
|
def _run_cmake(self, board, origin, cmake_opts): |
|
if board is None and config_getboolean('board_warn', True): |
|
self.wrn('This looks like a fresh build and BOARD is unknown;', |
|
"so it probably won't work. To fix, use", |
|
'--board=<your-board>.') |
|
self.inf('Note: to silence the above message, run', |
|
"'west config build.board_warn false'") |
|
|
|
if not self.run_cmake: |
|
return |
|
|
|
self._banner('generating a build system') |
|
|
|
if board is not None and origin != 'CMakeCache.txt': |
|
cmake_opts = [f'-DBOARD={board}'] |
|
else: |
|
cmake_opts = [] |
|
if self.args.cmake_opts: |
|
cmake_opts.extend(self.args.cmake_opts) |
|
if self.args.snippets: |
|
cmake_opts.append(f'-DSNIPPET={";".join(self.args.snippets)}') |
|
if self.args.shields: |
|
cmake_opts.append(f'-DSHIELD={";".join(self.args.shields)}') |
|
if self.args.extra_conf_files: |
|
cmake_opts.append(f'-DEXTRA_CONF_FILE={";".join(self.args.extra_conf_files)}') |
|
if self.args.extra_dtc_overlay_files: |
|
cmake_opts.append( |
|
f'-DEXTRA_DTC_OVERLAY_FILE=' |
|
f'{";".join(self.args.extra_dtc_overlay_files)}' |
|
) |
|
|
|
user_args = config_get('cmake-args', None) |
|
if user_args: |
|
cmake_opts.extend(shlex.split(user_args)) |
|
|
|
config_sysbuild = config_getboolean('sysbuild', False) |
|
if self.args.sysbuild or (config_sysbuild and not self.args.no_sysbuild): |
|
cmake_opts.extend([f'-S{SYSBUILD_PROJ_DIR}', |
|
f'-DAPP_DIR:PATH={self.source_dir}']) |
|
else: |
|
# self.args.no_sysbuild == True or config sysbuild False |
|
cmake_opts.extend([f'-S{self.source_dir}']) |
|
|
|
# Invoke CMake from the current working directory using the |
|
# -S and -B options (officially introduced in CMake 3.13.0). |
|
# This is important because users expect invocations like this |
|
# to Just Work: |
|
# |
|
# west build -- -DOVERLAY_CONFIG=relative-path.conf |
|
final_cmake_args = [f'-DWEST_PYTHON={pathlib.Path(sys.executable).as_posix()}', |
|
f'-B{self.build_dir}', |
|
f'-G{config_get("generator", DEFAULT_CMAKE_GENERATOR)}'] |
|
if cmake_opts: |
|
final_cmake_args.extend(cmake_opts) |
|
run_cmake(final_cmake_args, dry_run=self.args.dry_run) |
|
|
|
def _run_pristine(self): |
|
self._banner(f'making build dir {self.build_dir} pristine') |
|
if not is_zephyr_build(self.build_dir): |
|
self.die('Refusing to run pristine on a folder that is not a ' |
|
'Zephyr build system') |
|
|
|
cache = CMakeCache.from_build_dir(self.build_dir) |
|
|
|
app_src_dir = cache.get('APPLICATION_SOURCE_DIR') |
|
app_bin_dir = cache.get('APPLICATION_BINARY_DIR') |
|
|
|
cmake_args = [f'-DBINARY_DIR={app_bin_dir}', |
|
f'-DSOURCE_DIR={app_src_dir}', |
|
'-P', cache['ZEPHYR_BASE'] + '/cmake/pristine.cmake'] |
|
run_cmake(cmake_args, cwd=self.build_dir, dry_run=self.args.dry_run) |
|
|
|
def _run_build(self, target, domain): |
|
if target: |
|
self._banner(f'running target {target}') |
|
elif self.run_cmake: |
|
self._banner('building application') |
|
extra_args = ['--target', target] if target else [] |
|
if self.args.build_opt: |
|
extra_args.append('--') |
|
extra_args.extend(self.args.build_opt) |
|
if self.args.verbose: |
|
self._append_verbose_args(extra_args, |
|
not bool(self.args.build_opt)) |
|
|
|
domains = load_domains(self.build_dir) |
|
build_dir_list = [] |
|
|
|
if domain is None: |
|
# If no domain is specified, we just build top build dir as that |
|
# will build all domains. |
|
build_dir_list = [domains.get_top_build_dir()] |
|
else: |
|
self._banner('building domain(s): {}'.format(' '.join(domain))) |
|
domain_list = domains.get_domains(domain) |
|
for d in domain_list: |
|
build_dir_list.append(d.build_dir) |
|
|
|
for b in build_dir_list: |
|
run_build(b, extra_args=extra_args, |
|
dry_run=self.args.dry_run) |
|
|
|
def _append_verbose_args(self, extra_args, add_dashes): |
|
# These hacks are only needed for CMake versions earlier than |
|
# 3.14. When Zephyr's minimum version is at least that, we can |
|
# drop this nonsense and just run "cmake --build BUILD -v". |
|
self._update_cache() |
|
if not self.cmake_cache: |
|
return |
|
generator = self.cmake_cache.get('CMAKE_GENERATOR') |
|
if not generator: |
|
return |
|
# Substring matching is for things like "Eclipse CDT4 - Ninja". |
|
if 'Ninja' in generator: |
|
if add_dashes: |
|
extra_args.append('--') |
|
extra_args.append('-v') |
|
elif generator == 'Unix Makefiles': |
|
if add_dashes: |
|
extra_args.append('--') |
|
extra_args.append('VERBOSE=1')
|
|
|