pkgconfig: Generate -uninstalled.pc files

Closes: #3472.
pull/6589/head
Xavier Claessens 6 years ago committed by Xavier Claessens
parent b3ab022777
commit 4c5a9523be
  1. 11
      docs/markdown/Pkgconfig-module.md
  2. 8
      docs/markdown/snippets/uninstalled-pkgconfig.md
  3. 78
      mesonbuild/modules/pkgconfig.py
  4. 15
      run_unittests.py
  5. 6
      test cases/common/47 pkgconfig-gen/dependencies/main.c
  6. 3
      test cases/common/47 pkgconfig-gen/dependencies/meson.build
  7. 1
      test cases/common/47 pkgconfig-gen/meson.build

@ -54,6 +54,8 @@ keyword arguments.
`Version:` field. (*since 0.46.0*) Defaults to the project version if unspecified.
- `d_module_versions` a list of module version flags used when compiling
D sources referred to by this pkg-config file
- `uninstalled_variables` used instead of the `variables` keyword argument, when
generating the uninstalled pkg-config file. Since *0.54.0*
Since 0.46 a `StaticLibrary` or `SharedLibrary` object can optionally be passed
as first positional argument. If one is provided a default value will be
@ -62,6 +64,15 @@ provided for all required fields of the pc file:
- `description` is set to the project's name followed by the library's name.
- `name` is set to the library's name.
Since 0.54.0 uninstalled pkg-config files are generated as well. They are
located in `<build dir>/meson-uninstalled/`. It is sometimes
useful to build projects against libraries built by meson without having to
install them into a prefix. In order to do so, just set
`PKG_CONFIG_PATH=<builddir>/meson-uninstalled` before building your
application. That will cause pkg-config to prefer those `-uninstalled.pc` files
and find libraries and headers from the meson builddir. This is an experimental
feature provided on a best-effort basis, it might not work in all use-cases.
### Implicit dependencies
The exact rules followed to find dependencies that are implicitly added into the

@ -0,0 +1,8 @@
## Uninstalled pkg-config files
The `pkgconfig` module now generates uninstalled pc files as well. For any generated
`foo.pc` file, an extra `foo-uninstalled.pc` file is placed into
`<builddir>/meson-uninstalled`. They can be used to build applications against
libraries built by meson without installing them, by pointing `PKG_CONFIG_PATH`
to that directory. This is an experimental feature provided on a best-effort
basis, it might not work in all use-cases.

