summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNirbheek Chauhan <nirbheek@centricular.com>2018-08-28 15:17:00 +0530
committerNirbheek Chauhan <nirbheek@centricular.com>2018-08-29 14:23:44 +0530
commitb6e273d2f52edd8638c08ca2831a94da6ca36085 (patch)
treeb2ea25b4f74d14975194e6cc43926f535db6a5c4
parentcc37a66077ca953081d972efcff23969b186a767 (diff)
downloadmeson-nirbheek/add-osx-dylib-version.tar.gz
Improve support for macOS dylib versioningnirbheek/add-osx-dylib-version
We now use the soversion to set compatibility_version and current_version by default. This is the only sane thing we can do by default because of the restrictions on the values that can be used for compatibility and current version. Users can override this value with the `darwin_versions:` kwarg, which can be a single value or a two-element list of values. The first one is the compatibility version and the second is the current version. Fixes https://github.com/mesonbuild/meson/issues/3555 Fixes https://github.com/mesonbuild/meson/issues/1451
-rw-r--r--docs/markdown/Reference-manual.md11
-rw-r--r--docs/markdown/snippets/shared_library_darwin_versions.md9
-rw-r--r--mesonbuild/backend/ninjabackend.py3
-rw-r--r--mesonbuild/build.py49
-rw-r--r--mesonbuild/compilers/compilers.py30
-rw-r--r--mesonbuild/compilers/d.py6
-rwxr-xr-xrun_unittests.py129
-rw-r--r--test cases/osx/2 library versions/installed_files.txt2
-rw-r--r--test cases/osx/2 library versions/meson.build17
9 files changed, 183 insertions, 73 deletions
diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md
index e83055743..ea11f6001 100644
--- a/docs/markdown/Reference-manual.md
+++ b/docs/markdown/Reference-manual.md
@@ -1183,13 +1183,20 @@ extra keyword arguments.
`soversion` is `4`, a Windows DLL will be called `foo-4.dll` and one
of the aliases of the Linux shared library would be
`libfoo.so.4`. If this is not specified, the first part of `version`
- is used instead. For example, if `version` is `3.6.0` and
+ is used instead (see below). For example, if `version` is `3.6.0` and
`soversion` is not defined, it is set to `3`.
- `version` a string specifying the version of this shared library,
such as `1.1.0`. On Linux and OS X, this is used to set the shared
library version in the filename, such as `libfoo.so.1.1.0` and
`libfoo.1.1.0.dylib`. If this is not specified, `soversion` is used
- instead (see below).
+ instead (see above).
+- `darwin_versions` *(added 0.48)* an integer, string, or a list of
+ versions to use for setting dylib `compatibility version` and
+ `current version` on macOS. If a list is specified, it must be
+ either zero, one, or two elements. If only one element is specified
+ or if it's not a list, the specified value will be used for setting
+ both compatibility version and current version. If unspecified, the
+ `soversion` will be used as per the aforementioned rules.
- `vs_module_defs` a string, a File object, or Custom Target for a
Microsoft module definition file for controlling symbol exports,
etc., on platforms where that is possible (e.g. Windows).
diff --git a/docs/markdown/snippets/shared_library_darwin_versions.md b/docs/markdown/snippets/shared_library_darwin_versions.md
new file mode 100644
index 000000000..ad137f3ee
--- /dev/null
+++ b/docs/markdown/snippets/shared_library_darwin_versions.md
@@ -0,0 +1,9 @@
+## `shared_library()` now supports setting dylib compatibility and current version
+
+Now, by default `shared_library()` sets `-compatibility_version` and
+`-current_version` of a macOS dylib using the `soversion`.
+
+This can be overriden by using the `darwin_versions:` kwarg to
+[`shared_library()`](Reference-manual.md#shared_library). As usual, you can
+also pass this kwarg to `library()` or `build_target()` and it will be used in
+the appropriate circumstances.
diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py
index acbeed3ad..6daa9394d 100644
--- a/mesonbuild/backend/ninjabackend.py
+++ b/mesonbuild/backend/ninjabackend.py
@@ -2257,7 +2257,8 @@ rule FORTRAN_DEP_HACK%s
commands += linker.get_pic_args()
# Add -Wl,-soname arguments on Linux, -install_name on OS X
commands += linker.get_soname_args(target.prefix, target.name, target.suffix,
- target.soversion, isinstance(target, build.SharedModule))
+ target.soversion, target.darwin_versions,
+ isinstance(target, build.SharedModule))
# 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'):
commands += linker.gen_vs_module_defs_args(target.vs_module_defs.rel_to_builddir(self.build_to_src))
diff --git a/mesonbuild/build.py b/mesonbuild/build.py
index d35c697ec..d1002d1c3 100644
--- a/mesonbuild/build.py
+++ b/mesonbuild/build.py
@@ -83,7 +83,7 @@ known_build_target_kwargs = (
cs_kwargs)
known_exe_kwargs = known_build_target_kwargs | {'implib', 'export_dynamic'}
-known_shlib_kwargs = known_build_target_kwargs | {'version', 'soversion', 'vs_module_defs'}
+known_shlib_kwargs = known_build_target_kwargs | {'version', 'soversion', 'vs_module_defs', 'darwin_versions'}
known_shmod_kwargs = known_build_target_kwargs
known_stlib_kwargs = known_build_target_kwargs | {'pic'}
known_jar_kwargs = known_exe_kwargs | {'main_class'}
@@ -1392,6 +1392,8 @@ class SharedLibrary(BuildTarget):
def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs):
self.soversion = None
self.ltversion = None
+ # Max length 2, first element is compatibility_version, second is current_version
+ self.darwin_versions = []
self.vs_module_defs = None
# The import library this target will generate
self.import_filename = None
@@ -1518,6 +1520,44 @@ class SharedLibrary(BuildTarget):
self.filename = self.filename_tpl.format(self)
self.outputs = [self.filename]
+ @staticmethod
+ def _validate_darwin_versions(darwin_versions):
+ try:
+ if isinstance(darwin_versions, int):
+ darwin_versions = str(darwin_versions)
+ if isinstance(darwin_versions, str):
+ darwin_versions = 2 * [darwin_versions]
+ if not isinstance(darwin_versions, list):
+ raise InvalidArguments('Shared library darwin_versions: must be a string, integer,'
+ 'or a list, not {!r}'.format(darwin_versions))
+ if len(darwin_versions) > 2:
+ raise InvalidArguments('Shared library darwin_versions: list must contain 2 or fewer elements')
+ if len(darwin_versions) == 1:
+ darwin_versions = 2 * darwin_versions
+ for i, v in enumerate(darwin_versions[:]):
+ if isinstance(v, int):
+ v = str(v)
+ if not isinstance(v, str):
+ raise InvalidArguments('Shared library darwin_versions: list elements '
+ 'must be strings or integers, not {!r}'.format(v))
+ if not re.fullmatch(r'[0-9]+(\.[0-9]+){0,2}', v):
+ raise InvalidArguments('Shared library darwin_versions: must be X.Y.Z where '
+ 'X, Y, Z are numbers, and Y and Z are optional')
+ parts = v.split('.')
+ if len(parts) in (1, 2, 3) and int(parts[0]) > 65535:
+ raise InvalidArguments('Shared library darwin_versions: must be X.Y.Z '
+ 'where X is [0, 65535] and Y, Z are optional')
+ if len(parts) in (2, 3) and int(parts[1]) > 255:
+ raise InvalidArguments('Shared library darwin_versions: must be X.Y.Z '
+ 'where Y is [0, 255] and Y, Z are optional')
+ if len(parts) == 3 and int(parts[2]) > 255:
+ raise InvalidArguments('Shared library darwin_versions: must be X.Y.Z '
+ 'where Z is [0, 255] and Y, Z are optional')
+ darwin_versions[i] = v
+ except ValueError:
+ raise InvalidArguments('Shared library darwin_versions: value is invalid')
+ return darwin_versions
+
def process_kwargs(self, kwargs, environment):
super().process_kwargs(kwargs, environment)
@@ -1546,6 +1586,13 @@ class SharedLibrary(BuildTarget):
# We replicate what Autotools does here and take the first
# number of the version by default.
self.soversion = self.ltversion.split('.')[0]
+ # macOS and iOS dylib compatibility_version and current_version
+ if 'darwin_versions' in kwargs:
+ self.darwin_versions = self._validate_darwin_versions(kwargs['darwin_versions'])
+ elif self.soversion:
+ # If unspecified, pick the soversion
+ self.darwin_versions = 2 * [self.soversion]
+
# Visual Studio module-definitions file
if 'vs_module_defs' in kwargs:
path = kwargs['vs_module_defs']
diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py
index 71f8ebe3d..36507b01a 100644
--- a/mesonbuild/compilers/compilers.py
+++ b/mesonbuild/compilers/compilers.py
@@ -1056,14 +1056,14 @@ class Compiler:
return None
def build_osx_rpath_args(self, build_dir, rpath_paths, build_rpath):
+ # Ensure that there is enough space for large RPATHs and install_name
+ args = ['-Wl,-headerpad_max_install_names']
if not rpath_paths and not build_rpath:
- return []
+ return args
# On OSX, rpaths must be absolute.
abs_rpaths = [os.path.join(build_dir, p) for p in rpath_paths]
if build_rpath != '':
abs_rpaths.append(build_rpath)
- # Ensure that there is enough space for large RPATHs
- args = ['-Wl,-headerpad_max_install_names']
# Need to deduplicate abs_rpaths, as rpath_paths and
# build_rpath are not guaranteed to be disjoint sets
args += ['-Wl,-rpath,' + rp for rp in OrderedSet(abs_rpaths)]
@@ -1165,12 +1165,9 @@ def get_macos_dylib_install_name(prefix, shlib_name, suffix, 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:
- sostr = ''
- else:
- sostr = '.' + soversion
+def get_gcc_soname_args(gcc_type, prefix, shlib_name, suffix, soversion, darwin_versions, is_shared_module):
if gcc_type == GCC_STANDARD:
+ sostr = '' if soversion is None else '.' + soversion
return ['-Wl,-soname,%s%s.%s%s' % (prefix, shlib_name, suffix, sostr)]
elif gcc_type in (GCC_MINGW, GCC_CYGWIN):
# For PE/COFF the soname argument has no effect with GNU LD
@@ -1179,7 +1176,10 @@ def get_gcc_soname_args(gcc_type, prefix, shlib_name, suffix, soversion, is_shar
if is_shared_module:
return []
name = get_macos_dylib_install_name(prefix, shlib_name, suffix, soversion)
- return ['-install_name', name]
+ args = ['-install_name', name]
+ if darwin_versions:
+ args += ['-compatibility_version', darwin_versions[0], '-current_version', darwin_versions[1]]
+ return args
else:
raise RuntimeError('Not implemented yet.')
@@ -1325,8 +1325,8 @@ class GnuCompiler:
def split_shlib_to_parts(self, fname):
return os.path.dirname(fname), fname
- 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, soversion, is_shared_module)
+ def get_soname_args(self, *args):
+ return get_gcc_soname_args(self.gcc_type, *args)
def get_std_shared_lib_link_args(self):
return ['-shared']
@@ -1452,7 +1452,7 @@ class ClangCompiler:
# so it might change semantics at any time.
return ['-include-pch', os.path.join(pch_dir, self.get_pch_name(header))]
- def get_soname_args(self, prefix, shlib_name, suffix, soversion, is_shared_module):
+ def get_soname_args(self, *args):
if self.clang_type == CLANG_STANDARD:
gcc_type = GCC_STANDARD
elif self.clang_type == CLANG_OSX:
@@ -1461,7 +1461,7 @@ class ClangCompiler:
gcc_type = GCC_MINGW
else:
raise MesonException('Unreachable code when converting clang type to gcc type.')
- return get_gcc_soname_args(gcc_type, prefix, shlib_name, suffix, soversion, is_shared_module)
+ return get_gcc_soname_args(gcc_type, *args)
def has_multi_arguments(self, args, env):
myargs = ['-Werror=unknown-warning-option', '-Werror=unused-command-line-argument']
@@ -1620,7 +1620,7 @@ class IntelCompiler:
def split_shlib_to_parts(self, fname):
return os.path.dirname(fname), fname
- def get_soname_args(self, prefix, shlib_name, suffix, soversion, is_shared_module):
+ def get_soname_args(self, *args):
if self.icc_type == ICC_STANDARD:
gcc_type = GCC_STANDARD
elif self.icc_type == ICC_OSX:
@@ -1629,7 +1629,7 @@ class IntelCompiler:
gcc_type = GCC_MINGW
else:
raise MesonException('Unreachable code when converting icc type to gcc type.')
- return get_gcc_soname_args(gcc_type, prefix, shlib_name, suffix, soversion, is_shared_module)
+ return get_gcc_soname_args(gcc_type, *args)
# TODO: centralise this policy more globally, instead
# of fragmenting it into GnuCompiler and ClangCompiler
diff --git a/mesonbuild/compilers/d.py b/mesonbuild/compilers/d.py
index a03af3e57..c81c048df 100644
--- a/mesonbuild/compilers/d.py
+++ b/mesonbuild/compilers/d.py
@@ -118,16 +118,14 @@ class DCompiler(Compiler):
def get_std_shared_lib_link_args(self):
return ['-shared']
- def get_soname_args(self, prefix, shlib_name, suffix, soversion, is_shared_module):
+ def get_soname_args(self, *args):
# FIXME: Make this work for cross-compiling
gcc_type = GCC_STANDARD
if is_windows():
gcc_type = GCC_CYGWIN
if is_osx():
gcc_type = GCC_OSX
-
- return get_gcc_soname_args(gcc_type, prefix, shlib_name, suffix, soversion, is_shared_module)
-
+ return get_gcc_soname_args(gcc_type, *args)
def get_feature_args(self, kwargs, build_to_src):
res = []
diff --git a/run_unittests.py b/run_unittests.py
index 16eee3e3c..f17b7aee5 100755
--- a/run_unittests.py
+++ b/run_unittests.py
@@ -2980,10 +2980,88 @@ class WindowsTests(BasePlatformTests):
self.utime(os.path.join(testdir, 'res', 'resource.h'))
self.assertRebuiltTarget('prog_1')
+class DarwinTests(BasePlatformTests):
+ '''
+ Tests that should run on macOS
+ '''
+ def setUp(self):
+ super().setUp()
+ self.platform_test_dir = os.path.join(self.src_root, 'test cases/osx')
+
+ def test_apple_bitcode(self):
+ '''
+ Test that -fembed-bitcode is correctly added while compiling and
+ -bitcode_bundle is added while linking when b_bitcode is true and not
+ when it is false. This can't be an ordinary test case because we need
+ to inspect the compiler database.
+ '''
+ testdir = os.path.join(self.common_test_dir, '4 shared')
+ # Try with bitcode enabled
+ out = self.init(testdir, extra_args='-Db_bitcode=true')
+ # Warning was printed
+ self.assertRegex(out, 'WARNING:.*b_bitcode')
+ # Compiler options were added
+ compdb = self.get_compdb()
+ self.assertIn('-fembed-bitcode', compdb[0]['command'])
+ build_ninja = os.path.join(self.builddir, 'build.ninja')
+ # Linker options were added
+ with open(build_ninja, 'r', encoding='utf-8') as f:
+ contents = f.read()
+ m = re.search('LINK_ARGS =.*-bitcode_bundle', contents)
+ self.assertIsNotNone(m, msg=contents)
+ # Try with bitcode disabled
+ self.setconf('-Db_bitcode=false')
+ # Regenerate build
+ self.build()
+ compdb = self.get_compdb()
+ self.assertNotIn('-fembed-bitcode', compdb[0]['command'])
+ build_ninja = os.path.join(self.builddir, 'build.ninja')
+ with open(build_ninja, 'r', encoding='utf-8') as f:
+ contents = f.read()
+ m = re.search('LINK_ARGS =.*-bitcode_bundle', contents)
+ self.assertIsNone(m, msg=contents)
+
+ def test_apple_bitcode_modules(self):
+ '''
+ Same as above, just for shared_module()
+ '''
+ testdir = os.path.join(self.common_test_dir, '153 shared module resolving symbol in executable')
+ # Ensure that it builds even with bitcode enabled
+ self.init(testdir, extra_args='-Db_bitcode=true')
+ self.build()
+ self.run_tests()
+
+ def _get_darwin_versions(self, fname):
+ fname = os.path.join(self.builddir, fname)
+ out = subprocess.check_output(['otool', '-L', fname], universal_newlines=True)
+ m = re.match(r'.*version (.*), current version (.*)\)', out.split('\n')[1])
+ self.assertIsNotNone(m, msg=out)
+ return m.groups()
+
+ def test_library_versioning(self):
+ '''
+ Ensure that compatibility_version and current_version are set correctly
+ '''
+ testdir = os.path.join(self.platform_test_dir, '2 library versions')
+ self.init(testdir)
+ self.build()
+ targets = {}
+ for t in self.introspect('--targets'):
+ targets[t['name']] = t['filename']
+ self.assertEqual(self._get_darwin_versions(targets['some']), ('7.0.0', '7.0.0'))
+ self.assertEqual(self._get_darwin_versions(targets['noversion']), ('0.0.0', '0.0.0'))
+ self.assertEqual(self._get_darwin_versions(targets['onlyversion']), ('1.0.0', '1.0.0'))
+ self.assertEqual(self._get_darwin_versions(targets['onlysoversion']), ('5.0.0', '5.0.0'))
+ self.assertEqual(self._get_darwin_versions(targets['intver']), ('2.0.0', '2.0.0'))
+ self.assertEqual(self._get_darwin_versions(targets['stringver']), ('2.3.0', '2.3.0'))
+ self.assertEqual(self._get_darwin_versions(targets['stringlistver']), ('2.4.0', '2.4.0'))
+ self.assertEqual(self._get_darwin_versions(targets['intstringver']), ('1111.0.0', '2.5.0'))
+ self.assertEqual(self._get_darwin_versions(targets['stringlistvers']), ('2.6.0', '2.6.1'))
+
class LinuxlikeTests(BasePlatformTests):
'''
- Tests that should run on Linux and *BSD
+ Tests that should run on Linux, macOS, and *BSD
'''
def test_basic_soname(self):
'''
@@ -3763,53 +3841,6 @@ endian = 'little'
deps.append(b'-lintl')
self.assertEqual(set(deps), set(stdo.split()))
- def test_apple_bitcode(self):
- '''
- Test that -fembed-bitcode is correctly added while compiling and
- -bitcode_bundle is added while linking when b_bitcode is true and not
- when it is false. This can't be an ordinary test case because we need
- to inspect the compiler database.
- '''
- if not is_osx():
- raise unittest.SkipTest('Apple bitcode only works on macOS')
- testdir = os.path.join(self.common_test_dir, '4 shared')
- # Try with bitcode enabled
- out = self.init(testdir, extra_args='-Db_bitcode=true')
- # Warning was printed
- self.assertRegex(out, 'WARNING:.*b_bitcode')
- # Compiler options were added
- compdb = self.get_compdb()
- self.assertIn('-fembed-bitcode', compdb[0]['command'])
- build_ninja = os.path.join(self.builddir, 'build.ninja')
- # Linker options were added
- with open(build_ninja, 'r', encoding='utf-8') as f:
- contents = f.read()
- m = re.search('LINK_ARGS =.*-bitcode_bundle', contents)
- self.assertIsNotNone(m, msg=contents)
- # Try with bitcode disabled
- self.setconf('-Db_bitcode=false')
- # Regenerate build
- self.build()
- compdb = self.get_compdb()
- self.assertNotIn('-fembed-bitcode', compdb[0]['command'])
- build_ninja = os.path.join(self.builddir, 'build.ninja')
- with open(build_ninja, 'r', encoding='utf-8') as f:
- contents = f.read()
- m = re.search('LINK_ARGS =.*-bitcode_bundle', contents)
- self.assertIsNone(m, msg=contents)
-
- def test_apple_bitcode_modules(self):
- '''
- Same as above, just for shared_module()
- '''
- if not is_osx():
- raise unittest.SkipTest('Apple bitcode not relevant')
- testdir = os.path.join(self.common_test_dir, '153 shared module resolving symbol in executable')
- # Ensure that it builds even with bitcode enabled
- self.init(testdir, extra_args='-Db_bitcode=true')
- self.build()
- self.run_tests()
-
def test_deterministic_dep_order(self):
'''
Test that the dependencies are always listed in a deterministic order.
@@ -4159,5 +4190,7 @@ if __name__ == '__main__':
cases += ['LinuxCrossMingwTests']
if is_windows() or is_cygwin():
cases += ['WindowsTests']
+ if is_osx():
+ cases += ['DarwinTests']
unittest.main(defaultTest=cases, buffer=True)
diff --git a/test cases/osx/2 library versions/installed_files.txt b/test cases/osx/2 library versions/installed_files.txt
index de7b078dc..f9c629b4e 100644
--- a/test cases/osx/2 library versions/installed_files.txt
+++ b/test cases/osx/2 library versions/installed_files.txt
@@ -1,5 +1,5 @@
usr/lib/libsome.dylib
-usr/lib/libsome.0.dylib
+usr/lib/libsome.7.dylib
usr/lib/libnoversion.dylib
usr/lib/libonlyversion.dylib
usr/lib/libonlyversion.1.dylib
diff --git a/test cases/osx/2 library versions/meson.build b/test cases/osx/2 library versions/meson.build
index acd58a50b..26f945a77 100644
--- a/test cases/osx/2 library versions/meson.build
+++ b/test cases/osx/2 library versions/meson.build
@@ -8,7 +8,7 @@ some = shared_library('some', 'lib.c',
build_rpath : zlib_dep.get_pkgconfig_variable('libdir'),
dependencies : zlib_dep,
version : '1.2.3',
- soversion : '0',
+ soversion : '7',
install : true)
noversion = shared_library('noversion', 'lib.c',
@@ -23,6 +23,21 @@ onlysoversion = shared_library('onlysoversion', 'lib.c',
soversion : 5,
install : true)
+shared_library('intver', 'lib.c',
+ darwin_versions : 2)
+
+shared_library('stringver', 'lib.c',
+ darwin_versions : '2.3')
+
+shared_library('stringlistver', 'lib.c',
+ darwin_versions : ['2.4'])
+
+shared_library('intstringver', 'lib.c',
+ darwin_versions : [1111, '2.5'])
+
+shared_library('stringlistvers', 'lib.c',
+ darwin_versions : ['2.6', '2.6.1'])
+
# Hack to make the executables below depend on the shared libraries above
# without actually adding them as `link_with` dependencies since we want to try
# linking to them with -lfoo linker arguments.