| # 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 |
| import sys |
| from pathlib import Path |
| from west import log |
| from west.configuration import config |
| from west.util import escapes_directory |
| |
| # Domains.py must be imported from the pylib directory, since |
| # twister also uses the implementation |
| script_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) |
| sys.path.insert(0, os.path.join(script_dir, "pylib/build_helpers/")) |
| from domains import Domains |
| |
| 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 |
| |
| |
| def load_domains(path): |
| '''Load domains from a domains.yaml. |
| |
| If domains.yaml is not found, then a single 'app' domain referring to the |
| top-level build folder is created and returned. |
| ''' |
| domains_file = Path(path) / 'domains.yaml' |
| |
| if not domains_file.is_file(): |
| return Domains.from_data({'default': 'app', |
| 'build_dir': path, |
| 'domains': [{'name': 'app', 'build_dir': path}]}) |
| |
| return Domains.from_file(domains_file) |