From 114e032e6a27d0eb9ef5de1a811ce7b0461c3efc Mon Sep 17 00:00:00 2001 From: Xavier Claessens Date: Sat, 10 Jun 2023 16:07:21 -0400 Subject: cargo: Expose features as Meson boolean options --- docs/markdown/Wrap-dependency-system-manual.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'docs') diff --git a/docs/markdown/Wrap-dependency-system-manual.md b/docs/markdown/Wrap-dependency-system-manual.md index e1e947479..7a0cea6fe 100644 --- a/docs/markdown/Wrap-dependency-system-manual.md +++ b/docs/markdown/Wrap-dependency-system-manual.md @@ -335,6 +335,26 @@ method = cargo dependency_names = foo-bar-rs ``` +Cargo features are exposed as Meson boolean options, with the `feature-` prefix. +For example the `default` feature is named `feature-default` and can be set from +the command line with `-Dfoo-rs:feature-default=false`. When a cargo subproject +depends on another cargo subproject, it will automatically enable features it +needs using the `dependency('foo-rs', default_options: ...)` mechanism. However, +unlike Cargo, the set of enabled features is not managed globally. Let's assume +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 +issues by enabling extra features on each subproject: +```meson +project(..., + default_options: { + 'common-rs:feature-something': true, + }, +) +``` + ## Using wrapped projects Wraps provide a convenient way of obtaining a project into your -- cgit v1.2.3 From 4d55645c397e2f338a09ebd1f0f564c64b98dafa Mon Sep 17 00:00:00 2001 From: Xavier Claessens Date: Tue, 13 Jun 2023 12:56:19 -0400 Subject: cargo: Abort if features are missing --- docs/markdown/Wrap-dependency-system-manual.md | 3 +- mesonbuild/cargo/interpreter.py | 71 +++++++++++++++++++++++++- 2 files changed, 72 insertions(+), 2 deletions(-) (limited to 'docs') 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' -- cgit v1.2.3 From 435e881c18cda15fc4f8fc9e42f566cdc86cd791 Mon Sep 17 00:00:00 2001 From: Xavier Claessens Date: Mon, 12 Jun 2023 08:46:21 -0400 Subject: cargo: Call into meson subdir if it exists This allows projects to manually add extra rust args and deps. This is intended to replace build.rs logic. --- docs/markdown/Wrap-dependency-system-manual.md | 8 +++++ mesonbuild/cargo/interpreter.py | 35 +++++++++++++++++++++- .../subprojects/foo-rs/meson/meson.build | 1 + .../subprojects/foo-rs/src/lib.rs | 1 + 4 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 test cases/rust/22 cargo subproject/subprojects/foo-rs/meson/meson.build (limited to 'docs') diff --git a/docs/markdown/Wrap-dependency-system-manual.md b/docs/markdown/Wrap-dependency-system-manual.md index 70a7b3023..bba1971c4 100644 --- a/docs/markdown/Wrap-dependency-system-manual.md +++ b/docs/markdown/Wrap-dependency-system-manual.md @@ -356,6 +356,14 @@ project(..., ) ``` +In addition, if the file `meson/meson.build` exists, Meson will call `subdir('meson')` +where the project can add manual logic that would usually be part of `build.rs`. +Some naming conventions need to be respected: +- The `extra_args` variable is pre-defined and can be used to add any Rust arguments. + This is typically used as `extra_args += ['--cfg', 'foo']`. +- The `extra_deps` variable is pre-defined and can be used to add extra dependencies. + This is typically used as `extra_deps += dependency('foo')`. + ## Using wrapped projects Wraps provide a convenient way of obtaining a project into your diff --git a/mesonbuild/cargo/interpreter.py b/mesonbuild/cargo/interpreter.py index 6680ea1b0..2797f9fe0 100644 --- a/mesonbuild/cargo/interpreter.py +++ b/mesonbuild/cargo/interpreter.py @@ -366,6 +366,14 @@ def _options_varname(depname: str) -> str: return f'{fixup_meson_varname(depname)}_options' +def _extra_args_varname() -> str: + return 'extra_args' + + +def _extra_deps_varname() -> str: + return 'extra_deps' + + def _create_project(cargo: Manifest, build: builder.Builder) -> T.List[mparser.BaseNode]: """Create a function call @@ -561,6 +569,25 @@ def _create_dependencies(cargo: Manifest, build: builder.Builder) -> T.List[mpar return ast +def _create_meson_subdir(cargo: Manifest, build: builder.Builder) -> T.List[mparser.BaseNode]: + # Allow Cargo subprojects to add extra Rust args in meson/meson.build file. + # This is used to replace build.rs logic. + + # extra_args = [] + # extra_deps = [] + # fs = import('fs') + # if fs.is_dir('meson') + # subdir('meson') + # endif + return [ + build.assign(build.array([]), _extra_args_varname()), + build.assign(build.array([]), _extra_deps_varname()), + build.assign(build.function('import', [build.string('fs')]), 'fs'), + build.if_(build.method('is_dir', build.identifier('fs'), [build.string('meson')]), + build.block([build.function('subdir', [build.string('meson')])])) + ] + + def _create_lib(cargo: Manifest, build: builder.Builder, crate_type: manifest.CRATE_TYPE) -> T.List[mparser.BaseNode]: dependencies: T.List[mparser.BaseNode] = [] dependency_map: T.Dict[mparser.BaseNode, mparser.BaseNode] = {} @@ -570,7 +597,12 @@ def _create_lib(cargo: Manifest, build: builder.Builder, crate_type: manifest.CR if name != package_name: dependency_map[build.string(fixup_meson_varname(package_name))] = build.string(name) - rust_args: T.List[mparser.BaseNode] = [build.identifier('features_args')] + rust_args: T.List[mparser.BaseNode] = [ + build.identifier('features_args'), + build.identifier(_extra_args_varname()) + ] + + dependencies.append(build.identifier(_extra_deps_varname())) posargs: T.List[mparser.BaseNode] = [ build.string(fixup_meson_varname(cargo.package.name)), @@ -660,6 +692,7 @@ def interpret(subp_name: str, subdir: str, env: Environment) -> T.Tuple[mparser. ast += [build.assign(build.function('import', [build.string('rust')]), 'rust')] ast += _create_features(cargo, build) ast += _create_dependencies(cargo, build) + ast += _create_meson_subdir(cargo, build) # Libs are always auto-discovered and there's no other way to handle them, # which is unfortunate for reproducability diff --git a/test cases/rust/22 cargo subproject/subprojects/foo-rs/meson/meson.build b/test cases/rust/22 cargo subproject/subprojects/foo-rs/meson/meson.build new file mode 100644 index 000000000..67c7b82f9 --- /dev/null +++ b/test cases/rust/22 cargo subproject/subprojects/foo-rs/meson/meson.build @@ -0,0 +1 @@ +extra_args += ['--cfg', 'feature="foo"'] diff --git a/test cases/rust/22 cargo subproject/subprojects/foo-rs/src/lib.rs b/test cases/rust/22 cargo subproject/subprojects/foo-rs/src/lib.rs index 4f0a31079..1c8cbc9d3 100644 --- a/test cases/rust/22 cargo subproject/subprojects/foo-rs/src/lib.rs +++ b/test cases/rust/22 cargo subproject/subprojects/foo-rs/src/lib.rs @@ -2,6 +2,7 @@ extern "C" { fn extra_func() -> i32; } +#[cfg(feature = "foo")] #[no_mangle] pub extern "C" fn rust_func() -> i32 { let v: i32; -- cgit v1.2.3