diff --git a/docs/markdown/Wrap-dependency-system-manual.md b/docs/markdown/Wrap-dependency-system-manual.md index 7a0cea6fe..70a7b3023 100644 --- a/docs/markdown/Wrap-dependency-system-manual.md +++ b/docs/markdown/Wrap-dependency-system-manual.md @@ -345,7 +345,8 @@ the main project depends on `foo-rs` and `bar-rs`, and they both depend on `common-rs`. The main project will first look up `foo-rs` which itself will configure `common-rs` with a set of features. Later, when `bar-rs` does a lookup for `common-rs` it has already been configured and the set of features cannot be -changed. It is currently the responsability of the main project to resolve those +changed. If `bar-rs` wants extra features from `common-rs`, Meson will error out. +It is currently the responsability of the main project to resolve those issues by enabling extra features on each subproject: ```meson project(..., diff --git a/mesonbuild/cargo/interpreter.py b/mesonbuild/cargo/interpreter.py index 570087d6b..6680ea1b0 100644 --- a/mesonbuild/cargo/interpreter.py +++ b/mesonbuild/cargo/interpreter.py @@ -354,9 +354,12 @@ def _dependency_varname(package_name: str) -> str: return f'{fixup_meson_varname(package_name)}_dep' +_OPTION_NAME_PREFIX = 'feature-' + + def _option_name(feature: str) -> str: # Add a prefix to avoid collision with Meson reserved options (e.g. "debug") - return f'feature-{feature}' + return _OPTION_NAME_PREFIX + feature def _options_varname(depname: str) -> str: @@ -457,6 +460,11 @@ def _create_features(cargo: Manifest, build: builder.Builder) -> T.List[mparser. ast.append(build.if_(build.function('get_option', [build.string(_option_name(feature))]), build.block(lines))) + ast.append(build.function('message', [ + build.string('Enabled features:'), + build.method('keys', build.identifier('features'))], + )) + return ast @@ -482,7 +490,19 @@ def _create_dependencies(cargo: Manifest, build: builder.Builder) -> T.List[mpar build.string(name), build.bool(False) ]) + # Lookup for this dependency with the features we want in default_options kwarg. + # + # However, this subproject could have been previously configured with a + # different set of features. Cargo collects the set of features globally + # but Meson can only use features enabled by the first call that triggered + # the configuration of that subproject. + # + # Verify all features that we need are actually enabled for that dependency, + # otherwise abort with an error message. The user has to set the corresponding + # option manually with -Dxxx-rs:feature-yyy=true, or the main project can do + # that in its project(..., default_options: ['xxx-rs:feature-yyy=true']). ast.extend([ + # xxx_dep = dependency('xxx', version : ..., default_options : xxx_options) build.assign( build.function( 'dependency', @@ -491,6 +511,52 @@ def _create_dependencies(cargo: Manifest, build: builder.Builder) -> T.List[mpar ), _dependency_varname(package_name), ), + # if xxx_dep.found() + build.if_(build.method('found', build.identifier(_dependency_varname(package_name))), build.block([ + # actual_features = xxx_dep.get_variable('features', default_value : '').split(',') + build.assign( + build.method( + 'split', + build.method( + 'get_variable', + build.identifier(_dependency_varname(package_name)), + [build.string('features')], + {'default_value': build.string('')} + ), + [build.string(',')], + ), + 'actual_features' + ), + # needed_features = [] + # foreach f, _ : xxx_options + # needed_features += f.substring(8) + # endforeach + build.assign(build.array([]), 'needed_features'), + build.foreach(['f', 'enabled'], build.identifier(_options_varname(name)), build.block([ + build.if_(build.identifier('enabled'), build.block([ + build.plusassign( + build.method('substring', build.identifier('f'), [build.number(len(_OPTION_NAME_PREFIX))]), + 'needed_features'), + ])), + ])), + # foreach f : needed_features + # if f not in actual_features + # error() + # endif + # endforeach + build.foreach(['f'], build.identifier('needed_features'), build.block([ + build.if_(build.not_in(build.identifier('f'), build.identifier('actual_features')), build.block([ + build.function('error', [ + build.string('Dependency'), + build.string(_dependency_name(package_name)), + build.string('previously configured with features'), + build.identifier('actual_features'), + build.string('but need'), + build.identifier('needed_features'), + ]) + ])) + ])), + ])), ]) return ast @@ -555,6 +621,9 @@ def _create_lib(cargo: Manifest, build: builder.Builder, crate_type: manifest.CR 'declare_dependency', kw={ 'link_with': build.identifier('lib'), + 'variables': build.dict({ + build.string('features'): build.method('join', build.string(','), [build.method('keys', build.identifier('features'))]), + }) }, ), 'dep'