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