From 4f43a402504aa57ffa3f7b6091f5fdccfd2a3000 Mon Sep 17 00:00:00 2001 From: Sam James Date: Fri, 17 Oct 2025 16:40:07 +0100 Subject: Always check if found libraries are linkable We don't always check if a library is actually linkable because of concerns that a library may not be standalone, so linking against it may still have unresolved references. We can workaround that by building a shared library as a test rather than an executable, and in fact we already do that in a bunch of cases since bb5f2ca3da821d7a8e865cd55a8d5d638e0aab22. This comes up in particular with Fedora and dependency('atomic') because on x86_64, they provide a libatomic.so linker script (so our file existence check passes), but trying to use it later on will fail if the backing package isn't installed. _get_file_from_list has not been deleted because the Ninja backend's guess_library_absolute_path uses it. We might be able to change it to also use a link test but I've left that alone. Test notes: * The _test_all_naming unittest has been tweaked because we were using a blank file rather than an actual shared library, which (as expected), now fails. * I've also added a test for dependency('atomic') providing a result that can actually be linked against. I've not made it check generally whether dependency('atomic') finds something when we expect to (at least for now) as it'll involve some CI whack-a-mole. Bug: https://bugzilla.redhat.com/show_bug.cgi?id=2352531 Bug: https://github.com/mesonbuild/meson/issues/14946 Closes: https://github.com/mesonbuild/meson/issues/10936 --- mesonbuild/compilers/mixins/clike.py | 25 +++++++++++++++++-------- test cases/common/285 atomic/a.c | 3 +++ test cases/common/285 atomic/meson.build | 23 +++++++++++++++++++++++ unittests/internaltests.py | 23 ++++++++++++++++------- 4 files changed, 59 insertions(+), 15 deletions(-) create mode 100644 test cases/common/285 atomic/a.c create mode 100644 test cases/common/285 atomic/meson.build diff --git a/mesonbuild/compilers/mixins/clike.py b/mesonbuild/compilers/mixins/clike.py index 454f5fba8..d31332a68 100644 --- a/mesonbuild/compilers/mixins/clike.py +++ b/mesonbuild/compilers/mixins/clike.py @@ -1090,10 +1090,8 @@ class CLikeCompiler(Compiler): @staticmethod def _get_file_from_list(env: Environment, paths: T.List[str]) -> T.Optional[Path]: ''' - We just check whether the library exists. We can't do a link check - because the library might have unresolved symbols that require other - libraries. On macOS we check if the library matches our target - architecture. + Check whether the library exists by filename. On macOS, we also + check if the library matches our target architecture. ''' for p in paths: if os.path.isfile(p): @@ -1147,17 +1145,28 @@ class CLikeCompiler(Compiler): except (mesonlib.MesonException, KeyError): # TODO evaluate if catching KeyError is wanted here elf_class = 0 # Search in the specified dirs, and then in the system libraries + largs = self.get_linker_always_args() + self.get_allow_undefined_link_args() + lcargs = self.linker_to_compiler_args(largs) for d in itertools.chain(extra_dirs, [] if ignore_system_dirs else self.get_library_dirs(env, elf_class)): for p in patterns: trials = self._get_trials_from_pattern(p, d, libname) if not trials: continue - trial = self._get_file_from_list(env, trials) - if not trial: + + trial_result = "" + for trial in trials: + if not os.path.isfile(trial): + continue + extra_args = [trial] + lcargs + if self.links(code, env, extra_args=extra_args, disable_cache=True)[0]: + trial_result = trial + break + + if not trial_result: continue - if libname.startswith('lib') and trial.name.startswith(libname) and lib_prefix_warning: + if libname.startswith('lib') and trial_result.startswith(libname) and lib_prefix_warning: mlog.warning(f'find_library({libname!r}) starting in "lib" only works by accident and is not portable') - return [trial.as_posix()] + return [Path(trial_result).as_posix()] return None def _find_library_impl(self, libname: str, env: 'Environment', extra_dirs: T.List[str], diff --git a/test cases/common/285 atomic/a.c b/test cases/common/285 atomic/a.c new file mode 100644 index 000000000..03b2213bb --- /dev/null +++ b/test cases/common/285 atomic/a.c @@ -0,0 +1,3 @@ +int main(void) { + return 0; +} diff --git a/test cases/common/285 atomic/meson.build b/test cases/common/285 atomic/meson.build new file mode 100644 index 000000000..323b9ccb6 --- /dev/null +++ b/test cases/common/285 atomic/meson.build @@ -0,0 +1,23 @@ +project('meson system dependency', 'c', meson_version: '>=1.7.0') + +cc = meson.get_compiler('c') + +# We could check if dependency('atomic') actually finds something when +# we 'know' it exists (MESON_SKIP_TEST) but that's likely to be brittle, +# so don't bother (for now, at least). +atomic = dependency('atomic', required : false) + +# If the dependency provider says it found something, make sure it can +# be linked against (https://github.com/mesonbuild/meson/issues/14946). +dependencies = [ + atomic +] + +exe = executable( + 'a', + 'a.c', + dependencies : dependencies, + install : false, +) + +test('basic', exe) diff --git a/unittests/internaltests.py b/unittests/internaltests.py index 71e32ba3f..8bc9c00c5 100644 --- a/unittests/internaltests.py +++ b/unittests/internaltests.py @@ -551,10 +551,14 @@ class InternalTests(unittest.TestCase): for i in ['libfoo.so.6.0', 'libfoo.so.5.0', 'libfoo.so.54.0', 'libfoo.so.66a.0b', 'libfoo.so.70.0.so.1', 'libbar.so.7.10', 'libbar.so.7.9', 'libbar.so.7.9.3']: libpath = Path(tmpdir) / i - libpath.write_text('', encoding='utf-8') - found = cc._find_library_real('foo', env, [tmpdir], '', LibType.PREFER_SHARED, lib_prefix_warning=True, ignore_system_dirs=False) + src = libpath.with_suffix('.c') + with src.open('w', encoding='utf-8') as f: + f.write('int meson_foobar (void) { return 0; }') + subprocess.check_call(['gcc', str(src), '-o', str(libpath), '-shared']) + + found = cc._find_library_real('foo', env, [tmpdir], 'int main(void) { return 0; }', LibType.PREFER_SHARED, lib_prefix_warning=True, ignore_system_dirs=False) self.assertEqual(os.path.basename(found[0]), 'libfoo.so.54.0') - found = cc._find_library_real('bar', env, [tmpdir], '', LibType.PREFER_SHARED, lib_prefix_warning=True, ignore_system_dirs=False) + found = cc._find_library_real('bar', env, [tmpdir], 'int main(void) { return 0; }', LibType.PREFER_SHARED, lib_prefix_warning=True, ignore_system_dirs=False) self.assertEqual(os.path.basename(found[0]), 'libbar.so.7.10') def test_find_library_patterns(self): @@ -610,17 +614,22 @@ class InternalTests(unittest.TestCase): https://github.com/mesonbuild/meson/issues/3951 ''' def create_static_lib(name): - if not is_osx(): - name.open('w', encoding='utf-8').close() - return src = name.with_suffix('.c') out = name.with_suffix('.o') with src.open('w', encoding='utf-8') as f: f.write('int meson_foobar (void) { return 0; }') # use of x86_64 is hardcoded in run_tests.py:get_fake_env() - subprocess.check_call(['clang', '-c', str(src), '-o', str(out), '-arch', 'x86_64']) + if is_osx(): + subprocess.check_call(['clang', '-c', str(src), '-o', str(out), '-arch', 'x86_64']) + else: + subprocess.check_call(['gcc', '-c', str(src), '-o', str(out)]) subprocess.check_call(['ar', 'csr', str(name), str(out)]) + # The test relies on some open-coded toolchain invocations for + # library creation in create_static_lib. + if is_windows() or is_cygwin(): + return + with tempfile.TemporaryDirectory() as tmpdir: pkgbin = ExternalProgram('pkg-config', command=['pkg-config'], silent=True) env = get_fake_env() -- cgit v1.2.3