aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTing-Wei Lan <lantw@src.gnome.org>2018-10-04 23:30:28 +0800
committerXavier Claessens <xclaesse@gmail.com>2019-12-18 11:34:24 -0500
commit04e08f5a1ff89bf6ca099df2ae9b73233e9503ad (patch)
treebf661a47b3bb6b52e22530e7ceb11ede68ed827c
parentc4649704c8dc59f82826ddd41db2d1cc4f43af0b (diff)
downloadmeson-04e08f5a1ff89bf6ca099df2ae9b73233e9503ad.zip
meson-04e08f5a1ff89bf6ca099df2ae9b73233e9503ad.tar.gz
meson-04e08f5a1ff89bf6ca099df2ae9b73233e9503ad.tar.bz2
PkgConfigDependency: Sort -L flags according to PKG_CONFIG_PATH
When there is more than one path in PKG_CONFIG_PATH. It is almost always preferred to find things in the order specified by PKG_CONFIG_PATH instead of assuming pkg-config returns flags in a meaningful order. For example: /usr/local/lib/libgtk-3.so.0 /usr/local/lib/pkgconfig/gtk+-3.0.pc /usr/local/lib/libcanberra-gtk3.so /usr/local/lib/pkgconfig/libcanberra-gtk3.pc /home/mesonuser/.local/lib/libgtk-3.so.0 /home/mesonuser/.local/lib/pkgconfig/gtk+-3.0.pc PKG_CONFIG_PATH="/home/mesonuser/.local/lib/pkgconfig:/usr/local/lib/pkgconfig" libcanberra-gtk3 is a library which depends on gtk+-3.0. The dependency is mentioned in the .pc file with 'Requires', so flags from gtk+-3.0 are used in both dynamic and static linking. Assume the user wants to compile an application which needs both libcanberra-gtk3 and gtk+-3.0. The application depends on features added in the latest version of gtk+-3.0, which can be found in the home directory of the user but not in /usr/local. When meson asks pkg-config for linker flags of libcanberra-gtk3, pkg-config picks /usr/local/lib/pkgconfig/libcanberra-gtk3.pc and /home/mesonuser/.local/lib/pkgconfig/gtk+-3.0.pc. Since these two libraries come from different prefixes, there will be two -L arguments in the output of pkg-config. If -L/usr/local/lib is put before -L/home/mesonuser/.local/lib, meson will find both libraries in /usr/local/lib instead of picking libgtk-3.so.0 from the home directory. This can result in linking failure such as undefined references error when meson decides to put linker arguments of libcanberra-gtk3 before linker arguments of gtk+-3.0. When both /usr/local/lib/libgtk-3.so.0 and /home/mesonuser/.local/lib/libgtk-3.so.0 are present on the command line, the linker chooses the first one and ignores the second one. If the application needs new symbols that are only available in the second one, the linker will throw an error because of missing symbols. To resolve the issue, we should reorder -L flags according to PKG_CONFIG_PATH ourselves before using it to find the full path of library files. This makes sure that we always follow the preferences of users, without depending on the unreliable part of pkg-config output. Fixes https://github.com/mesonbuild/meson/issues/4271.
-rw-r--r--mesonbuild/dependencies/base.py40
-rwxr-xr-xrun_unittests.py20
2 files changed, 60 insertions, 0 deletions
diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py
index 9170400..60239de 100644
--- a/mesonbuild/dependencies/base.py
+++ b/mesonbuild/dependencies/base.py
@@ -788,6 +788,22 @@ class PkgConfigDependency(ExternalDependency):
# Resolve the path as a compiler in the build directory would
path = os.path.join(self.env.get_build_dir(), path)
prefix_libpaths.add(path)
+ # Library paths are not always ordered in a meaningful way
+ #
+ # Instead of relying on pkg-config or pkgconf to provide -L flags in a
+ # specific order, we reorder library paths ourselves, according to th
+ # order specified in PKG_CONFIG_PATH. See:
+ # https://github.com/mesonbuild/meson/issues/4271
+ #
+ # Only prefix_libpaths are reordered here because there should not be
+ # too many system_libpaths to cause library version issues.
+ pkg_config_path = os.environ.get('PKG_CONFIG_PATH')
+ if pkg_config_path:
+ pkg_config_path = pkg_config_path.split(os.pathsep)
+ else:
+ pkg_config_path = []
+ pkg_config_path = self._convert_mingw_paths(pkg_config_path)
+ prefix_libpaths = sort_libpaths(prefix_libpaths, pkg_config_path)
system_libpaths = OrderedSet()
full_args = self._convert_mingw_paths(self._split_args(out))
for arg in full_args:
@@ -2308,6 +2324,30 @@ def _build_external_dependency_list(name, env: Environment, kwargs: Dict[str, An
return candidates
+def sort_libpaths(libpaths: List[str], refpaths: List[str]) -> List[str]:
+ """Sort <libpaths> according to <refpaths>
+
+ It is intended to be used to sort -L flags returned by pkg-config.
+ Pkg-config returns flags in random order which cannot be relied on.
+ """
+ if len(refpaths) == 0:
+ return list(libpaths)
+
+ def key_func(libpath):
+ common_lengths = []
+ for refpath in refpaths:
+ try:
+ common_path = os.path.commonpath([libpath, refpath])
+ except ValueError:
+ common_path = ''
+ common_lengths.append(len(common_path))
+ max_length = max(common_lengths)
+ max_index = common_lengths.index(max_length)
+ reversed_max_length = len(refpaths[max_index]) - max_length
+ return (max_index, reversed_max_length)
+ return sorted(libpaths, key=key_func)
+
+
def strip_system_libdirs(environment, for_machine: MachineChoice, link_args):
"""Remove -L<system path> arguments.
diff --git a/run_unittests.py b/run_unittests.py
index 78de65f..669c8ff 100755
--- a/run_unittests.py
+++ b/run_unittests.py
@@ -58,6 +58,7 @@ from mesonbuild.mesonlib import (
from mesonbuild.environment import detect_ninja
from mesonbuild.mesonlib import MesonException, EnvironmentException
from mesonbuild.dependencies import PkgConfigDependency, ExternalProgram
+import mesonbuild.dependencies.base
from mesonbuild.build import Target
import mesonbuild.modules.pkgconfig
@@ -1170,6 +1171,25 @@ class InternalTests(unittest.TestCase):
actual = f.getvalue().strip()
self.assertEqual(actual.count('bar'), 1, actual)
+ def test_sort_libpaths(self):
+ sort_libpaths = mesonbuild.dependencies.base.sort_libpaths
+ self.assertEqual(sort_libpaths(
+ ['/home/mesonuser/.local/lib', '/usr/local/lib', '/usr/lib'],
+ ['/home/mesonuser/.local/lib/pkgconfig', '/usr/local/lib/pkgconfig']),
+ ['/home/mesonuser/.local/lib', '/usr/local/lib', '/usr/lib'])
+ self.assertEqual(sort_libpaths(
+ ['/usr/local/lib', '/home/mesonuser/.local/lib', '/usr/lib'],
+ ['/home/mesonuser/.local/lib/pkgconfig', '/usr/local/lib/pkgconfig']),
+ ['/home/mesonuser/.local/lib', '/usr/local/lib', '/usr/lib'])
+ self.assertEqual(sort_libpaths(
+ ['/usr/lib', '/usr/local/lib', '/home/mesonuser/.local/lib'],
+ ['/home/mesonuser/.local/lib/pkgconfig', '/usr/local/lib/pkgconfig']),
+ ['/home/mesonuser/.local/lib', '/usr/local/lib', '/usr/lib'])
+ self.assertEqual(sort_libpaths(
+ ['/usr/lib', '/usr/local/lib', '/home/mesonuser/.local/lib'],
+ ['/home/mesonuser/.local/lib/pkgconfig', '/usr/local/libdata/pkgconfig']),
+ ['/home/mesonuser/.local/lib', '/usr/local/lib', '/usr/lib'])
+
@unittest.skipIf(is_tarball(), 'Skipping because this is a tarball release')
class DataTests(unittest.TestCase):