Repository: mesonbuild/meson

Issue:
Meson doesn't automatically call ldconfig
We received an issue in fwupd (https://github.com/hughsie/fwupd/issues/914) that indicated that an application wasn't working properly after installed.

The issue ended up being that libraries were installed into `/usr/local/lib/$(triplet)` which was referred to in a file in  `/etc/ld.so.conf.d` however since `ldconfig` wasn't run `/etc/ld.so.cache` wasn't refreshed and the application was failing to run.

So I wanted to see what thoughts were on setting default behavior of `ninja install` to call `ldconfig`, but let distros such as those using deb or rpm with delayed callbacks turn this behavior off.


Discussion:
Kind of doesn't matter since Fedora doesn't even include /usr/local in its ld.so.conf: https://bugzilla.redhat.com/show_bug.cgi?id=144967

This would be a good argument for either meson changing the default prefix to /usr, or Fedora unbreaking its default install.
At least Debian and Ubuntu do that aspect correctly.

Of course there is always the option of parsing `ld.so.conf` and adding the prefix to it when found missing like that Fedora case too.
We are _not_ touching system global configuration files. 
OK well what about showing a warning during install time?  It's easy enough to grep if $prefix is in `/etc/ld.so.conf.d/*` or `/etc/ld.so.conf`.  If it's not there at least people will know that it's going to be broken without fumbling around google.

And if it is there call `ldconfig` so the cache is refreshed and stuff just works.
Calling ldconfig after install is something we could do. Are there any downsides to calling it even if there is no need? We might consider calling other such tools too, such as icon cache refreshers. I think there's a bug open about those.

In any case patches welcome.
The main downside is that packaging systems will take longer to run `ninja install` for a command that does nothing for them.
Generally they need to run this on the user's machine, not the build machine and they use a so called "delayed trigger" for this action to avoid it happening.

So if calling `ldconfig` automatically is added then it might need to be configurable to allow turning it off to keep packagers happy.  Maybe an easy way to do this is if `DESTDIR` is set to not call any refresher type stuff.

Resolving patch:
diff --git a/mesonbuild/minstall.py b/mesonbuild/minstall.py
--- a/mesonbuild/minstall.py
+++ b/mesonbuild/minstall.py
@@ -237,14 +237,25 @@ def restore_selinux_contexts() -> None:
         # If the list of files is empty, do not try to call restorecon.
         return
 
-    with subprocess.Popen(['restorecon', '-F', '-f-', '-0'],
-                          stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc:
-        out, err = proc.communicate(input=b'\0'.join(os.fsencode(f) for f in selinux_updates) + b'\0')
-        if proc.returncode != 0 and not os.environ.get('DESTDIR'):
-            print('Failed to restore SELinux context of installed files...',
-                  'Standard output:', out.decode(),
-                  'Standard error:', err.decode(), sep='\n')
+    proc, out, err = Popen_safe(['restorecon', '-F', '-f-', '-0'], ('\0'.join(f for f in selinux_updates) + '\0'))
+    if proc.returncode != 0 :
+        print('Failed to restore SELinux context of installed files...',
+              'Standard output:', out,
+              'Standard error:', err, sep='\n')
 
+def apply_ldconfig() -> None:
+    '''
+    Apply ldconfig to update the ld.so.cache.
+    '''
+    if not shutil.which('ldconfig'):
+        # If we don't have ldconfig, failure is ignored quietly.
+        return
+
+    proc, out, err = Popen_safe(['ldconfig'])
+    if proc.returncode != 0:
+        print('Failed to apply ldconfig ...',
+              'Standard output:', out,
+              'Standard error:', err, sep='\n')
 
 def get_destdir_path(destdir: str, fullprefix: str, path: str) -> str:
     if os.path.isabs(path):
@@ -341,10 +352,14 @@ def set_mode(self, *args: T.Any, **kwargs: T.Any) -> None:
         if not self.dry_run:
             set_mode(*args, **kwargs)
 
-    def restore_selinux_contexts(self) -> None:
-        if not self.dry_run:
+    def restore_selinux_contexts(self, destdir: str) -> None:
+        if not self.dry_run and not destdir:
             restore_selinux_contexts()
 
+    def apply_ldconfig(self, destdir: str) -> None:
+        if not self.dry_run and not destdir:
+            apply_ldconfig()
+
     def Popen_safe(self, *args: T.Any, **kwargs: T.Any) -> T.Tuple[int, str, str]:
         if not self.dry_run:
             p, o, e = Popen_safe(*args, **kwargs)
@@ -518,7 +533,8 @@ def do_install(self, datafilename: str) -> None:
                 self.install_headers(d, dm, destdir, fullprefix)
                 self.install_man(d, dm, destdir, fullprefix)
                 self.install_data(d, dm, destdir, fullprefix)
-                self.restore_selinux_contexts()
+                self.restore_selinux_contexts(destdir)
+                self.apply_ldconfig(destdir)
                 self.run_install_script(d, destdir, fullprefix)
                 if not self.did_install_something:
                     self.log('Nothing to install.')


Test patch: