Repository: mesonbuild/meson

Issue:
cross compilation, static libraries and ranlib
In Arch Linux it seems that we have to manually run ranlib to be able to use static libraries compiled using meson

here you can find the meson wrapper (after 0.48 release we can finally remove the wine hack, thanks for fixing!)

https://aur.archlinux.org/cgit/aur.git/tree/meson-mingw-wrapper?h=mingw-w64-meson

and the cross file

https://aur.archlinux.org/cgit/aur.git/tree/toolchain-mingw.meson?h=mingw-w64-meson

as you can see ranlib is defined, but we have to manually run ranlib to be able to use the static libraries, for example:

https://aur.archlinux.org/cgit/aur.git/tree/PKGBUILD?h=mingw-w64-glib2#n62

isn't meson supposed to do this?


Discussion:
We always use `csr` on `ar` to build static archives so ranlib should never be needed (and is in fact not even invoked by Meson). If this is not working then either the linker is not obeying the `s` argument or it is missing for some reason, which would be a bug in Meson.
In build.ninja I can see

```
rule STATIC_CROSS_LINKER
 command =  rm -f $out && x86_64-w64-mingw32-gcc-ar $LINK_ARGS $out $in
 description = Linking static target $out.
```

and

```
build glib/libglib-2.0.a: STATIC_CROSS_LINKER glib/glib@@glib-2.0@sha/deprecated_gallocator.c.obj glib/glib@@glib-2.0@sha/deprecated_gcache.c.obj glib/glib@@glib-2.0@sha/deprecated_gcompletion.c.obj glib/glib@@glib-2.0@sha/deprecated_grel.c.obj glib/glib@@glib-2.0@sha/deprecated_gthread-deprecated.c.obj glib/glib@@glib-2.0@sha/garcbox.c.obj glib/glib@@glib-2.0@sha/garray.c.obj glib/glib@@glib-2.0@sha/gasyncqueue.c.obj glib/glib@@glib-2.0@sha/gatomic.c.obj glib/glib@@glib-2.0@sha/gbacktrace.c.obj glib/glib@@glib-2.0@sha/gbase64.c.obj glib/glib@@glib-2.0@sha/gbitlock.c.obj glib/glib@@glib-2.0@sha/gbookmarkfile.c.obj glib/glib@@glib-2.0@sha/gbytes.c.obj glib/glib@@glib-2.0@sha/gcharset.c.obj glib/glib@@glib-2.0@sha/gchecksum.c.obj glib/glib@@glib-2.0@sha/gconvert.c.obj glib/glib@@glib-2.0@sha/gdataset.c.obj glib/glib@@glib-2.0@sha/gdate.c.obj glib/glib@@glib-2.0@sha/gdatetime.c.obj glib/glib@@glib-2.0@sha/gdir.c.obj glib/glib@@glib-2.0@sha/genviron.c.obj glib/glib@@glib-2.0@sha/gerror.c.obj glib/glib@@glib-2.0@sha/gfileutils.c.obj glib/glib@@glib-2.0@sha/ggettext.c.obj glib/glib@@glib-2.0@sha/ghash.c.obj glib/glib@@glib-2.0@sha/ghmac.c.obj glib/glib@@glib-2.0@sha/ghook.c.obj glib/glib@@glib-2.0@sha/ghostutils.c.obj glib/glib@@glib-2.0@sha/giochannel.c.obj glib/glib@@glib-2.0@sha/gkeyfile.c.obj glib/glib@@glib-2.0@sha/glib-init.c.obj glib/glib@@glib-2.0@sha/glib-private.c.obj glib/glib@@glib-2.0@sha/glist.c.obj glib/glib@@glib-2.0@sha/gmain.c.obj glib/glib@@glib-2.0@sha/gmappedfile.c.obj glib/glib@@glib-2.0@sha/gmarkup.c.obj glib/glib@@glib-2.0@sha/gmem.c.obj glib/glib@@glib-2.0@sha/gmessages.c.obj glib/glib@@glib-2.0@sha/gnode.c.obj glib/glib@@glib-2.0@sha/goption.c.obj glib/glib@@glib-2.0@sha/gpattern.c.obj glib/glib@@glib-2.0@sha/gpoll.c.obj glib/glib@@glib-2.0@sha/gprimes.c.obj glib/glib@@glib-2.0@sha/gqsort.c.obj glib/glib@@glib-2.0@sha/gquark.c.obj glib/glib@@glib-2.0@sha/gqueue.c.obj glib/glib@@glib-2.0@sha/grand.c.obj glib/glib@@glib-2.0@sha/grcbox.c.obj glib/glib@@glib-2.0@sha/grefcount.c.obj glib/glib@@glib-2.0@sha/grefstring.c.obj glib/glib@@glib-2.0@sha/gregex.c.obj glib/glib@@glib-2.0@sha/gscanner.c.obj glib/glib@@glib-2.0@sha/gsequence.c.obj glib/glib@@glib-2.0@sha/gshell.c.obj glib/glib@@glib-2.0@sha/gslice.c.obj glib/glib@@glib-2.0@sha/gslist.c.obj glib/glib@@glib-2.0@sha/gstdio.c.obj glib/glib@@glib-2.0@sha/gstrfuncs.c.obj glib/glib@@glib-2.0@sha/gstring.c.obj glib/glib@@glib-2.0@sha/gstringchunk.c.obj glib/glib@@glib-2.0@sha/gtestutils.c.obj glib/glib@@glib-2.0@sha/gthread.c.obj glib/glib@@glib-2.0@sha/gthreadpool.c.obj glib/glib@@glib-2.0@sha/gtimer.c.obj glib/glib@@glib-2.0@sha/gtimezone.c.obj glib/glib@@glib-2.0@sha/gtranslit.c.obj glib/glib@@glib-2.0@sha/gtrashstack.c.obj glib/glib@@glib-2.0@sha/gtree.c.obj glib/glib@@glib-2.0@sha/guniprop.c.obj glib/glib@@glib-2.0@sha/gutf8.c.obj glib/glib@@glib-2.0@sha/gunibreak.c.obj glib/glib@@glib-2.0@sha/gunicollate.c.obj glib/glib@@glib-2.0@sha/gunidecomp.c.obj glib/glib@@glib-2.0@sha/gurifuncs.c.obj glib/glib@@glib-2.0@sha/gutils.c.obj glib/glib@@glib-2.0@sha/guuid.c.obj glib/glib@@glib-2.0@sha/gvariant.c.obj glib/glib@@glib-2.0@sha/gvariant-core.c.obj glib/glib@@glib-2.0@sha/gvariant-parser.c.obj glib/glib@@glib-2.0@sha/gvariant-serialiser.c.obj glib/glib@@glib-2.0@sha/gvarianttypeinfo.c.obj glib/glib@@glib-2.0@sha/gvarianttype.c.obj glib/glib@@glib-2.0@sha/gversion.c.obj glib/glib@@glib-2.0@sha/gwakeup.c.obj glib/glib@@glib-2.0@sha/gprintf.c.obj glib/glib@@glib-2.0@sha/gwin32.c.obj glib/glib@@glib-2.0@sha/gspawn-win32.c.obj glib/glib@@glib-2.0@sha/giowin32.c.obj glib/glib@@glib-2.0@sha/gthread-win32.c.obj
 LINK_ARGS = csrD
```

but static linking the generated libraries fails with errors like these:

```
 /usr/lib/gcc/x86_64-w64-mingw32/8.2.0/../../../../x86_64-w64-mingw32/bin/ld: /usr/x86_64-w64-mingw32/lib/libglib-2.0.a: error adding symbols: archive has no index; run ranlib to add one
```

```
 /usr/lib/gcc/i686-w64-mingw32/8.1.1/../../../../i686-w64-mingw32/lib/../lib/libfribidi.a: error adding symbols: Archive has no index; run ranlib to add one
```

manually executing ranlib solves the issue
Try to compile the static library with `ninja -v`. If the flags are properly used and the library still does not work, then this would seem to be a bug in the static linker you are using.
the flags seem ok

```
[109/650] rm -f glib/libglib-2.0.a && i686-w64-mingw32-gcc-ar csrD glib/libglib-2.0.a 'glib/glib@@glib-2.0@sha/deprecated_gallocator.c.obj' 'glib/glib@@glib-2.0@sha/deprecated_gcache.c.obj' 'glib/glib@@glib-2.0@sha/deprecated_gcompletion.c.obj' 'glib/glib@@glib-2.0@sha/deprecated_grel.c.obj' 'glib/glib@@glib-2.0@sha/deprecated_gthread-deprecated.c.obj' 'glib/glib@@glib-2.0@sha/garcbox.c.obj' 'glib/glib@@glib-2.0@sha/garray.c.obj' 'glib/glib@@glib-2.0@sha/gasyncqueue.c.obj' 'glib/glib@@glib-2.0@sha/gatomic.c.obj' 'glib/glib@@glib-2.0@sha/gbacktrace.c.obj' 'glib/glib@@glib-2.0@sha/gbase64.c.obj' 'glib/glib@@glib-2.0@sha/gbitlock.c.obj' 'glib/glib@@glib-2.0@sha/gbookmarkfile.c.obj' 'glib/glib@@glib-2.0@sha/gbytes.c.obj' 'glib/glib@@glib-2.0@sha/gcharset.c.obj' 'glib/glib@@glib-2.0@sha/gchecksum.c.obj' 'glib/glib@@glib-2.0@sha/gconvert.c.obj' 'glib/glib@@glib-2.0@sha/gdataset.c.obj' 'glib/glib@@glib-2.0@sha/gdate.c.obj' 'glib/glib@@glib-2.0@sha/gdatetime.c.obj' 'glib/glib@@glib-2.0@sha/gdir.c.obj' 'glib/glib@@glib-2.0@sha/genviron.c.obj' 'glib/glib@@glib-2.0@sha/gerror.c.obj' 'glib/glib@@glib-2.0@sha/gfileutils.c.obj' 'glib/glib@@glib-2.0@sha/ggettext.c.obj' 'glib/glib@@glib-2.0@sha/ghash.c.obj' 'glib/glib@@glib-2.0@sha/ghmac.c.obj' 'glib/glib@@glib-2.0@sha/ghook.c.obj' 'glib/glib@@glib-2.0@sha/ghostutils.c.obj' 'glib/glib@@glib-2.0@sha/giochannel.c.obj' 'glib/glib@@glib-2.0@sha/gkeyfile.c.obj' 'glib/glib@@glib-2.0@sha/glib-init.c.obj' 'glib/glib@@glib-2.0@sha/glib-private.c.obj' 'glib/glib@@glib-2.0@sha/glist.c.obj' 'glib/glib@@glib-2.0@sha/gmain.c.obj' 'glib/glib@@glib-2.0@sha/gmappedfile.c.obj' 'glib/glib@@glib-2.0@sha/gmarkup.c.obj' 'glib/glib@@glib-2.0@sha/gmem.c.obj' 'glib/glib@@glib-2.0@sha/gmessages.c.obj' 'glib/glib@@glib-2.0@sha/gnode.c.obj' 'glib/glib@@glib-2.0@sha/goption.c.obj' 'glib/glib@@glib-2.0@sha/gpattern.c.obj' 'glib/glib@@glib-2.0@sha/gpoll.c.obj' 'glib/glib@@glib-2.0@sha/gprimes.c.obj' 'glib/glib@@glib-2.0@sha/gqsort.c.obj' 'glib/glib@@glib-2.0@sha/gquark.c.obj' 'glib/glib@@glib-2.0@sha/gqueue.c.obj' 'glib/glib@@glib-2.0@sha/grand.c.obj' 'glib/glib@@glib-2.0@sha/grcbox.c.obj' 'glib/glib@@glib-2.0@sha/grefcount.c.obj' 'glib/glib@@glib-2.0@sha/grefstring.c.obj' 'glib/glib@@glib-2.0@sha/gregex.c.obj' 'glib/glib@@glib-2.0@sha/gscanner.c.obj' 'glib/glib@@glib-2.0@sha/gsequence.c.obj' 'glib/glib@@glib-2.0@sha/gshell.c.obj' 'glib/glib@@glib-2.0@sha/gslice.c.obj' 'glib/glib@@glib-2.0@sha/gslist.c.obj' 'glib/glib@@glib-2.0@sha/gstdio.c.obj' 'glib/glib@@glib-2.0@sha/gstrfuncs.c.obj' 'glib/glib@@glib-2.0@sha/gstring.c.obj' 'glib/glib@@glib-2.0@sha/gstringchunk.c.obj' 'glib/glib@@glib-2.0@sha/gtestutils.c.obj' 'glib/glib@@glib-2.0@sha/gthread.c.obj' 'glib/glib@@glib-2.0@sha/gthreadpool.c.obj' 'glib/glib@@glib-2.0@sha/gtimer.c.obj' 'glib/glib@@glib-2.0@sha/gtimezone.c.obj' 'glib/glib@@glib-2.0@sha/gtranslit.c.obj' 'glib/glib@@glib-2.0@sha/gtrashstack.c.obj' 'glib/glib@@glib-2.0@sha/gtree.c.obj' 'glib/glib@@glib-2.0@sha/guniprop.c.obj' 'glib/glib@@glib-2.0@sha/gutf8.c.obj' 'glib/glib@@glib-2.0@sha/gunibreak.c.obj' 'glib/glib@@glib-2.0@sha/gunicollate.c.obj' 'glib/glib@@glib-2.0@sha/gunidecomp.c.obj' 'glib/glib@@glib-2.0@sha/gurifuncs.c.obj' 'glib/glib@@glib-2.0@sha/gutils.c.obj' 'glib/glib@@glib-2.0@sha/guuid.c.obj' 'glib/glib@@glib-2.0@sha/gvariant.c.obj' 'glib/glib@@glib-2.0@sha/gvariant-core.c.obj' 'glib/glib@@glib-2.0@sha/gvariant-parser.c.obj' 'glib/glib@@glib-2.0@sha/gvariant-serialiser.c.obj' 'glib/glib@@glib-2.0@sha/gvarianttypeinfo.c.obj' 'glib/glib@@glib-2.0@sha/gvarianttype.c.obj' 'glib/glib@@glib-2.0@sha/gversion.c.obj' 'glib/glib@@glib-2.0@sha/gwakeup.c.obj' 'glib/glib@@glib-2.0@sha/gprintf.c.obj' 'glib/glib@@glib-2.0@sha/gwin32.c.obj' 'glib/glib@@glib-2.0@sha/gspawn-win32.c.obj' 'glib/glib@@glib-2.0@sha/giowin32.c.obj' 'glib/glib@@glib-2.0@sha/gthread-win32.c.obj'
```

