summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaolo Bonzini <pbonzini@redhat.com>2025-10-17 14:45:07 +0200
committerPaolo Bonzini <pbonzini@redhat.com>2025-12-22 11:58:30 +0100
commit646593856800c55f44fe2b15991570737709a36e (patch)
treefca53f863fa98c9e3053d2285681f3e7dc5b7adc
parentd1ea846b6bbb5cbf09d8e9707707c40af3cf7c92 (diff)
downloadmeson-646593856800c55f44fe2b15991570737709a36e.tar.gz
rust: add rust.workspace() skeleton implementation
rust.workspace() is the entry point for global feature resolution. It loads a Cargo.toml file and ensures that all dependencies will be built with the correct set of features. Fixes: #13404 Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
-rw-r--r--docs/markdown/Rust-module.md22
-rw-r--r--mesonbuild/cargo/__init__.py3
-rw-r--r--mesonbuild/cargo/interpreter.py19
-rw-r--r--mesonbuild/modules/rust.py32
-rw-r--r--test cases/rust/31 rust.workspace package/Cargo.lock19
-rw-r--r--test cases/rust/31 rust.workspace package/Cargo.toml11
-rw-r--r--test cases/rust/31 rust.workspace package/meson.build12
-rw-r--r--test cases/rust/31 rust.workspace package/src/main.rs6
-rw-r--r--test cases/rust/31 rust.workspace package/subprojects/answer-2-rs.wrap2
-rw-r--r--test cases/rust/31 rust.workspace package/subprojects/answer-2.1/Cargo.toml10
-rw-r--r--test cases/rust/31 rust.workspace package/subprojects/answer-2.1/meson.build7
-rw-r--r--test cases/rust/31 rust.workspace package/subprojects/answer-2.1/src/lib.rs10
-rw-r--r--test cases/rust/31 rust.workspace package/subprojects/hello-1-rs.wrap3
-rw-r--r--test cases/rust/31 rust.workspace package/subprojects/hello-1.0/Cargo.toml7
-rw-r--r--test cases/rust/31 rust.workspace package/subprojects/hello-1.0/src/lib.rs4
-rw-r--r--test cases/rust/32 rust.workspace workspace/Cargo.lock19
-rw-r--r--test cases/rust/32 rust.workspace workspace/Cargo.toml14
-rw-r--r--test cases/rust/32 rust.workspace workspace/meson.build12
-rw-r--r--test cases/rust/32 rust.workspace workspace/src/main.rs6
-rw-r--r--test cases/rust/32 rust.workspace workspace/subprojects/answer-2-rs.wrap2
-rw-r--r--test cases/rust/32 rust.workspace workspace/subprojects/answer-2.1/Cargo.toml10
-rw-r--r--test cases/rust/32 rust.workspace workspace/subprojects/answer-2.1/meson.build7
-rw-r--r--test cases/rust/32 rust.workspace workspace/subprojects/answer-2.1/src/lib.rs10
-rw-r--r--test cases/rust/32 rust.workspace workspace/subprojects/hello-1-rs.wrap3
-rw-r--r--test cases/rust/32 rust.workspace workspace/subprojects/hello-1.0/Cargo.toml7
-rw-r--r--test cases/rust/32 rust.workspace workspace/subprojects/hello-1.0/src/lib.rs4
26 files changed, 252 insertions, 9 deletions
diff --git a/docs/markdown/Rust-module.md b/docs/markdown/Rust-module.md
index 5ce0fdcdb..64c4c2311 100644
--- a/docs/markdown/Rust-module.md
+++ b/docs/markdown/Rust-module.md
@@ -4,6 +4,9 @@ authors:
- name: Dylan Baker
email: dylan@pnwbakers.com
years: [2020, 2021, 2022, 2024]
+ - name: Paolo Bonzini
+ email: bonzini@gnu.org
+ years: [2025]
...
# Rust module
@@ -168,3 +171,22 @@ Only a subset of [[shared_library]] keyword arguments are allowed:
- link_depends
- link_with
- override_options
+
+### workspace()
+
+```
+cargo_ws = rustmod.workspace()
+```
+
+*Since 1.11.0*
+
+Create and return a `workspace` object for managing the project's Cargo
+workspace.
+
+A project that wishes to use Cargo subprojects should have `Cargo.lock` and `Cargo.toml`
+files in the root source directory, and should call this function before using
+Cargo subprojects.
+
+The first invocation of `workspace()` establishes the *Cargo interpreter*
+that resolves dependencies and features for both the toplevel project (the one
+containing `Cargo.lock`) and all subprojects that are invoked with the `cargo` method,
diff --git a/mesonbuild/cargo/__init__.py b/mesonbuild/cargo/__init__.py
index c5b157f3c..d461d10bb 100644
--- a/mesonbuild/cargo/__init__.py
+++ b/mesonbuild/cargo/__init__.py
@@ -1,7 +1,8 @@
__all__ = [
'Interpreter',
'TomlImplementationMissing',
+ 'WorkspaceState',
]
-from .interpreter import Interpreter
+from .interpreter import Interpreter, WorkspaceState
from .toml import TomlImplementationMissing
diff --git a/mesonbuild/cargo/interpreter.py b/mesonbuild/cargo/interpreter.py
index 4f3acc133..cc7e2a65b 100644
--- a/mesonbuild/cargo/interpreter.py
+++ b/mesonbuild/cargo/interpreter.py
@@ -227,6 +227,15 @@ class Interpreter:
def get_build_def_files(self) -> T.List[str]:
return self.build_def_files
+ def load_workspace(self, subdir: str) -> WorkspaceState:
+ """Load the root Cargo.toml package and prepare it with features and dependencies."""
+ subdir = os.path.normpath(subdir)
+ manifest, cached = self._load_manifest(subdir)
+ ws = self._get_workspace(manifest, subdir, False)
+ if not cached:
+ self._prepare_entry_point(ws)
+ return ws
+
def _prepare_entry_point(self, ws: WorkspaceState) -> None:
pkgs = [self._require_workspace_member(ws, m) for m in ws.workspace.default_members]
for pkg in pkgs:
@@ -234,18 +243,16 @@ class Interpreter:
self._enable_feature(pkg, 'default')
def interpret(self, subdir: str, project_root: T.Optional[str] = None) -> mparser.CodeBlockNode:
- manifest, cached = self._load_manifest(subdir)
filename = os.path.join(self.environment.source_dir, subdir, 'Cargo.toml')
build = builder.Builder(filename)
if project_root:
# this is a subdir()
+ manifest, _ = self._load_manifest(subdir)
assert isinstance(manifest, Manifest)
return self.interpret_package(manifest, build, subdir, project_root)
-
- ws = self._get_workspace(manifest, subdir, downloaded=False)
- if not cached:
- self._prepare_entry_point(ws)
- return self.interpret_workspace(ws, build, subdir)
+ else:
+ ws = self.load_workspace(subdir)
+ return self.interpret_workspace(ws, build, subdir)
def interpret_package(self, manifest: Manifest, build: builder.Builder, subdir: str, project_root: str) -> mparser.CodeBlockNode:
# Build an AST for this package
diff --git a/mesonbuild/modules/rust.py b/mesonbuild/modules/rust.py
index a8fcc86a0..84ea70fce 100644
--- a/mesonbuild/modules/rust.py
+++ b/mesonbuild/modules/rust.py
@@ -10,7 +10,7 @@ import typing as T
from mesonbuild.interpreterbase.decorators import FeatureNew
-from . import ExtensionModule, ModuleReturnValue, ModuleInfo
+from . import ExtensionModule, ModuleReturnValue, ModuleInfo, ModuleObject
from .. import mesonlib, mlog
from ..build import (BothLibraries, BuildTarget, CustomTargetIndex, Executable, ExtractedObjects, GeneratedList,
CustomTarget, InvalidArguments, Jar, StructuredSources, SharedLibrary, StaticLibrary)
@@ -19,7 +19,8 @@ from ..interpreter.type_checking import (
DEPENDENCIES_KW, LINK_WITH_KW, LINK_WHOLE_KW, SHARED_LIB_KWS, TEST_KWS, TEST_KWS_NO_ARGS,
OUTPUT_KW, INCLUDE_DIRECTORIES, SOURCES_VARARGS, NoneType, in_set_validator
)
-from ..interpreterbase import ContainerTypeInfo, InterpreterException, KwargInfo, typed_kwargs, typed_pos_args, noPosargs, permittedKwargs
+from ..interpreterbase import ContainerTypeInfo, InterpreterException, KwargInfo, typed_kwargs, typed_pos_args, noKwargs, noPosargs, permittedKwargs
+from ..interpreterbase.baseobjects import TYPE_kwargs
from ..interpreter.interpreterobjects import Doctest
from ..mesonlib import File, MachineChoice, MesonException, PerMachine
from ..programs import ExternalProgram, NonExistingExternalProgram
@@ -27,6 +28,7 @@ from ..programs import ExternalProgram, NonExistingExternalProgram
if T.TYPE_CHECKING:
from . import ModuleState
from ..build import BuildTargetTypes, ExecutableKeywordArguments, IncludeDirs, LibTypes
+ from .. import cargo
from ..compilers.rust import RustCompiler
from ..dependencies import Dependency, ExternalLibrary
from ..interpreter import Interpreter
@@ -81,6 +83,16 @@ def no_spaces_validator(arg: T.Optional[T.Union[str, T.List]]) -> T.Optional[str
return None
+class RustWorkspace(ModuleObject):
+ """Represents a Rust workspace, controlling the build of packages
+ recorded in a Cargo.lock file."""
+
+ def __init__(self, interpreter: Interpreter, ws: cargo.WorkspaceState) -> None:
+ super().__init__()
+ self.interpreter = interpreter
+ self.ws = ws
+
+
class RustModule(ExtensionModule):
"""A module that holds helper functions for rust."""
@@ -103,6 +115,7 @@ class RustModule(ExtensionModule):
'doctest': self.doctest,
'bindgen': self.bindgen,
'proc_macro': self.proc_macro,
+ 'workspace': self.workspace,
})
def test_common(self, funcname: str, state: ModuleState, args: T.Tuple[str, BuildTarget], kwargs: FuncRustTest) -> T.Tuple[Executable, _kwargs.FuncTest]:
@@ -500,6 +513,21 @@ class RustModule(ExtensionModule):
target = state._interpreter.build_target(state.current_node, args, kwargs, SharedLibrary)
return target
+ @FeatureNew('rust.workspace', '1.11.0')
+ @noPosargs
+ @noKwargs
+ def workspace(self, state: ModuleState, args: T.List, kwargs: TYPE_kwargs) -> RustWorkspace:
+ """Creates a Rust workspace object, controlling the build of
+ all the packages in a Cargo.lock file."""
+ if self.interpreter.cargo is None:
+ raise MesonException("rust.workspace() requires a Cargo project (Cargo.toml and Cargo.lock)")
+
+ self.interpreter.add_languages(['rust'], True, MachineChoice.HOST)
+ self.interpreter.add_languages(['rust'], True, MachineChoice.BUILD)
+
+ ws = self.interpreter.cargo.load_workspace(state.root_subdir)
+ return RustWorkspace(self.interpreter, ws)
+
def initialize(interp: Interpreter) -> RustModule:
return RustModule(interp)
diff --git a/test cases/rust/31 rust.workspace package/Cargo.lock b/test cases/rust/31 rust.workspace package/Cargo.lock
new file mode 100644
index 000000000..989f6ff5b
--- /dev/null
+++ b/test cases/rust/31 rust.workspace package/Cargo.lock
@@ -0,0 +1,19 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "answer"
+version = "2.1.0"
+
+[[package]]
+name = "hello"
+version = "1.0.0"
+
+[[package]]
+name = "package_test"
+version = "0.1.0"
+dependencies = [
+ "answer",
+ "hello",
+]
diff --git a/test cases/rust/31 rust.workspace package/Cargo.toml b/test cases/rust/31 rust.workspace package/Cargo.toml
new file mode 100644
index 000000000..53bc49528
--- /dev/null
+++ b/test cases/rust/31 rust.workspace package/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "package_test"
+version = "0.1.0"
+edition = "2021"
+
+[features]
+default = ["dep:answer"]
+
+[dependencies]
+hello = { version = "1.0", path = "subprojects/hello-1.0" }
+answer = { version = "2.1", path = "subprojects/answer-2.1", optional = true }
diff --git a/test cases/rust/31 rust.workspace package/meson.build b/test cases/rust/31 rust.workspace package/meson.build
new file mode 100644
index 000000000..483b9ae52
--- /dev/null
+++ b/test cases/rust/31 rust.workspace package/meson.build
@@ -0,0 +1,12 @@
+project('package test', 'rust', default_options: ['rust_std=2021'])
+
+rust = import('rust')
+cargo_ws = rust.workspace()
+
+hello_dep = dependency('hello-1-rs')
+answer_dep = dependency('answer-2-rs')
+
+e = executable('package-test', 'src/main.rs',
+ dependencies: [hello_dep, answer_dep],
+)
+test('package-test', e)
diff --git a/test cases/rust/31 rust.workspace package/src/main.rs b/test cases/rust/31 rust.workspace package/src/main.rs
new file mode 100644
index 000000000..39b324801
--- /dev/null
+++ b/test cases/rust/31 rust.workspace package/src/main.rs
@@ -0,0 +1,6 @@
+use hello::greet;
+
+fn main() {
+ println!("{}", greet());
+ println!("{}", answer::answer());
+}
diff --git a/test cases/rust/31 rust.workspace package/subprojects/answer-2-rs.wrap b/test cases/rust/31 rust.workspace package/subprojects/answer-2-rs.wrap
new file mode 100644
index 000000000..d16d1f7a8
--- /dev/null
+++ b/test cases/rust/31 rust.workspace package/subprojects/answer-2-rs.wrap
@@ -0,0 +1,2 @@
+[wrap-file]
+directory = answer-2.1
diff --git a/test cases/rust/31 rust.workspace package/subprojects/answer-2.1/Cargo.toml b/test cases/rust/31 rust.workspace package/subprojects/answer-2.1/Cargo.toml
new file mode 100644
index 000000000..b87782264
--- /dev/null
+++ b/test cases/rust/31 rust.workspace package/subprojects/answer-2.1/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "answer"
+version = "2.1.0"
+edition = "2021"
+
+[lib]
+crate-type = ["lib"]
+
+[features]
+large = []
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
new file mode 100644
index 000000000..dc7df4bba
--- /dev/null
+++ b/test cases/rust/31 rust.workspace package/subprojects/answer-2.1/meson.build
@@ -0,0 +1,7 @@
+project('answer', 'rust', default_options: ['rust_std=2021'])
+
+rust = import('rust')
+
+l = static_library('answer', 'src/lib.rs')
+dep = declare_dependency(link_with: l)
+meson.override_dependency('answer-2-rs', dep)
diff --git a/test cases/rust/31 rust.workspace package/subprojects/answer-2.1/src/lib.rs b/test cases/rust/31 rust.workspace package/subprojects/answer-2.1/src/lib.rs
new file mode 100644
index 000000000..b7a721b05
--- /dev/null
+++ b/test cases/rust/31 rust.workspace package/subprojects/answer-2.1/src/lib.rs
@@ -0,0 +1,10 @@
+pub fn answer() -> u8
+{
+ 42
+}
+
+#[cfg(feature = "large")]
+pub fn large_answer() -> u64
+{
+ 42
+}
diff --git a/test cases/rust/31 rust.workspace package/subprojects/hello-1-rs.wrap b/test cases/rust/31 rust.workspace package/subprojects/hello-1-rs.wrap
new file mode 100644
index 000000000..25e7751d0
--- /dev/null
+++ b/test cases/rust/31 rust.workspace package/subprojects/hello-1-rs.wrap
@@ -0,0 +1,3 @@
+[wrap-file]
+directory = hello-1.0
+method = cargo
diff --git a/test cases/rust/31 rust.workspace package/subprojects/hello-1.0/Cargo.toml b/test cases/rust/31 rust.workspace package/subprojects/hello-1.0/Cargo.toml
new file mode 100644
index 000000000..ad0ae458c
--- /dev/null
+++ b/test cases/rust/31 rust.workspace package/subprojects/hello-1.0/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "hello"
+version = "1.0.0"
+edition = "2021"
+
+[lib]
+crate-type = ["lib"] \ No newline at end of file
diff --git a/test cases/rust/31 rust.workspace package/subprojects/hello-1.0/src/lib.rs b/test cases/rust/31 rust.workspace package/subprojects/hello-1.0/src/lib.rs
new file mode 100644
index 000000000..0631292f3
--- /dev/null
+++ b/test cases/rust/31 rust.workspace package/subprojects/hello-1.0/src/lib.rs
@@ -0,0 +1,4 @@
+pub fn greet() -> &'static str
+{
+ "hello world"
+}
diff --git a/test cases/rust/32 rust.workspace workspace/Cargo.lock b/test cases/rust/32 rust.workspace workspace/Cargo.lock
new file mode 100644
index 000000000..0434b6028
--- /dev/null
+++ b/test cases/rust/32 rust.workspace workspace/Cargo.lock
@@ -0,0 +1,19 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "answer"
+version = "2.1.0"
+
+[[package]]
+name = "hello"
+version = "1.0.0"
+
+[[package]]
+name = "workspace_test"
+version = "0.1.0"
+dependencies = [
+ "answer",
+ "hello",
+]
diff --git a/test cases/rust/32 rust.workspace workspace/Cargo.toml b/test cases/rust/32 rust.workspace workspace/Cargo.toml
new file mode 100644
index 000000000..ac3a340bd
--- /dev/null
+++ b/test cases/rust/32 rust.workspace workspace/Cargo.toml
@@ -0,0 +1,14 @@
+[workspace]
+members = ["."]
+
+[package]
+name = "workspace_test"
+version = "0.1.0"
+edition = "2021"
+
+[features]
+default = ["dep:answer"]
+
+[dependencies]
+hello = { version = "1.0", path = "subprojects/hello-1.0" }
+answer = { version = "2.1", path = "subprojects/answer-2.1", optional = true }
diff --git a/test cases/rust/32 rust.workspace workspace/meson.build b/test cases/rust/32 rust.workspace workspace/meson.build
new file mode 100644
index 000000000..35e1c8482
--- /dev/null
+++ b/test cases/rust/32 rust.workspace workspace/meson.build
@@ -0,0 +1,12 @@
+project('workspace test', 'rust', default_options: ['rust_std=2021'])
+
+rust = import('rust')
+cargo_ws = rust.workspace()
+
+hello_dep = dependency('hello-1-rs')
+answer_dep = dependency('answer-2-rs')
+
+e = executable('workspace-test', 'src/main.rs',
+ dependencies: [hello_dep, answer_dep],
+)
+test('workspace-test', e)
diff --git a/test cases/rust/32 rust.workspace workspace/src/main.rs b/test cases/rust/32 rust.workspace workspace/src/main.rs
new file mode 100644
index 000000000..39b324801
--- /dev/null
+++ b/test cases/rust/32 rust.workspace workspace/src/main.rs
@@ -0,0 +1,6 @@
+use hello::greet;
+
+fn main() {
+ println!("{}", greet());
+ println!("{}", answer::answer());
+}
diff --git a/test cases/rust/32 rust.workspace workspace/subprojects/answer-2-rs.wrap b/test cases/rust/32 rust.workspace workspace/subprojects/answer-2-rs.wrap
new file mode 100644
index 000000000..d16d1f7a8
--- /dev/null
+++ b/test cases/rust/32 rust.workspace workspace/subprojects/answer-2-rs.wrap
@@ -0,0 +1,2 @@
+[wrap-file]
+directory = answer-2.1
diff --git a/test cases/rust/32 rust.workspace workspace/subprojects/answer-2.1/Cargo.toml b/test cases/rust/32 rust.workspace workspace/subprojects/answer-2.1/Cargo.toml
new file mode 100644
index 000000000..b87782264
--- /dev/null
+++ b/test cases/rust/32 rust.workspace workspace/subprojects/answer-2.1/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "answer"
+version = "2.1.0"
+edition = "2021"
+
+[lib]
+crate-type = ["lib"]
+
+[features]
+large = []
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
new file mode 100644
index 000000000..dc7df4bba
--- /dev/null
+++ b/test cases/rust/32 rust.workspace workspace/subprojects/answer-2.1/meson.build
@@ -0,0 +1,7 @@
+project('answer', 'rust', default_options: ['rust_std=2021'])
+
+rust = import('rust')
+
+l = static_library('answer', 'src/lib.rs')
+dep = declare_dependency(link_with: l)
+meson.override_dependency('answer-2-rs', dep)
diff --git a/test cases/rust/32 rust.workspace workspace/subprojects/answer-2.1/src/lib.rs b/test cases/rust/32 rust.workspace workspace/subprojects/answer-2.1/src/lib.rs
new file mode 100644
index 000000000..b7a721b05
--- /dev/null
+++ b/test cases/rust/32 rust.workspace workspace/subprojects/answer-2.1/src/lib.rs
@@ -0,0 +1,10 @@
+pub fn answer() -> u8
+{
+ 42
+}
+
+#[cfg(feature = "large")]
+pub fn large_answer() -> u64
+{
+ 42
+}
diff --git a/test cases/rust/32 rust.workspace workspace/subprojects/hello-1-rs.wrap b/test cases/rust/32 rust.workspace workspace/subprojects/hello-1-rs.wrap
new file mode 100644
index 000000000..25e7751d0
--- /dev/null
+++ b/test cases/rust/32 rust.workspace workspace/subprojects/hello-1-rs.wrap
@@ -0,0 +1,3 @@
+[wrap-file]
+directory = hello-1.0
+method = cargo
diff --git a/test cases/rust/32 rust.workspace workspace/subprojects/hello-1.0/Cargo.toml b/test cases/rust/32 rust.workspace workspace/subprojects/hello-1.0/Cargo.toml
new file mode 100644
index 000000000..ad0ae458c
--- /dev/null
+++ b/test cases/rust/32 rust.workspace workspace/subprojects/hello-1.0/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "hello"
+version = "1.0.0"
+edition = "2021"
+
+[lib]
+crate-type = ["lib"] \ No newline at end of file
diff --git a/test cases/rust/32 rust.workspace workspace/subprojects/hello-1.0/src/lib.rs b/test cases/rust/32 rust.workspace workspace/subprojects/hello-1.0/src/lib.rs
new file mode 100644
index 000000000..0631292f3
--- /dev/null
+++ b/test cases/rust/32 rust.workspace workspace/subprojects/hello-1.0/src/lib.rs
@@ -0,0 +1,4 @@
+pub fn greet() -> &'static str
+{
+ "hello world"
+}