diff options
| author | Paolo Bonzini <pbonzini@redhat.com> | 2025-10-17 14:45:07 +0200 |
|---|---|---|
| committer | Paolo Bonzini <pbonzini@redhat.com> | 2025-12-22 11:58:30 +0100 |
| commit | 646593856800c55f44fe2b15991570737709a36e (patch) | |
| tree | fca53f863fa98c9e3053d2285681f3e7dc5b7adc | |
| parent | d1ea846b6bbb5cbf09d8e9707707c40af3cf7c92 (diff) | |
| download | meson-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>
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" +} |
