Repository: mesonbuild/meson

Issue:
Meson cannot install dylibs on macOS if strip=true
**Describe the bug**
When a project is set up with `-Dstrip=true`, Meson will strip installed dynamic libraries' symbols wholesale. This is not possible on macOS, where dylibs are expected to be stripped with `-X` ([source](https://www.unix.com/man-page/osx/1/strip/)):

```
       When an executable is built with all its dependent dynamic shared libraries, it is typically stripped with:
	      % strip -u -r executable
       which saves all undefined symbols (usually defined in the dynamic shared libraries) and all global symbols defined in the executable refer-
       enced  by the dynamic libraries (as marked by the static link editor when the executable was built).  This is the maximum level of striping
       for an executable that will still allow the program to run correctly with its libraries.

       If the executable loads objects, however, the global symbols that the objects reference from the executable also must not be stripped.	In
       this  case, you should list the global symbols that the executable wants to allow the objects to reference in a file, and those global sym-
       bols are then saved when the executable is stripped. For example:
	      % strip -u -r -s interface_symbols executable
       where the file interface_symbols would contain only those global symbols from the executable that the executable wants the  loaded  objects
       to have access to.

       For  objects  that will be loaded into an executable, you should trim the symbol table to limit the global symbols the executable will see.
       This would be done with:
	      % strip -s interface_symbols -u object
       which would leave only the undefined symbols and symbols listed in the file interface_symbols in the object file.  In this  case,  [strip(1)](https://www.unix.com/man-page/osx/1/strip/)
       has updated the relocation entries and indirect symbol table to reflect the new symbol table.

       For dynamic shared libraries, the maximum level of stripping is usually -x (to remove all non-global symbols).
```

Since the `strip` command is always called with no flags, irrespective of the operating system,

https://github.com/mesonbuild/meson/blob/fa2585d0b364143fb588b8bb611eaa4d1c277811/mesonbuild/minstall.py#L569

this will strip **all** symbols, and so,

```
Installing crunch/libcrunch.1.dylib to /Users/runner/.local/lib
Stripping target 'crunch/libcrunch.1.dylib'.
Could not strip file.

Stdout:


Stderr:
/Applications/Xcode_14.0.1.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/strip: error: symbols referenced by indirect symbol table entries that can't be stripped in: /Users/runner/.local/lib/libcrunch.1.dylib
___memcpy_chk
___memset_chk
___stack_chk_fail
___vsnprintf_chk
___vsprintf_chk
_dup
_dup2
_fclose
_fdopen$DARWIN_EXTSN
_feholdexcept
_fesetenv
_fileno
_flock
_fopen$DARWIN_EXTSN
_free
_ioctl
_malloc
_memcmp
_printf
_pthread_attr_destroy
_pthread_attr_init
_pthread_attr_setdetachstate
_pthread_attr_setscope
_pthread_create
_pthread_exit
_pthread_join
_strcmp
_strdup
_strlen
_strncmp
_vfprintf
___stack_chk_guard
___stdoutp


FAILED: meson-internal__install 
```

**To Reproduce**

The project is available here: https://github.com/amyspark/crunch/blob/amyspark/test/meson.build. Issue:

