diff --git a/lix/lix-doc/meson.build b/lix/lix-doc/meson.build index 81ae07fec..933f1b0ed 100644 --- a/lix/lix-doc/meson.build +++ b/lix/lix-doc/meson.build @@ -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, ) diff --git a/lix/meson.build b/lix/meson.build index ac411679c..207fcb1a7 100644 --- a/lix/meson.build +++ b/lix/meson.build @@ -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') diff --git a/lix/rust-monocrate/Cargo.toml b/lix/rust-monocrate/Cargo.toml new file mode 100644 index 000000000..53c2f5745 --- /dev/null +++ b/lix/rust-monocrate/Cargo.toml @@ -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" } diff --git a/lix/rust-monocrate/README.md b/lix/rust-monocrate/README.md new file mode 100644 index 000000000..1cbcb552a --- /dev/null +++ b/lix/rust-monocrate/README.md @@ -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. diff --git a/lix/rust-monocrate/meson.build b/lix/rust-monocrate/meson.build new file mode 100644 index 000000000..71a94726c --- /dev/null +++ b/lix/rust-monocrate/meson.build @@ -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, +) diff --git a/lix/rust-monocrate/src/lib.rs b/lix/rust-monocrate/src/lib.rs new file mode 100644 index 000000000..480f7504c --- /dev/null +++ b/lix/rust-monocrate/src/lib.rs @@ -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::*; diff --git a/meson.build b/meson.build index 9cb181546..523bcd542 100644 --- a/meson.build +++ b/meson.build @@ -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') diff --git a/package.nix b/package.nix index bd3a5e80d..c301e37d3 100644 --- a/package.nix +++ b/package.nix @@ -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.