From 7a1aa179087a274d6f6b13c2c666043ac9760ea5 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 24 Oct 2025 10:24:45 +0200 Subject: modules: rust: implement workspace.package() Note that, as shown in the testcase, package() works in the subproject as well. This means that in the future the Cargo code generator can be changed to reduce the amount of generated code and instead rely on the package object. Signed-off-by: Paolo Bonzini --- docs/markdown/Rust-module.md | 29 ++++++++++++++++------ mesonbuild/cargo/interpreter.py | 15 +++++++++++ mesonbuild/modules/rust.py | 29 +++++++++++++++++++--- .../rust/31 rust.workspace package/meson.build | 15 +++++++++++ .../subprojects/answer-2.1/meson.build | 4 +++ .../rust/32 rust.workspace workspace/meson.build | 15 +++++++++++ .../subprojects/answer-2.1/meson.build | 4 +++ 7 files changed, 100 insertions(+), 11 deletions(-) diff --git a/docs/markdown/Rust-module.md b/docs/markdown/Rust-module.md index fbe65f1b6..ba500e7ee 100644 --- a/docs/markdown/Rust-module.md +++ b/docs/markdown/Rust-module.md @@ -241,6 +241,19 @@ packages = ws.packages() Returns a list of package names in the workspace. +### workspace.package() + +```meson +pkg = ws.package([package_name]) +``` + +Returns a package object for the given package member. If empty, returns +the object for the root package. + +Arguments: +- `package_name`: (str, optional) Name of the package; not needed for the + root package of a workspace + ### workspace.subproject() ```meson @@ -258,15 +271,15 @@ Positional arguments: The package object returned by `workspace.subproject()` provides methods for working with individual packages in a Cargo workspace. -### subproject.name() +### package.name(), subproject.name() ```meson name = pkg.name() ``` -Returns the name of the subproject. +Returns the name of a package or subproject. -### subproject.version() +### package.version(), subproject.version() ```meson version = pkg.version() @@ -274,7 +287,7 @@ version = pkg.version() Returns the normalized version number of the subproject. -### subproject.api() +### package.api(), subproject.api() ```meson api = pkg.api() @@ -283,21 +296,21 @@ api = pkg.api() Returns the API version of the subproject, that is the version up to the first nonzero element. -### subproject.features() +### package.features(), subproject.features() ```meson features = pkg.features() ``` -Returns selected features for a specific subproject. +Returns selected features for a specific package or subproject. -### subproject.all_features() +### package.all_features(), subproject.all_features() ```meson all_features = pkg.all_features() ``` -Returns all defined features for a specific subproject. +Returns all defined features for a specific package or subproject. ### subproject.dependency() diff --git a/mesonbuild/cargo/interpreter.py b/mesonbuild/cargo/interpreter.py index 30639f526..dc5cdef2a 100644 --- a/mesonbuild/cargo/interpreter.py +++ b/mesonbuild/cargo/interpreter.py @@ -298,6 +298,21 @@ class Interpreter: for feature in self.features: self._enable_feature(pkg, feature) + def load_package(self, ws: WorkspaceState, package_name: T.Optional[str]) -> PackageState: + if package_name is None: + if not ws.workspace.root_package: + raise MesonException('no root package in workspace') + path = '.' + else: + try: + path = ws.packages_to_member[package_name] + except KeyError: + raise MesonException(f'workspace member "{package_name}" not found') + + if is_parent_path(self.subprojects_dir, path): + raise MesonException('argument to package() cannot be a subproject') + return ws.packages[path] + def interpret(self, subdir: str, project_root: T.Optional[str] = None) -> mparser.CodeBlockNode: filename = os.path.join(self.environment.source_dir, subdir, 'Cargo.toml') build = builder.Builder(filename) diff --git a/mesonbuild/modules/rust.py b/mesonbuild/modules/rust.py index 29d1b1fbb..186774413 100644 --- a/mesonbuild/modules/rust.py +++ b/mesonbuild/modules/rust.py @@ -101,6 +101,7 @@ class RustWorkspace(ModuleObject): self.ws = ws self.methods.update({ 'packages': self.packages_method, + 'package': self.package_method, 'subproject': self.subproject_method, }) @@ -111,6 +112,12 @@ class RustWorkspace(ModuleObject): package_names = [pkg.manifest.package.name for pkg in self.ws.packages.values()] return sorted(package_names) + @typed_pos_args('workspace.package', optargs=[str]) + def package_method(self, state: 'ModuleState', args: T.List, kwargs: TYPE_kwargs) -> RustPackage: + """Returns a package object.""" + package_name = args[0] if args else None + return RustPackage(self, self.interpreter.cargo.load_package(self.ws, package_name)) + def _do_subproject(self, pkg: cargo.PackageState) -> None: kw: _kwargs.DoSubproject = { 'required': True, @@ -138,8 +145,8 @@ class RustWorkspace(ModuleObject): return RustSubproject(self, pkg) -class RustSubproject(ModuleObject): - """Represents a Rust package within a workspace.""" +class RustCrate(ModuleObject): + """Abstract base class for Rust crate representations.""" def __init__(self, rust_ws: RustWorkspace, package: cargo.PackageState) -> None: super().__init__() @@ -148,7 +155,6 @@ class RustSubproject(ModuleObject): self.methods.update({ 'all_features': self.all_features_method, 'api': self.api_method, - 'dependency': self.dependency_method, 'features': self.features_method, 'name': self.name_method, 'version': self.version_method, @@ -184,6 +190,23 @@ class RustSubproject(ModuleObject): """Returns chosen features for specific package.""" return sorted(list(self.package.cfg.features)) + +class RustPackage(RustCrate): + """Represents a Rust package within a workspace.""" + + def __init__(self, rust_ws: RustWorkspace, package: cargo.PackageState) -> None: + super().__init__(rust_ws, package) + + +class RustSubproject(RustCrate): + """Represents a Cargo subproject.""" + + def __init__(self, rust_ws: RustWorkspace, package: cargo.PackageState) -> None: + super().__init__(rust_ws, package) + self.methods.update({ + 'dependency': self.dependency_method, + }) + @noPosargs @typed_kwargs('package.dependency', KwargInfo('rust_abi', (str, NoneType), default=None, validator=in_set_validator({'rust', 'c', 'proc-macro'}))) diff --git a/test cases/rust/31 rust.workspace package/meson.build b/test cases/rust/31 rust.workspace package/meson.build index d6e9aa42d..ec37a33d7 100644 --- a/test cases/rust/31 rust.workspace package/meson.build +++ b/test cases/rust/31 rust.workspace package/meson.build @@ -6,6 +6,13 @@ cargo_ws = rust.workspace() # Test workspace.packages() method assert(cargo_ws.packages() == ['answer', 'hello', 'package_test']) +main_pkg = cargo_ws.package() +assert(main_pkg.name() == 'package_test') +assert(main_pkg.version() == '0.1.0') +assert(main_pkg.api() == '0.1') +assert(main_pkg.all_features() == ['answer', 'default', 'feature1', 'feature2']) +assert(main_pkg.features() == ['default', 'feature1']) + hello_rs = cargo_ws.subproject('hello') assert(hello_rs.name() == 'hello') assert(hello_rs.version() == '1.0.0') @@ -25,6 +32,14 @@ e = executable('package-test', 'src/main.rs', ) test('package-test', e) +# failure test cases for package() +testcase expect_error('argument to package() cannot be a subproject') + cargo_ws.package('hello') +endtestcase +testcase expect_error('workspace member "nonexistent" not found') + cargo_ws.package('nonexistent') +endtestcase + # failure test cases for dependency() testcase expect_error('package.dependency.*must be one of c, proc-macro, rust.*', how: 're') hello_rs.dependency(rust_abi: 'something else') diff --git a/test cases/rust/31 rust.workspace package/subprojects/answer-2.1/meson.build b/test cases/rust/31 rust.workspace package/subprojects/answer-2.1/meson.build index e8d10117f..ece234f27 100644 --- a/test cases/rust/31 rust.workspace package/subprojects/answer-2.1/meson.build +++ b/test cases/rust/31 rust.workspace package/subprojects/answer-2.1/meson.build @@ -4,6 +4,10 @@ rust = import('rust') cargo_ws = rust.workspace() assert(cargo_ws.packages() == ['answer']) +answer_pkg = cargo_ws.package() +assert(answer_pkg.all_features() == ['default', 'large']) +assert(answer_pkg.features() == ['default', 'large']) + l = static_library('answer', 'src/lib.rs', rust_args: ['--cfg', 'feature="large"']) dep = declare_dependency(link_with: l) meson.override_dependency('answer-2-rs', dep) diff --git a/test cases/rust/32 rust.workspace workspace/meson.build b/test cases/rust/32 rust.workspace workspace/meson.build index 2626d3d50..185759413 100644 --- a/test cases/rust/32 rust.workspace workspace/meson.build +++ b/test cases/rust/32 rust.workspace workspace/meson.build @@ -6,6 +6,13 @@ cargo_ws = rust.workspace() # Test workspace.packages() method assert(cargo_ws.packages() == ['answer', 'hello', 'workspace_test']) +main_pkg = cargo_ws.package() +assert(main_pkg.name() == 'workspace_test') +assert(main_pkg.version() == '0.1.0') +assert(main_pkg.api() == '0.1') +assert(main_pkg.all_features() == ['answer', 'default', 'feature1', 'feature2']) +assert(main_pkg.features() == ['default', 'feature1']) + hello_rs = cargo_ws.subproject('hello') assert(hello_rs.name() == 'hello') assert(hello_rs.version() == '1.0.0') @@ -25,6 +32,14 @@ e = executable('workspace-test', 'src/main.rs', ) test('workspace-test', e) +# failure test cases for package() +testcase expect_error('argument to package() cannot be a subproject') + cargo_ws.package('hello') +endtestcase +testcase expect_error('workspace member "nonexistent" not found') + cargo_ws.package('nonexistent') +endtestcase + # failure test cases for dependency() testcase expect_error('package.dependency.*must be one of c, proc-macro, rust.*', how: 're') hello_rs.dependency(rust_abi: 'something else') diff --git a/test cases/rust/32 rust.workspace workspace/subprojects/answer-2.1/meson.build b/test cases/rust/32 rust.workspace workspace/subprojects/answer-2.1/meson.build index e8d10117f..ece234f27 100644 --- a/test cases/rust/32 rust.workspace workspace/subprojects/answer-2.1/meson.build +++ b/test cases/rust/32 rust.workspace workspace/subprojects/answer-2.1/meson.build @@ -4,6 +4,10 @@ rust = import('rust') cargo_ws = rust.workspace() assert(cargo_ws.packages() == ['answer']) +answer_pkg = cargo_ws.package() +assert(answer_pkg.all_features() == ['default', 'large']) +assert(answer_pkg.features() == ['default', 'large']) + l = static_library('answer', 'src/lib.rs', rust_args: ['--cfg', 'feature="large"']) dep = declare_dependency(link_with: l) meson.override_dependency('answer-2-rs', dep) -- cgit v1.2.3