meson: add -Drust= for programs written in Rust

Add optional support for programs written in Rust. Nothing else
changes. libsystemd-shared, libsystemd and the NSS and PAM modules stay
C and a program written in Rust links libsystemd-shared dynamically
just like the C programs do. Rust code gets core. There's no std, the
alloc crate isn't used either and there are no external crates.

The option is a feature and defaults to auto. We need rustc >= 1.85 and
bindgen >= 0.71.1 which is exactly what Debian 13 ships. 1.85 is the
minimum supported Rust version and we hand it to bindgen as well so the
generated bindings never require a newer compiler than the hand-written
code. bindgen 0.72.0 is refused as it generates code that doesn't
compile. With auto the toolchain is used when it's found and
-Drust=enabled fails loudly when it isn't.

Rust support also needs meson 1.4.0 for the rust module's bindgen()
integration. The tree keeps its meson floor and -Drust=auto silently
turns itself off on older meson. Cross-compilation isn't supported yet
as bindgen would need to be told about the target. HAVE_RUST tells
units, man pages and tests whether Rust programs are built.

bindgen loads libclang at runtime so finding the binary proves nothing
about being able to generate bindings. So run it once at configure time
over tools/bindgen-probe.h. That reports the libclang version, which is
shown in the summary, and contains the construct bindgen < 0.72.1
mishandles with libclang >= 22. A broken combination is thus refused or
disables Rust support instead of failing the build later.

The lints and the codegen options are project-wide rustc arguments.
Warnings are only errors with --werror just like for C. The lint set is
the kernel's:

- unsafe operations need an unsafe block
- every unsafe block needs a SAFETY comment and every unsafe function a
  Safety section, enforced via clippy's undocumented_unsafe_blocks and
  friends
- public items need docs
- the pointer cast lints keep raw pointer handling explicit

Overflow checks are enabled in every build type. rustc's default
depends on the optimization level and meson always passes
overflow-checks=no but Rust code shouldn't get to wrap silently where C
code checks. Everything is built with panic=abort. Unwinding couldn't
cross the C frames of an event loop anyway and without std there's no
unwinder. meson passes source paths relative to the build directory and
that's what file!() and thus CODE_FILE= in the journal would show. So
remap the prefix away like PROJECT_FILE does for C.

rustfmt.toml and .clippy.toml carry the formatting and lint settings
for Rust code. rustfmt uses its defaults except for the line length the
tree already uses and clippy gets the MSRV. The edition is 2021 for the
whole project and .editorconfig learns about *.rs files.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
This commit is contained in:
Christian Brauner committed 2026-10-23 16:31:43 +02:00
1 parent da22c6eeed
commit 770c05374e
6 files changed
+151

No files matched your search