the issue happens with both gcc 8.1 and 8.2, do you know a working compiler version?
Sorry, no.
The libraries are built fine. The problem is that meson is striping installed static archives a little too aggressively during install when --strip option is used. That's the real bug here and the thing that needs to be fixed.

If you drop --strip from that archlinux wrapper, you'll be able to link with the installed static libraries.
@megous thanks for your investigation, if this will not be fixed in meson I'll remove the strip option from archlinux wrapper
What is the exact thing Meson does that is "too aggressive" and what would be the correct behaviour?
@jpakkane I haven't investigated what strip options messon uses. It should only be removing non-global symbols though.
@jpakkane Quick look shows that minstall is just calling strip without any extra options. When stripping installed static libraries, it should use `-x` option or something similar.
If you know the correct thing to do instead and can test it, please file a pull request with your changes.
@jpakkane I just run meson without --strip for now. Correct thing is to not strip all symbols from the symbol table, just the local ones, which the `-x` option ensures. Just read the manpage for strip. It's that way forever, and it's the *only* way to keep static libraries in a usable state after strip.
I will not do the pull request, I don't know enough Python, to write test for the testsuite. The test would need to build some static library run meson install --strip and then check with nm for presence of global symbols in the installed library.

There don't seem to be any tests for stripping during install in meson, so a blind fix, may be enough. I don't know what the policy is.

