mirror of
https://github.com/zephyrproject-rtos/zephyr
synced 2025-08-02 18:07:59 +00:00
The west build command has historically tried not to rm -rf directories that don't look like zephyr build directories. The way it does this is by checking for the presence of a CMake cache with a Zephyr-specific variable (ZEPHYR_TOOLCHAIN_VARIANT) in it. The problem with this approach is that if the build system fails before this cache variable is set, the directory doesn't look like a zephyr build directory, and therefore west build won't make it pristine even with --pristine=always, even though build directories resulting from failed runs like that are almost certainly irrecoverably broken and need to be made pristine before anything will work. This leads to users having to rm -rf their directories manually, which is not so nice. To avoid this from happening, just check for ZEPHYR_BASE, which is set early on in ZephyrConfig.cmake in 'modern' zephyr build systems. Keep the ZEPHYR_TOOLCHAIN_VARIANT check in place for compatibility. We could consider being less selective and just using shutil.rmtree() whenever we have --pristine=always, but that would be a bigger behavioral change than I'm comfortable doing without a good reason. Fixes: #28876 Signed-off-by: Martí Bolívar <marti.bolivar@nordicsemi.no>
136 lines
4.8 KiB
Python
136 lines
4.8 KiB
Python
# Copyright 2018 (c) Foundries.io.
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
'''Common definitions for building Zephyr applications.
|
|
|
|
This provides some default settings and convenience wrappers for
|
|
building Zephyr applications needed by multiple commands.
|
|
|
|
See build.py for the build command itself.
|
|
'''
|
|
|
|
import zcmake
|
|
import os
|
|
from pathlib import Path
|
|
from west import log
|
|
from west.configuration import config
|
|
from west.util import escapes_directory
|
|
|
|
DEFAULT_BUILD_DIR = 'build'
|
|
'''Name of the default Zephyr build directory.'''
|
|
|
|
DEFAULT_CMAKE_GENERATOR = 'Ninja'
|
|
'''Name of the default CMake generator.'''
|
|
|
|
FIND_BUILD_DIR_DESCRIPTION = '''\
|
|
If the build directory is not given, the default is {}/ unless the
|
|
build.dir-fmt configuration variable is set. The current directory is
|
|
checked after that. If either is a Zephyr build directory, it is used.
|
|
'''.format(DEFAULT_BUILD_DIR)
|
|
|
|
def _resolve_build_dir(fmt, guess, cwd, **kwargs):
|
|
# Remove any None values, we do not want 'None' as a string
|
|
kwargs = {k: v for k, v in kwargs.items() if v is not None}
|
|
# Check if source_dir is below cwd first
|
|
source_dir = kwargs.get('source_dir')
|
|
if source_dir:
|
|
if escapes_directory(cwd, source_dir):
|
|
kwargs['source_dir'] = os.path.relpath(source_dir, cwd)
|
|
else:
|
|
# no meaningful relative path possible
|
|
kwargs['source_dir'] = ''
|
|
|
|
try:
|
|
return fmt.format(**kwargs)
|
|
except KeyError:
|
|
if not guess:
|
|
return None
|
|
|
|
# Guess the build folder by iterating through all sub-folders from the
|
|
# root of the format string and trying to resolve. If resolving fails,
|
|
# proceed to iterate over subfolders only if there is a single folder
|
|
# present on each iteration.
|
|
parts = Path(fmt).parts
|
|
b = Path('.')
|
|
for p in parts:
|
|
# default to cwd in the first iteration
|
|
curr = b
|
|
b = b.joinpath(p)
|
|
try:
|
|
# if fmt is an absolute path, the first iteration will always
|
|
# resolve '/'
|
|
b = Path(str(b).format(**kwargs))
|
|
except KeyError:
|
|
# Missing key, check sub-folders and match if a single one exists
|
|
while True:
|
|
if not curr.exists():
|
|
return None
|
|
dirs = [f for f in curr.iterdir() if f.is_dir()]
|
|
if len(dirs) != 1:
|
|
return None
|
|
curr = dirs[0]
|
|
if is_zephyr_build(str(curr)):
|
|
return str(curr)
|
|
return str(b)
|
|
|
|
def find_build_dir(dir, guess=False, **kwargs):
|
|
'''Heuristic for finding a build directory.
|
|
|
|
The default build directory is computed by reading the build.dir-fmt
|
|
configuration option, defaulting to DEFAULT_BUILD_DIR if not set. It might
|
|
be None if the build.dir-fmt configuration option is set but cannot be
|
|
resolved.
|
|
If the given argument is truthy, it is returned. Otherwise, if
|
|
the default build folder is a build directory, it is returned.
|
|
Next, if the current working directory is a build directory, it is
|
|
returned. Finally, the default build directory is returned (may be None).
|
|
'''
|
|
|
|
if dir:
|
|
build_dir = dir
|
|
else:
|
|
cwd = os.getcwd()
|
|
default = config.get('build', 'dir-fmt', fallback=DEFAULT_BUILD_DIR)
|
|
default = _resolve_build_dir(default, guess, cwd, **kwargs)
|
|
log.dbg('config dir-fmt: {}'.format(default), level=log.VERBOSE_EXTREME)
|
|
if default and is_zephyr_build(default):
|
|
build_dir = default
|
|
elif is_zephyr_build(cwd):
|
|
build_dir = cwd
|
|
else:
|
|
build_dir = default
|
|
log.dbg('build dir: {}'.format(build_dir), level=log.VERBOSE_EXTREME)
|
|
if build_dir:
|
|
return os.path.abspath(build_dir)
|
|
else:
|
|
return None
|
|
|
|
def is_zephyr_build(path):
|
|
'''Return true if and only if `path` appears to be a valid Zephyr
|
|
build directory.
|
|
|
|
"Valid" means the given path is a directory which contains a CMake
|
|
cache with a 'ZEPHYR_BASE' or 'ZEPHYR_TOOLCHAIN_VARIANT' variable.
|
|
|
|
(The check for ZEPHYR_BASE introduced sometime after Zephyr 2.4 to
|
|
fix https://github.com/zephyrproject-rtos/zephyr/issues/28876; we
|
|
keep support for the second variable around for compatibility with
|
|
versions 2.2 and earlier, which didn't have ZEPHYR_BASE in cache.
|
|
The cached ZEPHYR_BASE was added in
|
|
https://github.com/zephyrproject-rtos/zephyr/pull/23054.)
|
|
'''
|
|
try:
|
|
cache = zcmake.CMakeCache.from_build_dir(path)
|
|
except FileNotFoundError:
|
|
cache = {}
|
|
|
|
if 'ZEPHYR_BASE' in cache or 'ZEPHYR_TOOLCHAIN_VARIANT' in cache:
|
|
log.dbg(f'{path} is a zephyr build directory',
|
|
level=log.VERBOSE_EXTREME)
|
|
return True
|
|
|
|
log.dbg(f'{path} is NOT a valid zephyr build directory',
|
|
level=log.VERBOSE_EXTREME)
|
|
return False
|