From 480fdf146d9c29d3de19b856409dc648df3ddcf6 Mon Sep 17 00:00:00 2001 From: Alois Wohlschlager Date: Sun, 27 Jul 2025 11:39:51 +0200 Subject: [PATCH] packaging: prelink static libraries that need to be linked fully Some of the Lix libraries always need to be linked in full due to their reliance on static initializers. This was achieved internally using link_whole, but they are still easy to abuse by external users who manually need to remember passing linker flags such as `--whole-archive` (GNU) or `-force_load` (Apple), and the obvious way to shove it in pkg-config breaks Meson due to potentially including a library's flags multiple times, and then deduplicating only the file names leaving a stale `-force_load` around causing trouble. Instead we now "prelink" the static libraries, by merging them into one object file. Since the static linker will always link entire object files, this will have the same effect as whole-archive linking (except the library won't be included if it's completely unused, which should not cause trouble since it's unused after all, and dynamic libraries behave the same way). Unfortunately Meson's native prelink functionality cannot be used due to missing (non-Apple) Clang support [1], so write our own one. While not particularly portable, it should work with Clang which is the only officially supported compiler, as well as GCC. [1] https://github.com/mesonbuild/meson/pull/14846 Change-Id: I6a6a6964a82241ce3b0b11fe8397fd451b8027f2 --- lix/libexpr/meson.build | 64 +++++++++++++++++---------- lix/libfetchers/meson.build | 57 +++++++++++++++--------- lix/libstore/meson.build | 43 +++++++++++++------ lix/libutil/meson.build | 86 +++++++++++++++++++------------------ lix/nix/meson.build | 8 ++-- meson.build | 13 ------ tests/unit/meson.build | 14 +++--- 7 files changed, 163 insertions(+), 122 deletions(-) diff --git a/lix/libexpr/meson.build b/lix/libexpr/meson.build index f0f5618bc..1b6104039 100644 --- a/lix/libexpr/meson.build +++ b/lix/libexpr/meson.build @@ -270,32 +270,59 @@ libexpr_headers = files( # keep-sorted end ) -libexpr = library( - 'lixexpr', +dependencies = [ + liblixutil, + liblixstore, + liblixfetchers, + boehm, + boost, + toml11, + nlohmann_json, + kj, +] + +libexpr_temp = library( + is_static ? 'lixexpr_temp' : 'lixexpr', libexpr_sources, libexpr_settings_header, libexpr_generated_headers, register_builtins_header, register_builtin_constants_header, - dependencies : [ - liblixutil, - liblixstore, - liblixfetchers, - boehm, - boost, - toml11, - nlohmann_json, - kj, - ], + dependencies : dependencies, # for shared.hh include_directories : [ '../libmain', ], cpp_pch : cpp_pch, - install : true, + install : not is_static, # FIXME(Qyriad): is this right? install_rpath : libdir, ) +# FIXME: remove when https://git.lix.systems/lix-project/lix/issues/359 is fixed. +# FIXME: replace by prelink when https://github.com/mesonbuild/meson/pull/14846 is widely available. +if is_static + libexpr_prelink = custom_target( + 'lixexpr-prelink', + output : 'lixexpr-prelink.o', + input : libexpr_temp, + command : [ + cxx.cmd_array(), + '-r', + '-o', + '@OUTPUT@', + is_darwin ? '-Wl,-force_load' : '-Wl,--whole-archive', + '@INPUT@', + ], + ) + libexpr = library( + 'lixexpr', + [libexpr_prelink], + dependencies : dependencies, + install : true, + ) +else + libexpr = libexpr_temp +endif install_headers( libexpr_headers, @@ -312,16 +339,7 @@ liblixexpr = declare_dependency( ) # FIXME: remove when https://git.lix.systems/lix-project/lix/issues/359 is fixed. -if is_static - liblixexpr_mstatic = declare_dependency( - include_directories : include_directories('../..'), - sources : libexpr_settings_header, - dependencies : [boehm], - link_whole : libexpr, - ) -else - liblixexpr_mstatic = liblixexpr -endif +liblixexpr_mstatic = is_static ? liblixexpr.as_link_whole() : liblixexpr meson.override_dependency('lix-expr', liblixexpr_mstatic) # FIXME: not using the pkg-config module because it creates way too many deps diff --git a/lix/libfetchers/meson.build b/lix/libfetchers/meson.build index 95c110736..e4b1532f2 100644 --- a/lix/libfetchers/meson.build +++ b/lix/libfetchers/meson.build @@ -46,22 +46,49 @@ libfetchers_settings_header = custom_target( install_dir : includedir / 'lix/libfetchers', ) -libfetchers = library( - 'lixfetchers', +dependencies = [ + liblixstore, + liblixutil, + nlohmann_json, + kj, +] + +libfetchers_temp = library( + is_static ? 'lixfetchers_temp' : 'lixfetchers', libfetchers_settings_header, libfetchers_sources, - dependencies : [ - liblixstore, - liblixutil, - nlohmann_json, - kj, - ], + dependencies : dependencies, include_directories : [ '../..' ], cpp_pch : cpp_pch, - install : true, + install : not is_static, # FIXME(Qyriad): is this right? install_rpath : libdir, ) +# FIXME: remove when https://git.lix.systems/lix-project/lix/issues/359 is fixed. +# FIXME: replace by prelink when https://github.com/mesonbuild/meson/pull/14846 is widely available. +if is_static + libfetchers_prelink = custom_target( + 'lixfetchers-prelink', + output : 'lixfetchers-prelink.o', + input : libfetchers_temp, + command : [ + cxx.cmd_array(), + '-r', + '-o', + '@OUTPUT@', + is_darwin ? '-Wl,-force_load' : '-Wl,--whole-archive', + '@INPUT@', + ], + ) + libfetchers = library( + 'lixfetchers', + [libfetchers_prelink], + dependencies : dependencies, + install : true, + ) +else + libfetchers = libfetchers_temp +endif install_headers(libfetchers_headers, subdir : 'lix/libfetchers', preserve_path : true) @@ -85,14 +112,4 @@ liblixfetchers = declare_dependency( link_with : libfetchers, ) -# FIXME: remove when https://git.lix.systems/lix-project/lix/issues/359 is fixed. -if is_static - liblixfetchers_mstatic = declare_dependency( - include_directories : include_directories('../..'), - sources : libfetchers_settings_header, - link_whole : libfetchers, - ) -else - liblixfetchers_mstatic = liblixfetchers -endif -meson.override_dependency('lix-fetchers', liblixfetchers_mstatic) +meson.override_dependency('lix-fetchers', liblixfetchers) diff --git a/lix/libstore/meson.build b/lix/libstore/meson.build index 2cc827e9f..24ffb5d86 100644 --- a/lix/libstore/meson.build +++ b/lix/libstore/meson.build @@ -402,8 +402,8 @@ if host_machine.system() == 'freebsd' dependencies += [ libprocstat ] endif -libstore = library( - 'lixstore', +libstore_temp = library( + is_static ? 'lixstore_temp' : 'lixstore', libstore_sources, libstore_generated_sources, libstore_settings_headers, @@ -413,10 +413,35 @@ libstore = library( dependencies : dependencies, cpp_args : cpp_args, cpp_pch : cpp_pch, - install : true, + install : not is_static, # FIXME(Qyriad): is this right? install_rpath : libdir, ) +# FIXME: remove when https://git.lix.systems/lix-project/lix/issues/359 is fixed. +# FIXME: replace by prelink when https://github.com/mesonbuild/meson/pull/14846 is widely available. +if is_static + libstore_prelink = custom_target( + 'lixstore-prelink', + output : 'lixstore-prelink.o', + input : libstore_temp, + command : [ + cxx.cmd_array(), + '-r', + '-o', + '@OUTPUT@', + is_darwin ? '-Wl,-force_load' : '-Wl,--whole-archive', + '@INPUT@', + ], + ) + libstore = library( + 'lixstore', + [libstore_prelink], + dependencies : dependencies, + install : true, + ) +else + libstore = libstore_temp +endif install_headers(libstore_headers, subdir : 'lix/libstore', preserve_path : true) @@ -427,17 +452,7 @@ liblixstore = declare_dependency( link_with : libstore, ) -# FIXME: remove when https://git.lix.systems/lix-project/lix/issues/359 is fixed. -if is_static - liblixstore_mstatic = declare_dependency( - include_directories : include_directories('../..'), - sources : libstore_settings_headers, - link_whole : libstore, - ) -else - liblixstore_mstatic = liblixstore -endif -meson.override_dependency('lix-store', liblixstore_mstatic) +meson.override_dependency('lix-store', liblixstore) # FIXME: not using the pkg-config module because it creates way too many deps # while meson migration is in progress, and we want to not include boost here diff --git a/lix/libutil/meson.build b/lix/libutil/meson.build index 19dba69f4..adcb571f2 100644 --- a/lix/libutil/meson.build +++ b/lix/libutil/meson.build @@ -323,8 +323,23 @@ foreach rpc : libutil_rpc libutil_rpc_sources += rpc[1] endforeach -libutil = library( - 'lixutil', +dependencies = [ + aws_sdk, + aws_s3, + boehm, + boost, + cpuid, + seccomp, + libarchive, + brotli, + openssl, + nlohmann_json, + kj, + capnp_rpc, +] + +libutil_temp = library( + is_static ? 'lixutil_temp' : 'lixutil', libutil_sources, libutil_rpc_sources, libutil_rpc_headers, @@ -333,25 +348,37 @@ libutil = library( deprecated_features_header, deprecated_features_impl_header, libutil_settings_headers, - dependencies : [ - aws_sdk, - aws_s3, - boehm, - boost, - cpuid, - seccomp, - libarchive, - brotli, - openssl, - nlohmann_json, - kj, - capnp_rpc, - ], + dependencies : dependencies, include_directories : [ '../..' ], cpp_pch : cpp_pch, implicit_include_directories : true, - install : true, + install : not is_static, ) +# FIXME: remove when https://git.lix.systems/lix-project/lix/issues/359 is fixed. +# FIXME: replace by prelink when https://github.com/mesonbuild/meson/pull/14846 is widely available. +if is_static + libutil_prelink = custom_target( + 'lixutil-prelink', + output : 'lixutil-prelink.o', + input : libutil_temp, + command : [ + cxx.cmd_array(), + '-r', + '-o', + '@OUTPUT@', + is_darwin ? '-Wl,-force_load' : '-Wl,--whole-archive', + '@INPUT@', + ], + ) + libutil = library( + 'lixutil', + [libutil_prelink], + dependencies : dependencies, + install : true, + ) +else + libutil = libutil_temp +endif install_headers(libutil_headers, subdir : 'lix/libutil', preserve_path : true) @@ -389,27 +416,4 @@ liblixutil = declare_dependency( link_with : libutil ) -# FIXME: remove when https://git.lix.systems/lix-project/lix/issues/359 is fixed. -if is_static - liblixutil_mstatic = declare_dependency( - include_directories : include_directories('../..'), - sources : [ - experimental_features_header, - deprecated_features_header, - libutil_settings_headers, - libutil_rpc_headers, - ], - dependencies: [ - boost, - # Everything has to link to kj if it uses libutil internally (ensured by - # lix-base pkg-config externally) - kj, - libarchive, - capnp_rpc, - ], - link_whole : libutil, - ) -else - liblixutil_mstatic = liblixutil -endif -meson.override_dependency('lix-util', liblixutil_mstatic) +meson.override_dependency('lix-util', liblixutil) diff --git a/lix/nix/meson.build b/lix/nix/meson.build index 08c4df6f7..6e09b5b78 100644 --- a/lix/nix/meson.build +++ b/lix/nix/meson.build @@ -169,10 +169,10 @@ nix = executable( dependencies : [ libasanoptions, liblixcmd, - liblixutil_mstatic, - liblixstore_mstatic, - liblixexpr_mstatic, - liblixfetchers_mstatic, + liblixutil, + liblixstore, + liblixexpr, + liblixfetchers, liblixmain, boehm, nlohmann_json, diff --git a/meson.build b/meson.build index 7a8750afc..9c696a8fe 100644 --- a/meson.build +++ b/meson.build @@ -16,19 +16,6 @@ # # Finally, lix/nix/meson.build defines the Nix command itself, relying on all prior meson files. # -# libstore, libexpr, and libfetchers have some special handling to make static builds work. -# Their use static constructors for dynamic registration of primops, store backends, etc -# gets borked during static link. We can't simply wholesale apply `link_whole :` either, -# because these libraries get linked multiple times since Lix's components are transitively -# dependent. So instead, each of those libraries have two dependency objects: -# liblix{store,expr,fetchers,util} and liblix{store,expr,fetchers,util}_mstatic ("maybe static"). -# The _mstatic versions should be used in the `dependencies :` arguments to ALL EXECUTABLES -# but executables ONLY. When we are not building statically (default_library != 'static'), -# they are equivalent. When we are building statically, the _mstatic version will be -# `link_whole :` rather than `link_with :`. -# FIXME: This hack should be removed when https://git.lix.systems/lix-project/lix/issues/359 -# is fixed. -# # lix-doc is built with Meson in lix-doc/meson.build, and linked into libcmd in # lix/libcmd/meson.build. When building outside the Nix sandbox, Meson will use the .wrap # files in subprojects/ to download and extract the dependency crates into subprojects/. diff --git a/tests/unit/meson.build b/tests/unit/meson.build index 3bac0b05e..1c835fd41 100644 --- a/tests/unit/meson.build +++ b/tests/unit/meson.build @@ -87,7 +87,7 @@ libutil_tester = executable( rapidcheck, gtest, liblixutil, - liblixexpr_mstatic, + liblixexpr, liblixutil_test_support, nlohmann_json, kj, @@ -155,7 +155,7 @@ libstore_tester = executable( libasanoptions, liblixstore_test_support, liblixutil_test_support, - liblixstore_mstatic, + liblixstore, liblixutil, rapidcheck, gtest, @@ -221,10 +221,10 @@ libexpr_tester = executable( libasanoptions, liblixexpr_test_support, liblixstore_test_support, - liblixstore_mstatic, + liblixstore, liblixutil, - liblixexpr_mstatic, - liblixfetchers_mstatic, + liblixexpr, + liblixfetchers, rapidcheck, gtest, nlohmann_json, @@ -253,8 +253,8 @@ libcmd_tester = executable( liblixcmd, liblixutil, liblixmain, - liblixexpr_mstatic, - liblixstore_mstatic, + liblixexpr, + liblixstore, gtest, boost, kj,