Fix involves adding -x here:

    Popen_safe(d.strip_bin + [outname])

probably like this:

    Popen_safe(d.strip_bin + ['-x', outname])

So anyone who cares can try to fix it.
Or --strip-unneeded may also be used instead of -x. That's what Slackware used ages ago. I also checked what ArchLinux makepkg uses, and it uses -S (short for --strip-debug), which is more conservative.

Adding any of these will fix the issue.

ArchLinux's makepkg also differentiates between executables and libraries. Executables are stripped completely and any libraries with --strip-debug. So meson can perhaps make that distinction too.
@jpakkane Please rename the issue to reflect the root cause, so that people can at least find it when searching for strip issues.

I suggest: "Meson strips global symbols from libraries when meson setup --strip option is used"

The current title is inadequate.

Resolving patch:
diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py
--- a/mesonbuild/backend/backends.py
+++ b/mesonbuild/backend/backends.py
@@ -1089,7 +1089,20 @@ def generate_target_install(self, d):
             install_mode = t.get_custom_install_mode()
             # Install the target output(s)
             if isinstance(t, build.BuildTarget):
-                should_strip = self.get_option_for_target('strip', t)
+                # In general, stripping static archives is tricky and full of pitfalls.
+                # Wholesale stripping of static archives with a command such as
+                #
+                #   strip libfoo.a
+                #
+                # is broken, as GNU's strip will remove *every* symbol in a static
+                # archive. One solution to this nonintuitive behaviour would be
+                # to only strip local/debug symbols. Unfortunately, strip arguments
+                # are not specified by POSIX and therefore not portable. GNU's `-g`
+                # option (i.e. remove debug symbols) is equivalent to Apple's `-S`.
+                #
+                # TODO: Create GNUStrip/AppleStrip/etc. hierarchy for more
+                #       fine-grained stripping of static archives.
+                should_strip = not isinstance(t, build.StaticLibrary) and self.get_option_for_target('strip', t)
                 # Install primary build output (library/executable/jar, etc)
                 # Done separately because of strip/aliases/rpath
                 if outdirs[0] is not False:
