Merge pull request #1247 from centricular/fix-installed-soname-aliases

Fix soname symlinking after installation
pull/1250/head
Jussi Pakkanen 8 years ago committed by GitHub
commit 4b7c39d9d1
  1. 23
      mesonbuild/backend/ninjabackend.py
  2. 27
      mesonbuild/build.py
  3. 2
      mesonbuild/modules/rpm.py
  4. 4
      mesonbuild/scripts/meson_install.py
  5. 36
      run_unittests.py
  6. 8
      test cases/unit/1 soname/meson.build

@ -626,7 +626,7 @@ int dummy;
self.environment.get_import_lib_dir(),
# It has no aliases, should not be stripped, and
# doesn't have an install_rpath
[], False, '']
{}, False, '']
d.targets.append(i)
outdir = self.environment.get_shared_lib_dir()
elif isinstance(t, build.StaticLibrary):
@ -641,16 +641,16 @@ int dummy;
# Install the debug symbols file in the same place as
# the target itself. It has no aliases, should not be
# stripped, and doesn't have an install_rpath
i = [self.get_target_debug_filename(t), outdir, [], False, '']
i = [self.get_target_debug_filename(t), outdir, {}, False, '']
d.targets.append(i)
if isinstance(t, build.BuildTarget):
i = [self.get_target_filename(t), outdir, t.get_aliaslist(),\
should_strip, t.install_rpath]
i = [self.get_target_filename(t), outdir, t.get_aliases(),
should_strip, t.install_rpath]
d.targets.append(i)
elif isinstance(t, build.CustomTarget):
for output in t.get_outputs():
f = os.path.join(self.get_target_dir(t), output)
d.targets.append([f, outdir, [], False, None])
d.targets.append([f, outdir, {}, False, None])
def generate_custom_install_script(self, d):
d.install_scripts = self.build.install_scripts
@ -2087,22 +2087,15 @@ rule FORTRAN_DEP_HACK
def generate_shlib_aliases(self, target, outdir):
basename = target.get_filename()
aliases = target.get_aliaslist()
for i, alias in enumerate(aliases):
aliases = target.get_aliases()
for alias, to in aliases.items():
aliasfile = os.path.join(self.environment.get_build_dir(), outdir, alias)
try:
os.remove(aliasfile)
except Exception:
pass
# If both soversion and version are set and to different values,
# the .so symlink must point to the soversion symlink rather than the
# original file.
if i == 0 and len(aliases) > 1:
pointed_to_filename = aliases[1]
else:
pointed_to_filename = basename
try:
os.symlink(pointed_to_filename, aliasfile)
os.symlink(to, aliasfile)
except NotImplementedError:
mlog.debug("Library versioning disabled because symlinks are not supported.")
except OSError:

@ -798,8 +798,8 @@ class BuildTarget():
else:
self.extra_args[language] = args
def get_aliaslist(self):
return []
def get_aliases(self):
return {}
def get_clike_dynamic_linker(self):
'''
@ -1038,7 +1038,7 @@ class SharedLibrary(BuildTarget):
First we determine the filename template (self.filename_tpl), then we
set the output filename (self.filename).
The template is needed while creating aliases (self.get_aliaslist),
The template is needed while creating aliases (self.get_aliases),
which are needed while generating .so shared libraries for Linux.
Besides this, there's also the import library name, which is only used
@ -1184,25 +1184,30 @@ class SharedLibrary(BuildTarget):
def get_all_link_deps(self):
return [self] + self.get_transitive_link_deps()
def get_aliaslist(self):
def get_aliases(self):
"""
If the versioned library name is libfoo.so.0.100.0, aliases are:
* libfoo.so.0 (soversion)
* libfoo.so (unversioned; for linking)
* libfoo.so.0 (soversion) -> libfoo.so.0.100.0
* libfoo.so (unversioned; for linking) -> libfoo.so.0
"""
aliases = {}
# Aliases are only useful with .so libraries. Also if the .so library
# ends with .so (no versioning), we don't need aliases.
if self.suffix != 'so' or self.filename.endswith('.so'):
return []
# Unversioned alias: libfoo.so
aliases = [self.basic_filename_tpl.format(self)]
# If ltversion != soversion we create an soversion alias: libfoo.so.X
return {}
# If ltversion != soversion we create an soversion alias:
# libfoo.so.0 -> libfoo.so.0.100.0
if self.ltversion and self.ltversion != self.soversion:
if not self.soversion:
# This is done in self.process_kwargs()
raise AssertionError('BUG: If library version is defined, soversion must have been defined')
alias_tpl = self.filename_tpl.replace('ltversion', 'soversion')
aliases.append(alias_tpl.format(self))
ltversion_filename = alias_tpl.format(self)
aliases[ltversion_filename] = self.filename
else:
ltversion_filename = self.filename
# Unversioned alias: libfoo.so -> libfoo.so.0
aliases[self.basic_filename_tpl.format(self)] = ltversion_filename
return aliases
def type_suffix(self):

@ -55,7 +55,7 @@ class RPMModule:
files.add('%%{_bindir}/%s' % target.get_filename())
elif isinstance(target, build.SharedLibrary) and target.need_install:
files.add('%%{_libdir}/%s' % target.get_filename())
for alias in target.get_aliaslist():
for alias in target.get_aliases():
if alias.endswith('.so'):
files_devel.add('%%{_libdir}/%s' % alias)
else:

@ -222,14 +222,14 @@ def install_targets(d):
else:
raise RuntimeError('Unknown file type for {!r}'.format(fname))
printed_symlink_error = False
for alias in aliases:
for alias, to in aliases.items():
try:
symlinkfilename = os.path.join(outdir, alias)
try:
os.unlink(symlinkfilename)
except FileNotFoundError:
pass
os.symlink(os.path.split(fname)[-1], symlinkfilename)
os.symlink(to, symlinkfilename)
append_to_log(symlinkfilename)
except (NotImplementedError, OSError):
if not printed_symlink_error:

@ -57,6 +57,9 @@ class LinuxlikeTests(unittest.TestCase):
src_root = os.path.dirname(__file__)
src_root = os.path.join(os.getcwd(), src_root)
self.builddir = tempfile.mkdtemp()
self.prefix = '/usr'
self.libdir = os.path.join(self.prefix, 'lib')
self.installdir = os.path.join(self.builddir, 'install')
self.meson_command = [sys.executable, os.path.join(src_root, 'meson.py')]
self.mconf_command = [sys.executable, os.path.join(src_root, 'mesonconf.py')]
self.mintro_command = [sys.executable, os.path.join(src_root, 'mesonintrospect.py')]
@ -77,11 +80,18 @@ class LinuxlikeTests(unittest.TestCase):
self.output += subprocess.check_output(command, env=os.environ.copy())
def init(self, srcdir):
self._run(self.meson_command + [srcdir, self.builddir])
args = [srcdir, self.builddir,
'--prefix', self.prefix,
'--libdir', self.libdir]
self._run(self.meson_command + args)
def build(self):
self._run(self.ninja_command)
def install(self):
os.environ['DESTDIR'] = self.installdir
self._run(self.ninja_command + ['install'])
def run_target(self, target):
self.output += subprocess.check_output(self.ninja_command + [target])
@ -236,8 +246,8 @@ class LinuxlikeTests(unittest.TestCase):
intro = self.introspect('--targets')
if intro[0]['type'] == 'executable':
intro = intro[::-1]
self.assertEqual(intro[0]['install_filename'], '/usr/local/libtest/libstat.a')
self.assertEqual(intro[1]['install_filename'], '/usr/local/bin/prog')
self.assertEqual(intro[0]['install_filename'], '/usr/lib/libstat.a')
self.assertEqual(intro[1]['install_filename'], '/usr/bin/prog')
def test_run_target_files_path(self):
'''
@ -282,47 +292,55 @@ class LinuxlikeTests(unittest.TestCase):
return line.split('[')[1].split(']')[0]
raise RuntimeError('Readelf gave no SONAME.')
def test_soname(self):
def _test_soname_impl(self, libpath, install):
testdir = os.path.join(self.unit_test_dir, '1 soname')
self.init(testdir)
self.build()
if install:
self.install()
# File without aliases set.
nover = os.path.join(self.builddir, 'libnover.so')
nover = os.path.join(libpath, 'libnover.so')
self.assertTrue(os.path.exists(nover))
self.assertFalse(os.path.islink(nover))
self.assertEqual(self.get_soname(nover), 'libnover.so')
self.assertEqual(len(glob(nover[:-3] + '*')), 1)
# File with version set
verset = os.path.join(self.builddir, 'libverset.so')
verset = os.path.join(libpath, 'libverset.so')
self.assertTrue(os.path.exists(verset + '.4.5.6'))
self.assertEqual(os.readlink(verset), 'libverset.so.4')
self.assertEqual(self.get_soname(verset), 'libverset.so.4')
self.assertEqual(len(glob(verset[:-3] + '*')), 3)
# File with soversion set
soverset = os.path.join(self.builddir, 'libsoverset.so')
soverset = os.path.join(libpath, 'libsoverset.so')
self.assertTrue(os.path.exists(soverset + '.1.2.3'))
self.assertEqual(os.readlink(soverset), 'libsoverset.so.1.2.3')
self.assertEqual(self.get_soname(soverset), 'libsoverset.so.1.2.3')
self.assertEqual(len(glob(soverset[:-3] + '*')), 2)
# File with version and soversion set to same values
settosame = os.path.join(self.builddir, 'libsettosame.so')
settosame = os.path.join(libpath, 'libsettosame.so')
self.assertTrue(os.path.exists(settosame + '.7.8.9'))
self.assertEqual(os.readlink(settosame), 'libsettosame.so.7.8.9')
self.assertEqual(self.get_soname(settosame), 'libsettosame.so.7.8.9')
self.assertEqual(len(glob(settosame[:-3] + '*')), 2)
# File with version and soversion set to different values
bothset = os.path.join(self.builddir, 'libbothset.so')
bothset = os.path.join(libpath, 'libbothset.so')
self.assertTrue(os.path.exists(bothset + '.1.2.3'))
self.assertEqual(os.readlink(bothset), 'libbothset.so.1.2.3')
self.assertEqual(os.readlink(bothset + '.1.2.3'), 'libbothset.so.4.5.6')
self.assertEqual(self.get_soname(bothset), 'libbothset.so.1.2.3')
self.assertEqual(len(glob(bothset[:-3] + '*')), 3)
def test_soname(self):
self._test_soname_impl(self.builddir, False)
def test_installed_soname(self):
self._test_soname_impl(self.installdir + self.libdir, True)
def test_compiler_check_flags_order(self):
'''
Test that compiler check flags override all other flags. This can't be

@ -1,18 +1,22 @@
project('vertest', 'c')
shared_library('nover', 'versioned.c')
shared_library('nover', 'versioned.c',
install : true)
shared_library('verset', 'versioned.c',
install : true,
version : '4.5.6')
shared_library('soverset', 'versioned.c',
install : true,
soversion : '1.2.3')
shared_library('bothset', 'versioned.c',
install : true,
soversion : '1.2.3',
version : '4.5.6')
shared_library('settosame', 'versioned.c',
install : true,
soversion : '7.8.9',
version : '7.8.9')

Loading…
Cancel
Save