Skip to content

Commit 30f879b

Browse files
committed
fix: Resolve west-command paths correctly from manifests in subdirectories
Allow west commands to be imported from a project subdirectory if the manifest is located in a subdirectory, e.g. `import: mf_subdir/west.yml`. Fixes issue #725
1 parent 548b9ee commit 30f879b

2 files changed

Lines changed: 89 additions & 14 deletions

File tree

src/west/manifest.py

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2496,7 +2496,7 @@ def _load_project(self, pd: dict, url_bases: dict[str, str], defaults: _defaults
24962496
path=path,
24972497
submodules=self._load_submodules(pd.get('submodules'), f'project {name}'),
24982498
clone_depth=pd.get('clone-depth'),
2499-
west_commands=pd.get('west-commands'),
2499+
west_commands=Path(pd.get('west-commands')).as_posix() if pd.get('west-commands') else None,
25002500
topdir=self.topdir,
25012501
remote_name=remote,
25022502
groups=groups,
@@ -2628,7 +2628,7 @@ def _import_path_from_project(self, project: Project, path: str) -> None:
26282628
return
26292629

26302630
for data in imported:
2631-
self._import_data_from_project(project, data, None)
2631+
self._import_data_from_project(project, data, path)
26322632

26332633
_logger.debug(f'done resolving import {path} for {project}')
26342634

@@ -2647,16 +2647,22 @@ def _import_map_from_project(self, project: Project, imp: dict) -> None:
26472647
_logger.debug(f'done resolving import {imap} for {project}')
26482648

26492649
def _import_data_from_project(
2650-
self, project: Project, data: Any, imap: _import_map | None
2650+
self, project: Project, data: Any, imap_or_mfpath: _import_map | str
26512651
) -> None:
26522652
# Destructively add the imported data into our 'projects' map.
2653-
2654-
if imap is not None:
2655-
imap_filter = _compose_imap_filters(self._ctx.imap_filter, _imap_filter(imap))
2656-
imap_path_prefix = imap.path_prefix
2657-
else:
2658-
imap_filter = self._ctx.imap_filter
2659-
imap_path_prefix = '.'
2653+
match imap_or_mfpath:
2654+
case _import_map():
2655+
imap_filter = _compose_imap_filters(
2656+
self._ctx.imap_filter, _imap_filter(imap_or_mfpath)
2657+
)
2658+
imap_path_prefix = imap_or_mfpath.path_prefix
2659+
mfst_path = imap_or_mfpath.file
2660+
case str():
2661+
imap_filter = self._ctx.imap_filter
2662+
imap_path_prefix = '.'
2663+
mfst_path = imap_or_mfpath
2664+
case _:
2665+
raise AssertionError(f'imap_or_mfpath has unexpected type {type(imap_or_mfpath)}')
26602666

26612667
child_ctx = self._ctx._replace(
26622668
imap_filter=imap_filter,
@@ -2674,13 +2680,21 @@ def _import_data_from_project(
26742680
try:
26752681
submanifest = Manifest(topdir=self.topdir, internal_import_ctx=child_ctx)
26762682
except RecursionError as e:
2677-
raise _ManifestImportDepth(None, imap.file if imap else None) from e
2683+
raise _ManifestImportDepth(None, mfst_path) from e
26782684

26792685
# Patch up any extension commands in the imported data
26802686
# by allocating them to the project.
2681-
project.west_commands = _west_commands_merge(
2682-
project.west_commands, submanifest._ctx.manifest_west_commands
2683-
)
2687+
2688+
# If the manifest was imported from a project subdirectory
2689+
# (manifest_path is a relative path within the project),
2690+
# we need to adjust the west_commands paths to be relative
2691+
# to the project root, not to the manifest subdirectory.
2692+
mfst_dir = Path(mfst_path).parent if _is_yml(mfst_path) else Path(mfst_path)
2693+
west_commands_to_merge = [
2694+
(mfst_dir / cmd).as_posix() for cmd in submanifest._ctx.manifest_west_commands
2695+
]
2696+
2697+
project.west_commands = _west_commands_merge(project.west_commands, west_commands_to_merge)
26842698

26852699
def _import_content_from_project(self, project: Project, path: str) -> ImportedContentType:
26862700
if not (self._ctx.import_flags & ImportFlag.FORCE_PROJECTS) and project.is_cloned():

tests/test_manifest.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2095,6 +2095,67 @@ def test_import_project_submanifest_commands_both(manifest_repo):
20952095
assert p1.west_commands == expected
20962096

20972097

2098+
def test_import_project_submanifest_commands_from_project_subdirectory(manifest_repo):
2099+
# When a manifest is imported from a project subdirectory (e.g., mf_subdir/west.yml),
2100+
# and that manifest defines west-commands, the paths should be
2101+
# resolved relative to the manifest subdirectory.
2102+
# This tests _import_path_from_project with a string path.
2103+
2104+
with open(manifest_repo / 'west.yml', 'w') as f:
2105+
f.write('''\
2106+
manifest:
2107+
projects:
2108+
- name: p1
2109+
url: url-placeholder
2110+
import: mf_subdir/west.yml
2111+
''')
2112+
2113+
p1 = manifest_repo / '..' / 'p1'
2114+
create_repo(p1)
2115+
create_branch(p1, 'manifest-rev', checkout=True)
2116+
add_commit(
2117+
p1,
2118+
'add mf_subdir/west.yml with west-commands',
2119+
files={
2120+
'mf_subdir/west.yml': '''\
2121+
manifest:
2122+
projects:
2123+
- name: p2
2124+
url: url-placeholder2
2125+
self:
2126+
west-commands: p2subdir/west-commands.yml
2127+
''',
2128+
},
2129+
)
2130+
checkout_branch(p1, 'master')
2131+
2132+
# Case A: import as a string path to the submanifest file.
2133+
p1_proj = MF().get_projects(['p1'])[0]
2134+
# The west_commands path should be 'mf_subdir/p2subdir/west-commands.yml',
2135+
# not 'west-commands.yml', to be resolved correctly
2136+
# relative to the project root. See issue #725.
2137+
expected = ['mf_subdir/p2subdir/west-commands.yml']
2138+
assert p1_proj.west_commands == expected
2139+
2140+
# Case B: import using an import-map whose 'file' is a directory.
2141+
# Re-write the top-level manifest to use an import map instead of
2142+
# a string; the imported manifest still lives at mf_subdir/west.yml.
2143+
with open(manifest_repo / 'west.yml', 'w') as f:
2144+
f.write('''\
2145+
manifest:
2146+
projects:
2147+
- name: p1
2148+
url: url-placeholder
2149+
import:
2150+
file: mf_subdir
2151+
''')
2152+
2153+
# Reload and check the west_commands were resolved the same way.
2154+
p1_proj = MF().get_projects(['p1'])[0]
2155+
expected = ['mf_subdir/p2subdir/west-commands.yml']
2156+
assert p1_proj.west_commands == expected
2157+
2158+
20982159
def test_import_map_error_handling():
20992160
# Make sure we handle expected errors when loading import:
21002161
# values that are maps.

0 commit comments

Comments
 (0)