Skip to content

Commit

Permalink
pkgconfig: Generate -uninstalled.pc files
Browse files Browse the repository at this point in the history
  • Loading branch information
xclaesse committed Jan 11, 2019
1 parent 1422344 commit 2109d9a
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 17 deletions.
11 changes: 11 additions & 0 deletions docs/markdown/Pkgconfig-module.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ keyword arguments.
`Version:` field. 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.49.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
Expand All @@ -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.49.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
Expand Down
8 changes: 8 additions & 0 deletions docs/markdown/snippets/uninstalled-pkgconfig.md
Original file line number Diff line number Diff line change
@@ -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.
86 changes: 70 additions & 16 deletions mesonbuild/modules/pkgconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,9 @@ def _fn(xs, libs=False):
self.priv_reqs = [i for i in self.priv_reqs if i not in self.pub_reqs]

class PkgConfigModule(ExtensionModule):
def __init__(self, interpreter):
super().__init__(interpreter)
self.uninstalled_info = []

def _get_lname(self, l, msg, pcfile):
# Nothing special
Expand Down Expand Up @@ -251,19 +254,30 @@ def _make_relative(self, prefix, subdir):
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
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'))
if not uninstalled:
fname = os.path.join(state.environment.scratch_dir, pcfile)
else:
outdir = os.path.join(state.environment.build_dir, 'meson-uninstalled')
if not os.path.exists(outdir):
os.mkdir(outdir)
fname = os.path.join(outdir, pcfile)
with open(fname, 'w') 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 not uninstalled:
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'))
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)))
else:
ofile.write('prefix=\n')
ofile.write('libdir=\n')
ofile.write('includedir=\n')
if variables:
ofile.write('\n')
for k, v in variables:
Expand Down Expand Up @@ -292,6 +306,11 @@ def generate_libs_flags(libs):
for l in libs:
if isinstance(l, str):
yield l
elif uninstalled:
path = state.backend.get_target_filename_abs(l)
if path not in Lflags:
Lflags.append(path)
yield path
else:
install_dir = l.get_custom_install_dir()[0]
if install_dir is False:
Expand All @@ -317,22 +336,41 @@ def generate_libs_flags(libs):
if 'cs' not in l.compilers:
yield '-l%s' % lname

def generate_uninstalled_cflags():
result = []
for h in state.headers:
if h.subproject != state.subproject:
continue
for f in h.sources:
if not isinstance(f, mesonlib.File):
continue
abspath = f.absolute_path(state.environment.get_source_dir(),
state.environment.get_build_dir())
incdir = os.path.dirname(abspath)
if incdir not in result:
result.append(incdir)
yield '-I%s' % self._escape(incdir)

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()))
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.49.0', ['uninstalled_variables'])
@FeatureNewKwargs('pkgconfig.generate', '0.42.0', ['extra_cflags'])
@FeatureNewKwargs('pkgconfig.generate', '0.41.0', ['variables'])
@permittedKwargs({'libraries', 'version', 'name', 'description', 'filebase',
Expand Down Expand Up @@ -427,6 +465,15 @@ def parse_variable_list(stringlist):
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'
# postpone generation of uninstalled pc files because we need the complete list
# of installed headers, and user could call install_headers() after generating
# the pc file. Keep all needed information into a list that will be processed
# when interpreter calls _finish_subproject() method.
generate_pkgconfig_file_args = (deps, subdirs, name, description, url,
version, pcfile, conflicts, variables)
self.uninstalled_info.append((state.subproject, generate_pkgconfig_file_args))
# 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:'.
Expand All @@ -445,5 +492,12 @@ def parse_variable_list(stringlist):
lineno=state.current_lineno)
return ModuleReturnValue(res, [res])

# Called by interpreter when it's done parsing a (sub)project
def _finish_subproject(self, state):
for info in self.uninstalled_info:
subproject, args = info
if state.subproject == subproject:
self.generate_pkgconfig_file(state, *args, uninstalled=True)

def initialize(*args, **kwargs):
return PkgConfigModule(*args, **kwargs)
15 changes: 15 additions & 0 deletions run_unittests.py
Original file line number Diff line number Diff line change
Expand Up @@ -3910,6 +3910,21 @@ def test_pkgconfig_gen_deps(self):
out = self._run(cmd + ['--print-requires-private']).strip().split('\n')
self.assertEqual(sorted(out), sorted(['libexposed', 'libfoo >= 1.0', 'libhello']))

def test_pkgconfig_uninstalled(self):
testdir = os.path.join(self.common_test_dir, '48 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, '48 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)
Expand Down
6 changes: 6 additions & 0 deletions test cases/common/48 pkgconfig-gen/dependencies/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include <simple.h>

int main(int argc, char *argv[])
{
return simple_function() == 42 ? 0 : 1;
}
3 changes: 3 additions & 0 deletions test cases/common/48 pkgconfig-gen/dependencies/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion test cases/common/48 pkgconfig-gen/meson.build
Original file line number Diff line number Diff line change
@@ -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')
Expand Down

0 comments on commit 2109d9a

Please sign in to comment.