Skip to content

Commit 15c128a

Browse files
committed
modules: rust: implement workspace.subproject() and package.dependency()
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
1 parent 2e9b132 commit 15c128a

File tree

6 files changed

+158
-10
lines changed

6 files changed

+158
-10
lines changed

docs/markdown/Rust-module.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,3 +203,36 @@ are specified.
203203

204204
When `features` is provided without `default_features`, the 'default' feature is
205205
automatically included.
206+
207+
#### workspace.subproject()
208+
209+
```meson
210+
package = ws.subproject(package_name, ...)
211+
```
212+
213+
Returns a `package` object for managing a specific package within the workspace.
214+
215+
Positional arguments:
216+
- `package_name`: (`str`) The name of the package to retrieve
217+
218+
Keyword arguments:
219+
- `version`: (`list[str]`, optional) List of version constraints for the package
220+
221+
## Package object
222+
223+
The package object returned by `workspace.subproject()` provides methods for working with individual packages in a Cargo workspace.
224+
225+
### package.dependency()
226+
227+
```meson
228+
dep = package.dependency(...)
229+
```
230+
231+
Returns a dependency object for the package that can be used with other Meson targets.
232+
233+
*Note*: right now, this method is implemented on top of the normal Meson function
234+
[[dependency]]; this is subject to change in future releases. It is recommended
235+
to always retrieve a Cargo subproject's dependency object via this method.
236+
237+
Keyword arguments:
238+
- `rust_abi`: (`str`, optional) The ABI to use for the dependency. Valid values are `'rust'` (default), `'c'`, or `'proc-macro'`. The package must support the specified ABI.

mesonbuild/cargo/interpreter.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,15 @@
2828
if T.TYPE_CHECKING:
2929
from . import raw
3030
from .. import mparser
31+
from typing_extensions import Literal
32+
3133
from .manifest import Dependency, SystemDependency
3234
from ..environment import Environment
3335
from ..interpreterbase import SubProject
3436
from ..compilers.rust import RustCompiler
3537

38+
RUST_ABI = Literal['rust', 'c', 'proc-macro']
39+
3640
def _dependency_name(package_name: str, api: str, suffix: str = '-rs') -> str:
3741
basename = package_name[:-len(suffix)] if package_name.endswith(suffix) else package_name
3842
return f'{basename}-{api}{suffix}'
@@ -70,6 +74,34 @@ class PackageState:
7074
# Package configuration state
7175
cfg: T.Optional[PackageConfiguration] = None
7276

77+
def uses_abi(self, abi: str) -> bool:
78+
"""Check if this package supports the given ABI."""
79+
crate_types = self.manifest.lib.crate_type
80+
if abi == 'rust':
81+
return any(ct in {'lib', 'rlib'} for ct in crate_types)
82+
elif abi == 'c':
83+
return any(ct in {'staticlib', 'cdylib'} for ct in crate_types)
84+
elif abi == 'proc-macro':
85+
return 'proc-macro' in crate_types
86+
else:
87+
return False
88+
89+
def get_dependency_name(self, rust_abi: RUST_ABI) -> str:
90+
"""Get the dependency name for a package with the given ABI."""
91+
if not self.uses_abi(rust_abi):
92+
raise MesonException(f'Package {self.manifest.package.name} does not support ABI {rust_abi}')
93+
94+
# TODO: Implement dependency name generation
95+
package_name = self.manifest.package.name
96+
api = self.manifest.package.api
97+
98+
if rust_abi in {'rust', 'proc-macro'}:
99+
return f'{package_name}-{api}-rs'
100+
elif rust_abi == 'c':
101+
return f'{package_name}-{api}'
102+
else:
103+
raise MesonException(f'Unknown rust_abi: {rust_abi}')
104+
73105

74106
@dataclasses.dataclass(frozen=True)
75107
class PackageKey:
@@ -300,6 +332,11 @@ def _resolve_package(self, package_name: str, version_constraints: T.List[str])
300332
raise MesonException(f'Cannot determine version of cargo package {package_name}')
301333
return None
302334

335+
def resolve_package(self, package_name: str, version_constraints: T.List[str]) -> T.Optional[PackageState]:
336+
cargo_pkg = self._resolve_package(package_name, version_constraints)
337+
api = version.api(cargo_pkg.version)
338+
return self._fetch_package(package_name, api)
339+
303340
def _fetch_package_from_subproject(self, package_name: str, meson_depname: str) -> PackageState:
304341
subp_name, _ = self.environment.wrap_resolver.find_dep_provider(meson_depname)
305342
if subp_name is None:

