find_library: Check arch of libraries on Darwin

macOS provides the tool `lipo` to check the archs supported by an
object (executable, static library, dylib, etc). This is especially
useful for fat archives, but it also helps with thin archives.

Without this, the linker will fail to link to the library we mistakenly
'found' like so:

ld: warning: ignoring file /path/to/libfoo.a, missing required architecture armv7 in file /path/to/libfoo.a
pull/4862/head
Nirbheek Chauhan 6 years ago committed by Nirbheek Chauhan
parent 52936e4a46
commit 50b863032e
  1. 5
      mesonbuild/backend/ninjabackend.py
  2. 28
      mesonbuild/compilers/c.py
  3. 18
      mesonbuild/mesonlib.py
  4. 1
      run_tests.py
  5. 72
      run_unittests.py

@ -2347,15 +2347,14 @@ rule FORTRAN_DEP_HACK%s
target_args = self.build_target_link_arguments(linker, target.link_whole_targets)
return linker.get_link_whole_for(target_args) if len(target_args) else []
@staticmethod
@lru_cache(maxsize=None)
def guess_library_absolute_path(linker, libname, search_dirs, patterns):
def guess_library_absolute_path(self, linker, libname, search_dirs, patterns):
for d in search_dirs:
for p in patterns:
trial = CCompiler._get_trials_from_pattern(p, d, libname)
if not trial:
continue
trial = CCompiler._get_file_from_list(trial)
trial = CCompiler._get_file_from_list(self.environment, trial)
if not trial:
continue
# Return the first result

@ -27,6 +27,7 @@ from . import compilers
from ..mesonlib import (
EnvironmentException, MesonException, version_compare, Popen_safe, listify,
for_windows, for_darwin, for_cygwin, for_haiku, for_openbsd,
darwin_get_object_archs
)
from .c_function_attributes import C_FUNC_ATTRIBUTES
@ -980,10 +981,28 @@ class CCompiler(Compiler):
return [f.as_posix()]
@staticmethod
def _get_file_from_list(files: List[str]) -> str:
def _get_file_from_list(env, files: List[str]) -> str:
'''
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.
'''
# If not building on macOS for Darwin, do a simple file check
if not env.machines.host.is_darwin() or not env.machines.build.is_darwin():
for f in files:
if os.path.isfile(f):
return f
# Run `lipo` and check if the library supports the arch we want
for f in files:
if os.path.isfile(f):
if not os.path.isfile(f):
continue
archs = darwin_get_object_archs(f)
if archs and env.machines.host.cpu_family in archs:
return f
else:
mlog.debug('Rejected {}, supports {} but need {}'
.format(f, archs, env.machines.host.cpu_family))
return None
@functools.lru_cache()
@ -1024,10 +1043,7 @@ class CCompiler(Compiler):
trial = self._get_trials_from_pattern(p, d, libname)
if not trial:
continue
# 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.
trial = self._get_file_from_list(trial)
trial = self._get_file_from_list(env, trial)
if not trial:
continue
return [trial]

