summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/markdown/Wrap-dependency-system-manual.md29
-rw-r--r--mesonbuild/ast/printer.py3
-rw-r--r--mesonbuild/cargo/builder.py71
-rw-r--r--mesonbuild/cargo/interpreter.py244
-rw-r--r--mesonbuild/interpreter/interpreter.py5
-rw-r--r--mesonbuild/interpreter/type_checking.py2
-rw-r--r--mesonbuild/modules/pkgconfig.py2
-rw-r--r--test cases/failing/47 pkgconfig variables zero length value/meson.build16
-rw-r--r--test cases/failing/47 pkgconfig variables zero length value/simple.c5
-rw-r--r--test cases/failing/47 pkgconfig variables zero length value/simple.h6
-rw-r--r--test cases/failing/47 pkgconfig variables zero length value/test.json7
-rw-r--r--test cases/rust/22 cargo subproject/subprojects/bar-rs/Cargo.toml11
-rw-r--r--test cases/rust/22 cargo subproject/subprojects/extra-dep-rs/lib.c4
-rw-r--r--test cases/rust/22 cargo subproject/subprojects/extra-dep-rs/meson.build10
-rw-r--r--test cases/rust/22 cargo subproject/subprojects/extra-dep-rs/meson_options.txt1
-rw-r--r--test cases/rust/22 cargo subproject/subprojects/foo-rs/Cargo.toml19
-rw-r--r--test cases/rust/22 cargo subproject/subprojects/foo-rs/meson/meson.build1
-rw-r--r--test cases/rust/22 cargo subproject/subprojects/foo-rs/src/lib.rs11
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
}