1# Copyright 2018 (c) Foundries.io. 2# 3# SPDX-License-Identifier: Apache-2.0 4 5'''Common definitions for building Zephyr applications. 6 7This provides some default settings and convenience wrappers for 8building Zephyr applications needed by multiple commands. 9 10See build.py for the build command itself. 11''' 12 13import os 14import sys 15from pathlib import Path 16 17import zcmake 18from west import log 19from west.configuration import config 20from west.util import escapes_directory 21 22# Domains.py must be imported from the pylib directory, since 23# twister also uses the implementation 24script_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) 25sys.path.insert(0, os.path.join(script_dir, "pylib/build_helpers/")) 26from domains import Domains # noqa: E402 27 28DEFAULT_BUILD_DIR = 'build' 29'''Name of the default Zephyr build directory.''' 30 31DEFAULT_CMAKE_GENERATOR = 'Ninja' 32'''Name of the default CMake generator.''' 33 34FIND_BUILD_DIR_DESCRIPTION = f'''\ 35If the build directory is not given, the default is {DEFAULT_BUILD_DIR}/ unless the 36build.dir-fmt configuration variable is set. The current directory is 37checked after that. If either is a Zephyr build directory, it is used. 38''' 39 40def _resolve_build_dir(fmt, guess, cwd, **kwargs): 41 # Remove any None values, we do not want 'None' as a string 42 kwargs = {k: v for k, v in kwargs.items() if v is not None} 43 # Check if source_dir is below cwd first 44 source_dir = kwargs.get('source_dir') 45 if source_dir: 46 if escapes_directory(cwd, source_dir): 47 kwargs['source_dir'] = os.path.relpath(source_dir, cwd) 48 else: 49 # no meaningful relative path possible 50 kwargs['source_dir'] = '' 51 52 try: 53 return fmt.format(**kwargs) 54 except KeyError: 55 if not guess: 56 return None 57 58 # Guess the build folder by iterating through all sub-folders from the 59 # root of the format string and trying to resolve. If resolving fails, 60 # proceed to iterate over subfolders only if there is a single folder 61 # present on each iteration. 62 parts = Path(fmt).parts 63 b = Path('.') 64 for p in parts: 65 # default to cwd in the first iteration 66 curr = b 67 b = b.joinpath(p) 68 try: 69 # if fmt is an absolute path, the first iteration will always 70 # resolve '/' 71 b = Path(str(b).format(**kwargs)) 72 except KeyError: 73 # Missing key, check sub-folders and match if a single one exists 74 while True: 75 if not curr.exists(): 76 return None 77 dirs = [f for f in curr.iterdir() if f.is_dir()] 78 if len(dirs) != 1: 79 return None 80 curr = dirs[0] 81 if is_zephyr_build(str(curr)): 82 return str(curr) 83 return str(b) 84 85def find_build_dir(dir, guess=False, **kwargs): 86 '''Heuristic for finding a build directory. 87 88 The default build directory is computed by reading the build.dir-fmt 89 configuration option, defaulting to DEFAULT_BUILD_DIR if not set. It might 90 be None if the build.dir-fmt configuration option is set but cannot be 91 resolved. 92 If the given argument is truthy, it is returned. Otherwise, if 93 the default build folder is a build directory, it is returned. 94 Next, if the current working directory is a build directory, it is 95 returned. Finally, the default build directory is returned (may be None). 96 ''' 97 98 if dir: 99 build_dir = dir 100 else: 101 cwd = os.getcwd() 102 default = config.get('build', 'dir-fmt', fallback=DEFAULT_BUILD_DIR) 103 default = _resolve_build_dir(default, guess, cwd, **kwargs) 104 log.dbg(f'config dir-fmt: {default}', level=log.VERBOSE_EXTREME) 105 if default and is_zephyr_build(default): 106 build_dir = default 107 elif is_zephyr_build(cwd): 108 build_dir = cwd 109 else: 110 build_dir = default 111 log.dbg(f'build dir: {build_dir}', level=log.VERBOSE_EXTREME) 112 if build_dir: 113 return os.path.abspath(build_dir) 114 else: 115 return None 116 117def is_zephyr_build(path): 118 '''Return true if and only if `path` appears to be a valid Zephyr 119 build directory. 120 121 "Valid" means the given path is a directory which contains a CMake 122 cache with a 'ZEPHYR_BASE' or 'ZEPHYR_TOOLCHAIN_VARIANT' variable. 123 124 (The check for ZEPHYR_BASE introduced sometime after Zephyr 2.4 to 125 fix https://github.com/zephyrproject-rtos/zephyr/issues/28876; we 126 keep support for the second variable around for compatibility with 127 versions 2.2 and earlier, which didn't have ZEPHYR_BASE in cache. 128 The cached ZEPHYR_BASE was added in 129 https://github.com/zephyrproject-rtos/zephyr/pull/23054.) 130 ''' 131 try: 132 cache = zcmake.CMakeCache.from_build_dir(path) 133 except FileNotFoundError: 134 cache = {} 135 136 if 'ZEPHYR_BASE' in cache or 'ZEPHYR_TOOLCHAIN_VARIANT' in cache: 137 log.dbg(f'{path} is a zephyr build directory', 138 level=log.VERBOSE_EXTREME) 139 return True 140 141 log.dbg(f'{path} is NOT a valid zephyr build directory', 142 level=log.VERBOSE_EXTREME) 143 return False 144 145 146def load_domains(path): 147 '''Load domains from a domains.yaml. 148 149 If domains.yaml is not found, then a single 'app' domain referring to the 150 top-level build folder is created and returned. 151 ''' 152 domains_file = Path(path) / 'domains.yaml' 153 154 if not domains_file.is_file(): 155 return Domains.from_yaml(f'''\ 156default: app 157build_dir: {path} 158domains: 159 - name: app 160 build_dir: {path} 161flash_order: 162 - app 163''') 164 165 return Domains.from_file(domains_file) 166