diff --git a/docs/markdown/Rust-module.md b/docs/markdown/Rust-module.md index 5ce0fdcdb0d6..ce7f4bccee44 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,76 @@ Only a subset of [[shared_library]] keyword arguments are allowed: - link_depends - link_with - override_options + +### workspace() + +```meson +rustmod.workspace(...) +``` + +*Since 1.10.0* + +Create and return a `workspace` object for managing the project's Cargo +workspace. + +Keyword arguments: +- `default_features`: (`bool`, optional) Whether to enable default features. + If not specified and `features` is provided, defaults to true. +- `features`: (`list[str]`, optional) List of additional features to enable globally + +The function must be called in a project with `Cargo.lock` and `Cargo.toml` +files in the root source directory. While the object currently has +no methods, upon its creation Meson analyzes the `Cargo.toml` file and +computes the full set of dependencies and features needed to build the +package in `Cargo.toml`. Therefore, this function should be invoked before +using Cargo subprojects. Methods will be added in future versions of Meson. + +If either argument is provided, the build will use a custom set of features. +Features can only be set once - subsequent calls will fail if different features +are specified. + +When `features` is provided without `default_features`, the 'default' feature is +automatically included. + +#### workspace.subproject() + +```meson +package = ws.subproject(package_name, ...) +``` + +Returns a `package` object for managing a specific package within the workspace. + +Positional arguments: +- `package_name`: (`str`) The name of the package to retrieve + +Keyword arguments: +- `version`: (`list[str]`, optional) List of version constraints for the package + +## Package object + +The package object returned by `workspace.subproject()` provides methods for working with individual packages in a Cargo workspace. + +#### package.features() + +```meson +features = pkg.features() +``` + +Returns selected features for a specific package. + +#### package.all_features() + +```meson +all_features = pkg.all_features() +``` + +### package.dependency() + +```meson +dep = package.dependency(...) +``` + +Returns a dependency object for the package that can be used with other Meson targets. + +Keyword arguments: +- `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. diff --git a/mesonbuild/cargo/__init__.py b/mesonbuild/cargo/__init__.py index c5b157f3c791..65e018a9d3b1 100644 --- a/mesonbuild/cargo/__init__.py +++ b/mesonbuild/cargo/__init__.py @@ -1,7 +1,9 @@ __all__ = [ 'Interpreter', + 'PackageState', 'TomlImplementationMissing', + 'WorkspaceState', ] -from .interpreter import Interpreter +from .interpreter import Interpreter, PackageState, WorkspaceState from .toml import TomlImplementationMissing diff --git a/mesonbuild/cargo/interpreter.py b/mesonbuild/cargo/interpreter.py index 52c4816f8b5a..7f95c7050bdd 100644 --- a/mesonbuild/cargo/interpreter.py +++ b/mesonbuild/cargo/interpreter.py @@ -20,18 +20,22 @@ from . import builder, version, cfg from .toml import load_toml from .manifest import Manifest, CargoLock, Workspace, fixup_meson_varname -from ..mesonlib import MesonException, MachineChoice, version_compare +from ..mesonlib import MesonException, MachineChoice, unique_list, version_compare from .. import coredata, mlog from ..wrap.wrap import PackageDefinition if T.TYPE_CHECKING: from . import raw from .. import mparser + from typing_extensions import Literal + from .manifest import Dependency, SystemDependency from ..environment import Environment from ..interpreterbase import SubProject from ..compilers.rust import RustCompiler + RUST_ABI = Literal['rust', 'c', 'proc-macro'] + def _dependency_name(package_name: str, api: str, suffix: str = '-rs') -> str: basename = package_name[:-len(suffix)] if package_name.endswith(suffix) else package_name return f'{basename}-{api}{suffix}' @@ -55,11 +59,41 @@ class PackageState: downloaded: bool = False features: T.Set[str] = dataclasses.field(default_factory=set) required_deps: T.Set[str] = dataclasses.field(default_factory=set) + visited_deps: T.Set[str] = dataclasses.field(default_factory=set) optional_deps_features: T.Dict[str, T.Set[str]] = dataclasses.field(default_factory=lambda: collections.defaultdict(set)) + dev_dependencies: bool = False # If this package is member of a workspace. ws_subdir: T.Optional[str] = None ws_member: T.Optional[str] = None + def uses_abi(self, abi: str) -> bool: + """Check if this package supports the given ABI.""" + crate_types = self.manifest.lib.crate_type + if abi == 'rust': + return any(ct in {'lib', 'rlib'} for ct in crate_types) + elif abi == 'c': + return any(ct in {'staticlib', 'cdylib'} for ct in crate_types) + elif abi == 'proc-macro': + return 'proc-macro' in crate_types + else: + return False + + def get_dependency_name(self, rust_abi: RUST_ABI) -> str: + """Get the dependency name for a package with the given ABI.""" + if not self.uses_abi(rust_abi): + raise MesonException(f'Package {self.manifest.package.name} does not support ABI {rust_abi}') + + # TODO: Implement dependency name generation + package_name = self.manifest.package.name + api = self.manifest.package.api + + if rust_abi in {'rust', 'proc-macro'}: + return f'{package_name}-{api}-rs' + elif rust_abi == 'c': + return f'{package_name}-{api}' + else: + raise MesonException(f'Unknown rust_abi: {rust_abi}') + @dataclasses.dataclass(frozen=True) class PackageKey: @@ -80,6 +114,8 @@ class WorkspaceState: class Interpreter: + _features: T.Optional[T.List[str]] = None + def __init__(self, env: Environment, subdir: str, subprojects_dir: str) -> None: self.environment = env # Map Cargo.toml's subdir to loaded manifest. @@ -90,6 +126,7 @@ def __init__(self, env: Environment, subdir: str, subprojects_dir: str) -> None: self.workspaces: T.Dict[str, WorkspaceState] = {} # Files that should trigger a reconfigure if modified self.build_def_files: T.List[str] = [] + self.dev_dependencies = False # Cargo packages filename = os.path.join(self.environment.get_source_dir(), subdir, 'Cargo.lock') subprojects_dir = os.path.join(self.environment.get_source_dir(), subprojects_dir) @@ -98,9 +135,54 @@ def __init__(self, env: Environment, subdir: str, subprojects_dir: str) -> None: self.environment.wrap_resolver.merge_wraps(self.cargolock.wraps) self.build_def_files.append(filename) + @property + def features(self) -> T.List[str]: + """Get the features list. Once read, it cannot be modified.""" + if self._features is None: + self._features = ['default'] + return self._features + + @features.setter + def features(self, value: T.List[str]) -> None: + """Set the features list. Can only be set before first read.""" + value_unique = sorted(unique_list(value)) + if self._features is not None and value_unique != self._features: + raise MesonException("Cannot modify features after they have been selected or used") + self._features = value_unique + def get_build_def_files(self) -> T.List[str]: return self.build_def_files + def load_package(self, path: str = '.') -> T.Union[WorkspaceState, PackageState]: + """Load the root Cargo.toml package and prepare it with features and dependencies.""" + pkgs: T.Iterable[PackageState] + ret: T.Union[WorkspaceState, PackageState] + if path == '.': + manifest = self._load_manifest(path) + if isinstance(manifest, Workspace): + ret = self._get_workspace(manifest, path) + pkgs = list(ret.packages[m] for m in ret.workspace.default_members) + else: + key = PackageKey(manifest.package.name, manifest.package.api) + if key not in self.packages: + self.packages[key] = PackageState(manifest, False) + ret = self.packages[key] + pkgs = [ret] + else: + ws = self.workspaces['.'] + ret = ws.packages[path] + pkgs = [ret] + + if self.dev_dependencies: + for pkg in pkgs: + pkg.dev_dependencies = True + + for pkg in pkgs: + self._prepare_package(pkg) + for feature in self.features: + self._enable_feature(pkg, feature) + return ret + def interpret(self, subdir: str, project_root: T.Optional[str] = None) -> mparser.CodeBlockNode: manifest = self._load_manifest(subdir) filename = os.path.join(self.environment.source_dir, subdir, 'Cargo.toml') @@ -189,6 +271,7 @@ def _process_member(member: str) -> None: return build.block(ast) def _load_workspace_member(self, ws: WorkspaceState, m: str) -> None: + print(m) m = os.path.normpath(m) # Load member's manifest m_subdir = os.path.join(ws.subdir, m) @@ -231,6 +314,26 @@ def _fetch_package(self, package_name: str, api: str) -> PackageState: meson_depname = _dependency_name(package_name, api) return self._fetch_package_from_subproject(package_name, meson_depname) + def _resolve_package(self, package_name: str, version_constraints: T.List[str]) -> T.Optional[raw.CargoLockPackage]: + """From all available versions from Cargo.lock, pick the most recent + satisfying the constraints and return it.""" + if self.cargolock: + cargo_lock_pkgs = self.cargolock.named(package_name) + else: + cargo_lock_pkgs = [] + for cargo_pkg in cargo_lock_pkgs: + if all(version_compare(cargo_pkg.version, v) for v in version_constraints): + return cargo_pkg + + if not version_constraints: + raise MesonException(f'Cannot determine version of cargo package {package_name}') + return None + + def resolve_package(self, package_name: str, version_constraints: T.List[str]) -> T.Optional[PackageState]: + cargo_pkg = self._resolve_package(package_name, version_constraints) + api = version.api(cargo_pkg.version) + return self._fetch_package(package_name, api) + def _fetch_package_from_subproject(self, package_name: str, meson_depname: str) -> PackageState: subp_name, _ = self.environment.wrap_resolver.find_dep_provider(meson_depname) if subp_name is None: @@ -287,6 +390,10 @@ def _prepare_package(self, pkg: PackageState) -> None: for depname, dep in pkg.manifest.dependencies.items(): if not dep.optional: self._add_dependency(pkg, depname) + if pkg.dev_dependencies: + for depname, dep in pkg.manifest.dev_dependencies.items(): + if not dep.optional: + self._add_dependency(pkg, depname) def _dep_package(self, pkg: PackageState, dep: Dependency) -> PackageState: if dep.path: @@ -300,19 +407,8 @@ def _dep_package(self, pkg: PackageState, dep: Dependency) -> PackageState: _, _, directory = _parse_git_url(dep.git, dep.branch) dep_pkg = self._fetch_package_from_subproject(dep.package, directory) else: - # From all available versions from Cargo.lock, pick the most recent - # satisfying the constraints - if self.cargolock: - cargo_lock_pkgs = self.cargolock.named(dep.package) - else: - cargo_lock_pkgs = [] - for cargo_pkg in cargo_lock_pkgs: - if all(version_compare(cargo_pkg.version, v) for v in dep.meson_version): - dep.update_version(f'={cargo_pkg.version}') - break - else: - if not dep.meson_version: - raise MesonException(f'Cannot determine version of cargo package {dep.package}') + cargo_pkg = self._resolve_package(dep.package, dep.meson_version) + dep.update_version(f'={cargo_pkg.version}') dep_pkg = self._fetch_package(dep.package, dep.api) return dep_pkg @@ -332,21 +428,31 @@ def _load_manifest(self, subdir: str, workspace: T.Optional[Workspace] = None, m self.manifests[subdir] = manifest_ return manifest_ - def _add_dependency(self, pkg: PackageState, depname: str) -> None: - if depname in pkg.required_deps: - return - dep = pkg.manifest.dependencies.get(depname) - if not dep: - # It could be build/dev/target dependency. Just ignore it. - return - pkg.required_deps.add(depname) - dep_pkg = self._dep_package(pkg, dep) + def _add_dependency_features(self, pkg: PackageState, dep: Dependency, dep_pkg: PackageState) -> None: if dep.default_features: self._enable_feature(dep_pkg, 'default') for f in dep.features: self._enable_feature(dep_pkg, f) - for f in pkg.optional_deps_features[depname]: - self._enable_feature(dep_pkg, f) + + def _add_dependency(self, pkg: PackageState, depname: str) -> None: + if depname in pkg.visited_deps: + return + pkg.visited_deps.add(depname) + + dep_pkg = None + if pkg.dev_dependencies: + dep = pkg.manifest.dev_dependencies.get(depname) + if dep: + dep_pkg = self._dep_package(pkg, dep) + self._add_dependency_features(pkg, dep, dep_pkg) + dep = pkg.manifest.dependencies.get(depname) + if dep: + dep_pkg = dep_pkg or self._dep_package(pkg, dep) + self._add_dependency_features(pkg, dep, dep_pkg) + if dep_pkg is not None: + pkg.required_deps.add(depname) + for f in pkg.optional_deps_features[depname]: + self._enable_feature(dep_pkg, f) def _enable_feature(self, pkg: PackageState, feature: str) -> None: if feature in pkg.features: diff --git a/mesonbuild/modules/__init__.py b/mesonbuild/modules/__init__.py index 87892e6d7b30..a9b5ea57cc5f 100644 --- a/mesonbuild/modules/__init__.py +++ b/mesonbuild/modules/__init__.py @@ -7,7 +7,7 @@ import dataclasses import typing as T -from .. import build, mesonlib +from .. import build, dependencies, mesonlib from ..options import OptionKey from ..build import IncludeDirs from ..interpreterbase.decorators import noKwargs, noPosargs @@ -46,6 +46,7 @@ def __init__(self, interpreter: 'Interpreter') -> None: # The backend object is under-used right now, but we will need it: # https://github.com/mesonbuild/meson/issues/1419 self.backend = interpreter.backend + self.dependency_overrides = interpreter.build.dependency_overrides self.targets = interpreter.build.targets self.data = interpreter.build.data self.headers = interpreter.build.get_headers() @@ -108,6 +109,13 @@ def find_tool(self, name: str, depname: str, varname: str, required: bool = True # Normal program lookup return self.find_program(name, required=required, wanted=wanted) + def overridden_dependency(self, depname: str, for_machine: MachineChoice = MachineChoice.HOST) -> Dependency: + identifier = dependencies.get_dep_identifier(depname, {}) + try: + return self.dependency_overrides[for_machine][identifier].dep + except KeyError: + raise mesonlib.MesonException(f'dependency "{depname}" was not overridden for the {for_machine}') + def dependency(self, depname: str, native: bool = False, required: bool = True, wanted: T.Optional[str] = None) -> 'Dependency': kwargs: T.Dict[str, object] = {'native': native, 'required': required} diff --git a/mesonbuild/modules/rust.py b/mesonbuild/modules/rust.py index 89c20230b425..26620360ddaf 100644 --- a/mesonbuild/modules/rust.py +++ b/mesonbuild/modules/rust.py @@ -10,29 +10,32 @@ from mesonbuild.interpreterbase.decorators import FeatureNew -from . import ExtensionModule, ModuleReturnValue, ModuleInfo +from . import ExtensionModule, ModuleReturnValue, ModuleInfo, MutableModuleObject from .. import mesonlib, mlog from ..build import (BothLibraries, BuildTarget, CustomTargetIndex, Executable, ExtractedObjects, GeneratedList, CustomTarget, InvalidArguments, Jar, StructuredSources, SharedLibrary, StaticLibrary) +from ..cargo import PackageState from ..compilers.compilers import are_asserts_disabled_for_subproject, lang_suffixes 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 ..interpreter.interpreterobjects import Doctest -from ..mesonlib import File, MesonException, PerMachine +from ..mesonlib import File, MachineChoice, MesonException, PerMachine 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 + from ..interpreter import Interpreter, SubprojectHolder from ..interpreter import kwargs as _kwargs from ..interpreter.interpreter import SourceInputs, SourceOutputs from ..interpreter.interpreterobjects import Test + from ..interpreterbase import TYPE_kwargs from ..programs import OverrideProgram from ..interpreter.type_checking import SourcesVarargsType @@ -63,6 +66,16 @@ class FuncBindgen(TypedDict): language: T.Optional[Literal['c', 'cpp']] bindgen_version: T.List[str] + class FuncWorkspace(TypedDict): + default_features: T.Optional[bool] + features: T.List[str] + dev_dependencies: bool + + class FuncWorkspaceSubproject(TypedDict): + version: T.List[str] + + class FuncDependency(TypedDict): + rust_abi: str RUST_TEST_KWS: T.List[KwargInfo] = [ KwargInfo( @@ -81,6 +94,73 @@ def no_spaces_validator(arg: T.Optional[T.Union[str, T.List]]) -> T.Optional[str return None +class RustWorkspace(MutableModuleObject): + """Represents a Rust workspace, controlling the build of packages + recorded in a Cargo.lock file.""" + + def __init__(self, state: ModuleState, interpreter: Interpreter, root_package: T.Union[cargo.WorkspaceState, cargo.PackageState]) -> None: + super().__init__() + self.state = state + self.interpreter = interpreter + self.root_package = root_package + self.methods.update({ + 'subproject': self.subproject_method, + }) + + @typed_pos_args('workspace.subproject', str) + @typed_kwargs('workspace.subproject', + KwargInfo('version', ContainerTypeInfo(list, str), default=[], listify=True)) + def subproject_method(self, state: ModuleState, args: T.Tuple[str], kwargs: FuncWorkspaceSubproject) -> T.Union[SubprojectHolder, RustPackage]: + """Returns a package object for a subproject package.""" + package_name = args[0] + pkg = self.interpreter.cargo.resolve_package(package_name, kwargs['version']) + if pkg is None: + raise MesonException(f'No version of cargo package {package_name} satisfies constraints {kwargs['version']}') + + kw: _kwargs.DoSubproject = { + 'required': True, + 'version': kwargs['version'], + 'default_options': {}, + } + subp_name = f'{pkg.manifest.package.name}-{pkg.manifest.package.api}-rs' + self.interpreter.do_subproject(subp_name, kw, force_method='cargo') + return RustPackage(state, pkg) + + +class RustPackage(MutableModuleObject): + """Represents a Rust package within a workspace.""" + + def __init__(self, state: ModuleState, package: PackageState) -> None: + super().__init__() + self.state = state + self.package = package + self.methods.update({ + 'all_features': self.all_features_method, + 'dependency': self.dependency_method, + 'features': self.features_method, + }) + + @typed_pos_args('package.all_features', optargs=[str]) + @noKwargs + def all_features_method(self, state: 'ModuleState', args: T.Tuple[T.Optional[str]], kwargs: TYPE_kwargs) -> T.List[str]: + """Returns all features for specific package.""" + return sorted(list(self.package.manifest.features.keys())) + + @noPosargs + @typed_kwargs('package.dependency', + KwargInfo('rust_abi', str, default='rust', validator=in_set_validator({'rust', 'c', 'proc-macro'}))) + def dependency_method(self, state: 'ModuleState', args: T.List, kwargs: FuncDependency): + """Returns dependency for the package with the given ABI.""" + depname = self.package.get_dependency_name(kwargs['rust_abi']) + return self.state.overridden_dependency(depname) + + @typed_pos_args('package.features', optargs=[str]) + @noKwargs + def features_method(self, state: 'ModuleState', args: T.Tuple[T.Optional[str]], kwargs: TYPE_kwargs) -> T.List[str]: + """Returns chosen features for specific package.""" + return sorted(list(self.package.features)) + + class RustModule(ExtensionModule): """A module that holds helper functions for rust.""" @@ -88,6 +168,7 @@ class RustModule(ExtensionModule): INFO = ModuleInfo('rust', '0.57.0', stabilized='1.0.0') _bindgen_rust_target: T.Optional[str] rustdoc: PerMachine[T.Optional[ExternalProgram]] = PerMachine(None, None) + _workspace_cache: T.Dict[cargo.Interpreter, RustWorkspace] = {} def __init__(self, interpreter: Interpreter) -> None: super().__init__(interpreter) @@ -103,6 +184,7 @@ def __init__(self, interpreter: Interpreter) -> None: '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 +582,51 @@ def proc_macro(self, state: ModuleState, args: T.Tuple[str, SourcesVarargsType], target = state._interpreter.build_target(state.current_node, args, kwargs, SharedLibrary) return target + @FeatureNew('rust.workspace', '1.10.0') + @noPosargs + @typed_kwargs( + 'rust.workspace', + KwargInfo('default_features', (bool, NoneType), default=None), + KwargInfo('dev_dependencies', (bool), default=True), + KwargInfo( + 'features', + (ContainerTypeInfo(list, str), NoneType), + default=None, + listify=True, + ), + ) + def workspace(self, state: ModuleState, args: T.List, kwargs: FuncWorkspace) -> 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) + + default_features = kwargs['default_features'] + features = kwargs['features'] + if default_features is not None or features is not None: + # If custom features are provided, default_features = None should be treated as True + if default_features is None: + default_features = True + + cargo_features = ['default'] if default_features else [] + if features is not None: + cargo_features.extend(features) + self.interpreter.cargo.features = cargo_features + self.interpreter.cargo.dev_dependencies = kwargs['dev_dependencies'] + + # Check if we already have a cached workspace for this cargo interpreter + # TODO: this should be per-subproject + ws_obj = self._workspace_cache.get(self.interpreter.cargo) + if ws_obj is None: + root_pkg = self.interpreter.cargo.load_package() + ws_obj = RustWorkspace(state, self.interpreter, root_pkg) + self._workspace_cache[self.interpreter.cargo] = ws_obj + + return ws_obj + def initialize(interp: Interpreter) -> RustModule: return RustModule(interp) diff --git a/test cases/rust/31 workspace test/Cargo.lock b/test cases/rust/31 workspace test/Cargo.lock new file mode 100644 index 000000000000..30395b49bb13 --- /dev/null +++ b/test cases/rust/31 workspace test/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "hello" +version = "1.0.0" +source = "git://example.com/hello" +checksum = "e677cd353f583f408f480e465f951d4b9d2e69b6552a606ea4ec9ea362f06a83" + +[[package]] +name = "workspace_test" +version = "0.1.0" +dependencies = [ + "hello", +] diff --git a/test cases/rust/31 workspace test/Cargo.toml b/test cases/rust/31 workspace test/Cargo.toml new file mode 100644 index 000000000000..2ab03a85fdbd --- /dev/null +++ b/test cases/rust/31 workspace test/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "workspace_test" +version = "0.1.0" +edition = "2021" + +[features] +default = ["feature1", "hello?/goodbye"] +feature1 = [] +feature2 = [] + +[dependencies] +hello = "1.0" diff --git a/test cases/rust/31 workspace test/meson.build b/test cases/rust/31 workspace test/meson.build new file mode 100644 index 000000000000..6dd7b023997b --- /dev/null +++ b/test cases/rust/31 workspace test/meson.build @@ -0,0 +1,21 @@ +project('workspace test', 'rust', default_options: ['rust_std=2021']) + +rust = import('rust') +cargo = rust.workspace() + +hello_rs = cargo.subproject('hello') +assert(hello_rs.all_features() == ['default', 'goodbye']) +assert(hello_rs.features() == ['default', 'goodbye']) + +e = executable('workspace-test', 'src/main.rs', + dependencies: [hello_rs.dependency()], +) +test('workspace-test', e) + +# 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') +endtestcase +testcase expect_error('Package hello does not support ABI c') + hello_rs.dependency(rust_abi: 'c') +endtestcase diff --git a/test cases/rust/31 workspace test/src/main.rs b/test cases/rust/31 workspace test/src/main.rs new file mode 100644 index 000000000000..261af51b129d --- /dev/null +++ b/test cases/rust/31 workspace test/src/main.rs @@ -0,0 +1,6 @@ +use hello::{farewell, greet}; + +fn main() { + println!("{}", greet()); + println!("{}", farewell()); +} diff --git a/test cases/rust/31 workspace test/subprojects/hello-1-rs.wrap b/test cases/rust/31 workspace test/subprojects/hello-1-rs.wrap new file mode 100644 index 000000000000..25e7751d0c9f --- /dev/null +++ b/test cases/rust/31 workspace test/subprojects/hello-1-rs.wrap @@ -0,0 +1,3 @@ +[wrap-file] +directory = hello-1.0 +method = cargo diff --git a/test cases/rust/31 workspace test/subprojects/hello-1.0/Cargo.toml b/test cases/rust/31 workspace test/subprojects/hello-1.0/Cargo.toml new file mode 100644 index 000000000000..f6ab8eb91eeb --- /dev/null +++ b/test cases/rust/31 workspace test/subprojects/hello-1.0/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "hello" +version = "1.0.0" +edition = "2021" + +[lib] +crate-type = ["lib"] + +[features] +goodbye = [] diff --git a/test cases/rust/31 workspace test/subprojects/hello-1.0/src/lib.rs b/test cases/rust/31 workspace test/subprojects/hello-1.0/src/lib.rs new file mode 100644 index 000000000000..47346350bd21 --- /dev/null +++ b/test cases/rust/31 workspace test/subprojects/hello-1.0/src/lib.rs @@ -0,0 +1,10 @@ +pub fn greet() -> &'static str +{ + "hello world" +} + +#[cfg(feature = "goodbye")] +pub fn farewell() -> &'static str +{ + "goodbye" +}