From 814f4909f959d6d8a84e89058819a0894821fb63 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Thu, 13 Oct 2016 00:54:01 +0530 Subject: [PATCH 1/3] pkgconfig: Handle library names starting with 'lib' Sometimes people want the library to start with 'lib' everywhere, which is achieved by setting name_prefix to '' and the target name to 'libfoo'. In that case, try to get the pkg-config '-lfoo' arg right. Also, don't warn about anything on Windows Fixes https://github.com/mesonbuild/meson/issues/889 --- mesonbuild/modules/pkgconfig.py | 35 +++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/mesonbuild/modules/pkgconfig.py b/mesonbuild/modules/pkgconfig.py index daa11ea69..9c235b5a5 100644 --- a/mesonbuild/modules/pkgconfig.py +++ b/mesonbuild/modules/pkgconfig.py @@ -19,6 +19,24 @@ import os class PkgConfigModule: + def _get_lname(self, l, msg, pcfile): + # Nothing special + if not l.name_prefix_set: + return l.name + # Sometimes people want the library to start with 'lib' everywhere, + # which is achieved by setting name_prefix to '' and the target name to + # 'libfoo'. In that case, try to get the pkg-config '-lfoo' arg correct. + if l.prefix == '' and l.name.startswith('lib'): + return l.name[3:] + # If the library is imported via an import library which is always + # named after the target name, '-lfoo' is correct. + if l.import_filename: + return l.name + # In other cases, we can't guarantee that the compiler will be able to + # find the library via '-lfoo', so tell the user that. + mlog.log(mlog.red('WARNING:'), msg.format(l.name, 'name_prefix', l.name, pcfile)) + return l.name + def generate_pkgconfig_file(self, state, libraries, subdirs, name, description, version, pcfile, pub_reqs, priv_reqs, priv_libs): coredata = state.environment.get_coredata() @@ -45,20 +63,17 @@ class PkgConfigModule: 'Libraries.private: {}\n'.format(' '.join(priv_libs))) ofile.write('Libs: -L${libdir} ') msg = 'Library target {0!r} has {1!r} set. Compilers ' \ - 'may not find it from its \'-l{0}\' linker flag in the ' \ - '{2!r} pkg-config file.' + 'may not find it from its \'-l{2}\' linker flag in the ' \ + '{3!r} pkg-config file.' for l in libraries: if l.custom_install_dir: ofile.write('-L${prefix}/%s ' % l.custom_install_dir) - # Warn, but not if the filename starts with 'lib'. This can - # happen, for instance, if someone really wants to use the - # 'lib' prefix on all systems, not just on UNIX, or if the the - # target name itself starts with 'lib'. - if l.name_prefix_set and not l.filename.startswith('lib'): - mlog.log(mlog.red('WARNING:'), msg.format(l.name, 'name_prefix', pcfile)) + lname = self._get_lname(l, msg, pcfile) + # If using a custom suffix, the compiler may not be able to + # find the library if l.name_suffix_set: - mlog.log(mlog.red('WARNING:'), msg.format(l.name, 'name_suffix', pcfile)) - ofile.write('-l%s ' % l.name) + mlog.log(mlog.red('WARNING:'), msg.format(l.name, 'name_suffix', lname, pcfile)) + ofile.write('-l{} '.format(lname)) ofile.write('\n') ofile.write('CFlags: ') for h in subdirs: From 5e384b8396d5472fc8f8ec46b5e775f4ac6557ef Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Thu, 13 Oct 2016 02:05:43 +0530 Subject: [PATCH 2/3] dependencies: Use a wrapper for calling pkg-config Reduces duplicated code, and also use universal_newlines=True which avoids having to decode the bytes output to string. We mostly need this so we can pass the *current* process environment to pkg-config which might've changed since the module was imported. Without this, subprocess.Popen invokes pkg-config with a stale environment (used in the unittest added later) --- mesonbuild/dependencies.py | 41 ++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/mesonbuild/dependencies.py b/mesonbuild/dependencies.py index ccff7a72c..fd1fc7239 100644 --- a/mesonbuild/dependencies.py +++ b/mesonbuild/dependencies.py @@ -115,16 +115,12 @@ class PkgConfigDependency(Dependency): mlog.debug('Determining dependency %s with pkg-config executable %s.' % (name, pkgbin)) self.pkgbin = pkgbin - p = subprocess.Popen([pkgbin, '--modversion', name], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - out = p.communicate()[0] - if p.returncode != 0: + ret, self.modversion = self._call_pkgbin(['--modversion', name]) + if ret != 0: if self.required: raise DependencyException('%s dependency %s not found.' % (self.type_string, name)) self.modversion = 'none' return - self.modversion = out.decode().strip() found_msg = ['%s dependency' % self.type_string, mlog.bold(name), 'found:'] self.version_requirement = kwargs.get('version', None) if self.version_requirement is None: @@ -149,27 +145,30 @@ class PkgConfigDependency(Dependency): # Fetch the libraries and library paths needed for using this self._set_libs() - def _set_cargs(self): - p = subprocess.Popen([self.pkgbin, '--cflags', self.name], - stdout=subprocess.PIPE, stderr=subprocess.PIPE) + def _call_pkgbin(self, args): + p = subprocess.Popen([self.pkgbin] + args, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + env=os.environ, universal_newlines=True) out = p.communicate()[0] - if p.returncode != 0: + return (p.returncode, out.strip()) + + def _set_cargs(self): + ret, out = self._call_pkgbin(['--cflags', self.name]) + if ret != 0: raise DependencyException('Could not generate cargs for %s:\n\n%s' % \ (self.name, out.decode(errors='ignore'))) - self.cargs = out.decode().split() + self.cargs = out.split() def _set_libs(self): - libcmd = [self.pkgbin, '--libs'] + libcmd = [self.name, '--libs'] if self.static: libcmd.append('--static') - p = subprocess.Popen(libcmd + [self.name], - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - out = p.communicate()[0] - if p.returncode != 0: + ret, out = self._call_pkgbin(libcmd) + if ret != 0: raise DependencyException('Could not generate libs for %s:\n\n%s' % \ (self.name, out.decode(errors='ignore'))) self.libs = [] - for lib in out.decode().split(): + for lib in out.split(): if lib.endswith(".la"): shared_libname = self.extract_libtool_shlib(lib) shared_lib = os.path.join(os.path.dirname(lib), shared_libname) @@ -185,16 +184,14 @@ class PkgConfigDependency(Dependency): self.libs.append(lib) def get_variable(self, variable_name): - p = subprocess.Popen([self.pkgbin, '--variable=%s' % variable_name, self.name], - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - out = p.communicate()[0] + ret, out = self._call_pkgbin(['--variable=' + variable_name, self.name]) variable = '' - if p.returncode != 0: + if ret != 0: if self.required: raise DependencyException('%s dependency %s not found.' % (self.type_string, self.name)) else: - variable = out.decode().strip() + variable = out.strip() mlog.debug('return of subprocess : %s' % variable) return variable From 6ffae922cc7df4929bc0990a0991cf75fd7de57a Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Thu, 13 Oct 2016 02:09:17 +0530 Subject: [PATCH 3/3] Add a unittest for pkg-config file generation This also tests that -lfoo is correctly added to libfoo.pc when the library name is 'libfoo' and name_prefix is '' --- mesonbuild/dependencies.py | 15 ++++++++----- run_unittests.py | 21 +++++++++++++++++++ .../51 pkgconfig-gen/installed_files.txt | 1 + .../common/51 pkgconfig-gen/meson.build | 14 +++++++++++-- 4 files changed, 44 insertions(+), 7 deletions(-) diff --git a/mesonbuild/dependencies.py b/mesonbuild/dependencies.py index fd1fc7239..6a4c32c7a 100644 --- a/mesonbuild/dependencies.py +++ b/mesonbuild/dependencies.py @@ -87,6 +87,7 @@ class PkgConfigDependency(Dependency): self.is_libtool = False self.required = kwargs.get('required', True) self.static = kwargs.get('static', False) + self.silent = kwargs.get('silent', False) if not isinstance(self.static, bool): raise DependencyException('Static keyword must be boolean') self.cargs = [] @@ -132,14 +133,16 @@ class PkgConfigDependency(Dependency): if not self.is_found: found_msg += [mlog.red('NO'), 'found {!r}'.format(self.modversion), 'but need {!r}'.format(self.version_requirement)] - mlog.log(*found_msg) + if not self.silent: + mlog.log(*found_msg) if self.required: raise DependencyException( 'Invalid version of a dependency, needed %s %s found %s.' % (name, self.version_requirement, self.modversion)) return found_msg += [mlog.green('YES'), self.modversion] - mlog.log(*found_msg) + if not self.silent: + mlog.log(*found_msg) # Fetch cargs to be used while using this dependency self._set_cargs() # Fetch the libraries and library paths needed for using this @@ -214,14 +217,16 @@ class PkgConfigDependency(Dependency): stderr=subprocess.PIPE) out = p.communicate()[0] if p.returncode == 0: - mlog.log('Found pkg-config:', mlog.bold(shutil.which('pkg-config')), - '(%s)' % out.decode().strip()) + if not self.silent: + mlog.log('Found pkg-config:', mlog.bold(shutil.which('pkg-config')), + '(%s)' % out.decode().strip()) PkgConfigDependency.pkgconfig_found = True return except Exception: pass PkgConfigDependency.pkgconfig_found = False - mlog.log('Found Pkg-config:', mlog.red('NO')) + if not self.silent: + mlog.log('Found Pkg-config:', mlog.red('NO')) def found(self): return self.is_found diff --git a/run_unittests.py b/run_unittests.py index 9ea9e237b..79cdae095 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -18,6 +18,7 @@ import subprocess import re, json import tempfile from mesonbuild.environment import detect_ninja +from mesonbuild.dependencies import PkgConfigDependency def get_soname(fname): # HACK, fix to not use shell. @@ -28,6 +29,13 @@ def get_soname(fname): if m is not None: return m.group(1) +class FakeEnvironment(object): + def __init__(self): + self.cross_info = None + + def is_cross_build(self): + return False + class LinuxlikeTests(unittest.TestCase): def setUp(self): super().setUp() @@ -38,9 +46,11 @@ class LinuxlikeTests(unittest.TestCase): self.ninja_command = [detect_ninja(), '-C', self.builddir] self.common_test_dir = os.path.join(src_root, 'test cases/common') self.output = b'' + self.orig_env = os.environ.copy() def tearDown(self): shutil.rmtree(self.builddir) + os.environ = self.orig_env super().tearDown() def init(self, srcdir): @@ -87,5 +97,16 @@ class LinuxlikeTests(unittest.TestCase): compdb = self.get_compdb() self.assertTrue('-fPIC' not in compdb[0]['command']) + def test_pkgconfig_gen(self): + testdir = os.path.join(self.common_test_dir, '51 pkgconfig-gen') + self.init(testdir) + env = FakeEnvironment() + kwargs = {'required': True, 'silent': True} + os.environ['PKG_CONFIG_LIBDIR'] = os.path.join(self.builddir, 'meson-private') + simple_dep = PkgConfigDependency('libfoo', env, kwargs) + self.assertTrue(simple_dep.found()) + self.assertEqual(simple_dep.get_version(), '1.0') + self.assertTrue('-lfoo' in simple_dep.get_link_args()) + if __name__ == '__main__': unittest.main() diff --git a/test cases/common/51 pkgconfig-gen/installed_files.txt b/test cases/common/51 pkgconfig-gen/installed_files.txt index d6a23d716..3c44d28cf 100644 --- a/test cases/common/51 pkgconfig-gen/installed_files.txt +++ b/test cases/common/51 pkgconfig-gen/installed_files.txt @@ -1,2 +1,3 @@ usr/include/simple.h usr/lib/pkgconfig/simple.pc +usr/lib/pkgconfig/libfoo.pc diff --git a/test cases/common/51 pkgconfig-gen/meson.build b/test cases/common/51 pkgconfig-gen/meson.build index b5f692105..e31bfe6ee 100644 --- a/test cases/common/51 pkgconfig-gen/meson.build +++ b/test cases/common/51 pkgconfig-gen/meson.build @@ -15,5 +15,15 @@ pkgg.generate( description : 'A simple demo library.', requires : 'glib-2.0', # Not really, but only here to test that this works. requires_private : ['gio-2.0', 'gobject-2.0'], - libraries_private : '-lz', -) + libraries_private : '-lz') + +# Test that name_prefix='' and name='libfoo' results in '-lfoo' +lib2 = shared_library('libfoo', 'simple.c', + name_prefix : '', + version : libver) + +pkgg.generate( + libraries : lib2, + name : 'libfoo', + version : libver, + description : 'A foo library.')