pkgconfig module: Escape all paths while generating

Without this, we produce unusable pkg-config files when
prefix/libdir/etc contain spaces, which is very common on Windows.
pull/2611/head
Nirbheek Chauhan 8 years ago
parent 65edbf35ef
commit 4fb978e1f7
  1. 33
      mesonbuild/modules/pkgconfig.py
  2. 34
      run_unittests.py

@ -13,6 +13,7 @@
# limitations under the License. # limitations under the License.
import os import os
from pathlib import PurePath
from .. import build from .. import build
from .. import mesonlib from .. import mesonlib
@ -42,20 +43,34 @@ class PkgConfigModule(ExtensionModule):
mlog.warning(msg.format(l.name, 'name_prefix', l.name, pcfile)) mlog.warning(msg.format(l.name, 'name_prefix', l.name, pcfile))
return l.name return l.name
def _escape(self, value):
'''
We cannot use shlex.quote because it quotes with ' and " which does not
work with pkg-config and pkgconf at all.
'''
# We should always write out paths with / because pkg-config requires
# spaces to be quoted with \ and that messes up on Windows:
# https://bugs.freedesktop.org/show_bug.cgi?id=103203
if isinstance(value, PurePath):
value = value.as_posix()
return value.replace(' ', '\ ')
def generate_pkgconfig_file(self, state, libraries, subdirs, name, description, def generate_pkgconfig_file(self, state, libraries, subdirs, name, description,
url, version, pcfile, pub_reqs, priv_reqs, url, version, pcfile, pub_reqs, priv_reqs,
conflicts, priv_libs, extra_cflags, variables): conflicts, priv_libs, extra_cflags, variables):
coredata = state.environment.get_coredata() coredata = state.environment.get_coredata()
outdir = state.environment.scratch_dir outdir = state.environment.scratch_dir
fname = os.path.join(outdir, pcfile) 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') as ofile: with open(fname, 'w') as ofile:
ofile.write('prefix=%s\n' % coredata.get_builtin_option('prefix')) ofile.write('prefix={}\n'.format(self._escape(prefix)))
# '${prefix}' is ignored if the second path is absolute (see ofile.write('libdir={}\n'.format(self._escape('${prefix}' / libdir)))
# 'os.path.join' for details) ofile.write('includedir={}\n'.format(self._escape('${prefix}' / incdir)))
ofile.write('libdir=%s\n' % os.path.join('${prefix}', coredata.get_builtin_option('libdir')))
ofile.write('includedir=%s\n' % os.path.join('${prefix}', coredata.get_builtin_option('includedir')))
for k, v in variables: for k, v in variables:
ofile.write('%s=%s\n' % (k, v)) ofile.write('{}={}\n'.format(k, self._escape(v)))
ofile.write('\n') ofile.write('\n')
ofile.write('Name: %s\n' % name) ofile.write('Name: %s\n' % name)
if len(description) > 0: if len(description) > 0:
@ -83,7 +98,7 @@ class PkgConfigModule(ExtensionModule):
if install_dir is False: if install_dir is False:
continue continue
if isinstance(install_dir, str): if isinstance(install_dir, str):
yield '-L${prefix}/%s ' % install_dir yield '-L${prefix}/%s ' % self._escape(install_dir)
else: # install_dir is True else: # install_dir is True
yield '-L${libdir}' yield '-L${libdir}'
lname = self._get_lname(l, msg, pcfile) lname = self._get_lname(l, msg, pcfile)
@ -103,10 +118,10 @@ class PkgConfigModule(ExtensionModule):
if h == '.': if h == '.':
ofile.write('-I${includedir}') ofile.write('-I${includedir}')
else: else:
ofile.write(os.path.join('-I${includedir}', h)) ofile.write(self._escape(PurePath('-I${includedir}') / h))
for f in extra_cflags: for f in extra_cflags:
ofile.write(' ') ofile.write(' ')
ofile.write(f) ofile.write(self._escape(f))
ofile.write('\n') ofile.write('\n')
def process_libs(self, libs): def process_libs(self, libs):

@ -1483,6 +1483,28 @@ int main(int argc, char **argv) {
if os.path.splitext(fname)[1] not in ['.c', '.h', '.in']: if os.path.splitext(fname)[1] not in ['.c', '.h', '.in']:
os.unlink(fname) os.unlink(fname)
def test_pkgconfig_gen_escaping(self):
if not shutil.which('pkg-config'):
raise unittest.SkipTest('pkg-config not found')
testdir = os.path.join(self.common_test_dir, '51 pkgconfig-gen')
prefix = '/usr/with spaces'
libdir = 'lib'
self.init(testdir, extra_args=['--prefix=' + prefix,
'--libdir=' + libdir])
# Find foo dependency
os.environ['PKG_CONFIG_LIBDIR'] = self.privatedir
env = FakeEnvironment()
kwargs = {'required': True, 'silent': True}
foo_dep = PkgConfigDependency('libfoo', env, kwargs)
# Ensure link_args are properly quoted
libdir = PurePath(prefix) / PurePath(libdir)
link_args = ['-L' + libdir.as_posix(), '-lfoo']
self.assertEqual(foo_dep.get_link_args(), link_args)
# Ensure include args are properly quoted
incdir = PurePath(prefix) / PurePath('include')
cargs = ['-I' + incdir.as_posix()]
self.assertEqual(foo_dep.get_compile_args(), cargs)
class FailureTests(BasePlatformTests): class FailureTests(BasePlatformTests):
''' '''
@ -1723,12 +1745,12 @@ class LinuxlikeTests(BasePlatformTests):
env = FakeEnvironment() env = FakeEnvironment()
kwargs = {'required': True, 'silent': True} kwargs = {'required': True, 'silent': True}
os.environ['PKG_CONFIG_LIBDIR'] = self.privatedir os.environ['PKG_CONFIG_LIBDIR'] = self.privatedir
simple_dep = PkgConfigDependency('libfoo', env, kwargs) foo_dep = PkgConfigDependency('libfoo', env, kwargs)
self.assertTrue(simple_dep.found()) self.assertTrue(foo_dep.found())
self.assertEqual(simple_dep.get_version(), '1.0') self.assertEqual(foo_dep.get_version(), '1.0')
self.assertIn('-lfoo', simple_dep.get_link_args()) self.assertIn('-lfoo', foo_dep.get_link_args())
self.assertEqual(simple_dep.get_pkgconfig_variable('foo'), 'bar') self.assertEqual(foo_dep.get_pkgconfig_variable('foo'), 'bar')
self.assertPathEqual(simple_dep.get_pkgconfig_variable('datadir'), '/usr/data') self.assertPathEqual(foo_dep.get_pkgconfig_variable('datadir'), '/usr/data')
def test_vala_c_warnings(self): def test_vala_c_warnings(self):
''' '''

Loading…
Cancel
Save