Repository: mesonbuild/meson

Issue:
Library search path needs to query the linker
The get_library_dirs() function in mesonlib hard-codes paths, and that's completely wrong. The search path for C/C++ libraries is decided by the linker. Specifically, the output of:

`gcc -Xlinker --verbose | grep SEARCH_DIR`

Gives the list of search directories in order of priority. I don't have a patch for this because I'm not sure how this ties in with the compiler detected/selected during configuration; especially cross-compilation.



Discussion:
This is a problem even on Linux btw, for example meson fails to find libm on my powerpc/Debian box where it is in /usr/lib/powerpc-linux-gnu/.

I forgot to add; it's a problem on 64-bit Fedora itself. The default search dirs for x86_64 are: 

```
$ gcc -Xlinker --verbose | grep SEARCH_DIR
SEARCH_DIR("/usr/x86_64-redhat-linux/lib64");
SEARCH_DIR("/usr/lib64");
SEARCH_DIR("/usr/local/lib64");
SEARCH_DIR("/lib64");
SEARCH_DIR("/usr/x86_64-redhat-linux/lib");
SEARCH_DIR("/usr/local/lib");
SEARCH_DIR("/lib");
SEARCH_DIR("/usr/lib");
```

(reformatted for readability)

So, Meson always finds the 32-bit libraries first, and then fails at compile time.

For system libraries like this a better check would be to check if the compiler accepts `-llibname` because sometimes the libraries don't exist physically. As an example OSX does not have libm because it is in libc. However it is perfectly happy to accept `-lm`, it is just ignored. Time spent detecting libraries manually is usually wasted because system library path thingy is massively complicated.

It even works with MSVC, but only because on Windows the system provides no libraries whatsoever. :)

That being said people seem to want to use this so it probably should work better, though personally I would be happier if we could just get rid of it altogether (which is not really feasible).

Thinking further, maybe we should make `find_library` a method of the compiler and it could do this in the background. Then you could do this:

```
libbob = cc.find_library('bob')
libcrossbob = cross_cc.find_library('bob')
```

and then the two would point to different libraries.

Yeah, I think `cc.find_library()` makes the most sense; would tie in with the language too. For instance finding Mono/C# libraries. I was actually surprised while reading the API reference in the very beginning that `find_library` was not already compiler-specific. I just assumed there was some magic there to take care of that. :)


Resolving patch:
diff --git a/mesonbuild/compilers.py b/mesonbuild/compilers.py
--- a/mesonbuild/compilers.py
+++ b/mesonbuild/compilers.py
@@ -190,6 +190,9 @@ def unix_link_flags_to_native(self, args):
     def unix_compile_flags_to_native(self, args):
         return args
 
+    def find_library(self, libname):
+        raise EnvironmentException('Language {} does not support library finding.'.format(self.language))
+
 class CCompiler(Compiler):
     def __init__(self, exelist, version, is_cross, exe_wrapper=None):
         super().__init__(exelist, version)
@@ -563,6 +566,16 @@ def has_type(self, typename, prefix, extra_args):
 '''
         return self.compiles(templ % (prefix, typename), extra_args)
 
+    def find_library(self, libname):
+        code = '''int main(int argc, char **argv) {
+    return 0;
+}
+        '''
+        linkarg = '-l' + libname
+        if self.links(code, extra_args=[linkarg]):
+            return linkarg
+        return None
+
     def thread_flags(self):
         return ['-pthread']
 
diff --git a/mesonbuild/dependencies.py b/mesonbuild/dependencies.py
--- a/mesonbuild/dependencies.py
+++ b/mesonbuild/dependencies.py
@@ -388,7 +388,14 @@ class ExternalLibrary(Dependency):
     def __init__(self, name, fullpath=None, silent=False):
         super().__init__()
         self.name = name
-        self.fullpath = fullpath
+        # Rename fullpath to link_args once standalone find_library() gets removed.
+        if fullpath is not None:
+            if isinstance(fullpath, list):
+                self.fullpath = fullpath
+            else:
+                self.fullpath = [fullpath]
+        else:
+            self.fullpath = fullpath
         if not silent:
             if self.found():
                 mlog.log('Library', mlog.bold(name), 'found:', mlog.green('YES'),
@@ -401,7 +408,7 @@ def found(self):
 
     def get_link_args(self):
         if self.found():
-            return [self.fullpath]
+            return self.fullpath
         return []
 
 class BoostDependency(Dependency):
diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py
--- a/mesonbuild/interpreter.py
+++ b/mesonbuild/interpreter.py
@@ -27,6 +27,8 @@
 
 import importlib
 
+find_lib_deprecation_printed = False
+
 class InterpreterException(coredata.MesonException):
     pass
 
@@ -581,6 +583,7 @@ def __init__(self, compiler, env):
                              'alignment' : self.alignment_method,
                              'version' : self.version_method,
                              'cmd_array' : self.cmd_array_method,
+                             'find_library': self.find_library_method,
                             })
 
     def version_method(self, args, kwargs):
@@ -750,6 +753,19 @@ def has_header_method(self, args, kwargs):
         mlog.log('Has header "%s":' % string, h)
         return haz
 
+    def find_library_method(self, args, kwargs):
+        if len(args) != 1:
+            raise InterpreterException('find_library method takes one argument.')
+        libname = args[0]
+        if not isinstance(libname, str):
+            raise InterpreterException('Library name not a string.')
+        required = kwargs.get('required', True)
+        if not isinstance(required, bool):
+            raise InterpreterException('required must be boolean.')
+        linkarg = self.compiler.find_library(libname)
+        lib = dependencies.ExternalLibrary(libname, linkarg)
+        return ExternalLibraryHolder(lib)
+
 class ModuleState:
     pass
 
@@ -1526,6 +1542,10 @@ def func_find_program(self, node, args, kwargs):
         return progobj
 
     def func_find_library(self, node, args, kwargs):
+        global find_lib_deprecation_printed
+        if not find_lib_deprecation_printed:
+            find_lib_deprecation_printed = True
+            mlog.log(mlog.red('DEPRECATION:'), 'find_library() is deprecated, use the corresponding method in compiler object instead.')
         self.validate_arguments(args, 1, [str])
         required = kwargs.get('required', True)
         if not isinstance(required, bool):


Test patch:
diff --git a/test cases/linuxlike/2 external library/meson.build b/test cases/linuxlike/2 external library/meson.build
--- a/test cases/linuxlike/2 external library/meson.build	
+++ b/test cases/linuxlike/2 external library/meson.build	
@@ -1,6 +1,8 @@
 project('external library', 'c')
 
-zlib = find_library('z')
+cc = meson.get_compiler('c')
+zlib = find_library('z') # DEPRECATED
+zlib2 = cc.find_library('z') # The modern way.
 
 # Verify that link testing works.
 linkcode = '''#include<zlib.h>
@@ -16,13 +18,16 @@ int main(int argc, char **argv) {
   return ptr == 0;
 }
 '''
-cc = meson.get_compiler('c')
+
 assert(cc.links(linkcode, args : '-lz', name : 'Test link against zlib'), 'Linking test failed.')
 assert(not cc.links(nolinkcode, name : 'Failing link'), 'Linking succeeded when it should have failed.')
 
 e = executable('zprog', 'prog.c', dependencies : zlib)
 test('libtest', e)
 
+e2 = executable('zprog_alt', 'prog.c', dependencies : zlib2)
+test('libtest_alt', e2)
+
 # Test that ext deps work via an internal dep.
 intdep = declare_dependency(dependencies : zlib)
 exe2 = executable('zprog2', 'prog.c', dependencies : intdep)