```
git clone https://github.com/amyspark/crunch --branch=amyspark/test
git submodule update --init
meson build -Dprefix=/tmp
ninja -C build install
``

**Expected behavior**

Libraries should be stripped and installed successfully.

**system parameters**
* Is this a [cross build](https://mesonbuild.com/Cross-compilation.html) or just a plain native build (for the same computer)? **Native**
* what operating system (e.g. MacOS Catalina, Windows 10, CentOS 8.0, Ubuntu 18.04, etc.) **macOS 11.7**
* what Python version are you using e.g. 3.8.0 **3.10.6**
* what `meson --version` **0.63.2**
* what `ninja --version` if it's a Ninja build **1.11.1**

Meson cannot install dylibs on macOS if strip=true
**Describe the bug**
When a project is set up with `-Dstrip=true`, Meson will strip installed dynamic libraries' symbols wholesale. This is not possible on macOS, where dylibs are expected to be stripped with `-X` ([source](https://www.unix.com/man-page/osx/1/strip/)):

```
       When an executable is built with all its dependent dynamic shared libraries, it is typically stripped with:
	      % strip -u -r executable
       which saves all undefined symbols (usually defined in the dynamic shared libraries) and all global symbols defined in the executable refer-
       enced  by the dynamic libraries (as marked by the static link editor when the executable was built).  This is the maximum level of striping
       for an executable that will still allow the program to run correctly with its libraries.

       If the executable loads objects, however, the global symbols that the objects reference from the executable also must not be stripped.	In
       this  case, you should list the global symbols that the executable wants to allow the objects to reference in a file, and those global sym-
       bols are then saved when the executable is stripped. For example:
	      % strip -u -r -s interface_symbols executable
       where the file interface_symbols would contain only those global symbols from the executable that the executable wants the  loaded  objects
       to have access to.

       For  objects  that will be loaded into an executable, you should trim the symbol table to limit the global symbols the executable will see.
       This would be done with:
	      % strip -s interface_symbols -u object
       which would leave only the undefined symbols and symbols listed in the file interface_symbols in the object file.  In this  case,  [strip(1)](https://www.unix.com/man-page/osx/1/strip/)
       has updated the relocation entries and indirect symbol table to reflect the new symbol table.

       For dynamic shared libraries, the maximum level of stripping is usually -x (to remove all non-global symbols).
```

Since the `strip` command is always called with no flags, irrespective of the operating system,

https://github.com/mesonbuild/meson/blob/fa2585d0b364143fb588b8bb611eaa4d1c277811/mesonbuild/minstall.py#L569

this will strip **all** symbols, and so,

```
Installing crunch/libcrunch.1.dylib to /Users/runner/.local/lib
Stripping target 'crunch/libcrunch.1.dylib'.
Could not strip file.

Stdout:


Stderr:
/Applications/Xcode_14.0.1.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/strip: error: symbols referenced by indirect symbol table entries that can't be stripped in: /Users/runner/.local/lib/libcrunch.1.dylib
___memcpy_chk
___memset_chk
___stack_chk_fail
___vsnprintf_chk
___vsprintf_chk
_dup
_dup2
_fclose
_fdopen$DARWIN_EXTSN
_feholdexcept
_fesetenv
_fileno
_flock
_fopen$DARWIN_EXTSN
_free
_ioctl
_malloc
_memcmp
_printf
_pthread_attr_destroy
_pthread_attr_init
_pthread_attr_setdetachstate
_pthread_attr_setscope
_pthread_create
_pthread_exit
_pthread_join
_strcmp
_strdup
_strlen
_strncmp
_vfprintf
___stack_chk_guard
___stdoutp


FAILED: meson-internal__install 
```

**To Reproduce**

The project is available here: https://github.com/amyspark/crunch/blob/amyspark/test/meson.build. Issue:

```
git clone https://github.com/amyspark/crunch --branch=amyspark/test
git submodule update --init
meson build -Dprefix=/tmp
ninja -C build install
``

**Expected behavior**

Libraries should be stripped and installed successfully.

**system parameters**
* Is this a [cross build](https://mesonbuild.com/Cross-compilation.html) or just a plain native build (for the same computer)? **Native**
* what operating system (e.g. MacOS Catalina, Windows 10, CentOS 8.0, Ubuntu 18.04, etc.) **macOS 11.7**
* what Python version are you using e.g. 3.8.0 **3.10.6**
* what `meson --version` **0.63.2**
* what `ninja --version` if it's a Ninja build **1.11.1**



Discussion:
This PR would be rather simple. Could you submit one?
@tristan957 sure, I also found a further bug while testing so I'll submit a couple separate MRs.
This PR would be rather simple. Could you submit one?
@tristan957 sure, I also found a further bug while testing so I'll submit a couple separate MRs.

Resolving patch:
diff --git a/mesonbuild/minstall.py b/mesonbuild/minstall.py
--- a/mesonbuild/minstall.py
+++ b/mesonbuild/minstall.py
@@ -27,7 +27,7 @@
 from . import build
 from . import environment
 from .backend.backends import InstallData
-from .mesonlib import MesonException, Popen_safe, RealPathAction, is_windows, setup_vsenv, pickle_load
+from .mesonlib import MesonException, Popen_safe, RealPathAction, is_windows, setup_vsenv, pickle_load, is_osx
 from .scripts import depfixer, destdir_join
 from .scripts.meson_exe import run_exe
 try:
@@ -566,7 +566,13 @@ def do_install(self, datafilename: str) -> None:
 
     def do_strip(self, strip_bin: T.List[str], fname: str, outname: str) -> None:
         self.log(f'Stripping target {fname!r}.')
-        returncode, stdo, stde = self.Popen_safe(strip_bin + [outname])
+        if is_osx():
+            # macOS expects dynamic objects to be stripped with -x maximum.
+            # To also strip the debug info, -S must be added.
+            # See: https://www.unix.com/man-page/osx/1/strip/
+            returncode, stdo, stde = self.Popen_safe(strip_bin + ['-S', '-x', outname])
+        else:
+            returncode, stdo, stde = self.Popen_safe(strip_bin + [outname])
         if returncode != 0:
             print('Could not strip file.\n')
             print(f'Stdout:\n{stdo}\n')
diff --git a/unittests/linuxliketests.py b/unittests/linuxliketests.py
--- a/unittests/linuxliketests.py
+++ b/unittests/linuxliketests.py
@@ -1773,25 +1773,41 @@ def test_cmake_multilib(self):
         # If so, we can test that cmake works with "gcc -m32"
         self.do_one_test_with_nativefile('../cmake/1 basic', "['gcc', '-m32']")
 
-    @skipUnless(is_linux(), 'Test only applicable to Linux')
+    @skipUnless(is_linux() or is_osx(), 'Test only applicable to Linux and macOS')
     def test_install_strip(self):
         testdir = os.path.join(self.unit_test_dir, '103 strip')
         self.init(testdir)
         self.build()
 
         destdir = self.installdir + self.prefix
-        lib = os.path.join(destdir, self.libdir, 'liba.so')
+        if is_linux():
+            lib = os.path.join(destdir, self.libdir, 'liba.so')
+        else:
+            lib = os.path.join(destdir, self.libdir, 'liba.dylib')
         install_cmd = self.meson_command + ['install', '--destdir', self.installdir]
 
         # Check we have debug symbols by default
         self._run(install_cmd, workdir=self.builddir)
-        stdout = self._run(['file', '-b', lib])
-        self.assertIn('not stripped', stdout)
+        if is_linux():
+            # file can detect stripped libraries on linux
+            stdout = self._run(['file', '-b', lib])
+            self.assertIn('not stripped', stdout)
+        else:
+            # on macOS we need to query dsymutil instead.
+            # Alternatively, check if __dyld_private is defined
+            # in the output of nm liba.dylib, but that is not
+            # 100% reliable, it needs linking to an external library
+            stdout = self._run(['dsymutil', '--dump-debug-map', lib])
+            self.assertIn('symbols:', stdout)
 
         # Check debug symbols got removed with --strip
         self._run(install_cmd + ['--strip'], workdir=self.builddir)
-        stdout = self._run(['file', '-b', lib])
-        self.assertNotIn('not stripped', stdout)
+        if is_linux():
+            stdout = self._run(['file', '-b', lib])
+            self.assertNotIn('not stripped', stdout)
+        else:
+            stdout = self._run(['dsymutil', '--dump-debug-map', lib])
+            self.assertNotIn('symbols:', stdout)
 
     def test_isystem_default_removal_with_symlink(self):
         env = get_fake_env()


Test patch:
diff --git a/test cases/unit/103 strip/lib.c b/test cases/unit/103 strip/lib.c
--- a/test cases/unit/103 strip/lib.c	
+++ b/test cases/unit/103 strip/lib.c	
@@ -1 +1,3 @@
-void func(void){}
+#include <stdio.h>
+
+void func(void){ fprintf(stderr, "Test 1 2 3\n"); }