diff options
18 files changed, 398 insertions, 49 deletions
diff --git a/docs/markdown/Wrap-dependency-system-manual.md b/docs/markdown/Wrap-dependency-system-manual.md index e1e947479..bba1971c4 100644 --- a/docs/markdown/Wrap-dependency-system-manual.md +++ b/docs/markdown/Wrap-dependency-system-manual.md @@ -335,6 +335,35 @@ 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. 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(..., + default_options: { + 'common-rs:feature-something': true, + }, +) +``` + +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/ast/printer.py b/mesonbuild/ast/printer.py index 459912f2d..06b05ba89 100644 --- a/mesonbuild/ast/printer.py +++ b/mesonbuild/ast/printer.py @@ -123,7 +123,7 @@ class AstPrinter(AstVisitor): def visit_ComparisonNode(self, node: mparser.ComparisonNode) -> None: node.left.accept(self) - self.append_padded(node.ctype, node) + self.append_padded(node.ctype if node.ctype != 'notin' else 'not in', node) node.lineno = self.curr_line or node.lineno node.right.accept(self) @@ -193,6 +193,7 @@ class AstPrinter(AstVisitor): i.accept(self) if not isinstance(node.elseblock, mparser.EmptyNode): self.append('else', node) + self.newline() node.elseblock.accept(self) self.append('endif', node) diff --git a/mesonbuild/cargo/builder.py b/mesonbuild/cargo/builder.py index 17b4ca52c..99659da11 100644 --- a/mesonbuild/cargo/builder.py +++ b/mesonbuild/cargo/builder.py @@ -91,7 +91,7 @@ class Builder: """ return mparser.IdNode(self._token('id', value)) - def method(self, name: str, id_: mparser.IdNode, + def method(self, name: str, id_: mparser.BaseNode, pos: T.Optional[T.List[mparser.BaseNode]] = None, kw: T.Optional[T.Mapping[str, mparser.BaseNode]] = None, ) -> mparser.MethodNode: @@ -137,6 +137,33 @@ class Builder: """ return mparser.ComparisonNode('==', lhs, self._symbol('=='), rhs) + def not_equal(self, lhs: mparser.BaseNode, rhs: mparser.BaseNode) -> mparser.ComparisonNode: + """Create an inequality operation + + :param lhs: The left hand side of the "!=" + :param rhs: the right hand side of the "!=" + :return: A compraison node + """ + return mparser.ComparisonNode('!=', lhs, self._symbol('!='), rhs) + + def in_(self, lhs: mparser.BaseNode, rhs: mparser.BaseNode) -> mparser.ComparisonNode: + """Create an "in" operation + + :param lhs: The left hand side of the "in" + :param rhs: the right hand side of the "in" + :return: A compraison node + """ + return mparser.ComparisonNode('in', lhs, self._symbol('in'), rhs) + + def not_in(self, lhs: mparser.BaseNode, rhs: mparser.BaseNode) -> mparser.ComparisonNode: + """Create an "not in" operation + + :param lhs: The left hand side of the "not in" + :param rhs: the right hand side of the "not in" + :return: A compraison node + """ + return mparser.ComparisonNode('notin', lhs, self._symbol('not in'), rhs) + def or_(self, lhs: mparser.BaseNode, rhs: mparser.BaseNode) -> mparser.OrNode: """Create and OrNode @@ -167,3 +194,45 @@ class Builder: block = mparser.CodeBlockNode(self._token('node', '')) block.lines = lines return block + + def plus(self, lhs: mparser.BaseNode, rhs: mparser.BaseNode) -> mparser.ArithmeticNode: + """Create an addition node + + :param lhs: The left of the addition + :param rhs: The right of the addition + :return: The ArithmeticNode + """ + return mparser.ArithmeticNode('add', lhs, self._symbol('+'), rhs) + + def plusassign(self, value: mparser.BaseNode, varname: str) -> mparser.PlusAssignmentNode: + """Create a "+=" node + + :param value: The value to add + :param varname: The variable to assign + :return: The PlusAssignmentNode + """ + return mparser.PlusAssignmentNode(self.identifier(varname), self._symbol('+='), value) + + def if_(self, condition: mparser.BaseNode, block: mparser.CodeBlockNode) -> mparser.IfClauseNode: + """Create a "if" block + + :param condition: The condition + :param block: Lines inside the condition + :return: The IfClauseNode + """ + clause = mparser.IfClauseNode(condition) + clause.ifs.append(mparser.IfNode(clause, self._symbol('if'), condition, block)) + clause.elseblock = mparser.EmptyNode(-1, -1, self.filename) + return clause + + def foreach(self, varnames: T.List[str], items: mparser.BaseNode, block: mparser.CodeBlockNode) -> mparser.ForeachClauseNode: + """Create a "foreach" loop + + :param varnames: Iterator variable names (one for list, two for dict). + :param items: The list of dict to iterate + :param block: Lines inside the loop + :return: The ForeachClauseNode + """ + varids = [self.identifier(i) for i in varnames] + commas = [self._symbol(',') for i in range(len(varnames) - 1)] + return mparser.ForeachClauseNode(self._symbol('foreach'), varids, commas, self._symbol(':'), items, block, self._symbol('endforeach')) diff --git a/mesonbuild/cargo/interpreter.py b/mesonbuild/cargo/interpreter.py index 66e45acc1..2797f9fe0 100644 --- a/mesonbuild/cargo/interpreter.py +++ b/mesonbuild/cargo/interpreter.py @@ -17,11 +17,13 @@ import itertools import json import os import shutil +import collections import typing as T from . import builder from . import version -from ..mesonlib import MesonException, Popen_safe +from ..mesonlib import MesonException, Popen_safe, OptionKey +from .. import coredata if T.TYPE_CHECKING: from types import ModuleType @@ -29,6 +31,7 @@ if T.TYPE_CHECKING: from . import manifest from .. import mparser from ..environment import Environment + from ..coredata import KeyedOptionDictType # tomllib is present in python 3.11, before that it is a pypi module called tomli, # we try to import tomllib, then tomli, @@ -156,7 +159,7 @@ class Dependency: path: T.Optional[str] = None optional: bool = False package: T.Optional[str] = None - default_features: bool = False + default_features: bool = True features: T.List[str] = dataclasses.field(default_factory=list) @classmethod @@ -269,6 +272,9 @@ class Manifest: subdir: str path: str = '' + def __post_init__(self) -> None: + self.features.setdefault('default', []) + def _convert_manifest(raw_manifest: manifest.Manifest, subdir: str, path: str = '') -> Manifest: # This cast is a bit of a hack to deal with proc-macro @@ -348,12 +354,31 @@ def _dependency_varname(package_name: str) -> str: return f'{fixup_meson_varname(package_name)}_dep' -def _create_project(cargo: Manifest, build: builder.Builder, env: Environment) -> T.List[mparser.BaseNode]: +_OPTION_NAME_PREFIX = 'feature-' + + +def _option_name(feature: str) -> str: + # Add a prefix to avoid collision with Meson reserved options (e.g. "debug") + return _OPTION_NAME_PREFIX + feature + + +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 :param cargo: The Manifest to generate from :param build: The AST builder - :param env: Meson environment :return: a list nodes """ args: T.List[mparser.BaseNode] = [] @@ -366,7 +391,7 @@ def _create_project(cargo: Manifest, build: builder.Builder, env: Environment) - # Always assume that the generated meson is using the latest features # This will warn when when we generate deprecated code, which is helpful # for the upkeep of the module - 'meson_version': build.string(f'>= {env.coredata.version}'), + 'meson_version': build.string(f'>= {coredata.stable_version}'), 'default_options': build.array([build.string(f'rust_std={cargo.package.edition}')]), } if cargo.package.license: @@ -377,14 +402,115 @@ def _create_project(cargo: Manifest, build: builder.Builder, env: Environment) - return [build.function('project', args, kwargs)] +def _process_feature(cargo: Manifest, feature: str) -> T.Tuple[T.Set[str], T.Dict[str, T.Set[str]], T.Set[str]]: + # Set of features that must also be enabled if this feature is enabled. + features: T.Set[str] = set() + # Map dependency name to a set of features that must also be enabled on that + # dependency if this feature is enabled. + dep_features: T.Dict[str, T.Set[str]] = collections.defaultdict(set) + # Set of dependencies that are required if this feature is enabled. + required_deps: T.Set[str] = set() + # Set of features that must be processed recursively. + to_process: T.Set[str] = {feature} + while to_process: + f = to_process.pop() + if '/' in f: + dep, dep_f = f.split('/', 1) + if dep[-1] == '?': + dep = dep[:-1] + else: + required_deps.add(dep) + dep_features[dep].add(dep_f) + elif f.startswith('dep:'): + required_deps.add(f[4:]) + elif f not in features: + features.add(f) + to_process.update(cargo.features.get(f, [])) + # A feature can also be a dependency + if f in cargo.dependencies: + required_deps.add(f) + return features, dep_features, required_deps + + +def _create_features(cargo: Manifest, build: builder.Builder) -> T.List[mparser.BaseNode]: + # https://doc.rust-lang.org/cargo/reference/features.html#the-features-section + + # Declare a dict that map enabled features to true. One for current project + # and one per dependency. + ast: T.List[mparser.BaseNode] = [] + ast.append(build.assign(build.dict({}), 'features')) + for depname in cargo.dependencies: + ast.append(build.assign(build.dict({}), _options_varname(depname))) + + # Declare a dict that map required dependencies to true + ast.append(build.assign(build.dict({}), 'required_deps')) + + for feature in cargo.features: + # if get_option(feature) + # required_deps += {'dep': true, ...} + # features += {'foo': true, ...} + # xxx_options += {'feature-foo': true, ...} + # ... + # endif + features, dep_features, required_deps = _process_feature(cargo, feature) + lines: T.List[mparser.BaseNode] = [ + build.plusassign( + build.dict({build.string(d): build.bool(True) for d in required_deps}), + 'required_deps'), + build.plusassign( + build.dict({build.string(f): build.bool(True) for f in features}), + 'features'), + ] + for depname, enabled_features in dep_features.items(): + lines.append(build.plusassign( + build.dict({build.string(_option_name(f)): build.bool(True) for f in enabled_features}), + _options_varname(depname))) + + 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 + + def _create_dependencies(cargo: Manifest, build: builder.Builder) -> T.List[mparser.BaseNode]: ast: T.List[mparser.BaseNode] = [] for name, dep in cargo.dependencies.items(): package_name = dep.package or name + + # xxx_options += {'feature-default': true, ...} + extra_options: T.Dict[mparser.BaseNode, mparser.BaseNode] = { + build.string(_option_name('default')): build.bool(dep.default_features), + } + for f in dep.features: + extra_options[build.string(_option_name(f))] = build.bool(True) + ast.append(build.plusassign(build.dict(extra_options), _options_varname(name))) + kw = { 'version': build.array([build.string(s) for s in dep.version]), + 'default_options': build.identifier(_options_varname(name)), } + if dep.optional: + kw['required'] = build.method('get', build.identifier('required_deps'), [ + 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', @@ -393,10 +519,75 @@ 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 +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] = {} @@ -406,6 +597,13 @@ 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'), + 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)), build.string(os.path.join('src', 'lib.rs')), @@ -414,6 +612,7 @@ def _create_lib(cargo: Manifest, build: builder.Builder, crate_type: manifest.CR kwargs: T.Dict[str, mparser.BaseNode] = { 'dependencies': build.array(dependencies), 'rust_dependency_map': build.dict(dependency_map), + 'rust_args': build.array(rust_args), } lib: mparser.BaseNode @@ -430,13 +629,33 @@ def _create_lib(cargo: Manifest, build: builder.Builder, crate_type: manifest.CR kwargs['rust_abi'] = build.string('c') lib = build.function(target_type, posargs, kwargs) + # features_args = [] + # foreach f, _ : features + # features_args += ['--cfg', 'feature="' + f + '"'] + # endforeach + # lib = xxx_library() + # dep = declare_dependency() + # meson.override_dependency() return [ + build.assign(build.array([]), 'features_args'), + build.foreach(['f', '_'], build.identifier('features'), build.block([ + build.plusassign( + build.array([ + build.string('--cfg'), + build.plus(build.string('feature="'), build.plus(build.identifier('f'), build.string('"'))), + ]), + 'features_args') + ]) + ), build.assign(lib, 'lib'), build.assign( build.function( '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' @@ -452,7 +671,7 @@ def _create_lib(cargo: Manifest, build: builder.Builder, crate_type: manifest.CR ] -def interpret(subp_name: str, subdir: str, env: Environment) -> mparser.CodeBlockNode: +def interpret(subp_name: str, subdir: str, env: Environment) -> T.Tuple[mparser.CodeBlockNode, KeyedOptionDictType]: package_name = subp_name[:-3] if subp_name.endswith('-rs') else subp_name manifests = _load_manifests(os.path.join(env.source_dir, subdir)) cargo = manifests.get(package_name) @@ -462,9 +681,18 @@ def interpret(subp_name: str, subdir: str, env: Environment) -> mparser.CodeBloc filename = os.path.join(cargo.subdir, cargo.path, 'Cargo.toml') build = builder.Builder(filename) - ast = _create_project(cargo, build, env) + # Generate project options + options: T.Dict[OptionKey, coredata.UserOption] = {} + for feature in cargo.features: + key = OptionKey(_option_name(feature), subproject=subp_name) + enabled = feature == 'default' + options[key] = coredata.UserBooleanOption(f'Cargo {feature} feature', enabled) + + ast = _create_project(cargo, build) 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 @@ -472,4 +700,4 @@ def interpret(subp_name: str, subdir: str, env: Environment) -> mparser.CodeBloc for crate_type in cargo.lib.crate_type: ast.extend(_create_lib(cargo, build, crate_type)) - return build.block(ast) + return build.block(ast), options diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index aaffec0b9..79a61ed0e 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -702,6 +702,8 @@ class Interpreter(InterpreterBase, HoldableObject): srcdir = Path(self.environment.source_dir) # convert variables which refer to an -uninstalled.pc style datadir for k, v in variables.items(): + if not v: + FeatureNew.single_use('empty variable value in declare_dependency', '1.4.0', self.subproject, location=node) try: p = Path(v) except ValueError: @@ -1032,7 +1034,8 @@ class Interpreter(InterpreterBase, HoldableObject): from .. import cargo FeatureNew.single_use('Cargo subproject', '1.3.0', self.subproject, location=self.current_node) with mlog.nested(subp_name): - ast = cargo.interpret(subp_name, subdir, self.environment) + ast, options = cargo.interpret(subp_name, subdir, self.environment) + self.coredata.update_project_options(options) return self._do_subproject_meson( subp_name, subdir, default_options, kwargs, ast, # FIXME: Are there other files used by cargo interpreter? diff --git a/mesonbuild/interpreter/type_checking.py b/mesonbuild/interpreter/type_checking.py index 616f4efbb..2ec7d58ce 100644 --- a/mesonbuild/interpreter/type_checking.py +++ b/mesonbuild/interpreter/type_checking.py @@ -144,8 +144,6 @@ def variables_validator(contents: T.Union[str, T.List[str], T.Dict[str, str]]) - for k, v in variables.items(): if not k: return 'empty variable name' - if not v: - return 'empty variable value' if any(c.isspace() for c in k): return f'invalid whitespace in variable name {k!r}' return None diff --git a/mesonbuild/modules/pkgconfig.py b/mesonbuild/modules/pkgconfig.py index 3f9ce7b71..ebe0d92d5 100644 --- a/mesonbuild/modules/pkgconfig.py +++ b/mesonbuild/modules/pkgconfig.py @@ -680,6 +680,8 @@ class PkgConfigModule(NewExtensionModule): reserved = ['prefix', 'libdir', 'includedir'] variables = [] for name, value in vardict.items(): + if not value: + FeatureNew.single_use('empty variable value in pkg.generate', '1.4.0', state.subproject, location=state.current_node) if not dataonly and name in reserved: raise mesonlib.MesonException(f'Variable "{name}" is reserved') variables.append((name, value)) diff --git a/test cases/failing/47 pkgconfig variables zero length value/meson.build b/test cases/failing/47 pkgconfig variables zero length value/meson.build deleted file mode 100644 index 33977b273..000000000 --- a/test cases/failing/47 pkgconfig variables zero length value/meson.build +++ /dev/null @@ -1,16 +0,0 @@ -project('variables-zero-length-value-test', 'c', version : '1.0') - -pkgg = import('pkgconfig') -lib = shared_library('simple', 'simple.c') -libver = '1.0' -h = install_headers('simple.h') - -pkgg.generate( - libraries : [lib, '-lz'], - subdirs : '.', - version : libver, - name : 'libsimple', - filebase : 'simple', - description : 'A simple demo library.', - variables : [ 'key=' ] -) diff --git a/test cases/failing/47 pkgconfig variables zero length value/simple.c b/test cases/failing/47 pkgconfig variables zero length value/simple.c deleted file mode 100644 index e8a6d8330..000000000 --- a/test cases/failing/47 pkgconfig variables zero length value/simple.c +++ /dev/null @@ -1,5 +0,0 @@ -#include"simple.h" - -int simple_function() { - return 42; -} diff --git a/test cases/failing/47 pkgconfig variables zero length value/simple.h b/test cases/failing/47 pkgconfig variables zero length value/simple.h deleted file mode 100644 index bb52e6d72..000000000 --- a/test cases/failing/47 pkgconfig variables zero length value/simple.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef SIMPLE_H_ -#define SIMPLE_H_ - -int simple_function(); - -#endif diff --git a/test cases/failing/47 pkgconfig variables zero length value/test.json b/test cases/failing/47 pkgconfig variables zero length value/test.json deleted file mode 100644 index 0be572535..000000000 --- a/test cases/failing/47 pkgconfig variables zero length value/test.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "stdout": [ - { - "line": "test cases/failing/47 pkgconfig variables zero length value/meson.build:8:5: ERROR: pkgconfig.generate keyword argument \"variables\" empty variable value" - } - ] -} diff --git a/test cases/rust/22 cargo subproject/subprojects/bar-rs/Cargo.toml b/test cases/rust/22 cargo subproject/subprojects/bar-rs/Cargo.toml index 232b4d7d4..d60a5d8f1 100644 --- a/test cases/rust/22 cargo subproject/subprojects/bar-rs/Cargo.toml +++ b/test cases/rust/22 cargo subproject/subprojects/bar-rs/Cargo.toml @@ -1,3 +1,14 @@ [package] name = "bar" version = "0.1" + +# This dependency does not exist, it is required by default but this subproject +# is called with default-features=false. +[dependencies.notfound] +optional = true +version = "1.0" + +[features] +default = ["f2"] +f1 = [] +f2 = ["notfound"] diff --git a/test cases/rust/22 cargo subproject/subprojects/extra-dep-rs/lib.c b/test cases/rust/22 cargo subproject/subprojects/extra-dep-rs/lib.c new file mode 100644 index 000000000..c2a0777ce --- /dev/null +++ b/test cases/rust/22 cargo subproject/subprojects/extra-dep-rs/lib.c @@ -0,0 +1,4 @@ +int extra_func(void) +{ + return 0; +} diff --git a/test cases/rust/22 cargo subproject/subprojects/extra-dep-rs/meson.build b/test cases/rust/22 cargo subproject/subprojects/extra-dep-rs/meson.build new file mode 100644 index 000000000..3ba7852cf --- /dev/null +++ b/test cases/rust/22 cargo subproject/subprojects/extra-dep-rs/meson.build @@ -0,0 +1,10 @@ +project('extra dep', 'c', version: '1.0') + +assert(get_option('feature-default') == true) + +l = static_library('extra-dep', 'lib.c') +d = declare_dependency(link_with: l, + variables: { + 'features': 'default', + }) +meson.override_dependency('extra-dep-rs', d) diff --git a/test cases/rust/22 cargo subproject/subprojects/extra-dep-rs/meson_options.txt b/test cases/rust/22 cargo subproject/subprojects/extra-dep-rs/meson_options.txt new file mode 100644 index 000000000..9311d9e8f --- /dev/null +++ b/test cases/rust/22 cargo subproject/subprojects/extra-dep-rs/meson_options.txt @@ -0,0 +1 @@ +option('feature-default', type: 'boolean', value: true) diff --git a/test cases/rust/22 cargo subproject/subprojects/foo-rs/Cargo.toml b/test cases/rust/22 cargo subproject/subprojects/foo-rs/Cargo.toml index 214c3279c..796548d63 100644 --- a/test cases/rust/22 cargo subproject/subprojects/foo-rs/Cargo.toml +++ b/test cases/rust/22 cargo subproject/subprojects/foo-rs/Cargo.toml @@ -6,5 +6,22 @@ edition = "2021" [lib] crate-type = ["cdylib"] +# This dependency does not exist, verify optional works. +[dependencies.notfound] +optional = true +version = "1.0" + +# This dependency is optional but required for f3 which is on by default. +[dependencies.extra-dep] +optional = true +version = "1.0" + [dependencies] -mybar = { version = "0.1", package = "bar" } +mybar = { version = "0.1", package = "bar", default-features = false } + +[features] +default = ["f1"] +f1 = ["f2", "f3"] +f2 = ["f1"] +f3 = ["mybar/f1", "dep:extra-dep", "notfound?/invalid"] +f4 = ["dep:notfound"] 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 732d7d20b..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 @@ -1,4 +1,13 @@ +extern "C" { + fn extra_func() -> i32; +} + +#[cfg(feature = "foo")] #[no_mangle] pub extern "C" fn rust_func() -> i32 { - mybar::VALUE + let v: i32; + unsafe { + v = extra_func(); + }; + mybar::VALUE + v } |
