macos: Rewrite install_name for dependent built libraries on install

On macOS, we set the install_name for built libraries to
@rpath/libfoo.dylib, and when linking to the library, we set the RPATH
to its path in the build directory. This allows all built binaries to
be run as-is from the build directory (uninstalled).

However, on install, we have to strip all the RPATHs because they
point to the build directory, and we change the install_name of all
built libraries to the absolute path to the library. This causes the
install name in binaries to be out of date.

We now change that install name to point to the absolute path to each
built library after installation.

Fixes https://github.com/mesonbuild/meson/issues/3038
Fixes https://github.com/mesonbuild/meson/issues/3077

With this, the default workflow on macOS matches what everyone seems
to do, including Autotools and CMake. The next step is providing a way
for build files to override the install_name that is used after
installation for use with, f.ex., private libraries when combined with
the install_rpath: kwarg on targets.
pull/3750/head
Nirbheek Chauhan 7 years ago committed by Nirbheek Chauhan
parent 69f817b0e3
commit 96b7fdb723
  1. 10
      mesonbuild/backend/backends.py
  2. 105
      mesonbuild/backend/ninjabackend.py
  3. 2
      mesonbuild/compilers/__init__.py
  4. 2
      mesonbuild/compilers/c.py
  5. 32
      mesonbuild/compilers/compilers.py
  6. 2
      mesonbuild/compilers/cs.py
  7. 4
      mesonbuild/compilers/d.py
  8. 8
      mesonbuild/compilers/fortran.py
  9. 2
      mesonbuild/compilers/java.py
  10. 11
      mesonbuild/mintro.py
  11. 7
      mesonbuild/scripts/depfixer.py
  12. 16
      mesonbuild/scripts/meson_install.py
  13. 27
      run_unittests.py
  14. 3
      test cases/unit/33 external, internal library rpath/built library/meson.build

@ -54,6 +54,16 @@ class InstallData:
self.install_subdirs = [] self.install_subdirs = []
self.mesonintrospect = mesonintrospect self.mesonintrospect = mesonintrospect
class TargetInstallData:
def __init__(self, fname, outdir, aliases, strip, install_name_mappings, install_rpath, install_mode):
self.fname = fname
self.outdir = outdir
self.aliases = aliases
self.strip = strip
self.install_name_mappings = install_name_mappings
self.install_rpath = install_rpath
self.install_mode = install_mode
class ExecutableSerialisation: class ExecutableSerialisation:
def __init__(self, name, fname, cmd_args, env, is_cross, exe_wrapper, def __init__(self, name, fname, cmd_args, env, is_cross, exe_wrapper,
workdir, extra_paths, capture): workdir, extra_paths, capture):

@ -24,11 +24,11 @@ from .. import build
from .. import mlog from .. import mlog
from .. import dependencies from .. import dependencies
from .. import compilers from .. import compilers
from ..compilers import CompilerArgs from ..compilers import CompilerArgs, get_macos_dylib_install_name
from ..linkers import ArLinker from ..linkers import ArLinker
from ..mesonlib import File, MesonException, OrderedSet from ..mesonlib import File, MesonException, OrderedSet
from ..mesonlib import get_compiler_for_source, has_path_sep from ..mesonlib import get_compiler_for_source, has_path_sep
from .backends import CleanTrees, InstallData from .backends import CleanTrees, InstallData, TargetInstallData
from ..build import InvalidArguments from ..build import InvalidArguments
if mesonlib.is_windows(): if mesonlib.is_windows():
@ -699,34 +699,61 @@ int dummy;
with open(install_data_file, 'wb') as ofile: with open(install_data_file, 'wb') as ofile:
pickle.dump(d, ofile) pickle.dump(d, ofile)
def get_target_install_dirs(self, t):
# Find the installation directory.
if isinstance(t, build.SharedModule):
default_install_dir = self.environment.get_shared_module_dir()
elif isinstance(t, build.SharedLibrary):
default_install_dir = self.environment.get_shared_lib_dir()
elif isinstance(t, build.StaticLibrary):
default_install_dir = self.environment.get_static_lib_dir()
elif isinstance(t, build.Executable):
default_install_dir = self.environment.get_bindir()
elif isinstance(t, build.CustomTarget):
default_install_dir = None
else:
assert(isinstance(t, build.BuildTarget))
# XXX: Add BuildTarget-specific install dir cases here
default_install_dir = self.environment.get_libdir()
outdirs = t.get_custom_install_dir()
if outdirs[0] is not None and outdirs[0] != default_install_dir and outdirs[0] is not True:
# Either the value is set to a non-default value, or is set to
# False (which means we want this specific output out of many
# outputs to not be installed).
custom_install_dir = True
else:
custom_install_dir = False
outdirs[0] = default_install_dir
return outdirs, custom_install_dir
def get_target_link_deps_mappings(self, t, prefix):
'''
On macOS, we need to change the install names of all built libraries
that a target depends on using install_name_tool so that the target
continues to work after installation. For this, we need a dictionary
mapping of the install_name value to the new one, so we can change them
on install.
'''
result = {}
if isinstance(t, build.StaticLibrary):
return result
for ld in t.get_all_link_deps():
if ld is t or not isinstance(ld, build.SharedLibrary):
continue
old = get_macos_dylib_install_name(ld.prefix, ld.name, ld.suffix, ld.soversion)
if old in result:
continue
fname = ld.get_filename()
outdirs, _ = self.get_target_install_dirs(ld)
new = os.path.join(prefix, outdirs[0], fname)
result.update({old: new})
return result
def generate_target_install(self, d): def generate_target_install(self, d):
for t in self.build.get_targets().values(): for t in self.build.get_targets().values():
if not t.should_install(): if not t.should_install():
continue continue
# Find the installation directory. outdirs, custom_install_dir = self.get_target_install_dirs(t)
if isinstance(t, build.SharedModule):
default_install_dir = self.environment.get_shared_module_dir()
elif isinstance(t, build.SharedLibrary):
default_install_dir = self.environment.get_shared_lib_dir()
elif isinstance(t, build.StaticLibrary):
default_install_dir = self.environment.get_static_lib_dir()
elif isinstance(t, build.Executable):
default_install_dir = self.environment.get_bindir()
elif isinstance(t, build.CustomTarget):
default_install_dir = None
else:
assert(isinstance(t, build.BuildTarget))
# XXX: Add BuildTarget-specific install dir cases here
default_install_dir = self.environment.get_libdir()
outdirs = t.get_custom_install_dir()
if outdirs[0] is not None and outdirs[0] != default_install_dir and outdirs[0] is not True:
# Either the value is set to a non-default value, or is set to
# False (which means we want this specific output out of many
# outputs to not be installed).
custom_install_dir = True
else:
custom_install_dir = False
outdirs[0] = default_install_dir
# Sanity-check the outputs and install_dirs # Sanity-check the outputs and install_dirs
num_outdirs, num_out = len(outdirs), len(t.get_outputs()) num_outdirs, num_out = len(outdirs), len(t.get_outputs())
if num_outdirs != 1 and num_outdirs != num_out: if num_outdirs != 1 and num_outdirs != num_out:
@ -741,8 +768,10 @@ int dummy;
# Install primary build output (library/executable/jar, etc) # Install primary build output (library/executable/jar, etc)
# Done separately because of strip/aliases/rpath # Done separately because of strip/aliases/rpath
if outdirs[0] is not False: if outdirs[0] is not False:
i = [self.get_target_filename(t), outdirs[0], mappings = self.get_target_link_deps_mappings(t, d.prefix)
t.get_aliases(), should_strip, t.install_rpath, install_mode] i = TargetInstallData(self.get_target_filename(t), outdirs[0],
t.get_aliases(), should_strip, mappings,
t.install_rpath, install_mode)
d.targets.append(i) d.targets.append(i)
# On toolchains/platforms that use an import library for # On toolchains/platforms that use an import library for
# linking (separate from the shared library with all the # linking (separate from the shared library with all the
@ -756,11 +785,8 @@ int dummy;
else: else:
implib_install_dir = self.environment.get_import_lib_dir() implib_install_dir = self.environment.get_import_lib_dir()
# Install the import library. # Install the import library.
i = [self.get_target_filename_for_linking(t), i = TargetInstallData(self.get_target_filename_for_linking(t),
implib_install_dir, implib_install_dir, {}, False, {}, '', install_mode)
# It has no aliases, should not be stripped, and
# doesn't have an install_rpath
{}, False, '', install_mode]
d.targets.append(i) d.targets.append(i)
# Install secondary outputs. Only used for Vala right now. # Install secondary outputs. Only used for Vala right now.
if num_outdirs > 1: if num_outdirs > 1:
@ -769,7 +795,8 @@ int dummy;
if outdir is False: if outdir is False:
continue continue
f = os.path.join(self.get_target_dir(t), output) f = os.path.join(self.get_target_dir(t), output)
d.targets.append([f, outdir, {}, False, None, install_mode]) i = TargetInstallData(f, outdir, {}, False, {}, None, install_mode)
d.targets.append(i)
elif isinstance(t, build.CustomTarget): elif isinstance(t, build.CustomTarget):
# If only one install_dir is specified, assume that all # If only one install_dir is specified, assume that all
# outputs will be installed into it. This is for # outputs will be installed into it. This is for
@ -781,14 +808,16 @@ int dummy;
if num_outdirs == 1 and num_out > 1: if num_outdirs == 1 and num_out > 1:
for output in t.get_outputs(): for output in t.get_outputs():
f = os.path.join(self.get_target_dir(t), output) f = os.path.join(self.get_target_dir(t), output)
d.targets.append([f, outdirs[0], {}, False, None, install_mode]) i = TargetInstallData(f, outdirs[0], {}, False, {}, None, install_mode)
d.targets.append(i)
else: else:
for output, outdir in zip(t.get_outputs(), outdirs): for output, outdir in zip(t.get_outputs(), outdirs):
# User requested that we not install this output # User requested that we not install this output
if outdir is False: if outdir is False:
continue continue
f = os.path.join(self.get_target_dir(t), output) f = os.path.join(self.get_target_dir(t), output)
d.targets.append([f, outdir, {}, False, None, install_mode]) i = TargetInstallData(f, outdir, {}, False, {}, None, install_mode)
d.targets.append(i)
def generate_custom_install_script(self, d): def generate_custom_install_script(self, d):
result = [] result = []
@ -2398,7 +2427,6 @@ rule FORTRAN_DEP_HACK%s
return linker.get_no_stdlib_link_args() return linker.get_no_stdlib_link_args()
def get_target_type_link_args(self, target, linker): def get_target_type_link_args(self, target, linker):
abspath = os.path.join(self.environment.get_build_dir(), target.subdir)
commands = [] commands = []
if isinstance(target, build.Executable): if isinstance(target, build.Executable):
# Currently only used with the Swift compiler to add '-emit-executable' # Currently only used with the Swift compiler to add '-emit-executable'
@ -2422,8 +2450,7 @@ rule FORTRAN_DEP_HACK%s
commands += linker.get_pic_args() commands += linker.get_pic_args()
# Add -Wl,-soname arguments on Linux, -install_name on OS X # Add -Wl,-soname arguments on Linux, -install_name on OS X
commands += linker.get_soname_args(target.prefix, target.name, target.suffix, commands += linker.get_soname_args(target.prefix, target.name, target.suffix,
abspath, target.soversion, target.soversion, isinstance(target, build.SharedModule))
isinstance(target, build.SharedModule))
# This is only visited when building for Windows using either GCC or Visual Studio # This is only visited when building for Windows using either GCC or Visual Studio
if target.vs_module_defs and hasattr(linker, 'gen_vs_module_defs_args'): if target.vs_module_defs and hasattr(linker, 'gen_vs_module_defs_args'):
commands += linker.gen_vs_module_defs_args(target.vs_module_defs.rel_to_builddir(self.build_to_src)) commands += linker.gen_vs_module_defs_args(target.vs_module_defs.rel_to_builddir(self.build_to_src))

@ -30,6 +30,7 @@ __all__ = [
'clike_langs', 'clike_langs',
'c_suffixes', 'c_suffixes',
'cpp_suffixes', 'cpp_suffixes',
'get_macos_dylib_install_name',
'get_base_compile_args', 'get_base_compile_args',
'get_base_link_args', 'get_base_link_args',
'is_assembly', 'is_assembly',
@ -105,6 +106,7 @@ from .compilers import (
clike_langs, clike_langs,
c_suffixes, c_suffixes,
cpp_suffixes, cpp_suffixes,
get_macos_dylib_install_name,
get_base_compile_args, get_base_compile_args,
get_base_link_args, get_base_link_args,
is_header, is_header,

@ -93,7 +93,7 @@ class CCompiler(Compiler):
# Almost every compiler uses this for disabling warnings # Almost every compiler uses this for disabling warnings
return ['-w'] return ['-w']
def get_soname_args(self, prefix, shlib_name, suffix, path, soversion, is_shared_module): def get_soname_args(self, *args):
return [] return []
def split_shlib_to_parts(self, fname): def split_shlib_to_parts(self, fname):

@ -970,7 +970,9 @@ class Compiler:
abs_rpaths = [os.path.join(build_dir, p) for p in rpath_paths] abs_rpaths = [os.path.join(build_dir, p) for p in rpath_paths]
if build_rpath != '': if build_rpath != '':
abs_rpaths.append(build_rpath) abs_rpaths.append(build_rpath)
args = ['-Wl,-rpath,' + rp for rp in abs_rpaths] # Ensure that there is enough space for large RPATHs
args = ['-Wl,-headerpad_max_install_names']
args += ['-Wl,-rpath,' + rp for rp in abs_rpaths]
return args return args
def build_unix_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath): def build_unix_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath):
@ -1056,7 +1058,14 @@ ICC_WIN = 2
GNU_LD_AS_NEEDED = '-Wl,--as-needed' GNU_LD_AS_NEEDED = '-Wl,--as-needed'
APPLE_LD_AS_NEEDED = '-Wl,-dead_strip_dylibs' APPLE_LD_AS_NEEDED = '-Wl,-dead_strip_dylibs'
def get_gcc_soname_args(gcc_type, prefix, shlib_name, suffix, path, soversion, is_shared_module): def get_macos_dylib_install_name(prefix, shlib_name, suffix, soversion):
install_name = prefix + shlib_name
if soversion is not None:
install_name += '.' + soversion
install_name += '.dylib'
return '@rpath/' + install_name
def get_gcc_soname_args(gcc_type, prefix, shlib_name, suffix, soversion, is_shared_module):
if soversion is None: if soversion is None:
sostr = '' sostr = ''
else: else:
@ -1069,11 +1078,8 @@ def get_gcc_soname_args(gcc_type, prefix, shlib_name, suffix, path, soversion, i
elif gcc_type == GCC_OSX: elif gcc_type == GCC_OSX:
if is_shared_module: if is_shared_module:
return [] return []
install_name = prefix + shlib_name name = get_macos_dylib_install_name(prefix, shlib_name, suffix, soversion)
if soversion is not None: return ['-install_name', name]
install_name += '.' + soversion
install_name += '.dylib'
return ['-install_name', os.path.join('@rpath', install_name)]
else: else:
raise RuntimeError('Not implemented yet.') raise RuntimeError('Not implemented yet.')
@ -1213,8 +1219,8 @@ class GnuCompiler:
def split_shlib_to_parts(self, fname): def split_shlib_to_parts(self, fname):
return os.path.dirname(fname), fname return os.path.dirname(fname), fname
def get_soname_args(self, prefix, shlib_name, suffix, path, soversion, is_shared_module): def get_soname_args(self, prefix, shlib_name, suffix, soversion, is_shared_module):
return get_gcc_soname_args(self.gcc_type, prefix, shlib_name, suffix, path, soversion, is_shared_module) return get_gcc_soname_args(self.gcc_type, prefix, shlib_name, suffix, soversion, is_shared_module)
def get_std_shared_lib_link_args(self): def get_std_shared_lib_link_args(self):
return ['-shared'] return ['-shared']
@ -1330,7 +1336,7 @@ class ClangCompiler:
# so it might change semantics at any time. # so it might change semantics at any time.
return ['-include-pch', os.path.join(pch_dir, self.get_pch_name(header))] return ['-include-pch', os.path.join(pch_dir, self.get_pch_name(header))]
def get_soname_args(self, prefix, shlib_name, suffix, path, soversion, is_shared_module): def get_soname_args(self, prefix, shlib_name, suffix, soversion, is_shared_module):
if self.clang_type == CLANG_STANDARD: if self.clang_type == CLANG_STANDARD:
gcc_type = GCC_STANDARD gcc_type = GCC_STANDARD
elif self.clang_type == CLANG_OSX: elif self.clang_type == CLANG_OSX:
@ -1339,7 +1345,7 @@ class ClangCompiler:
gcc_type = GCC_MINGW gcc_type = GCC_MINGW
else: else:
raise MesonException('Unreachable code when converting clang type to gcc type.') raise MesonException('Unreachable code when converting clang type to gcc type.')
return get_gcc_soname_args(gcc_type, prefix, shlib_name, suffix, path, soversion, is_shared_module) return get_gcc_soname_args(gcc_type, prefix, shlib_name, suffix, soversion, is_shared_module)
def has_multi_arguments(self, args, env): def has_multi_arguments(self, args, env):
myargs = ['-Werror=unknown-warning-option', '-Werror=unused-command-line-argument'] myargs = ['-Werror=unknown-warning-option', '-Werror=unused-command-line-argument']
@ -1422,7 +1428,7 @@ class IntelCompiler:
def split_shlib_to_parts(self, fname): def split_shlib_to_parts(self, fname):
return os.path.dirname(fname), fname return os.path.dirname(fname), fname
def get_soname_args(self, prefix, shlib_name, suffix, path, soversion, is_shared_module): def get_soname_args(self, prefix, shlib_name, suffix, soversion, is_shared_module):
if self.icc_type == ICC_STANDARD: if self.icc_type == ICC_STANDARD:
gcc_type = GCC_STANDARD gcc_type = GCC_STANDARD
elif self.icc_type == ICC_OSX: elif self.icc_type == ICC_OSX:
@ -1431,7 +1437,7 @@ class IntelCompiler:
gcc_type = GCC_MINGW gcc_type = GCC_MINGW
else: else:
raise MesonException('Unreachable code when converting icc type to gcc type.') raise MesonException('Unreachable code when converting icc type to gcc type.')
return get_gcc_soname_args(gcc_type, prefix, shlib_name, suffix, path, soversion, is_shared_module) return get_gcc_soname_args(gcc_type, prefix, shlib_name, suffix, soversion, is_shared_module)
# TODO: centralise this policy more globally, instead # TODO: centralise this policy more globally, instead
# of fragmenting it into GnuCompiler and ClangCompiler # of fragmenting it into GnuCompiler and ClangCompiler

@ -41,7 +41,7 @@ class CsCompiler(Compiler):
def get_link_args(self, fname): def get_link_args(self, fname):
return ['-r:' + fname] return ['-r:' + fname]
def get_soname_args(self, prefix, shlib_name, suffix, path, soversion, is_shared_module): def get_soname_args(self, *args):
return [] return []
def get_werror_args(self): def get_werror_args(self):

@ -89,9 +89,9 @@ class DCompiler(Compiler):
def get_std_shared_lib_link_args(self): def get_std_shared_lib_link_args(self):
return ['-shared'] return ['-shared']
def get_soname_args(self, prefix, shlib_name, suffix, path, soversion, is_shared_module): def get_soname_args(self, prefix, shlib_name, suffix, soversion, is_shared_module):
# FIXME: Make this work for Windows, MacOS and cross-compiling # FIXME: Make this work for Windows, MacOS and cross-compiling
return get_gcc_soname_args(GCC_STANDARD, prefix, shlib_name, suffix, path, soversion, is_shared_module) return get_gcc_soname_args(GCC_STANDARD, prefix, shlib_name, suffix, soversion, is_shared_module)
def get_feature_args(self, kwargs, build_to_src): def get_feature_args(self, kwargs, build_to_src):
res = [] res = []

@ -58,14 +58,14 @@ class FortranCompiler(Compiler):
def get_no_warn_args(self): def get_no_warn_args(self):
return CCompiler.get_no_warn_args(self) return CCompiler.get_no_warn_args(self)
def get_soname_args(self, prefix, shlib_name, suffix, path, soversion, is_shared_module): def get_soname_args(self, *args):
return CCompiler.get_soname_args(self, prefix, shlib_name, suffix, path, soversion, is_shared_module) return CCompiler.get_soname_args(self, *args)
def split_shlib_to_parts(self, fname): def split_shlib_to_parts(self, fname):
return CCompiler.split_shlib_to_parts(self, fname) return CCompiler.split_shlib_to_parts(self, fname)
def build_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath): def build_rpath_args(self, *args):
return CCompiler.build_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath) return CCompiler.build_rpath_args(self, *args)
def get_dependency_gen_args(self, outtarget, outfile): def get_dependency_gen_args(self, outtarget, outfile):
return [] return []

@ -25,7 +25,7 @@ class JavaCompiler(Compiler):
self.id = 'unknown' self.id = 'unknown'
self.javarunner = 'java' self.javarunner = 'java'
def get_soname_args(self, prefix, shlib_name, suffix, path, soversion, is_shared_module): def get_soname_args(self, *args):
return [] return []
def get_werror_args(self): def get_werror_args(self):

@ -53,14 +53,12 @@ def buildparser():
def determine_installed_path(target, installdata): def determine_installed_path(target, installdata):
install_target = None install_target = None
for i in installdata.targets: for i in installdata.targets:
if os.path.basename(i[0]) == target.get_filename(): # FIXME, might clash due to subprojects. if os.path.basename(i.fname) == target.get_filename(): # FIXME, might clash due to subprojects.
install_target = i install_target = i
break break
if install_target is None: if install_target is None:
raise RuntimeError('Something weird happened. File a bug.') raise RuntimeError('Something weird happened. File a bug.')
fname = i[0] outname = os.path.join(installdata.prefix, i.outdir, os.path.basename(i.fname))
outdir = i[1]
outname = os.path.join(installdata.prefix, outdir, os.path.basename(fname))
# Normalize the path by using os.path.sep consistently, etc. # Normalize the path by using os.path.sep consistently, etc.
# Does not change the effective path. # Does not change the effective path.
return str(pathlib.PurePath(outname)) return str(pathlib.PurePath(outname))
@ -69,8 +67,9 @@ def determine_installed_path(target, installdata):
def list_installed(installdata): def list_installed(installdata):
res = {} res = {}
if installdata is not None: if installdata is not None:
for path, installdir, aliases, *unknown in installdata.targets: for t in installdata.targets:
res[os.path.join(installdata.build_dir, path)] = os.path.join(installdata.prefix, installdir, os.path.basename(path)) res[os.path.join(installdata.build_dir, t.fname)] = \
os.path.join(installdata.prefix, t.outdir, os.path.basename(t.fname))
for path, installpath, unused_prefix in installdata.data: for path, installpath, unused_prefix in installdata.data:
res[path] = os.path.join(installdata.prefix, installpath) res[path] = os.path.join(installdata.prefix, installpath)
for path, installdir, unused_custom_install_mode in installdata.headers: for path, installdir, unused_custom_install_mode in installdata.headers:

@ -364,7 +364,7 @@ def get_darwin_rpaths_to_remove(fname):
result.append(rp) result.append(rp)
return result return result
def fix_darwin(fname, new_rpath, final_path): def fix_darwin(fname, new_rpath, final_path, install_name_mappings):
try: try:
rpaths = get_darwin_rpaths_to_remove(fname) rpaths = get_darwin_rpaths_to_remove(fname)
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
@ -385,6 +385,9 @@ def fix_darwin(fname, new_rpath, final_path):
# Rewrite -install_name @rpath/libfoo.dylib to /path/to/libfoo.dylib # Rewrite -install_name @rpath/libfoo.dylib to /path/to/libfoo.dylib
if fname.endswith('dylib'): if fname.endswith('dylib'):
args += ['-id', final_path] args += ['-id', final_path]
if install_name_mappings:
for old, new in install_name_mappings.items():
args += ['-change', old, new]
if args: if args:
subprocess.check_call(['install_name_tool', fname] + args, subprocess.check_call(['install_name_tool', fname] + args,
stdout=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
@ -393,7 +396,7 @@ def fix_darwin(fname, new_rpath, final_path):
raise raise
sys.exit(0) sys.exit(0)
def fix_rpath(fname, new_rpath, final_path, verbose=True): def fix_rpath(fname, new_rpath, final_path, install_name_mappings, verbose=True):
# Static libraries never have rpaths # Static libraries never have rpaths
if fname.endswith('.a'): if fname.endswith('.a'):
return return

@ -360,14 +360,16 @@ def check_for_stampfile(fname):
def install_targets(d): def install_targets(d):
for t in d.targets: for t in d.targets:
fname = check_for_stampfile(t[0]) fname = check_for_stampfile(t.fname)
outdir = get_destdir_path(d, t[1]) outdir = get_destdir_path(d, t.outdir)
final_path = os.path.join(d.prefix, t.outdir, fname)
outname = os.path.join(outdir, os.path.basename(fname)) outname = os.path.join(outdir, os.path.basename(fname))
final_path = os.path.join(d.prefix, outname) final_path = os.path.join(d.prefix, outname)
aliases = t[2] aliases = t.aliases
should_strip = t[3] should_strip = t.strip
install_rpath = t[4] install_name_mappings = t.install_name_mappings
install_mode = t[5] install_rpath = t.install_rpath
install_mode = t.install_mode
print('Installing %s to %s' % (fname, outname)) print('Installing %s to %s' % (fname, outname))
d.dirmaker.makedirs(outdir, exist_ok=True) d.dirmaker.makedirs(outdir, exist_ok=True)
if not os.path.exists(fname): if not os.path.exists(fname):
@ -416,7 +418,7 @@ def install_targets(d):
if os.path.isfile(outname): if os.path.isfile(outname):
try: try:
depfixer.fix_rpath(outname, install_rpath, final_path, depfixer.fix_rpath(outname, install_rpath, final_path,
verbose=False) install_name_mappings, verbose=False)
except SystemExit as e: except SystemExit as e:
if isinstance(e.code, int) and e.code == 0: if isinstance(e.code, int) and e.code == 0:
pass pass

@ -2759,7 +2759,8 @@ class LinuxlikeTests(BasePlatformTests):
self._test_soname_impl(self.builddir, False) self._test_soname_impl(self.builddir, False)
def test_installed_soname(self): def test_installed_soname(self):
self._test_soname_impl(self.installdir + self.libdir, True) libdir = self.installdir + os.path.join(self.prefix, self.libdir)
self._test_soname_impl(libdir, True)
def test_compiler_check_flags_order(self): def test_compiler_check_flags_order(self):
''' '''
@ -3333,33 +3334,45 @@ endian = 'little'
self.run_tests() self.run_tests()
@skipIfNoPkgconfig @skipIfNoPkgconfig
def test_uninstalled_usage_external_library(self): def test_usage_external_library(self):
''' '''
Test that uninstalled usage of an external library (from the system or Test that uninstalled usage of an external library (from the system or
PkgConfigDependency) works. On Linux/BSD/macOS it tests if RPATHs are PkgConfigDependency) works. On macOS, this workflow works out of the
set correctly. box. On Linux, BSDs, Windows, etc, you need to set extra arguments such
as LD_LIBRARY_PATH, etc, so this test is skipped.
TODO: On Windows, this can test whether PATH is set properly
The system library is found with cc.find_library() and pkg-config deps. The system library is found with cc.find_library() and pkg-config deps.
''' '''
if not is_osx():
raise unittest.SkipTest('workflow currently only works on macOS')
oldprefix = self.prefix oldprefix = self.prefix
# Install external library so we can find it # Install external library so we can find it
testdir = os.path.join(self.unit_test_dir, '33 external, internal library rpath', 'external library') testdir = os.path.join(self.unit_test_dir, '33 external, internal library rpath', 'external library')
# install into installdir without using DESTDIR
installdir = self.installdir installdir = self.installdir
self.prefix = installdir self.prefix = installdir
self.init(testdir) self.init(testdir)
self.prefix = oldprefix
self.build() self.build()
self.install(use_destdir=False) self.install(use_destdir=False)
self.prefix = oldprefix
# New builddir for the consumer # New builddir for the consumer
self.new_builddir() self.new_builddir()
os.environ['LIBRARY_PATH'] = os.path.join(installdir, self.libdir) os.environ['LIBRARY_PATH'] = os.path.join(installdir, self.libdir)
os.environ['PKG_CONFIG_PATH'] = os.path.join(installdir, self.libdir, 'pkgconfig') os.environ['PKG_CONFIG_PATH'] = os.path.join(installdir, self.libdir, 'pkgconfig')
testdir = os.path.join(self.unit_test_dir, '33 external, internal library rpath', 'built library') testdir = os.path.join(self.unit_test_dir, '33 external, internal library rpath', 'built library')
# install into installdir without using DESTDIR
self.prefix = self.installdir
self.init(testdir) self.init(testdir)
self.prefix = oldprefix
self.build() self.build()
# test uninstalled
self.run_tests() self.run_tests()
# test running after installation
self.install(use_destdir=False)
prog = os.path.join(self.installdir, 'bin', 'prog')
self._run([prog])
out = self._run(['otool', '-L', prog])
self.assertNotIn('@rpath', out)
class LinuxArmCrossCompileTests(BasePlatformTests): class LinuxArmCrossCompileTests(BasePlatformTests):

@ -5,7 +5,8 @@ foo_system_dep = cc.find_library('foo_in_system')
faa_pkg_dep = dependency('faa_pkg') faa_pkg_dep = dependency('faa_pkg')
l = shared_library('bar_built', 'bar.c', l = shared_library('bar_built', 'bar.c',
install: true,
dependencies : [foo_system_dep, faa_pkg_dep]) dependencies : [foo_system_dep, faa_pkg_dep])
e = executable('prog', 'prog.c', link_with: l) e = executable('prog', 'prog.c', link_with: l, install: true)
test('testprog', e) test('testprog', e)

Loading…
Cancel
Save