diff --git a/run_unittests.py b/run_unittests.py
--- a/run_unittests.py
+++ b/run_unittests.py
@@ -5330,6 +5330,33 @@ def test_pkgconfig_secondary_dependencies(self):
                       override_envvars={'PKG_CONFIG_PATH': ':'.join(pkg_dir)})
             self.build()
 
+    @skipIfNoPkgconfig
+    def test_static_archive_stripping(self):
+        '''
+        Check that Meson produces valid static archives with --strip enabled
+        '''
+        with tempfile.TemporaryDirectory() as tempdirname:
+            testdirbase = os.path.join(self.unit_test_dir, '68 static archive stripping')
+
+            # build lib
+            self.new_builddir()
+            testdirlib = os.path.join(testdirbase, 'lib')
+            testlibprefix = os.path.join(tempdirname, 'libprefix')
+            self.init(testdirlib, extra_args=['--prefix=' + testlibprefix,
+                                              '--libdir=lib',
+                                              '--default-library=static',
+                                              '--buildtype=debug',
+                                              '--strip'], default_args=False)
+            self.build()
+            self.install(use_destdir=False)
+
+            # build executable (uses lib, fails if static archive has been stripped incorrectly)
+            pkg_dir = os.path.join(testlibprefix, 'lib/pkgconfig')
+            self.new_builddir()
+            self.init(os.path.join(testdirbase, 'app'),
+                      override_envvars={'PKG_CONFIG_PATH': pkg_dir})
+            self.build()
+
     @skipIfNoPkgconfig
     def test_pkgconfig_formatting(self):
         testdir = os.path.join(self.unit_test_dir, '38 pkgconfig format')


