diff options
-rw-r--r-- | docs/markdown/Pkgconfig-module.md | 13 | ||||
-rw-r--r-- | mesonbuild/dependencies/misc.py | 5 | ||||
-rw-r--r-- | mesonbuild/dependencies/ui.py | 5 | ||||
-rw-r--r-- | mesonbuild/modules/pkgconfig.py | 140 | ||||
-rwxr-xr-x | run_unittests.py | 30 | ||||
-rw-r--r-- | test cases/common/51 pkgconfig-gen/meson.build | 61 |
6 files changed, 204 insertions, 50 deletions
diff --git a/docs/markdown/Pkgconfig-module.md b/docs/markdown/Pkgconfig-module.md index 7f767f1..cbe01b4 100644 --- a/docs/markdown/Pkgconfig-module.md +++ b/docs/markdown/Pkgconfig-module.md @@ -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 diff --git a/mesonbuild/dependencies/misc.py b/mesonbuild/dependencies/misc.py index 7ceda3e..ea0711f 100644 --- a/mesonbuild/dependencies/misc.py +++ b/mesonbuild/dependencies/misc.py @@ -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)) diff --git a/mesonbuild/dependencies/ui.py b/mesonbuild/dependencies/ui.py index 1db518c..c066c31 100644 --- a/mesonbuild/dependencies/ui.py +++ b/mesonbuild/dependencies/ui.py @@ -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 diff --git a/mesonbuild/modules/pkgconfig.py b/mesonbuild/modules/pkgconfig.py index f963323..54c2126 100644 --- a/mesonbuild/modules/pkgconfig.py +++ b/mesonbuild/modules/pkgconfig.py @@ -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]) diff --git a/run_unittests.py b/run_unittests.py index 61ee770..184386c 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -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 diff --git a/test cases/common/51 pkgconfig-gen/meson.build b/test cases/common/51 pkgconfig-gen/meson.build index 68ee812..a8dd092 100644 --- a/test cases/common/51 pkgconfig-gen/meson.build +++ b/test cases/common/51 pkgconfig-gen/meson.build @@ -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.' +) |