aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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 e830557..ea11f60 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 0000000..ad137f3
--- /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 acbeed3..6daa939 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 3debeeb..efe58f8 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 71f8ebe..36507b0 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 a03af3e..c81c048 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 16eee3e..f17b7ae 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 de7b078..f9c629b 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 acd58a5..26f945a 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.