Skip to content

Commit b951602

Browse files
thorsten-kleinhenrikbrixandersen
authored andcommitted
scripts: west_commands: fix default build directory fallback
fixed fallback to default build directory (build) in case that no other build directory could be determined. Signed-off-by: Thorsten Klein <[email protected]>
1 parent c155dfa commit b951602

File tree

2 files changed

+179
-26
lines changed

2 files changed

+179
-26
lines changed

scripts/west_commands/build_helpers.py

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -84,35 +84,33 @@ def _resolve_build_dir(fmt, guess, cwd, **kwargs):
8484

8585
def find_build_dir(dir, guess=False, **kwargs):
8686
'''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).
87+
If `dir` is specified, this directory is returned as the build directory.
88+
Otherwise, the default build directory is determined according to the
89+
following priorities:
90+
1. Resolved `build.dir-fmt` configuration option (all {args} are resolvable).
91+
Return this directory, if it is an already existing build directory.
92+
2. The current working directory, if it is an existing build directory.
93+
3. Resolved `build.dir-fmt` configuration option, no matter if it is an
94+
already existing build directory.
95+
4. DEFAULT_BUILD_DIR
9696
'''
9797

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
98+
build_dir = dir
99+
cwd = os.getcwd()
100+
dir_fmt = config.get('build', 'dir-fmt', fallback=None)
101+
if dir_fmt:
102+
log.dbg(f'config dir-fmt: {dir_fmt}', level=log.VERBOSE_EXTREME)
103+
dir_fmt = _resolve_build_dir(dir_fmt, guess, cwd, **kwargs)
104+
if not build_dir and is_zephyr_build(dir_fmt):
105+
build_dir = dir_fmt
106+
if not build_dir and is_zephyr_build(cwd):
107+
build_dir = cwd
108+
if not build_dir and dir_fmt:
109+
build_dir = dir_fmt
110+
if not build_dir:
111+
build_dir = DEFAULT_BUILD_DIR
111112
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
113+
return os.path.abspath(build_dir)
116114

117115
def is_zephyr_build(path):
118116
'''Return true if and only if `path` appears to be a valid Zephyr
@@ -128,6 +126,9 @@ def is_zephyr_build(path):
128126
The cached ZEPHYR_BASE was added in
129127
https://github.com/zephyrproject-rtos/zephyr/pull/23054.)
130128
'''
129+
if not path:
130+
return False
131+
131132
try:
132133
cache = zcmake.CMakeCache.from_build_dir(path)
133134
except FileNotFoundError:

scripts/west_commands/tests/test_build.py

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
from argparse import Namespace
66

77
from build import Build
8+
from build_helpers import DEFAULT_BUILD_DIR
9+
from pathlib import Path
10+
import configparser
11+
import os
812
import pytest
913

