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
This commit is contained in:
Alois Wohlschlager
2025-07-30 06:18:39 +02:00
parent 321807a40e
commit 480fdf146d
7 changed files with 163 additions and 122 deletions
+41 -23
View File
@@ -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
+37 -20
View File
@@ -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)
+29 -14
View File
@@ -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
+45 -41
View File
@@ -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)
+4 -4
View File
@@ -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,
-13
View File
@@ -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/.
+7 -7
View File
@@ -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,