build: attempt to normalize Rust handling to support static linking

Co-authored-by: Jade Lovelance <jade@lix.systems>

Change-Id: Icfd3cabaa6abc0a39f501b4b6c6b73df4cb66e6a
This commit is contained in:
Qyriad
2026-01-06 16:35:49 +01:00
parent b482ebbbc8
commit 357ee01002
8 changed files with 116 additions and 7 deletions
+16 -7
View File
@@ -3,22 +3,31 @@ rowan = dependency('rowan-0.15-rs')
rust = import('rust')
# This hack is required by the wombo combo of meson bugs:
# Meson does not set the soname for us: https://github.com/mesonbuild/meson/issues/13537
# Meson ignores link_args for Rust targets: https://github.com/mesonbuild/meson/issues/13538
lix_doc_rust_args = [
# link-arg=-soname has no effect when building a static lib,
# since the linker is never actually invoked.
'-C', f'link-arg=-Wl,@soname_arg@,liblix_doc.@dylib_suffix@',
]
lix_doc = static_library(
'lix_doc',
sources : files('src/lib.rs'),
rust_abi : 'c',
dependencies : [
rowan,
rnix,
],
# If an installed static library depends on this target, then Meson will force
# that to link with `-Wl,--whole-archive`, unless we also install this target.
# `-Wl,--whole-archive` can cause some Problems when linking multiple nested
# static libraries, so let's just install the damn thing.
install : is_static,
rust_args : [
# Empty when default_library=static
rust_dynamic_args,
lix_doc_rust_args,
],
)
liblix_doc = declare_dependency(
# NOTE: this dependency object is only used for `rust_monocrate`
liblix_doc_internal = declare_dependency(
link_with : lix_doc,
)
+7
View File
@@ -25,6 +25,13 @@ install_headers(config_h, subdir : 'lix')
# Subcomponents: these link into artifacts themselves, and have interdependencies.
subdir('lix-doc')
subdir('rust-monocrate')
# liblix_doc is actually rust-monocrate in a trenchcoat.
liblix_doc = declare_dependency(
link_with : rust_monocrate,
)
subdir('libutil')
# Load-bearing order. libstore depends on libutil.
subdir('libstore')
+8
View File
@@ -0,0 +1,8 @@
# This may not actually build with Cargo, but exists to help Meson and to manage lockfiles.
[package]
name = "rust-monocrate"
edition.workspace = true
[lib]
crate-type = ["staticlib"]
lix-doc = { path = "../lix-doc" }
+19
View File
@@ -0,0 +1,19 @@
# What the hell is this?
This is a cursed solution to the intersection of the following problems:
- Lix is separated into multiple libraries, which are intended to be buildable as dynamic/shared libraries *or* statlic libraries.
- Those libraries are linked into the Lix executable binary.
- Static libraries also statically link their library dependencies.
- We want to be able to use Rust code in multiple places in the codebase.
- Meson has poor support for Rust and non-Rust sources in the same target, with [internal errors](https://github.com/mesonbuild/meson/issues/15435) preventing mixed targets from linking against non-mixed targets.
- Linking more than one Rust `crate-type = staticlib` is [unsupported](https://github.com/rust-lang/rust/issues/44322) with Rust's standard library.
- Meson [does not support dynamically linking the Rust standard library for `crate-type = staticlib`](https://mesonbuild.com/Release-notes-for-1-9-0.html#new-experimental-option-rust_dynamic_std).
- Meson [ignores `link_args` for Rust targets](https://github.com/mesonbuild/meson/issues/13538)
- Meson [does not set soname of Rust cdylibs](https://github.com/mesonbuild/meson/issues/13537)
So! What do we do? For any Rust code that Lix wants to use, we always link to a single crate, which will reëxport Rust code from any crate (in-tree or out-of-tree) we wish to use.
This "monocrate" is *always* built as a static library, and anything in Lix that depends on Rust code will statically link it in.
When the Lix executable is linked against Lix libraries that are built statically, we do *not* `link_whole` them.
As far as we::Qyriad can tell, this is sufficient to not break everything.
But long-term, we should probably solve this problem by migrating to Rust code at top-level, and having Rustc do the final link.
+19
View File
@@ -0,0 +1,19 @@
# This crate is what ultimately links to Lix. All other Rust crates Lix uses must go through this.
rust_monocrate_sources = files(
'src/lib.rs',
)
rust_monocrate = static_library(
'rust_monocrate',
rust_abi : 'c',
sources : rust_monocrate_sources,
dependencies : [
liblix_doc_internal,
rnix,
rowan,
],
# If any installed target links against a static library,
# the static library must also be installed.
install : true,
)
+12
View File
@@ -0,0 +1,12 @@
//! This is a hack that forces Rust to link all the Lix libs as a single static
//! library for usage when building Lix itself in static mode.
//!
//! The reason for this is that Rust does not support linking multiple
//! staticlibs into one executable, as it will jam a libstd into every single
//! one of them. This is ridiculously goofy because it would be trivially
//! solved by linking libstd separately.
//!
//! https://github.com/rust-lang/rust/issues/44322
//!
//! It re-exports all the symbols that should be exported to C++.
pub use lix_doc::*;
+33
View File
@@ -223,6 +223,14 @@ is_darwin = host_machine.system() == 'darwin'
is_freebsd = host_machine.system() == 'freebsd'
is_x64 = host_machine.cpu_family() == 'x86_64'
# Meson knows these, but won't tell us. We need them to hack around some linker shenanigans.
# See the 'Rust Support' section below.
if is_darwin
dylib_suffix = 'dylib'
elif is_linux
dylib_suffix = 'so'
endif
# Per-platform arguments that you should probably pass to shared_module() invocations.
# Something like add_project_arguments() can't be scoped on only shared modules, so this
# variable is here instead.
@@ -238,6 +246,30 @@ elif is_linux
endif
configdata = { }
#
# Rust Support
#
if is_static
rust_dynamic_args = []
else
rust_dynamic_args = ['-C', 'prefer-dynamic']
endif
# This hack is required by the wombo combo of meson bugs:
# Meson does not set the soname for us: https://github.com/mesonbuild/meson/issues/13537
# Meson ignores link_args for Rust targets: https://github.com/mesonbuild/meson/issues/13538
# Thus we have to, when the build is dynamic, pass these in rust_args to make it link properly.
if cxx.get_linker_id().startswith('ld64')
# Rust actually sets the -install_name if you pass -C rpath=yes on macOS targets (?!):
# https://github.com/rust-lang/rust/blob/2a8af4f7c8262a90b886bc063fe0c271d2b51a45/compiler/rustc_codegen_ssa/src/back/linker.rs#L441-L445
# https://rust-lang.zulipchat.com/#narrow/channel/182449-t-compiler.2Fhelp/topic/libstd.2C.20rpath.2C.20dylibs.20and.20packaging
# This seems like a bug, so let's keep passing it deliberately.
soname_arg = '-install_name'
elif cxx.get_linker_id().startswith('ld')
soname_arg = '-soname'
endif
#
# Dependencies
#
@@ -404,6 +436,7 @@ endif
# Build-time tools
#
dot = find_program('dot', required : false, native : true)
cbindgen = find_program('cbindgen', native : true)
pymod = import('python')
python = pymod.find_installation('python3')
+2
View File
@@ -50,6 +50,7 @@
rapidcheck,
removeReferencesTo,
rustPlatform,
rust-cbindgen,
rustc,
sqlite,
systemtap-lix ? __forDefaults.systemtap-lix,
@@ -339,6 +340,7 @@ stdenv.mkDerivation (finalAttrs: {
meson
ninja
cmake
rust-cbindgen
rustc
capnproto
# Required for libstd++ assertions that leaks inside of the final binary.