# SPDX-License-Identifier: Apache-2.0 # Copyright 2016-2021 The Meson development team import subprocess import re import os import platform import unittest from mesonbuild.mesonlib import ( MachineChoice, is_osx, version_compare ) from mesonbuild.compilers import ( detect_c_compiler ) from run_tests import ( get_fake_env ) from .baseplatformtests import BasePlatformTests from .helpers import * @unittest.skipUnless(is_osx(), "requires Darwin") 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.platform_test_dir, '7 bitcode') env = get_fake_env(testdir, self.builddir, self.prefix) cc = detect_c_compiler(env, MachineChoice.HOST) if cc.id != 'clang': raise unittest.SkipTest('Not using Clang on OSX') # 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 for compdb in self.get_compdb(): if 'module' in compdb['file']: self.assertNotIn('-fembed-bitcode', compdb['command']) else: self.assertIn('-fembed-bitcode', compdb['command']) build_ninja = os.path.join(self.builddir, 'build.ninja') # Linker options were added with open(build_ninja, 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() for compdb in self.get_compdb(): self.assertNotIn('-fembed-bitcode', compdb['command']) build_ninja = os.path.join(self.builddir, 'build.ninja') with open(build_ninja, 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, '148 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() @unittest.skipIf(version_compare(platform.mac_ver()[0], '<10.7'), '-export_dynamic was added in 10.7') def test_apple_lto_export_dynamic(self): ''' Tests that -Wl,-export_dynamic is correctly added, when export_dynamic: true is set. On macOS, this is relevant for LTO builds only. ''' testdir = os.path.join(self.common_test_dir, '148 shared module resolving symbol in executable') # Ensure that it builds even with LTO enabled env = {'CFLAGS': '-flto'} self.init(testdir, override_envvars=env) 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 _get_darwin_rpaths(self, fname: str) -> T.List[str]: out = subprocess.check_output(['otool', '-l', fname], universal_newlines=True) pattern = re.compile(r'path (.*) \(offset \d+\)') rpaths = pattern.findall(out) return rpaths @skipIfNoPkgconfig 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'][0] if isinstance(t['filename'], list) else 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')) def test_duplicate_rpath(self): testdir = os.path.join(self.unit_test_dir, '10 build_rpath') # We purposely pass a duplicate rpath to Meson, in order # to ascertain that Meson does not call install_name_tool # with duplicate -delete_rpath arguments, which would # lead to erroring out on installation env = {"LDFLAGS": "-Wl,-rpath,/foo/bar"} self.init(testdir, override_envvars=env) self.build() self.install() def test_removing_unused_linker_args(self): testdir = os.path.join(self.common_test_dir, '104 has arg') env = {'CFLAGS': '-L/tmp -L /var/tmp -headerpad_max_install_names -Wl,-export_dynamic -framework Foundation'} self.init(testdir, override_envvars=env) def test_objc_versions(self): # Objective-C always uses the C standard version. # Objective-C++ always uses the C++ standard version. # This is what most people seem to want and in addition # it is the only setup supported by Xcode. testdir = os.path.join(self.objc_test_dir, '1 simple') self.init(testdir) self.assertIn('-std=c99', self.get_compdb()[0]['command']) self.wipe() testdir = os.path.join(self.objcpp_test_dir, '1 simple') self.init(testdir) self.assertIn('-std=c++14', self.get_compdb()[0]['command']) def test_darwin_get_object_archs(self): from mesonbuild.mesonlib import darwin_get_object_archs archs = darwin_get_object_archs('/bin/cat') self.assertEqual(archs, ['x86_64', 'aarch64']) def test_darwin_meson_rpaths_removed_on_install(self): testdir = os.path.join(self.darwin_test_dir, '1 rpath removal on install') self.init(testdir) self.build() # Meson-created RPATHs are usually only valid in the build directory rpaths = self._get_darwin_rpaths(os.path.join(self.builddir, 'libbar.dylib')) self.assertListEqual(rpaths, ['@loader_path/foo']) self.install() # Those RPATHs are no longer valid and should not be present after installation rpaths = self._get_darwin_rpaths(os.path.join(self.installdir, 'usr/lib/libbar.dylib')) self.assertListEqual(rpaths, [])