Merge pull request #2757 from xclaesse/pkgconfig

pkgconfig: Allow passing Dependency objects to library(_private)
pull/2838/head
Jussi Pakkanen 7 years ago committed by GitHub
commit dd3f49af0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 13
      docs/markdown/Pkgconfig-module.md
  2. 5
      mesonbuild/dependencies/misc.py
  3. 5
      mesonbuild/dependencies/ui.py
  4. 140
      mesonbuild/modules/pkgconfig.py
  5. 30
      run_unittests.py
  6. 61
      test cases/common/51 pkgconfig-gen/meson.build

@ -23,9 +23,16 @@ keyword arguments.
- `install_dir` the directory to install to, defaults to the value of
option `libdir` followed by `/pkgconfig`
- `libraries` a list of built libraries (usually results of
shared_library) that the user needs to link against
- `libraries_private` list of strings to put in the
`Libraries.private` field
shared_library) that the user needs to link against. Arbitraty strings can
also be provided and they will be added into the `Libs` field. Since 0.45.0
dependencies of built libraries will be automatically added to `Libs.private`
field. If a dependency is provided by pkg-config then it will be added in
`Requires.private` instead. Other type of dependency objects can also be passed
and will result in their `link_args` and `compile_args` to be added to `Libs`
and `Cflags` fields.
- `libraries_private` list of built libraries or strings to put in the
`Libs.private` field. Since 0.45.0 it can also contain dependency objects,
their `link_args` will be added to `Libs.private`.
- `name` the name of this library
- `subdirs` which subdirs of `include` should be added to the header
search path, for example if you install headers into

@ -429,6 +429,7 @@ class MPIDependency(ExternalDependency):
self.link_args = pkgdep.get_link_args()
self.version = pkgdep.get_version()
self.is_found = True
self.pcdep = pkgdep
break
except Exception:
pass
@ -630,6 +631,7 @@ class Python3Dependency(ExternalDependency):
self.link_args = self.pkgdep.get_link_args()
self.version = self.pkgdep.get_version()
self.is_found = True
self.pcdep = self.pkgdep
return
else:
self.pkgdep = None
@ -763,6 +765,7 @@ class PcapDependency(ExternalDependency):
self.compile_args = pcdep.get_compile_args()
self.link_args = pcdep.get_link_args()
self.version = pcdep.get_version()
self.pcdep = pcdep
return
except Exception as e:
mlog.debug('Pcap not found via pkgconfig. Trying next, error was:', str(e))
@ -805,6 +808,7 @@ class CupsDependency(ExternalDependency):
self.compile_args = pcdep.get_compile_args()
self.link_args = pcdep.get_link_args()
self.version = pcdep.get_version()
self.pcdep = pcdep
return
except Exception as e:
mlog.debug('cups not found via pkgconfig. Trying next, error was:', str(e))
@ -854,6 +858,7 @@ class LibWmfDependency(ExternalDependency):
self.compile_args = pcdep.get_compile_args()
self.link_args = pcdep.get_link_args()
self.version = pcdep.get_version()
self.pcdep = pcdep
return
except Exception as e:
mlog.debug('LibWmf not found via pkgconfig. Trying next, error was:', str(e))

@ -47,6 +47,7 @@ class GLDependency(ExternalDependency):
self.compile_args = pcdep.get_compile_args()
self.link_args = pcdep.get_link_args()
self.version = pcdep.get_version()
self.pcdep = pcdep
return
except Exception:
pass
@ -228,6 +229,7 @@ class QtBaseDependency(ExternalDependency):
self.link_args += m.get_link_args()
self.is_found = True
self.version = m.version
self.pcdep = list(modules.values())
# Try to detect moc, uic, rcc
if 'Core' in modules:
core = modules['Core']
@ -235,6 +237,7 @@ class QtBaseDependency(ExternalDependency):
corekwargs = {'required': 'false', 'silent': 'true'}
core = PkgConfigDependency(self.qtpkgname + 'Core', self.env, corekwargs,
language=self.language)
self.pcdep.append(core)
# Used by self.compilers_detect()
self.bindir = self.get_pkgconfig_host_bins(core)
if not self.bindir:
@ -387,6 +390,7 @@ class SDL2Dependency(ExternalDependency):
self.compile_args = pcdep.get_compile_args()
self.link_args = pcdep.get_link_args()
self.version = pcdep.get_version()
self.pcdep = pcdep
return
except Exception as e:
mlog.debug('SDL 2 not found via pkgconfig. Trying next, error was:', str(e))
@ -461,6 +465,7 @@ class VulkanDependency(ExternalDependency):
self.compile_args = pcdep.get_compile_args()
self.link_args = pcdep.get_link_args()
self.version = pcdep.get_version()
self.pcdep = pcdep
return
except Exception:
pass