1014
TEST_CASES = [
@@ -55,3 +59,151 @@ def test_parse_remainder(test_case):
5559
b._parse_remainder(test_case['r'])
5660
assert b.args.source_dir == test_case['s']
5761
assert b.args.cmake_opts == test_case['c']
62+
63+
64+
cwd = Path(os.getcwd())
65+
66+
DEFAULT_ARGS = Namespace(
67+
build_dir=None,
68+
board='native_sim',
69+
source_dir=os.path.join("any", "project", "app")
70+
)
71+
72+
BUILD_DIR_FMT_VALID = [
73+
# dir_fmt, expected
74+
('resolvable-{board}', cwd / f'resolvable-{DEFAULT_ARGS.board}'),
75+
('my-dir-fmt-build-folder', cwd / 'my-dir-fmt-build-folder'),
76+
]
77+
78+
BUILD_DIR_FMT_INVALID = [
79+
'unresolvable-because-of-unknown-{board}',
80+
'{invalid}'
81+
]
82+
83+
84+
def mock_dir_fmt_config(monkeypatch, dir_fmt):
85+
config = configparser.ConfigParser()
86+
config.add_section("build")
87+
config.set("build", "dir-fmt", dir_fmt)
88+
monkeypatch.setattr("build_helpers.config", config)
89+
monkeypatch.setattr("build.config", config)
90+
91+
def mock_is_zephyr_build(monkeypatch, func):
92+
monkeypatch.setattr("build_helpers.is_zephyr_build", func)
93+
94+
95+
@pytest.fixture
96+
def build_instance(monkeypatch):
97+
"""Create a Build instance with default args and patched helpers."""
98+
b = Build()
99+
b.args = DEFAULT_ARGS
100+
b.config_board = None
101+
102+
# mock configparser read method to bypass configs during test
103+
monkeypatch.setattr(configparser.ConfigParser, "read", lambda self, filenames: None)
104+
# mock os.makedirs to avoid filesystem operations during test
105+
monkeypatch.setattr("os.makedirs", lambda *a, **kw: None)
106+
# mock is_zephyr_build to always return False
107+
monkeypatch.setattr("build_helpers.is_zephyr_build", lambda path: False)
108+
# mock os.environ to make tests independent from environment variables
109+
monkeypatch.setattr("os.environ", {})
110+
111+
return b
112+
113+
def test_build_dir_fallback(monkeypatch, build_instance):
114+
"""Test: Fallback to DEFAULT_BUILD_DIR if no build dirs exist"""
115+
b = build_instance
116+
b._setup_build_dir()
117+
assert Path(b.build_dir) == cwd / DEFAULT_BUILD_DIR
118+
119+
120+
@pytest.mark.parametrize('test_case', BUILD_DIR_FMT_VALID)
121+
def test_build_dir_fmt_is_used(monkeypatch, build_instance, test_case):
122+
"""Test: build.dir-fmt is used if it is resolvable (no matter if it exists)"""
123+
dir_fmt, expected = test_case
124+
b = build_instance
125+
126+
# mock the config
127+
mock_dir_fmt_config(monkeypatch, dir_fmt)
128+
129+
b._setup_build_dir()
130+
assert Path(b.build_dir) == expected
131+
132+
133+
@pytest.mark.parametrize('dir_fmt', BUILD_DIR_FMT_INVALID)
134+
def test_build_dir_fmt_is_not_resolvable(monkeypatch, build_instance, dir_fmt):
135+
"""Test: Fallback to DEFAULT_BUILD_DIR if build.dir-fmt is not resolvable."""
136+
b = build_instance
137+
b.args.board = None
138+
139+
# mock the config
140+
mock_dir_fmt_config(monkeypatch, dir_fmt)
141+
142+
b._setup_build_dir()
143+
assert Path(b.build_dir) == cwd / DEFAULT_BUILD_DIR
144+
145+
146+
def test_dir_fmt_preferred_over_others(monkeypatch, build_instance):
147+
"""Test: build.dir-fmt is preferred over cwd and DEFAULT_BUILD_DIR (if all exist)"""
148+
b = build_instance
149+
dir_fmt = "my-dir-fmt-build-folder"
150+
151+
# mock the config and is_zephyr_build
152+
mock_dir_fmt_config(monkeypatch, dir_fmt)
153+
mock_is_zephyr_build(monkeypatch,
154+
lambda path: path in [dir_fmt, cwd, DEFAULT_BUILD_DIR])
155+
156+
b._setup_build_dir()
157+
assert Path(b.build_dir) == cwd / dir_fmt
158+
159+
def test_non_existent_dir_fmt_preferred_over_others(monkeypatch, build_instance):
160+
"""Test: build.dir-fmt is preferred over cwd and DEFAULT_BUILD_DIR (if it not exists)"""
161+
b = build_instance
162+
dir_fmt = "my-dir-fmt-build-folder"
163+
164+
# mock the config and is_zephyr_build
165+
mock_dir_fmt_config(monkeypatch, dir_fmt)
166+
mock_is_zephyr_build(monkeypatch,
167+
lambda path: path in [cwd, DEFAULT_BUILD_DIR])
168+
169+
b._setup_build_dir()
170+
assert Path(b.build_dir) == cwd / dir_fmt
171+
172+
173+
def test_cwd_preferred_over_default_build_dir(monkeypatch, build_instance):
174+
"""Test: cwd is preferred over DEFAULT_BUILD_DIR (if both exist)"""
175+
b = build_instance
176+
177+
# mock is_zephyr_build
178+
mock_is_zephyr_build(monkeypatch,
179+
lambda path: path in [str(cwd), DEFAULT_BUILD_DIR])
180+
181+
b._setup_build_dir()
182+
assert Path(b.build_dir) == cwd
183+
184+
@pytest.mark.parametrize('dir_fmt', BUILD_DIR_FMT_INVALID)
185+
def test_cwd_preferred_over_non_resolvable_dir_fmt(monkeypatch, build_instance, dir_fmt):
186+
"""Test: cwd is preferred over dir-fmt, if dir-fmt is not resolvable"""
187+
b = build_instance
188+
189+
# mock the config and is_zephyr_build
190+
mock_dir_fmt_config(monkeypatch, dir_fmt)
191+
mock_is_zephyr_build(monkeypatch,
192+
lambda path: path in [str(cwd)])
193+
194+
b._setup_build_dir()
195+
assert Path(b.build_dir) == cwd
196+
197+
@pytest.mark.parametrize('test_case', BUILD_DIR_FMT_VALID)
198+
def test_cwd_preferred_over_non_existent_dir_fmt(monkeypatch, build_instance, test_case):
199+
"""Test: cwd is preferred over build.dir-fmt if cwd is a build directory and dir_fmt not"""
200+
dir_fmt, _ = test_case
201+
b = build_instance
202+
203+
# mock the config and is_zephyr_build
204+
mock_dir_fmt_config(monkeypatch, dir_fmt)
205+
mock_is_zephyr_build(monkeypatch,
206+
lambda path: path in [str(cwd)])
207+
208+
b._setup_build_dir()
209+
assert Path(b.build_dir) == cwd

0 commit comments

Comments
 (0)