@ -461,6 +461,24 @@ def exe_exists(arglist):
pass
return False
lru_cache(maxsize=None)
def darwin_get_object_archs(objpath):
'''
For a specific object (executable, static library, dylib, etc), run `lipo`
to fetch the list of archs supported by it. Supports both thin objects and
'fat' objects.
'''
_, stdo, stderr = Popen_safe(['lipo', '-archs', objpath])
if not stdo:
mlog.debug('lipo {}: {}'.format(objpath, stderr))
return None
# Convert from lipo-style archs to meson-style CPUs
stdo = stdo.replace('i386', 'x86')
# Add generic name for armv7 and armv7s
if 'armv7' in stdo:
stdo += ' arm'
return stdo.split()
def detect_vcs(source_dir):
vcs_systems = [
dict(name = 'git', cmd = 'git', repo_dir = '.git', get_rev = 'git describe --dirty=+', rev_regex = '(.*)', dep = '.git/logs/HEAD'),

@ -81,6 +81,7 @@ def get_fake_env(sdir='', bdir=None, prefix='', opts=None):
opts = get_fake_options(prefix)
env = Environment(sdir, bdir, opts)
env.coredata.compiler_options['c_args'] = FakeCompilerOptions()
env.machines.host.cpu_family = 'x86_64' # Used on macOS inside find_library
return env

@ -45,7 +45,7 @@ from mesonbuild.interpreter import Interpreter, ObjectHolder
from mesonbuild.mesonlib import (
is_windows, is_osx, is_cygwin, is_dragonflybsd, is_openbsd, is_haiku,
windows_proof_rmtree, python_command, version_compare,
BuildDirLock, Version
BuildDirLock, Version, PerMachine
)
from mesonbuild.environment import detect_ninja
from mesonbuild.mesonlib import MesonException, EnvironmentException
@ -781,6 +781,17 @@ class InternalTests(unittest.TestCase):
https://github.com/mesonbuild/meson/issues/3951
'''
def create_static_lib(name):
if not is_osx():
name.open('w').close()
return
src = name.with_suffix('.c')
out = name.with_suffix('.o')
with src.open('w') as f:
f.write('int meson_foobar (void) { return 0; }')
subprocess.check_call(['clang', '-c', str(src), '-o', str(out)])
subprocess.check_call(['ar', 'csr', str(name), str(out)])
with tempfile.TemporaryDirectory() as tmpdir:
pkgbin = ExternalProgram('pkg-config', command=['pkg-config'], silent=True)
env = get_fake_env()
@ -792,16 +803,16 @@ class InternalTests(unittest.TestCase):
p1.mkdir()
p2.mkdir()
# libfoo.a is in one prefix
(p1 / 'libfoo.a').open('w').close()
create_static_lib(p1 / 'libfoo.a')
# libbar.a is in both prefixes
(p1 / 'libbar.a').open('w').close()
(p2 / 'libbar.a').open('w').close()
create_static_lib(p1 / 'libbar.a')
create_static_lib(p2 / 'libbar.a')
# Ensure that we never statically link to these
(p1 / 'libpthread.a').open('w').close()
(p1 / 'libm.a').open('w').close()
(p1 / 'libc.a').open('w').close()
(p1 / 'libdl.a').open('w').close()
(p1 / 'librt.a').open('w').close()
create_static_lib(p1 / 'libpthread.a')
create_static_lib(p1 / 'libm.a')
create_static_lib(p1 / 'libc.a')
create_static_lib(p1 / 'libdl.a')
create_static_lib(p1 / 'librt.a')
def fake_call_pkgbin(self, args, env=None):
if '--libs' not in args:
@ -815,30 +826,31 @@ class InternalTests(unittest.TestCase):
old_call = PkgConfigDependency._call_pkgbin
old_check = PkgConfigDependency.check_pkgconfig
old_pkgbin = PkgConfigDependency.class_pkgbin
PkgConfigDependency._call_pkgbin = fake_call_pkgbin
PkgConfigDependency.check_pkgconfig = lambda x, _: pkgbin
# Test begins
kwargs = {'required': True, 'silent': True}
foo_dep = PkgConfigDependency('foo', env, kwargs)
self.assertEqual(foo_dep.get_link_args(),
[(p1 / 'libfoo.a').as_posix(), (p2 / 'libbar.a').as_posix()])
bar_dep = PkgConfigDependency('bar', env, kwargs)
self.assertEqual(bar_dep.get_link_args(), [(p2 / 'libbar.a').as_posix()])
internal_dep = PkgConfigDependency('internal', env, kwargs)
if compiler.get_argument_syntax() == 'msvc':
self.assertEqual(internal_dep.get_link_args(), [])
else:
link_args = internal_dep.get_link_args()
for link_arg in link_args:
for lib in ('pthread', 'm', 'c', 'dl', 'rt'):
self.assertNotIn('lib{}.a'.format(lib), link_arg, msg=link_args)
# Test ends
PkgConfigDependency._call_pkgbin = old_call
PkgConfigDependency.check_pkgconfig = old_check
# Reset dependency class to ensure that in-process configure doesn't mess up
PkgConfigDependency.pkgbin_cache = {}
PkgConfigDependency.class_pkgbin = old_pkgbin
try:
kwargs = {'required': True, 'silent': True}
foo_dep = PkgConfigDependency('foo', env, kwargs)
self.assertEqual(foo_dep.get_link_args(),
[(p1 / 'libfoo.a').as_posix(), (p2 / 'libbar.a').as_posix()])
bar_dep = PkgConfigDependency('bar', env, kwargs)
self.assertEqual(bar_dep.get_link_args(), [(p2 / 'libbar.a').as_posix()])
internal_dep = PkgConfigDependency('internal', env, kwargs)
if compiler.get_argument_syntax() == 'msvc':
self.assertEqual(internal_dep.get_link_args(), [])
else:
link_args = internal_dep.get_link_args()
for link_arg in link_args:
for lib in ('pthread', 'm', 'c', 'dl', 'rt'):
self.assertNotIn('lib{}.a'.format(lib), link_arg, msg=link_args)
finally:
# Test ends
PkgConfigDependency._call_pkgbin = old_call
PkgConfigDependency.check_pkgconfig = old_check
# Reset dependency class to ensure that in-process configure doesn't mess up
PkgConfigDependency.pkgbin_cache = {}
PkgConfigDependency.class_pkgbin = PerMachine(None, None, None)
def test_version_compare(self):
comparefunc = mesonbuild.mesonlib.version_compare_many

Loading…
Cancel
Save