Carles Cufi | 31bdad5 | 2019-04-26 21:53:02 +0200 | [diff] [blame] | 1 | # Copyright 2018 (c) Foundries.io. |
| 2 | # |
| 3 | # SPDX-License-Identifier: Apache-2.0 |
| 4 | |
| 5 | '''Common definitions for building Zephyr applications. |
| 6 | |
| 7 | This provides some default settings and convenience wrappers for |
| 8 | building Zephyr applications needed by multiple commands. |
| 9 | |
| 10 | See build.py for the build command itself. |
| 11 | ''' |
| 12 | |
Jan Van Winkel | 4d975db | 2019-05-04 14:44:56 +0200 | [diff] [blame] | 13 | import zcmake |
Carles Cufi | 31bdad5 | 2019-04-26 21:53:02 +0200 | [diff] [blame] | 14 | import os |
Daniel DeGrasse | 6a5b88e | 2022-10-05 16:04:44 -0500 | [diff] [blame] | 15 | import sys |
Carles Cufi | 41f1f64 | 2019-06-17 11:47:11 +0200 | [diff] [blame] | 16 | from pathlib import Path |
Carles Cufi | 31bdad5 | 2019-04-26 21:53:02 +0200 | [diff] [blame] | 17 | from west import log |
Carles Cufi | 98980c6 | 2019-06-05 16:04:29 +0200 | [diff] [blame] | 18 | from west.configuration import config |
| 19 | from west.util import escapes_directory |
Daniel DeGrasse | 6a5b88e | 2022-10-05 16:04:44 -0500 | [diff] [blame] | 20 | |
| 21 | # Domains.py must be imported from the pylib directory, since |
| 22 | # twister also uses the implementation |
| 23 | script_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) |
| 24 | sys.path.insert(0, os.path.join(script_dir, "pylib/build_helpers/")) |
Torsten Rasmussen | 8408af6 | 2021-11-22 10:29:56 +0100 | [diff] [blame] | 25 | from domains import Domains |
Carles Cufi | 31bdad5 | 2019-04-26 21:53:02 +0200 | [diff] [blame] | 26 | |
| 27 | DEFAULT_BUILD_DIR = 'build' |
| 28 | '''Name of the default Zephyr build directory.''' |
| 29 | |
| 30 | DEFAULT_CMAKE_GENERATOR = 'Ninja' |
| 31 | '''Name of the default CMake generator.''' |
| 32 | |
Carles Cufi | 98980c6 | 2019-06-05 16:04:29 +0200 | [diff] [blame] | 33 | FIND_BUILD_DIR_DESCRIPTION = '''\ |
Martí Bolívar | 6e4c2b9 | 2020-06-25 12:58:58 -0700 | [diff] [blame] | 34 | If the build directory is not given, the default is {}/ unless the |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 35 | build.dir-fmt configuration variable is set. The current directory is |
| 36 | checked after that. If either is a Zephyr build directory, it is used. |
Carles Cufi | 98980c6 | 2019-06-05 16:04:29 +0200 | [diff] [blame] | 37 | '''.format(DEFAULT_BUILD_DIR) |
Carles Cufi | 31bdad5 | 2019-04-26 21:53:02 +0200 | [diff] [blame] | 38 | |
Carles Cufi | 41f1f64 | 2019-06-17 11:47:11 +0200 | [diff] [blame] | 39 | def _resolve_build_dir(fmt, guess, cwd, **kwargs): |
Carles Cufi | 98980c6 | 2019-06-05 16:04:29 +0200 | [diff] [blame] | 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'] = '' |
Carles Cufi | 31bdad5 | 2019-04-26 21:53:02 +0200 | [diff] [blame] | 50 | |
Carles Cufi | 41f1f64 | 2019-06-17 11:47:11 +0200 | [diff] [blame] | 51 | try: |
| 52 | return fmt.format(**kwargs) |
| 53 | except KeyError: |
| 54 | if not guess: |
| 55 | return None |
Carles Cufi | 98980c6 | 2019-06-05 16:04:29 +0200 | [diff] [blame] | 56 | |
Carles Cufi | 41f1f64 | 2019-06-17 11:47:11 +0200 | [diff] [blame] | 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: |
Carles Cufi | 4a50444 | 2019-09-06 15:18:41 +0200 | [diff] [blame] | 74 | if not curr.exists(): |
| 75 | return None |
Carles Cufi | 41f1f64 | 2019-06-17 11:47:11 +0200 | [diff] [blame] | 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 | |
| 84 | def find_build_dir(dir, guess=False, **kwargs): |
Carles Cufi | 31bdad5 | 2019-04-26 21:53:02 +0200 | [diff] [blame] | 85 | '''Heuristic for finding a build directory. |
| 86 | |
Carles Cufi | 98980c6 | 2019-06-05 16:04:29 +0200 | [diff] [blame] | 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 | |
Carles Cufi | 31bdad5 | 2019-04-26 21:53:02 +0200 | [diff] [blame] | 97 | if dir: |
| 98 | build_dir = dir |
| 99 | else: |
| 100 | cwd = os.getcwd() |
Carles Cufi | 98980c6 | 2019-06-05 16:04:29 +0200 | [diff] [blame] | 101 | default = config.get('build', 'dir-fmt', fallback=DEFAULT_BUILD_DIR) |
Carles Cufi | 41f1f64 | 2019-06-17 11:47:11 +0200 | [diff] [blame] | 102 | default = _resolve_build_dir(default, guess, cwd, **kwargs) |
| 103 | log.dbg('config dir-fmt: {}'.format(default), level=log.VERBOSE_EXTREME) |
Carles Cufi | 98980c6 | 2019-06-05 16:04:29 +0200 | [diff] [blame] | 104 | if default and is_zephyr_build(default): |
Carles Cufi | 49c4b1c | 2019-06-03 12:43:38 +0200 | [diff] [blame] | 105 | build_dir = default |
| 106 | elif is_zephyr_build(cwd): |
Carles Cufi | 31bdad5 | 2019-04-26 21:53:02 +0200 | [diff] [blame] | 107 | build_dir = cwd |
| 108 | else: |
Carles Cufi | 98980c6 | 2019-06-05 16:04:29 +0200 | [diff] [blame] | 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 |
Carles Cufi | 31bdad5 | 2019-04-26 21:53:02 +0200 | [diff] [blame] | 115 | |
| 116 | def 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 |
Martí Bolívar | f752c5e | 2020-10-06 16:36:24 -0700 | [diff] [blame] | 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.) |
Carles Cufi | 31bdad5 | 2019-04-26 21:53:02 +0200 | [diff] [blame] | 129 | ''' |
| 130 | try: |
Jan Van Winkel | 4d975db | 2019-05-04 14:44:56 +0200 | [diff] [blame] | 131 | cache = zcmake.CMakeCache.from_build_dir(path) |
Carles Cufi | 31bdad5 | 2019-04-26 21:53:02 +0200 | [diff] [blame] | 132 | except FileNotFoundError: |
| 133 | cache = {} |
| 134 | |
Martí Bolívar | f752c5e | 2020-10-06 16:36:24 -0700 | [diff] [blame] | 135 | if 'ZEPHYR_BASE' in cache or 'ZEPHYR_TOOLCHAIN_VARIANT' in cache: |
| 136 | log.dbg(f'{path} is a zephyr build directory', |
Carles Cufi | 31bdad5 | 2019-04-26 21:53:02 +0200 | [diff] [blame] | 137 | level=log.VERBOSE_EXTREME) |
| 138 | return True |
Martí Bolívar | f752c5e | 2020-10-06 16:36:24 -0700 | [diff] [blame] | 139 | |
| 140 | log.dbg(f'{path} is NOT a valid zephyr build directory', |
| 141 | level=log.VERBOSE_EXTREME) |
| 142 | return False |
Torsten Rasmussen | 8408af6 | 2021-11-22 10:29:56 +0100 | [diff] [blame] | 143 | |
| 144 | |
| 145 | def 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(): |
Grzegorz Swiderski | 652b0a8 | 2023-09-28 09:36:18 +0200 | [diff] [blame] | 154 | return Domains.from_yaml(f'''\ |
| 155 | default: app |
| 156 | build_dir: {path} |
| 157 | domains: |
| 158 | - name: app |
| 159 | build_dir: {path} |
| 160 | flash_order: |
| 161 | - app |
| 162 | ''') |
Torsten Rasmussen | 8408af6 | 2021-11-22 10:29:56 +0100 | [diff] [blame] | 163 | |
| 164 | return Domains.from_file(domains_file) |