@ -16,12 +16,87 @@ import os
from pathlib import PurePath
from .. import build
from .. import dependencies
from .. import mesonlib
from .. import mlog
from . import ModuleReturnValue
from . import ExtensionModule
from ..interpreterbase import permittedKwargs
class DependenciesHelper:
def __init__(self, name):
self.name = name
self.pub_libs = []
self.pub_reqs = []
self.priv_libs = []
self.priv_reqs = []
self.cflags = []
def add_pub_libs(self, libs):
libs, reqs, cflags = self._process_libs(libs, True)
self.pub_libs += libs
self.pub_reqs += reqs
self.cflags += cflags
def add_priv_libs(self, libs):
libs, reqs, _ = self._process_libs(libs, False)
self.priv_libs += libs
self.priv_reqs += reqs
def add_pub_reqs(self, reqs):
self.pub_reqs += mesonlib.stringlistify(reqs)
def add_priv_reqs(self, reqs):
self.priv_reqs += mesonlib.stringlistify(reqs)
def add_cflags(self, cflags):
self.cflags += mesonlib.stringlistify(cflags)
def _process_libs(self, libs, public):
libs = mesonlib.listify(libs)
processed_libs = []
processed_reqs = []
processed_cflags = []
for obj in libs:
if hasattr(obj, 'held_object'):
obj = obj.held_object
if hasattr(obj, 'pcdep'):
pcdeps = mesonlib.listify(obj.pcdep)
processed_reqs += [i.name for i in pcdeps]
elif hasattr(obj, 'generated_pc'):
processed_reqs.append(obj.generated_pc)
elif isinstance(obj, dependencies.PkgConfigDependency):
processed_reqs.append(obj.name)
elif isinstance(obj, dependencies.ThreadDependency):
processed_libs += obj.get_compiler().thread_link_flags(obj.env)
processed_cflags += obj.get_compiler().thread_flags(obj.env)
elif isinstance(obj, dependencies.Dependency):
processed_libs += obj.get_link_args()
processed_cflags += obj.get_compile_args()
elif isinstance(obj, (build.SharedLibrary, build.StaticLibrary)):
processed_libs.append(obj)
if public:
if not hasattr(obj, 'generated_pc'):
obj.generated_pc = self.name
self.add_priv_libs(obj.get_dependencies())
self.add_priv_libs(obj.get_external_deps())
elif isinstance(obj, str):
processed_libs.append(obj)
else:
raise mesonlib.MesonException('library argument not a string, library or dependency object.')
return processed_libs, processed_reqs, processed_cflags
def remove_dups(self):
self.pub_libs = list(set(self.pub_libs))
self.pub_reqs = list(set(self.pub_reqs))
self.priv_libs = list(set(self.priv_libs))
self.priv_reqs = list(set(self.priv_reqs))
self.cflags = list(set(self.cflags))
# Remove from pivate libs/reqs if they are in public already
self.priv_libs = [i for i in self.priv_libs if i not in self.pub_libs]
self.priv_reqs = [i for i in self.priv_reqs if i not in self.pub_reqs]
class PkgConfigModule(ExtensionModule):
@ -64,9 +139,9 @@ class PkgConfigModule(ExtensionModule):
subdir = subdir.replace(prefix, '')
return subdir
def generate_pkgconfig_file(self, state, libraries, subdirs, name, description,
url, version, pcfile, pub_reqs, priv_reqs,
conflicts, priv_libs, extra_cflags, variables):
def generate_pkgconfig_file(self, state, deps, subdirs, name, description,
url, version, pcfile, conflicts, variables):
deps.remove_dups()
coredata = state.environment.get_coredata()
outdir = state.environment.scratch_dir
fname = os.path.join(outdir, pcfile)
@ -78,6 +153,8 @@ class PkgConfigModule(ExtensionModule):
ofile.write('prefix={}\n'.format(self._escape(prefix)))
ofile.write('libdir={}\n'.format(self._escape('${prefix}' / libdir)))
ofile.write('includedir={}\n'.format(self._escape('${prefix}' / incdir)))
if variables:
ofile.write('\n')
for k, v in variables:
ofile.write('{}={}\n'.format(k, self._escape(v)))
ofile.write('\n')
@ -87,11 +164,11 @@ class PkgConfigModule(ExtensionModule):
if len(url) > 0:
ofile.write('URL: %s\n' % url)
ofile.write('Version: %s\n' % version)
if len(pub_reqs) > 0:
ofile.write('Requires: {}\n'.format(' '.join(pub_reqs)))
if len(priv_reqs) > 0:
if len(deps.pub_reqs) > 0:
ofile.write('Requires: {}\n'.format(' '.join(deps.pub_reqs)))
if len(deps.priv_reqs) > 0:
ofile.write(
'Requires.private: {}\n'.format(' '.join(priv_reqs)))
'Requires.private: {}\n'.format(' '.join(deps.priv_reqs)))
if len(conflicts) > 0:
ofile.write('Conflicts: {}\n'.format(' '.join(conflicts)))
@ -99,6 +176,7 @@ class PkgConfigModule(ExtensionModule):
msg = 'Library target {0!r} has {1!r} set. Compilers ' \
'may not find it from its \'-l{2}\' linker flag in the ' \
'{3!r} pkg-config file.'
Lflags = []
for l in libs:
if isinstance(l, str):
yield l
@ -107,9 +185,12 @@ class PkgConfigModule(ExtensionModule):
if install_dir is False:
continue
if isinstance(install_dir, str):
yield '-L${prefix}/%s ' % self._escape(self._make_relative(prefix, install_dir))
Lflag = '-L${prefix}/%s ' % self._escape(self._make_relative(prefix, install_dir))
else: # install_dir is True
yield '-L${libdir}'
Lflag = '-L${libdir}'
if Lflag not in Lflags:
Lflags.append(Lflag)
yield Lflag
lname = self._get_lname(l, msg, pcfile)
# If using a custom suffix, the compiler may not be able to
# find the library
@ -117,10 +198,10 @@ class PkgConfigModule(ExtensionModule):
mlog.warning(msg.format(l.name, 'name_suffix', lname, pcfile))
yield '-l%s' % lname
if len(libraries) > 0:
ofile.write('Libs: {}\n'.format(' '.join(generate_libs_flags(libraries))))
if len(priv_libs) > 0:
ofile.write('Libs.private: {}\n'.format(' '.join(generate_libs_flags(priv_libs))))
if len(deps.pub_libs) > 0:
ofile.write('Libs: {}\n'.format(' '.join(generate_libs_flags(deps.pub_libs))))
if len(deps.priv_libs) > 0:
ofile.write('Libs.private: {}\n'.format(' '.join(generate_libs_flags(deps.priv_libs))))
ofile.write('Cflags:')
for h in subdirs:
ofile.write(' ')
@ -128,30 +209,18 @@ class PkgConfigModule(ExtensionModule):
ofile.write('-I${includedir}')
else:
ofile.write(self._escape(PurePath('-I${includedir}') / h))
for f in extra_cflags:
for f in deps.cflags:
ofile.write(' ')
ofile.write(self._escape(f))
ofile.write('\n')
def process_libs(self, libs):
libs = mesonlib.listify(libs)
processed_libs = []
for l in libs:
if hasattr(l, 'held_object'):
l = l.held_object
if not isinstance(l, (build.SharedLibrary, build.StaticLibrary, str)):
raise mesonlib.MesonException('Library argument not a library object nor a string.')
processed_libs.append(l)
return processed_libs
@permittedKwargs({'libraries', 'version', 'name', 'description', 'filebase',
'subdirs', 'requires', 'requires_private', 'libraries_private',
'install_dir', 'extra_cflags', 'variables', 'url', 'd_module_versions'})
def generate(self, state, args, kwargs):
if len(args) > 0:
raise mesonlib.MesonException('Pkgconfig_gen takes no positional arguments.')
libs = self.process_libs(kwargs.get('libraries', []))
priv_libs = self.process_libs(kwargs.get('libraries_private', []))
subdirs = mesonlib.stringlistify(kwargs.get('subdirs', ['.']))
version = kwargs.get('version', None)
if not isinstance(version, str):
@ -168,16 +237,20 @@ class PkgConfigModule(ExtensionModule):
url = kwargs.get('url', '')
if not isinstance(url, str):
raise mesonlib.MesonException('URL is not a string.')
pub_reqs = mesonlib.stringlistify(kwargs.get('requires', []))
priv_reqs = mesonlib.stringlistify(kwargs.get('requires_private', []))
conflicts = mesonlib.stringlistify(kwargs.get('conflicts', []))
extra_cflags = mesonlib.stringlistify(kwargs.get('extra_cflags', []))
deps = DependenciesHelper(filebase)
deps.add_pub_libs(kwargs.get('libraries', []))
deps.add_priv_libs(kwargs.get('libraries_private', []))
deps.add_pub_reqs(kwargs.get('requires', []))
deps.add_priv_reqs(kwargs.get('requires_private', []))
deps.add_cflags(kwargs.get('extra_cflags', []))
dversions = kwargs.get('d_module_versions', None)
if dversions:
compiler = state.environment.coredata.compilers.get('d')
if compiler:
extra_cflags.extend(compiler.get_feature_args({'versions': dversions}))
deps.add_cflags(compiler.get_feature_args({'versions': dversions}))
def parse_variable_list(stringlist):
reserved = ['prefix', 'libdir', 'includedir']
@ -211,9 +284,8 @@ class PkgConfigModule(ExtensionModule):
pkgroot = os.path.join(state.environment.coredata.get_builtin_option('libdir'), 'pkgconfig')
if not isinstance(pkgroot, str):
raise mesonlib.MesonException('Install_dir must be a string.')
self.generate_pkgconfig_file(state, libs, subdirs, name, description, url,
version, pcfile, pub_reqs, priv_reqs,
conflicts, priv_libs, extra_cflags, variables)
self.generate_pkgconfig_file(state, deps, subdirs, name, description, url,
version, pcfile, conflicts, variables)
res = build.Data(mesonlib.File(True, state.environment.get_scratch_dir(), pcfile), pkgroot)
return ModuleReturnValue(res, [res])

