Skip to content

Commit 272c585

Browse files
manifest: support URL modification in downstream manifests
Add support for a new 'import-modifications' section in the manifest. Under the 'url-replace' key, users can define search-and-replace patterns that are applied to project URLs during manifest import. This allows downstream projects to modify remote URLs, e.g. when using mirrored repositories.
1 parent a4ce10a commit 272c585

File tree

2 files changed

+65
-0
lines changed

2 files changed

+65
-0
lines changed

src/west/manifest-schema.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,25 @@ mapping:
6666
required: true
6767
type: str
6868

69+
# allow some modification of imported projects by downstream projects
70+
import-modifications:
71+
required: false
72+
type: map
73+
mapping:
74+
# search-replace within URLs (e.g. to use mirror URLs)
75+
url-replace:
76+
required: false
77+
type: seq
78+
sequence:
79+
- type: map
80+
mapping:
81+
old:
82+
required: true
83+
type: str
84+
new:
85+
required: true
86+
type: str
87+
6988
# The "projects" key specifies a sequence of "projects", each of which has a
7089
# remote, and may specify additional configuration.
7190
#

src/west/manifest.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
Parser and abstract data types for west manifests.
88
'''
99

10+
import copy
1011
import enum
1112
import errno
1213
import logging
@@ -182,6 +183,34 @@ def _err(message):
182183
_logger = logging.getLogger(__name__)
183184

184185

186+
# Representation of import-modifications
187+
188+
189+
class ImportModifications:
190+
"""Represents `import-modifications` within a manifest."""
191+
192+
def __init__(self, manifest_data: dict | None = None):
193+
"""Initialize a new ImportModifications instance."""
194+
self.url_replaces: list[tuple[str, str]] = []
195+
self.append(manifest_data or {})
196+
197+
def append(self, manifest_data: dict):
198+
"""Append values from a manifest data (dictionary) to this instance."""
199+
for kind, values in manifest_data.get('import-modifications', {}).items():
200+
if kind == 'url-replace':
201+
self.url_replaces += [(v['old'], v['new']) for v in values]
202+
203+
def merge(self, other):
204+
"""Merge another ImportModifications instance into this one."""
205+
if not isinstance(other, ImportModifications):
206+
raise TypeError(f"Unsupported type'{type(other).__name__}'")
207+
self.url_replaces += other.url_replaces
208+
209+
def copy(self):
210+
"""Return a deep copy of this instance."""
211+
return copy.deepcopy(self)
212+
213+
185214
# Type for the submodule value passed through the manifest file.
186215
class Submodule(NamedTuple):
187216
'''Represents a Git submodule within a project.'''
@@ -456,6 +485,9 @@ class _import_ctx(NamedTuple):
456485
# Bit vector of flags that modify import behavior.
457486
import_flags: 'ImportFlag'
458487

488+
# import-modifications
489+
modifications: ImportModifications
490+
459491

460492
def _imap_filter_allows(imap_filter: ImapFilterFnType, project: 'Project') -> bool:
461493
# imap_filter(project) if imap_filter is not None; True otherwise.
@@ -2052,6 +2084,7 @@ def get_option(option, default=None):
20522084
current_repo_abspath=current_repo_abspath,
20532085
project_importer=project_importer,
20542086
import_flags=import_flags,
2087+
modifications=ImportModifications(),
20552088
)
20562089

20572090
def _recursive_init(self, ctx: _import_ctx):
@@ -2074,6 +2107,10 @@ def _load_validated(self) -> None:
20742107

20752108
manifest_data = self._ctx.current_data['manifest']
20762109

2110+
# append values from resolved manifest_data to current context
2111+
new_modifications = ImportModifications(manifest_data)
2112+
self._ctx.modifications.merge(new_modifications)
2113+
20772114
schema_version = str(manifest_data.get('version', SCHEMA_VERSION))
20782115

20792116
# We want to make an ordered map from project names to
@@ -2322,6 +2359,7 @@ def _import_pathobj_from_self(self, pathobj_abs: Path, pathobj: Path) -> None:
23222359
current_abspath=pathobj_abs,
23232360
current_relpath=pathobj,
23242361
current_data=pathobj_abs.read_text(encoding=Manifest.encoding),
2362+
modifications=self._ctx.modifications.copy(),
23252363
)
23262364
try:
23272365
Manifest(topdir=self.topdir, internal_import_ctx=child_ctx)
@@ -2452,6 +2490,13 @@ def _load_project(self, pd: dict, url_bases: dict[str, str], defaults: _defaults
24522490
else:
24532491
self._malformed(f'project {name} has no remote or url and no default remote is set')
24542492

2493+
# modify the url
2494+
if url:
2495+
url_replaces = self._ctx.modifications.url_replaces
2496+
for url_replace in reversed(url_replaces):
2497+
old, new = url_replace
2498+
url = url.replace(old, new)
2499+
24552500
# The project's path needs to respect any import: path-prefix,
24562501
# regardless of self._ctx.import_flags. The 'ignore' type flags
24572502
# just mean ignore the imported data. The path-prefix in this
@@ -2672,6 +2717,7 @@ def _import_data_from_project(
26722717
# We therefore use a separate list for tracking them
26732718
# from our current list.
26742719
manifest_west_commands=[],
2720+
modifications=self._ctx.modifications.copy(),
26752721
)
26762722
try:
26772723
submanifest = Manifest(topdir=self.topdir, internal_import_ctx=child_ctx)

0 commit comments

Comments
 (0)