Skip to content

Commit b13d2d2

Browse files
bonzinidcbaker
authored andcommitted
cargo: manifest: support workspace inheritance
Add support for passing a workspace dictionary and setting fields from it into the dataclasses. Signed-off-by: Paolo Bonzini <[email protected]>
1 parent dc93d36 commit b13d2d2

File tree

2 files changed

+111
-11
lines changed

2 files changed

+111
-11
lines changed

mesonbuild/cargo/manifest.py

Lines changed: 103 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,13 @@ def fixup_meson_varname(name: str) -> str:
4444
return name.replace('-', '_')
4545

4646

47-
def _depv_to_dep(depv: raw.DependencyV) -> raw.Dependency:
47+
@T.overload
48+
def _depv_to_dep(depv: raw.FromWorkspace) -> raw.FromWorkspace: ...
49+
50+
@T.overload
51+
def _depv_to_dep(depv: raw.DependencyV) -> raw.Dependency: ...
52+
53+
def _depv_to_dep(depv: T.Union[raw.FromWorkspace, raw.DependencyV]) -> T.Union[raw.FromWorkspace, raw.Dependency]:
4854
return {'version': depv} if isinstance(depv, str) else depv
4955

5056

@@ -85,6 +91,51 @@ def _raw_to_dataclass(raw: T.Mapping[str, object], cls: T.Type[_DI],
8591
return cls(**new_dict)
8692

8793

94+
@T.overload
95+
def _inherit_from_workspace(raw: raw.Package,
96+
raw_from_workspace: T.Optional[T.Mapping[str, object]],
97+
msg: str,
98+
**kwargs: T.Callable[[T.Any, T.Any], object]) -> raw.Package: ...
99+
100+
@T.overload
101+
def _inherit_from_workspace(raw: T.Union[raw.FromWorkspace, raw.Dependency],
102+
raw_from_workspace: T.Optional[T.Mapping[str, object]],
103+
msg: str,
104+
**kwargs: T.Callable[[T.Any, T.Any], object]) -> raw.Dependency: ...
105+
106+
def _inherit_from_workspace(raw_: T.Union[raw.FromWorkspace, raw.Package, raw.Dependency], # type: ignore[misc]
107+
raw_from_workspace: T.Optional[T.Mapping[str, object]],
108+
msg: str,
109+
**kwargs: T.Callable[[T.Any, T.Any], object]) -> T.Mapping[str, object]:
110+
# allow accesses by non-literal key below
111+
raw = T.cast('T.Mapping[str, object]', raw_)
112+
113+
if not raw_from_workspace:
114+
if raw.get('workspace', False) or \
115+
any(isinstance(v, dict) and v.get('workspace', False) for v in raw):
116+
raise MesonException(f'Cargo.toml file requests {msg} from workspace')
117+
118+
return raw
119+
120+
result = {k: v for k, v in raw.items() if k != 'workspace'}
121+
for k, v in raw.items():
122+
if isinstance(v, dict) and v.get('workspace', False):
123+
if k in raw_from_workspace:
124+
result[k] = raw_from_workspace[k]
125+
if k in kwargs:
126+
result[k] = kwargs[k](v, result[k])
127+
else:
128+
del result[k]
129+
130+
if raw.get('workspace', False):
131+
for k, v in raw_from_workspace.items():
132+
if k not in result or k in kwargs:
133+
if k in kwargs:
134+
v = kwargs[k](raw.get(k), v)
135+
result[k] = v
136+
return result
137+
138+
88139
@dataclasses.dataclass
89140
class Package:
90141

@@ -124,7 +175,12 @@ def api(self) -> str:
124175
return version.api(self.version)
125176

126177
@classmethod
127-
def from_raw(cls, raw_pkg: raw.Package) -> Self:
178+
def from_raw(cls, raw_pkg: raw.Package, workspace: T.Optional[Workspace] = None) -> Self:
179+
raw_ws_pkg = None
180+
if workspace is not None:
181+
raw_ws_pkg = workspace.package
182+
183+
raw_pkg = _inherit_from_workspace(raw_pkg, raw_ws_pkg, f'Package entry {raw_pkg["name"]}')
128184
return _raw_to_dataclass(raw_pkg, cls, f'Package entry {raw_pkg["name"]}')
129185

130186
@dataclasses.dataclass
@@ -204,15 +260,24 @@ def api(self) -> str:
204260
raise MesonException(f'Cannot determine minimum API version from {self.version}.')
205261

206262
@classmethod
207-
def from_raw_dict(cls, name: str, raw_dep: raw.Dependency) -> Dependency:
263+
def from_raw_dict(cls, name: str, raw_dep: T.Union[raw.FromWorkspace, raw.Dependency], member_path: str = '', raw_ws_dep: T.Optional[raw.Dependency] = None) -> Dependency:
264+
raw_dep = _inherit_from_workspace(raw_dep, raw_ws_dep,
265+
f'Dependency entry {name}',
266+
path=lambda pkg_path, ws_path: os.path.relpath(ws_path, member_path),
267+
features=lambda pkg_path, ws_path: (pkg_path or []) + (ws_path or []))
208268
raw_dep.setdefault('package', name)
209269
return _raw_to_dataclass(raw_dep, cls, f'Dependency entry {name}')
210270

211271
@classmethod
212-
def from_raw(cls, name: str, raw_depv: raw.DependencyV) -> Dependency:
272+
def from_raw(cls, name: str, raw_depv: T.Union[raw.FromWorkspace, raw.DependencyV], member_path: str = '', workspace: T.Optional[Workspace] = None) -> Dependency:
213273
"""Create a dependency from a raw cargo dictionary or string"""
274+
raw_ws_dep: T.Optional[raw.Dependency] = None
275+
if workspace is not None:
276+
raw_ws_depv = workspace.dependencies.get(name, {})
277+
raw_ws_dep = _depv_to_dep(raw_ws_depv)
278+
214279
raw_dep = _depv_to_dep(raw_depv)
215-
return cls.from_raw_dict(name, raw_dep)
280+
return cls.from_raw_dict(name, raw_dep, member_path, raw_ws_dep)
216281

217282

218283
@dataclasses.dataclass
@@ -362,29 +427,56 @@ def system_dependencies(self) -> T.Dict[str, SystemDependency]:
362427
return {k: SystemDependency.from_raw(k, v) for k, v in self.package.metadata.get('system-deps', {}).items()}
363428

364429
@classmethod
365-
def from_raw(cls, raw: raw.Manifest, path: str = '') -> Self:
430+
def from_raw(cls, raw: raw.Manifest, path: str = '', workspace: T.Optional[Workspace] = None, member_path: str = '') -> Self:
366431
# Libs are always auto-discovered and there's no other way to handle them,
367432
# which is unfortunate for reproducability
368-
pkg = Package.from_raw(raw['package'])
433+
pkg = Package.from_raw(raw['package'], workspace)
369434
if pkg.autolib and 'lib' not in raw and \
370435
os.path.exists(os.path.join(path, 'src/lib.rs')):
371436
raw['lib'] = {}
372437
fixed = _raw_to_dataclass(raw, cls, f'Cargo.toml package {raw["package"]["name"]}',
373438
package=lambda x: pkg,
374-
dependencies=lambda x: {k: Dependency.from_raw(k, v) for k, v in x.items()},
375-
dev_dependencies=lambda x: {k: Dependency.from_raw(k, v) for k, v in x.items()},
376-
build_dependencies=lambda x: {k: Dependency.from_raw(k, v) for k, v in x.items()},
439+
dependencies=lambda x: {k: Dependency.from_raw(k, v, member_path, workspace) for k, v in x.items()},
440+
dev_dependencies=lambda x: {k: Dependency.from_raw(k, v, member_path, workspace) for k, v in x.items()},
441+
build_dependencies=lambda x: {k: Dependency.from_raw(k, v, member_path, workspace) for k, v in x.items()},
377442
lib=lambda x: Library.from_raw(x, raw['package']['name']),
378443
bin=lambda x: [Binary.from_raw(b) for b in x],
379444
test=lambda x: [Test.from_raw(b) for b in x],
380445
bench=lambda x: [Benchmark.from_raw(b) for b in x],
381446
example=lambda x: [Example.from_raw(b) for b in x],
382-
target=lambda x: {k: {k2: Dependency.from_raw(k2, v2) for k2, v2 in v.get('dependencies', {}).items()}
447+
target=lambda x: {k: {k2: Dependency.from_raw(k2, v2, member_path, workspace) for k2, v2 in v.get('dependencies', {}).items()}
383448
for k, v in x.items()})
384449
fixed.path = path
385450
return fixed
386451

387452

453+
@dataclasses.dataclass
454+
class Workspace:
455+
456+
"""Cargo Workspace definition.
457+
"""
458+
459+
resolver: str = dataclasses.field(default_factory=lambda: '2')
460+
members: T.List[str] = dataclasses.field(default_factory=list)
461+
exclude: T.List[str] = dataclasses.field(default_factory=list)
462+
default_members: T.List[str] = dataclasses.field(default_factory=list)
463+
464+
# inheritable settings are kept in raw format, for use with _inherit_from_workspace
465+
package: T.Optional[raw.Package] = None
466+
dependencies: T.Dict[str, raw.Dependency] = dataclasses.field(default_factory=dict)
467+
lints: T.Dict[str, T.Any] = dataclasses.field(default_factory=dict)
468+
metadata: T.Dict[str, T.Any] = dataclasses.field(default_factory=dict)
469+
470+
# A workspace can also have a root package.
471+
root_package: T.Optional[Manifest] = dataclasses.field(init=False)
472+
473+
@classmethod
474+
def from_raw(cls, raw: raw.VirtualManifest) -> Workspace:
475+
ws_raw = raw['workspace']
476+
fixed = _raw_to_dataclass(ws_raw, cls, 'Workspace')
477+
return fixed
478+
479+
388480
@dataclasses.dataclass
389481
class CargoLockPackage:
390482

mesonbuild/cargo/raw.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@
1111
EDITION = Literal['2015', '2018', '2021']
1212
CRATE_TYPE = Literal['bin', 'lib', 'dylib', 'staticlib', 'cdylib', 'rlib', 'proc-macro']
1313

14+
15+
class FromWorkspace(TypedDict):
16+
17+
"""An entry or section that is copied from the workspace."""
18+
19+
workspace: bool
20+
21+
1422
Package = TypedDict(
1523
'Package',
1624
{

0 commit comments

Comments
 (0)