@ -2010,6 +2010,36 @@ class LinuxlikeTests(BasePlatformTests):
self.assertEqual(foo_dep.get_pkgconfig_variable('foo', {}), 'bar')
self.assertPathEqual(foo_dep.get_pkgconfig_variable('datadir', {}), '/usr/data')
def test_pkgconfig_gen_deps(self):
'''
Test that generated pkg-config files correctly handle dependencies
'''
testdir = os.path.join(self.common_test_dir, '51 pkgconfig-gen')
self.init(testdir)
os.environ['PKG_CONFIG_LIBDIR'] = self.privatedir
cmd = ['pkg-config', 'dependency-test']
out = self._run(cmd + ['--print-requires']).strip().split()
self.assertEqual(sorted(out), sorted(['libexposed']))
out = self._run(cmd + ['--print-requires-private']).strip().split()
self.assertEqual(sorted(out), sorted(['libfoo']))
out = self._run(cmd + ['--cflags-only-other']).strip().split()
self.assertEqual(sorted(out), sorted(['-pthread', '-DCUSTOM']))
out = self._run(cmd + ['--libs-only-l', '--libs-only-other']).strip().split()
self.assertEqual(sorted(out), sorted(['-pthread', '-lcustom',
'-llibmain', '-llibexposed']))
out = self._run(cmd + ['--libs-only-l', '--libs-only-other', '--static']).strip().split()
self.assertEqual(sorted(out), sorted(['-pthread', '-lcustom',
'-llibmain', '-llibexposed',
'-llibinternal', '-lcustom2',
'-lfoo']))
def test_vala_c_warnings(self):
'''
Test that no warnings are emitted for C code generated by Vala. This

@ -1,5 +1,17 @@
project('pkgconfig-gen', 'c')
# First check we have pkg-config >= 0.29
pkgconfig = find_program('pkg-config', required: false)
if not pkgconfig.found()
error('MESON_SKIP_TEST: pkg-config not found')
endif
v = run_command(pkgconfig, '--version').stdout().strip()
if v.version_compare('<0.29')
error('MESON_SKIP_TEST: pkg-config version \'' + v + '\' too old')
endif
pkgg = import('pkgconfig')
lib = shared_library('simple', 'simple.c')
@ -18,19 +30,9 @@ pkgg.generate(
libraries_private : [lib, '-lz'],
)
pkgconfig = find_program('pkg-config', required: false)
if pkgconfig.found()
v = run_command(pkgconfig, '--version').stdout().strip()
if v.version_compare('>=0.29')
test('pkgconfig-validation', pkgconfig,
args: ['--validate', 'simple'],
env: ['PKG_CONFIG_PATH=' + meson.current_build_dir() + '/meson-private' ])
else
message('pkg-config version \'' + v + '\' too old, skipping validate test')
endif
else
message('pkg-config not found, skipping validate test')
endif
test('pkgconfig-validation', pkgconfig,
args: ['--validate', 'simple'],
env: [ 'PKG_CONFIG_PATH=' + meson.current_build_dir() + '/meson-private' ])
# Test that name_prefix='' and name='libfoo' results in '-lfoo'
lib2 = shared_library('libfoo', 'simple.c',
@ -44,3 +46,36 @@ pkgg.generate(
description : 'A foo library.',
variables : ['foo=bar', 'datadir=${prefix}/data']
)
# libmain internally use libinternal and expose libexpose in its API
exposed_lib = shared_library('libexposed', 'simple.c')
internal_lib = shared_library('libinternal', 'simple.c')
main_lib = shared_library('libmain', link_with : [exposed_lib, internal_lib])
pkgg.generate(libraries : exposed_lib,
version : libver,
name : 'libexposed',
description : 'An exposed library in dependency test.'
)
# Declare a few different Dependency objects
pc_dep = dependency('libfoo', required : false)
threads_dep = dependency('threads', required : false)
custom_dep = declare_dependency(link_args : ['-lcustom'], compile_args : ['-DCUSTOM'])
custom2_dep = declare_dependency(link_args : ['-lcustom2'], compile_args : ['-DCUSTOM2'])
# Generate a PC file:
# - Having libmain in libraries should pull implicitely libexposed and libinternal in Libs.private
# - Having libexposed in libraries should remove it from Libs.private
# - We generated a pc file for libexposed so it should be in Requires instead of Libs
# - Having threads_dep in libraries should add '-pthread' in both Libs and Cflags
# - Having custom_dep in libraries and libraries_private should only add it in Libs
# - Having custom2_dep in libraries_private should not add its Cflags
# - Having pc_dep in libraries_private should add it in Requires.private
pkgg.generate(libraries : [main_lib, exposed_lib, threads_dep , custom_dep],
libraries_private : [custom_dep, custom2_dep, pc_dep],
version : libver,
name : 'dependency-test',
filebase : 'dependency-test',
description : 'A dependency test.'
)

Loading…
Cancel
Save