mesonbuild/modules/__init__.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import dataclasses
88
import typing as T
99

10-
from .. import build, mesonlib
10+
from .. import build, dependencies, mesonlib
1111
from ..options import OptionKey
1212
from ..build import IncludeDirs
1313
from ..interpreterbase.decorators import noKwargs, noPosargs
@@ -46,6 +46,7 @@ def __init__(self, interpreter: 'Interpreter') -> None:
4646
# The backend object is under-used right now, but we will need it:
4747
# https://github.com/mesonbuild/meson/issues/1419
4848
self.backend = interpreter.backend
49+
self.dependency_overrides = interpreter.build.dependency_overrides
4950
self.targets = interpreter.build.targets
5051
self.data = interpreter.build.data
5152
self.headers = interpreter.build.get_headers()
@@ -108,6 +109,13 @@ def find_tool(self, name: str, depname: str, varname: str, required: bool = True
108109
# Normal program lookup
109110
return self.find_program(name, required=required, wanted=wanted)
110111

112+
def overridden_dependency(self, depname: str, for_machine: MachineChoice = MachineChoice.HOST) -> Dependency:
113+
identifier = dependencies.get_dep_identifier(depname, {})
114+
try:
115+
return self.dependency_overrides[for_machine][identifier].dep
116+
except KeyError:
117+
raise mesonlib.MesonException(f'dependency "{depname}" was not overridden for the {for_machine}')
118+
111119
def dependency(self, depname: str, native: bool = False, required: bool = True,
112120
wanted: T.Optional[str] = None) -> 'Dependency':
113121
kwargs: T.Dict[str, object] = {'native': native, 'required': required}

mesonbuild/modules/rust.py

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414
from .. import mesonlib, mlog
1515
from ..build import (BothLibraries, BuildTarget, CustomTargetIndex, Executable, ExtractedObjects, GeneratedList,
1616
CustomTarget, InvalidArguments, Jar, StructuredSources, SharedLibrary, StaticLibrary)
17+
from ..cargo import PackageState
1718
from ..compilers.compilers import are_asserts_disabled_for_subproject, lang_suffixes
19+
from ..dependencies import Dependency
1820
from ..interpreter.type_checking import (
1921
DEPENDENCIES_KW, LINK_WITH_KW, LINK_WHOLE_KW, SHARED_LIB_KWS, TEST_KWS, TEST_KWS_NO_ARGS,
2022
OUTPUT_KW, INCLUDE_DIRECTORIES, SOURCES_VARARGS, NoneType, in_set_validator
@@ -28,8 +30,9 @@
2830
from . import ModuleState
2931
from ..build import BuildTargetTypes, ExecutableKeywordArguments, IncludeDirs, LibTypes
3032
from .. import cargo
33+
from ..cargo.interpreter import RUST_ABI
3134
from ..compilers.rust import RustCompiler
32-
from ..dependencies import Dependency, ExternalLibrary
35+
from ..dependencies import ExternalLibrary
3336
from ..interpreter import Interpreter
3437
from ..interpreter import kwargs as _kwargs
3538
from ..interpreter.interpreter import SourceInputs, SourceOutputs
@@ -69,6 +72,12 @@ class FuncWorkspace(TypedDict):
6972
features: T.List[str]
7073
dev_dependencies: bool
7174

75+
class FuncWorkspaceSubproject(TypedDict):
76+
version: T.List[str]
77+
78+
class FuncDependency(TypedDict):
79+
rust_abi: RUST_ABI
80+
7281
RUST_TEST_KWS: T.List[KwargInfo] = [
7382
KwargInfo(
7483
'rust_args',
@@ -90,10 +99,55 @@ class RustWorkspace(MutableModuleObject):
9099
"""Represents a Rust workspace, controlling the build of packages
91100
recorded in a Cargo.lock file."""
92101

93-
def __init__(self, state: ModuleState, root_package: T.Union[cargo.WorkspaceState, cargo.PackageState]) -> None:
102+
def __init__(self, state: ModuleState, interpreter: Interpreter, root_package: T.Union[cargo.WorkspaceState, cargo.PackageState]) -> None:
94103
super().__init__()
95104
self.state = state
105+
self.interpreter = interpreter
96106
self.root_package = root_package
107+
self.methods.update({
108+
'subproject': self.subproject_method,
109+
})
110+
111+
@typed_pos_args('workspace.subproject', str)
112+
@typed_kwargs('workspace.subproject',
113+
KwargInfo('version', ContainerTypeInfo(list, str), default=[], listify=True))
114+
def subproject_method(self, state: ModuleState, args: T.Tuple[str], kwargs: FuncWorkspaceSubproject) -> RustPackage:
115+
"""Returns a package object for a subproject package."""
116+
package_name = args[0]
117+
pkg = self.interpreter.cargo.resolve_package(package_name, kwargs['version'])
118+
if pkg is None:
119+
raise MesonException(f'No version of cargo package {package_name} satisfies constraints {kwargs["version"]}')
120+
121+
kw: _kwargs.DoSubproject = {
122+
'required': True,
123+
'version': kwargs['version'],
124+
'options': None,
125+
'cmake_options': [],
126+
'default_options': {},
127+
}
128+
subp_name = f'{pkg.manifest.package.name}-{pkg.manifest.package.api}-rs'
129+
self.interpreter.do_subproject(subp_name, kw, force_method='cargo')
130+
return RustPackage(state, pkg)
131+
132+
133+
class RustPackage(MutableModuleObject):
134+
"""Represents a Rust package within a workspace."""
135+
136+
def __init__(self, state: ModuleState, package: PackageState) -> None:
137+
super().__init__()
138+
self.state = state
139+
self.package = package
140+
self.methods.update({
141+
'dependency': self.dependency_method,
142+
})
143+
144+
@noPosargs
145+
@typed_kwargs('package.dependency',
146+
KwargInfo('rust_abi', str, default='rust', validator=in_set_validator({'rust', 'c', 'proc-macro'})))
147+
def dependency_method(self, state: 'ModuleState', args: T.List, kwargs: FuncDependency) -> Dependency:
148+
"""Returns dependency for the package with the given ABI."""
149+
depname = self.package.get_dependency_name(kwargs['rust_abi'])
150+
return self.state.overridden_dependency(depname)
97151

98152

99153
class RustModule(ExtensionModule):
@@ -557,7 +611,7 @@ def workspace(self, state: ModuleState, args: T.List, kwargs: FuncWorkspace) ->
557611
ws_obj = self._workspace_cache.get(self.interpreter.cargo)
558612
if ws_obj is None:
559613
root_pkg = self.interpreter.cargo.load_workspace()
560-
ws_obj = RustWorkspace(state, root_pkg)
614+
ws_obj = RustWorkspace(state, self.interpreter, root_pkg)
561615
self._workspace_cache[self.interpreter.cargo] = ws_obj
562616

563617
return ws_obj

test cases/rust/31 rust.workspace package/meson.build

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,18 @@ project('package test', 'rust', default_options: ['rust_std=2021'])
33
rust = import('rust')
44
cargo = rust.workspace()
55

6-
hello_dep = dependency('hello-1-rs')
7-
answer_dep = dependency('answer-1-rs')
6+
hello_rs = cargo.subproject('hello')
7+
answer_rs = cargo.subproject('answer')
88

99
e = executable('package-test', 'src/main.rs',
10-
dependencies: [hello_dep, answer_dep],
10+
dependencies: [hello_rs.dependency(), answer_rs.dependency()],
1111
)
1212
test('package-test', e)
13+
14+
# failure test cases for dependency()
15+
testcase expect_error('package.dependency.*must be one of c, proc-macro, rust.*', how: 're')
16+
hello_rs.dependency(rust_abi: 'something else')
17+
endtestcase
18+
testcase expect_error('Package hello does not support ABI c')
19+
hello_rs.dependency(rust_abi: 'c')
20+
endtestcase

test cases/rust/32 rust.workspace workspace/meson.build

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,18 @@ project('workspace test', 'rust', default_options: ['rust_std=2021'])
33
rust = import('rust')
44
cargo = rust.workspace()
55

6-
hello_dep = dependency('hello-1-rs')
7-
answer_dep = dependency('answer-1-rs')
6+
hello_rs = cargo.subproject('hello')
7+
answer_rs = cargo.subproject('answer')
88

99
e = executable('workspace-test', 'src/main.rs',
10-
dependencies: [hello_dep, answer_dep],
10+
dependencies: [hello_rs.dependency(), answer_rs.dependency()],
1111
)
1212
test('workspace-test', e)
13+
14+
# failure test cases for dependency()
15+
testcase expect_error('package.dependency.*must be one of c, proc-macro, rust.*', how: 're')
16+
hello_rs.dependency(rust_abi: 'something else')
17+
endtestcase
18+
testcase expect_error('Package hello does not support ABI c')
19+
hello_rs.dependency(rust_abi: 'c')
20+
endtestcase

0 commit comments

Comments
 (0)