Repository: mesonbuild/meson

Issue:
ldconfig on install corrupts freebsd cache
**Describe the bug**
2c3a68bc30cf and 6b7d43ebe0d7 make it so `ldconfig` is invoked after install. But it seems like thos corrupts the cache on FreeBSD (maybe other BSDs as well), and results in errors similar to:

    ld-elf.so.1: Shared object "libreadline.so.8" not found, required by "bash"

when running executables after `ninja install`.

Example full log: https://builds.sr.ht/~emersion/job/630186#task-tinywl

Downstream discussion: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/3349#note_1162574

cc @jbeich 

**To Reproduce**
Run `ninja install` on freebsd.

**Expected behavior**
`ldconfig` cache isn't corrupted.

**system parameters**
* Is this a [cross build](https://mesonbuild.com/Cross-compilation.html) or just a plain native build (for the same computer)? no
* what operating system: FreeBSD
* what Python version are you using: N/A
* what `meson --version`: 0.60.1
* what `ninja --version`: 1.10.2



Discussion:
What a fascinating problem. We don't even specify any directories, so according to https://www.freebsd.org/cgi/man.cgi?query=ldconfig&sektion=8 it does... What? Removes the hints file and replaces it with absolutely nothing at all? That seems like quite a pointless design footgun, either it should require a directory and error out if none are given, or when given none, it should default to refreshing from the existing cached ones.

Sigh...

Given we don't specify any directories, what does even the -m do? If it cleared everything as is, does -m scan zero directories and merge nothing with the existing cache? That seems like it's just completely pointless to run ldconfig on the BSDs.
`ldconfig` (no arguments) vs. `ldconfig -v` (no paths) behaving differently is probably a FreeBSD bug. CC @kostikbel

When you pass the "verbose" flag it should not change the actual effective functionality, yeah.
There is no cache, ldconfig does not scan anything.  The hints file only contains pathes. 

I will consider changing 'ldconfig -v'.
https://reviews.freebsd.org/D33058
Nice.

Meanwhile I've spent most of the evening trying to get the meson testsuite running for freebsd on a sourcehut build runner, hopefully we can improve the quality of freebsd support by actually testing it. ;)

I actually discovered a couple of long-standing bugs...
On Meson side `apply_ldconfig` could support non-default `--prefix`. `ldconfig -m /path/to/prefix` is probably safe as https://github.com/netbsd/src/commit/d8f640e15a63 predates OpenBSD fork and https://github.com/freebsd/freebsd-src/commit/f606c848fa4c predates DragonFly fork.

Steps to reproduce:
```
$ pkg install meson git
$ git clone https://github.com/swaywm/sway
$ git clone https://gitlab.freedesktop.org/wlroots/wlroots.git sway/subprojects/wlroots
$ su root -c "sed -n '/packages:/,/:/ { /:/d; s/#.*//; s/- //; p; }' sway/.builds/freebsd.yml | xargs -o pkg install"
$ meson setup --prefix=/tmp/prefix sway_build sway
$ meson compile -C sway_build
$ su root -c 'meson install -C sway_build'

$ /tmp/prefix/bin/sway
ld-elf.so.1: Shared object "libwlroots.so.10" not found, required by "sway"
```

Here's my two cents, sorry to chirp in but I must.

If you're going to have meson automatically run ldconfig on Linux, then on other systems it should do absolutely nothing with ldconfig. 
FWIW, wlroots CI is green again on FreeBSD. This Meson patch is what fixes it: https://cgit.freebsd.org/ports/commit/?id=51c83df293e0770cfb9778b53d55149fa3d27b3f

Resolving patch:
diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py
--- a/mesonbuild/backend/backends.py
+++ b/mesonbuild/backend/backends.py
@@ -107,7 +107,7 @@ def __init__(self, build_dir: str, trees: T.List[str]):
         self.trees = trees
 
 class InstallData:
-    def __init__(self, source_dir: str, build_dir: str, prefix: str,
+    def __init__(self, source_dir: str, build_dir: str, prefix: str, libdir: str,
                  strip_bin: T.List[str], install_umask: T.Union[str, int],
                  mesonintrospect: T.List[str], version: str):
         # TODO: in python 3.8 or with typing_Extensions install_umask could be:
@@ -115,6 +115,7 @@ def __init__(self, source_dir: str, build_dir: str, prefix: str,
         self.source_dir = source_dir
         self.build_dir = build_dir
         self.prefix = prefix
+        self.libdir = libdir
         self.strip_bin = strip_bin
         self.install_umask = install_umask
         self.targets: T.List[TargetInstallData] = []
@@ -1485,6 +1486,7 @@ def create_install_data(self) -> InstallData:
         d = InstallData(self.environment.get_source_dir(),
                         self.environment.get_build_dir(),
                         self.environment.get_prefix(),
+                        self.environment.get_libdir(),
                         strip_bin,
                         umask,
                         self.environment.get_build_command() + ['introspect'],
diff --git a/mesonbuild/minstall.py b/mesonbuild/minstall.py
--- a/mesonbuild/minstall.py
+++ b/mesonbuild/minstall.py
@@ -244,7 +244,7 @@ def restore_selinux_contexts() -> None:
               'Standard output:', out,
               'Standard error:', err, sep='\n')
 
-def apply_ldconfig(dm: DirMaker) -> None:
+def apply_ldconfig(dm: DirMaker, libdir: str) -> None:
     '''
     Apply ldconfig to update the ld.so.cache.
     '''
@@ -252,7 +252,14 @@ def apply_ldconfig(dm: DirMaker) -> None:
         # If we don't have ldconfig, failure is ignored quietly.
         return
 
-    if 'bsd' in platform.system().lower():
+    platlower = platform.system().lower()
+    if platlower == 'dragonfly' or 'bsd' in platlower:
+        if libdir in dm.all_dirs:
+            proc, out, err = Popen_safe(['ldconfig', '-m', libdir])
+            if proc.returncode != 0:
+                print('Failed to apply ldconfig ...',
+                      'Standard output:', out,
+                      'Standard error:', err, sep='\n')
         return
 
     # Try to update ld cache, it could fail if we don't have permission.
@@ -372,9 +379,9 @@ def restore_selinux_contexts(self, destdir: str) -> None:
         if not self.dry_run and not destdir:
             restore_selinux_contexts()
 
-    def apply_ldconfig(self, dm: DirMaker, destdir: str) -> None:
+    def apply_ldconfig(self, dm: DirMaker, destdir: str, libdir: str) -> None:
         if not self.dry_run and not destdir:
-            apply_ldconfig(dm)
+            apply_ldconfig(dm, libdir)
 
     def Popen_safe(self, *args: T.Any, **kwargs: T.Any) -> T.Tuple[int, str, str]:
         if not self.dry_run:
@@ -536,6 +543,7 @@ def do_install(self, datafilename: str) -> None:
             os.environ['DESTDIR'] = destdir
         destdir = destdir or ''
         fullprefix = destdir_join(destdir, d.prefix)
+        libdir = os.path.join(d.prefix, d.libdir)
 
         if d.install_umask != 'preserve':
             assert isinstance(d.install_umask, int)
@@ -551,7 +559,7 @@ def do_install(self, datafilename: str) -> None:
                 self.install_emptydir(d, dm, destdir, fullprefix)
                 self.install_data(d, dm, destdir, fullprefix)
                 self.restore_selinux_contexts(destdir)
-                self.apply_ldconfig(dm, destdir)
+                self.apply_ldconfig(dm, destdir, libdir)
                 self.run_install_script(d, destdir, fullprefix)
                 if not self.did_install_something:
                     self.log('Nothing to install.')


Test patch: