diff options
-rw-r--r-- | docs/markdown/Reference-manual.md | 11 | ||||
-rw-r--r-- | docs/markdown/snippets/shared_library_darwin_versions.md | 9 | ||||
-rw-r--r-- | mesonbuild/backend/ninjabackend.py | 3 | ||||
-rw-r--r-- | mesonbuild/build.py | 49 | ||||
-rw-r--r-- | mesonbuild/compilers/compilers.py | 30 | ||||
-rw-r--r-- | mesonbuild/compilers/d.py | 6 | ||||
-rwxr-xr-x | run_unittests.py | 129 | ||||
-rw-r--r-- | test cases/osx/2 library versions/installed_files.txt | 2 | ||||
-rw-r--r-- | test cases/osx/2 library versions/meson.build | 17 |
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. |