+4
View File
@@ -0,0 +1,4 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
msrv = "1.85"
check-private-items = true
+5
View File
@@ -32,6 +32,11 @@ indent_size = 4
indent_style = space
indent_size = 8
[*.rs]
indent_style = space
indent_size = 4
max_line_length = 109
[man/*.xml]
indent_size = 2
indent_style = space
+125
View File
@@ -5,6 +5,7 @@ project('systemd', 'c',
license : 'LGPLv2+',
default_options: [
'c_std=gnu17',
'rust_std=2021',
'prefix=/usr',
'sysconfdir=/etc',
'localstatedir=/var',
@@ -371,6 +372,129 @@ else
cxx_cmd = ''
endif
#####################################################################
rust_feature = get_option('rust')
# The oldest rustc we support. Also handed to bindgen, so that the generated bindings never require a
# newer compiler than the hand-written code does.
rust_msrv = '1.85'
bindgen_min_version = '0.71.1'
have_rust = false
if rust_feature.allowed()
# The rust module's bindgen() integration needs this meson version.
if meson.version().version_compare('< 1.4.0')
if rust_feature.enabled()
error('Rust support requires meson >= 1.4.0, found @0@'.format(meson.version()))
endif
message('Rust support requires meson >= 1.4.0, disabling')
elif meson.is_cross_build()
if rust_feature.enabled()
error('Rust support is not available when cross-compiling yet')
endif
message('Rust support is not available when cross-compiling yet, disabling')
else
have_rust = add_languages('rust', native : false, required : rust_feature)
endif
endif
if have_rust
rustc = meson.get_compiler('rust')
if rustc.version().version_compare('< ' + rust_msrv)
if rust_feature.enabled()
error('rustc >= @0@ is required, found @1@'.format(rust_msrv, rustc.version()))
endif
message('rustc >= @0@ is required, found @1@, disabling Rust support'.format(rust_msrv, rustc.version()))
have_rust = false
endif
endif
if have_rust
bindgen = find_program('bindgen', required : rust_feature, version : '>= ' + bindgen_min_version)
have_rust = bindgen.found()
# 0.72.0 generates bindings that do not compile, 0.72.1 fixed that.
if have_rust and bindgen.version() == '0.72.0'
if rust_feature.enabled()
error('bindgen 0.72.0 is known to be broken, use 0.71.1 or >= 0.72.1')
endif
message('bindgen 0.72.0 is known to be broken, disabling Rust support')
have_rust = false
endif
endif
if have_rust
# bindgen parses C with libclang, which it loads at run time, so find_program() proves nothing about
# it. Run it once over a header that reports the libclang version and contains the construct bindgen
# < 0.72.1 mishandles with libclang >= 22 (https://github.com/rust-lang/rust-bindgen/pull/3278).
bindgen_probe = run_command(bindgen, files('tools/bindgen-probe.h'), '--formatter', 'none', check : false)
libclang_version = ''
if bindgen_probe.returncode() == 0
foreach line : bindgen_probe.stderr().split('\n')
if line.contains('clang version ')
libclang_version = line.split('clang version ')[1].split(' ')[0]
endif
endforeach
endif
if libclang_version == ''
if rust_feature.enabled()
error('bindgen cannot generate bindings, is libclang installed?\n' + bindgen_probe.stderr())
endif
message('bindgen cannot generate bindings, disabling Rust support')
have_rust = false
elif libclang_version.version_compare('>= 22') and bindgen.version().version_compare('< 0.72.1') and not bindgen_probe.stdout().contains('pub foo')
if rust_feature.enabled()
error('bindgen @0@ does not work with libclang @1@, use bindgen >= 0.72.1'.format(bindgen.version(), libclang_version))
endif
message('bindgen @0@ does not work with libclang @1@, disabling Rust support'.format(bindgen.version(), libclang_version))
have_rust = false
endif
endif
conf.set10('HAVE_RUST', have_rust)
if have_rust
rustfmt = find_program('rustfmt', required : false)
# Warnings are errors only under --werror, like for C.
rust_args = [
'-Dnon_ascii_idents',
'-Dunsafe_op_in_unsafe_fn',
'-Wmissing_docs',
'-Wrust_2018_idioms',
'-Wunreachable_pub',
'-Wclippy::undocumented_unsafe_blocks',
'-Wclippy::unnecessary_safety_comment',
'-Wclippy::unnecessary_safety_doc',
'-Wclippy::ptr_as_ptr',
'-Wclippy::ptr_cast_constness',
'-Wclippy::ref_as_ptr',
'-Wclippy::as_ptr_cast_mut',
'-Wclippy::cast_lossless',
'-Wclippy::dbg_macro',
'-Wrustdoc::missing_crate_level_docs',
# rustc's default depends on the optimization level. C code in systemd checks its arithmetic
# where it matters, Rust code does not get to wrap silently either.
'-Coverflow-checks=yes',
# Unwinding could not cross the C frames of an event loop anyway, and without std there is no
# unwinder.
'-Cpanic=abort',
]
# meson passes source paths relative to the build directory, which is what file!() and hence
# CODE_FILE= in the journal would show. Strip the prefix like PROJECT_FILE does for C. This also
# makes the paths in the debug info relative to the source root (--remap-path-scope=macro would
# limit it to file!(), but rustdoc does not accept that flag on stable).
rust_args += ['--remap-path-prefix=' + relative_source_path + '/=']
add_project_arguments(rust_args, language : 'rust')
summary({
'rustc' : rustc.version(),
'bindgen' : bindgen.version(),
'libclang' : libclang_version,
}, section : 'Rust')
endif
if want_libfuzzer
if cc.has_argument('-fsanitize=fuzzer-no-link')
userspace_c_args += '-fsanitize=fuzzer-no-link'
@@ -3286,6 +3410,7 @@ foreach tuple : [
['report'],
['resolve'],
['rfkill'],
['rust', have_rust],
['sysext'],
['sysinstall'],
['systemd-analyze', conf.get('ENABLE_ANALYZE') == 1],
+2
View File
@@ -524,6 +524,8 @@ option('pwquality', type : 'feature', deprecated : { 'true' : 'enabled', 'false'
description : 'libpwquality support')
option('qrencode', type : 'feature', deprecated : { 'true' : 'enabled', 'false' : 'disabled' },
description : 'libqrencode support')
option('rust', type : 'feature',
description : 'support for programs written in Rust (needs rustc and bindgen)')
option('tpm2', type : 'feature', deprecated : { 'true' : 'enabled', 'false' : 'disabled' },
description : 'TPM2 support')
option('tpm2-nvpcr-base', type : 'integer', value: 0x01d10200,
+4
View File
@@ -0,0 +1,4 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
edition = "2021"
max_width = 109
+11
View File
@@ -0,0 +1,11 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/* Run through bindgen at configure time: it reports the libclang version and contains the construct that
* bindgen < 0.72.1 mishandles with libclang >= 22 (https://github.com/rust-lang/rust-bindgen/pull/3278). */
#pragma message("clang version " __clang_version__)
struct S;
struct S {
int foo;
};