@ -265,19 +265,30 @@ class PkgConfigModule(ExtensionModule):
return subdir
def generate_pkgconfig_file(self, state, deps, subdirs, name, description,
url, version, pcfile, conflicts, variables):
url, version, pcfile, conflicts, variables,
uninstalled=False):
deps.remove_dups()
coredata = state.environment.get_coredata()
outdir = state.environment.scratch_dir
if uninstalled:
outdir = os.path.join(state.environment.build_dir, 'meson-uninstalled')
if not os.path.exists(outdir):
os.mkdir(outdir)
prefix = PurePath(state.environment.get_build_dir())
srcdir = PurePath(state.environment.get_source_dir())
else:
outdir = state.environment.scratch_dir
prefix = PurePath(coredata.get_builtin_option('prefix'))
# These always return paths relative to prefix
libdir = PurePath(coredata.get_builtin_option('libdir'))
incdir = PurePath(coredata.get_builtin_option('includedir'))
fname = os.path.join(outdir, pcfile)
prefix = PurePath(coredata.get_builtin_option('prefix'))
# These always return paths relative to prefix
libdir = PurePath(coredata.get_builtin_option('libdir'))
incdir = PurePath(coredata.get_builtin_option('includedir'))
with open(fname, 'w', encoding='utf-8') as ofile:
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 uninstalled:
ofile.write('srcdir={}\n'.format(self._escape(srcdir)))
else:
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:
@ -307,17 +318,20 @@ class PkgConfigModule(ExtensionModule):
if isinstance(l, str):
yield l
else:
install_dir = l.get_custom_install_dir()[0]
if uninstalled:
install_dir = os.path.dirname(state.backend.get_target_filename_abs(l))
else:
install_dir = l.get_custom_install_dir()[0]
if install_dir is False:
continue
if 'cs' in l.compilers:
if isinstance(install_dir, str):
Lflag = '-r${prefix}/%s/%s ' % (self._escape(self._make_relative(prefix, install_dir)), l.filename)
Lflag = '-r${prefix}/%s/%s' % (self._escape(self._make_relative(prefix, install_dir)), l.filename)
else: # install_dir is True
Lflag = '-r${libdir}/%s' % l.filename
else:
if isinstance(install_dir, str):
Lflag = '-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
Lflag = '-L${libdir}'
if Lflag not in Lflags:
@ -331,22 +345,47 @@ class PkgConfigModule(ExtensionModule):
if 'cs' not in l.compilers:
yield '-l%s' % lname
def get_uninstalled_include_dirs(libs):
result = []
for l in libs:
if isinstance(l, str):
continue
if l.get_subdir() not in result:
result.append(l.get_subdir())
for i in l.get_include_dirs():
curdir = i.get_curdir()
for d in i.get_incdirs():
path = os.path.join(curdir, d)
if path not in result:
result.append(path)
return result
def generate_uninstalled_cflags(libs):
for d in get_uninstalled_include_dirs(libs):
for basedir in ['${prefix}', '${srcdir}']:
path = os.path.join(basedir, d)
yield '-I%s' % self._escape(path)
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(' ')
if h == '.':
ofile.write('-I${includedir}')
else:
ofile.write(self._escape(PurePath('-I${includedir}') / h))
if uninstalled:
ofile.write(' '.join(generate_uninstalled_cflags(deps.pub_libs + deps.priv_libs)))
else:
for h in subdirs:
ofile.write(' ')
if h == '.':
ofile.write('-I${includedir}')
else:
ofile.write(self._escape(PurePath('-I${includedir}') / h))
for f in deps.cflags:
ofile.write(' ')
ofile.write(self._escape(f))
ofile.write('\n')
@FeatureNewKwargs('pkgconfig.generate', '0.54.0', ['uninstalled_variables'])
@FeatureNewKwargs('pkgconfig.generate', '0.42.0', ['extra_cflags'])
@FeatureNewKwargs('pkgconfig.generate', '0.41.0', ['variables'])
@permittedKwargs({'libraries', 'version', 'name', 'description', 'filebase',
@ -451,6 +490,11 @@ class PkgConfigModule(ExtensionModule):
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)
variables = parse_variable_list(mesonlib.stringlistify(kwargs.get('uninstalled_variables', [])))
pcfile = filebase + '-uninstalled.pc'
self.generate_pkgconfig_file(state, deps, subdirs, name, description, url,
version, pcfile, conflicts, variables,
uninstalled=True)
# Associate the main library with this generated pc file. If the library
# is used in any subsequent call to the generated, it will generate a
# 'Requires:' or 'Requires.private:'.

@ -5011,6 +5011,21 @@ class LinuxlikeTests(BasePlatformTests):
out = self._run(cmd + ['--libs'], override_envvars=env).strip().split()
self.assertEqual(out, ['-llibmain2', '-llibinternal'])
def test_pkgconfig_uninstalled(self):
testdir = os.path.join(self.common_test_dir, '47 pkgconfig-gen')
self.init(testdir)
self.build()
os.environ['PKG_CONFIG_LIBDIR'] = os.path.join(self.builddir, 'meson-uninstalled')
if is_cygwin():
os.environ['PATH'] += os.pathsep + self.builddir
self.new_builddir()
testdir = os.path.join(self.common_test_dir, '47 pkgconfig-gen', 'dependencies')
self.init(testdir)
self.build()
self.run_tests()
def test_pkg_unfound(self):
testdir = os.path.join(self.unit_test_dir, '23 unfound pkgconfig')
self.init(testdir)

@ -0,0 +1,6 @@
#include <simple.h>
int main(int argc, char *argv[])
{
return simple_function() == 42 ? 0 : 1;
}

@ -18,6 +18,9 @@ threads_dep = dependency('threads')
custom_dep = declare_dependency(link_with : custom_lib, compile_args : ['-DCUSTOM'])
custom2_dep = declare_dependency(link_args : ['-lcustom2'], compile_args : ['-DCUSTOM2'])
exe = executable('test1', 'main.c', dependencies : [pc_dep])
test('Test1', exe)
# Generate a PC file:
# - Having libmain in libraries should pull implicitly libexposed and libinternal in Libs.private
# - Having libexposed in libraries should remove it from Libs.private

@ -1,7 +1,6 @@
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')

Loading…
Cancel
Save