Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions docs/markdown/Wrap-dependency-system-manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,26 @@ Some naming conventions need to be respected:
- The `extra_deps` variable is pre-defined and can be used to add extra dependencies.
This is typically used as `extra_deps += dependency('foo')`.

Cargo features are exposed as Meson boolean options, with the `feature-` prefix.
This can be used to request features with `dependency('foo', default_options: '...')`.

Currently, Meson is able to manage the set of enabled features for all crates found by a
single invocation of `dependency()`, but not globally. Let's assume
the main project depends on `foo-1-rs` and `bar-1-rs`, and they both depend on
`common-1-rs`. The main project will first look up `foo-1-rs` which itself will
configure `common-rs` with a set of features. Later, when `bar-1-rs` does a lookup
for `common-1-rs` it has already been configured and the set of features cannot be
changed. If `bar-1-rs` wants extra features from `common-1-rs`, Meson will error out.
It is currently the responsibility of the main project to resolve those
issues by enabling extra features on each subproject:
```meson
project(...,
default_options: {
'common-1-rs:feature-something': true,
},
)
```

Since *1.5.0* Cargo wraps can also be provided with `Cargo.lock` file at the root
of (sub)project source tree. Meson will automatically load that file and convert
it into a series of wraps definitions.
Expand Down
72 changes: 60 additions & 12 deletions mesonbuild/cargo/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,16 @@
from .toml import load_toml, TomlImplementationMissing
from .manifest import Manifest, CargoLock, fixup_meson_varname
from ..mesonlib import MesonException, MachineChoice
from .. import coredata, mlog
from ..options import OptionKey
from .. import coredata, mlog, options
from ..wrap.wrap import PackageDefinition

if T.TYPE_CHECKING:
from . import raw
from .. import mparser
from .manifest import Dependency, SystemDependency
from ..environment import Environment
from ..options import OptionDict
from ..interpreterbase import SubProject
from ..compilers.rust import RustCompiler

Expand All @@ -41,6 +43,14 @@ 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 _OPTION_NAME_PREFIX + feature


def _extra_args_varname() -> str:
return 'extra_args'

Expand Down Expand Up @@ -78,23 +88,61 @@ def __init__(self, env: Environment) -> None:
def get_build_def_files(self) -> T.List[str]:
return [os.path.join(subdir, 'Cargo.toml') for subdir in self.manifests]

def interpret(self, subdir: str) -> mparser.CodeBlockNode:
def interpret(self, subp_name: str, subdir: str, default_options: OptionDict) -> T.Tuple[mparser.CodeBlockNode, T.Dict[OptionKey, options.UserOption]]:
manifest = self._load_manifest(subdir)
pkg, cached = self._fetch_package(manifest.package.name, manifest.package.api)
if not cached:
# This is an entry point, always enable the 'default' feature.
# FIXME: We should have a Meson option similar to `cargo build --no-default-features`
self._enable_feature(pkg, 'default')

# default_options -> features
for key in default_options.keys():
if key.name.startswith(_OPTION_NAME_PREFIX) and default_options[key] is True:
self._enable_feature(pkg, key.name[len(_OPTION_NAME_PREFIX):])

# features -> default_options
for f in pkg.features:
key = OptionKey(_option_name(f))
default_options[key] = True

# Build an AST for this package
filename = os.path.join(self.environment.source_dir, subdir, 'Cargo.toml')
build = builder.Builder(filename)
ast = self._create_project(pkg, build)

# features = []
# features_args = []
ast += [
build.assign(build.array([]), 'features'),
build.assign(build.array([]), 'features_args')]

project_options: T.Dict[OptionKey, options.UserOption] = {}
for f in itertools.chain(pkg.manifest.dependencies.keys(), pkg.manifest.features.keys()):
if f == 'default':
continue

# option('f1', type: 'boolean', value: true/false, description: 'Cargo feature f1')
key = OptionKey(_option_name(f), subproject=subp_name)
project_options[key] = options.UserBooleanOption(key.name, f'Cargo feature {f}', False)

# if get_option('feature-f1')
# features_args += ['--cfg', 'feature="f1"']
# features += ['--f1']
# endif
lines: T.List[mparser.BaseNode] = [
build.plusassign(build.array([build.string('--cfg'), build.string(f'feature="{f}"')]),
'features_args'),
build.plusassign(build.array([build.string(f)]),
'features')]

ast.append(build.if_(build.function('get_option', [build.string(_option_name(f))]), build.block(lines)))

ast += [
build.assign(build.function('import', [build.string('rust')]), 'rust'),
build.function('message', [
build.string('Enabled features:'),
build.array([build.string(f) for f in pkg.features]),
build.method('join', build.string(','), [build.identifier('features')])
]),
]
ast += self._create_dependencies(pkg, build)
Expand All @@ -104,7 +152,7 @@ def interpret(self, subdir: str) -> mparser.CodeBlockNode:
for crate_type in pkg.manifest.lib.crate_type:
ast.extend(self._create_lib(pkg, build, crate_type))

return build.block(ast)
return build.block(ast), project_options

def _fetch_package(self, package_name: str, api: str) -> T.Tuple[PackageState, bool]:
key = PackageKey(package_name, api)
Expand Down Expand Up @@ -289,8 +337,14 @@ def _create_system_dependency(self, name: str, dep: SystemDependency, build: bui

def _create_dependency(self, dep: Dependency, build: builder.Builder) -> T.List[mparser.BaseNode]:
pkg = self._dep_package(dep)

# { 'feature-f1': true }
options = build.dict({build.string(f'feature-{f}'): build.bool(True)
for f in pkg.features})

kw = {
'version': build.array([build.string(s) for s in dep.meson_version]),
'default_options': options,
}
# Lookup for this dependency with the features we want in default_options kwarg.
#
Expand Down Expand Up @@ -333,7 +387,7 @@ def _create_dependency(self, dep: Dependency, build: builder.Builder) -> T.List[
# error()
# endif
# endforeach
build.assign(build.array([build.string(f) for f in pkg.features]), 'needed_features'),
build.assign(build.array([build.string(f) for f in pkg.features if f != 'default']), 'needed_features'),
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', [
Expand Down Expand Up @@ -416,24 +470,18 @@ def _create_lib(self, pkg: PackageState, build: builder.Builder, crate_type: raw
kwargs['rust_abi'] = build.string('c')
lib = build.function(target_type, posargs, kwargs)

features_args: T.List[mparser.BaseNode] = []
for f in pkg.features:
features_args += [build.string('--cfg'), build.string(f'feature="{f}"')]

# features_args = ['--cfg', 'feature="f1"', ...]
# lib = xxx_library()
# dep = declare_dependency()
# meson.override_dependency()
return [
build.assign(build.array(features_args), '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.string(','.join(pkg.features)),
build.string('features'): build.method('join', build.string(','), [build.identifier('features')]),
}),
'version': build.string(pkg.manifest.package.version),
},
Expand Down
3 changes: 2 additions & 1 deletion mesonbuild/interpreter/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -1065,7 +1065,8 @@ def _do_subproject_cargo(self, subp_name: str, subdir: str,
self.add_languages(['rust'], True, MachineChoice.HOST)
self.environment.cargo = cargo.Interpreter(self.environment)
with mlog.nested(subp_name):
ast = self.environment.cargo.interpret(subdir)
ast, options = self.environment.cargo.interpret(subp_name, subdir, default_options)
self.coredata.optstore.update_project_options(options, subp_name)
return self._do_subproject_meson(
subp_name, subdir, default_options, kwargs, ast,
# FIXME: Are there other files used by cargo interpreter?
Expand Down
Loading