Test patch:
diff --git a/test cases/unit/68 static archive stripping/app/appA.c b/test cases/unit/68 static archive stripping/app/appA.c
new file mode 100644
--- /dev/null
+++ b/test cases/unit/68 static archive stripping/app/appA.c	
@@ -0,0 +1,4 @@
+#include <stdio.h>
+#include <libA.h>
+
+int main() { printf("The answer is: %d\n", libA_func()); }
diff --git a/test cases/unit/68 static archive stripping/app/appB.c b/test cases/unit/68 static archive stripping/app/appB.c
new file mode 100644
--- /dev/null
+++ b/test cases/unit/68 static archive stripping/app/appB.c	
@@ -0,0 +1,4 @@
+#include <stdio.h>
+#include <libB.h>
+
+int main() { printf("The answer is: %d\n", libB_func()); }
diff --git a/test cases/unit/68 static archive stripping/app/meson.build b/test cases/unit/68 static archive stripping/app/meson.build
new file mode 100644
--- /dev/null
+++ b/test cases/unit/68 static archive stripping/app/meson.build	
@@ -0,0 +1,7 @@
+project('app', ['c'])
+
+a = dependency('test-a')
+b = dependency('test-b')
+
+executable('appA', files('appA.c'), dependencies : a)
+executable('appB', files('appB.c'), dependencies : b)
diff --git a/test cases/unit/68 static archive stripping/lib/libA.c b/test cases/unit/68 static archive stripping/lib/libA.c
new file mode 100644
--- /dev/null
+++ b/test cases/unit/68 static archive stripping/lib/libA.c	
@@ -0,0 +1,5 @@
+#include <libA.h>
+
+static int libA_func_impl(void) { return 0; }
+
+int libA_func(void) { return libA_func_impl(); }
diff --git a/test cases/unit/68 static archive stripping/lib/libA.h b/test cases/unit/68 static archive stripping/lib/libA.h
new file mode 100644
--- /dev/null
+++ b/test cases/unit/68 static archive stripping/lib/libA.h	
@@ -0,0 +1 @@
+int libA_func(void);
diff --git a/test cases/unit/68 static archive stripping/lib/libB.c b/test cases/unit/68 static archive stripping/lib/libB.c
new file mode 100644
--- /dev/null
+++ b/test cases/unit/68 static archive stripping/lib/libB.c	
@@ -0,0 +1,5 @@
+#include <libB.h>
+
+static int libB_func_impl(void) { return 0; }
+
+int libB_func(void) { return libB_func_impl(); }
diff --git a/test cases/unit/68 static archive stripping/lib/libB.h b/test cases/unit/68 static archive stripping/lib/libB.h
new file mode 100644
--- /dev/null
+++ b/test cases/unit/68 static archive stripping/lib/libB.h	
@@ -0,0 +1 @@
+int libB_func(void);
diff --git a/test cases/unit/68 static archive stripping/lib/meson.build b/test cases/unit/68 static archive stripping/lib/meson.build
new file mode 100644
--- /dev/null
+++ b/test cases/unit/68 static archive stripping/lib/meson.build	
@@ -0,0 +1,23 @@
+project('lib', ['c'])
+
+pkg = import('pkgconfig')
+
+a = library('test-a', files('libA.c'), install: true)
+install_headers(files('libA.h'), subdir: 'libA')
+pkg.generate(
+    a,
+    version: '0.0',
+    description: 'test library libA',
+    filebase: 'test-a',
+    name: 'test library libA',
+    subdirs: 'libA')
+
+b = static_library('test-b', files('libB.c'), install: true)
+install_headers(files('libB.h'), subdir: 'libB')
+pkg.generate(
+    b,
+    version: '0.0',
+    description: 'test library libB',
+    filebase: 'test-b',
+    name: 'test library libB',
+    subdirs: 'libB')