1# Copyright 2016-2021 The Meson development team 2 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6 7# http://www.apache.org/licenses/LICENSE-2.0 8 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15import stat 16import subprocess 17import re 18import tempfile 19import textwrap 20import os 21import shutil 22import hashlib 23from unittest import mock, skipUnless, SkipTest 24from glob import glob 25from pathlib import Path 26import typing as T 27 28import mesonbuild.mlog 29import mesonbuild.depfile 30import mesonbuild.dependencies.base 31import mesonbuild.dependencies.factory 32import mesonbuild.envconfig 33import mesonbuild.environment 34import mesonbuild.coredata 35import mesonbuild.modules.gnome 36from mesonbuild.mesonlib import ( 37 MachineChoice, is_windows, is_osx, is_cygwin, is_openbsd, is_haiku, 38 is_sunos, windows_proof_rmtree, version_compare, is_linux, 39 OptionKey, EnvironmentException 40) 41from mesonbuild.compilers import ( 42 detect_c_compiler, detect_cpp_compiler, compiler_from_language, 43 AppleClangCCompiler, AppleClangCPPCompiler, AppleClangObjCCompiler, 44 AppleClangObjCPPCompiler 45) 46from mesonbuild.dependencies import PkgConfigDependency 47import mesonbuild.modules.pkgconfig 48 49 50from run_tests import ( 51 get_fake_env 52) 53 54from .baseplatformtests import BasePlatformTests 55from .helpers import * 56 57def _clang_at_least(compiler: 'Compiler', minver: str, apple_minver: T.Optional[str]) -> bool: 58 """ 59 check that Clang compiler is at least a specified version, whether AppleClang or regular Clang 60 61 Parameters 62 ---------- 63 compiler: 64 Meson compiler object 65 minver: str 66 Clang minimum version 67 apple_minver: str 68 AppleCLang minimum version 69 70 Returns 71 ------- 72 at_least: bool 73 Clang is at least the specified version 74 """ 75 if isinstance(compiler, (AppleClangCCompiler, AppleClangCPPCompiler)): 76 if apple_minver is None: 77 return False 78 return version_compare(compiler.version, apple_minver) 79 return version_compare(compiler.version, minver) 80 81@skipUnless(not is_windows(), "requires something Unix-like") 82class LinuxlikeTests(BasePlatformTests): 83 ''' 84 Tests that should run on Linux, macOS, and *BSD 85 ''' 86 87 def test_basic_soname(self): 88 ''' 89 Test that the soname is set correctly for shared libraries. This can't 90 be an ordinary test case because we need to run `readelf` and actually 91 check the soname. 92 https://github.com/mesonbuild/meson/issues/785 93 ''' 94 testdir = os.path.join(self.common_test_dir, '4 shared') 95 self.init(testdir) 96 self.build() 97 lib1 = os.path.join(self.builddir, 'libmylib.so') 98 soname = get_soname(lib1) 99 self.assertEqual(soname, 'libmylib.so') 100 101 def test_custom_soname(self): 102 ''' 103 Test that the soname is set correctly for shared libraries when 104 a custom prefix and/or suffix is used. This can't be an ordinary test 105 case because we need to run `readelf` and actually check the soname. 106 https://github.com/mesonbuild/meson/issues/785 107 ''' 108 testdir = os.path.join(self.common_test_dir, '24 library versions') 109 self.init(testdir) 110 self.build() 111 lib1 = os.path.join(self.builddir, 'prefixsomelib.suffix') 112 soname = get_soname(lib1) 113 self.assertEqual(soname, 'prefixsomelib.suffix') 114 115 def test_pic(self): 116 ''' 117 Test that -fPIC is correctly added to static libraries when b_staticpic 118 is true and not when it is false. This can't be an ordinary test case 119 because we need to inspect the compiler database. 120 ''' 121 if is_windows() or is_cygwin() or is_osx(): 122 raise SkipTest('PIC not relevant') 123 124 testdir = os.path.join(self.common_test_dir, '3 static') 125 self.init(testdir) 126 compdb = self.get_compdb() 127 self.assertIn('-fPIC', compdb[0]['command']) 128 self.setconf('-Db_staticpic=false') 129 # Regenerate build 130 self.build() 131 compdb = self.get_compdb() 132 self.assertNotIn('-fPIC', compdb[0]['command']) 133 134 @mock.patch.dict(os.environ) 135 def test_pkgconfig_gen(self): 136 ''' 137 Test that generated pkg-config files can be found and have the correct 138 version and link args. This can't be an ordinary test case because we 139 need to run pkg-config outside of a Meson build file. 140 https://github.com/mesonbuild/meson/issues/889 141 ''' 142 testdir = os.path.join(self.common_test_dir, '44 pkgconfig-gen') 143 self.init(testdir) 144 env = get_fake_env(testdir, self.builddir, self.prefix) 145 kwargs = {'required': True, 'silent': True} 146 os.environ['PKG_CONFIG_LIBDIR'] = self.privatedir 147 foo_dep = PkgConfigDependency('libfoo', env, kwargs) 148 self.assertTrue(foo_dep.found()) 149 self.assertEqual(foo_dep.get_version(), '1.0') 150 self.assertIn('-lfoo', foo_dep.get_link_args()) 151 self.assertEqual(foo_dep.get_pkgconfig_variable('foo', {}), 'bar') 152 self.assertPathEqual(foo_dep.get_pkgconfig_variable('datadir', {}), '/usr/data') 153 154 libhello_nolib = PkgConfigDependency('libhello_nolib', env, kwargs) 155 self.assertTrue(libhello_nolib.found()) 156 self.assertEqual(libhello_nolib.get_link_args(), []) 157 self.assertEqual(libhello_nolib.get_compile_args(), []) 158 self.assertEqual(libhello_nolib.get_pkgconfig_variable('foo', {}), 'bar') 159 self.assertEqual(libhello_nolib.get_pkgconfig_variable('prefix', {}), self.prefix) 160 if version_compare(libhello_nolib.check_pkgconfig(libhello_nolib.pkgbin),">=0.29.1"): 161 self.assertEqual(libhello_nolib.get_pkgconfig_variable('escaped_var', {}), r'hello\ world') 162 self.assertEqual(libhello_nolib.get_pkgconfig_variable('unescaped_var', {}), 'hello world') 163 164 cc = detect_c_compiler(env, MachineChoice.HOST) 165 if cc.get_id() in {'gcc', 'clang'}: 166 for name in {'ct', 'ct0'}: 167 ct_dep = PkgConfigDependency(name, env, kwargs) 168 self.assertTrue(ct_dep.found()) 169 self.assertIn('-lct', ct_dep.get_link_args()) 170 171 def test_pkgconfig_gen_deps(self): 172 ''' 173 Test that generated pkg-config files correctly handle dependencies 174 ''' 175 testdir = os.path.join(self.common_test_dir, '44 pkgconfig-gen') 176 self.init(testdir) 177 privatedir1 = self.privatedir 178 179 self.new_builddir() 180 testdir = os.path.join(self.common_test_dir, '44 pkgconfig-gen', 'dependencies') 181 self.init(testdir, override_envvars={'PKG_CONFIG_LIBDIR': privatedir1}) 182 privatedir2 = self.privatedir 183 184 env = { 185 'PKG_CONFIG_LIBDIR': os.pathsep.join([privatedir1, privatedir2]), 186 'PKG_CONFIG_SYSTEM_LIBRARY_PATH': '/usr/lib', 187 } 188 self._run(['pkg-config', 'dependency-test', '--validate'], override_envvars=env) 189 190 # pkg-config strips some duplicated flags so we have to parse the 191 # generated file ourself. 192 expected = { 193 'Requires': 'libexposed', 194 'Requires.private': 'libfoo >= 1.0', 195 'Libs': '-L${libdir} -llibmain -pthread -lcustom', 196 'Libs.private': '-lcustom2 -L${libdir} -llibinternal', 197 'Cflags': '-I${includedir} -pthread -DCUSTOM', 198 } 199 if is_osx() or is_haiku(): 200 expected['Cflags'] = expected['Cflags'].replace('-pthread ', '') 201 with open(os.path.join(privatedir2, 'dependency-test.pc'), encoding='utf-8') as f: 202 matched_lines = 0 203 for line in f: 204 parts = line.split(':', 1) 205 if parts[0] in expected: 206 key = parts[0] 207 val = parts[1].strip() 208 expected_val = expected[key] 209 self.assertEqual(expected_val, val) 210 matched_lines += 1 211 self.assertEqual(len(expected), matched_lines) 212 213 cmd = ['pkg-config', 'requires-test'] 214 out = self._run(cmd + ['--print-requires'], override_envvars=env).strip().split('\n') 215 if not is_openbsd(): 216 self.assertEqual(sorted(out), sorted(['libexposed', 'libfoo >= 1.0', 'libhello'])) 217 else: 218 self.assertEqual(sorted(out), sorted(['libexposed', 'libfoo>=1.0', 'libhello'])) 219 220 cmd = ['pkg-config', 'requires-private-test'] 221 out = self._run(cmd + ['--print-requires-private'], override_envvars=env).strip().split('\n') 222 if not is_openbsd(): 223 self.assertEqual(sorted(out), sorted(['libexposed', 'libfoo >= 1.0', 'libhello'])) 224 else: 225 self.assertEqual(sorted(out), sorted(['libexposed', 'libfoo>=1.0', 'libhello'])) 226 227 cmd = ['pkg-config', 'pub-lib-order'] 228 out = self._run(cmd + ['--libs'], override_envvars=env).strip().split() 229 self.assertEqual(out, ['-llibmain2', '-llibinternal']) 230 231 # See common/44 pkgconfig-gen/meson.build for description of the case this test 232 with open(os.path.join(privatedir1, 'simple2.pc'), encoding='utf-8') as f: 233 content = f.read() 234 self.assertIn('Libs: -L${libdir} -lsimple2 -lsimple1', content) 235 self.assertIn('Libs.private: -lz', content) 236 237 with open(os.path.join(privatedir1, 'simple3.pc'), encoding='utf-8') as f: 238 content = f.read() 239 self.assertEqual(1, content.count('-lsimple3')) 240 241 with open(os.path.join(privatedir1, 'simple5.pc'), encoding='utf-8') as f: 242 content = f.read() 243 self.assertNotIn('-lstat2', content) 244 245 @mock.patch.dict(os.environ) 246 def test_pkgconfig_uninstalled(self): 247 testdir = os.path.join(self.common_test_dir, '44 pkgconfig-gen') 248 self.init(testdir) 249 self.build() 250 251 os.environ['PKG_CONFIG_LIBDIR'] = os.path.join(self.builddir, 'meson-uninstalled') 252 if is_cygwin(): 253 os.environ['PATH'] += os.pathsep + self.builddir 254 255 self.new_builddir() 256 testdir = os.path.join(self.common_test_dir, '44 pkgconfig-gen', 'dependencies') 257 self.init(testdir) 258 self.build() 259 self.run_tests() 260 261 def test_pkg_unfound(self): 262 testdir = os.path.join(self.unit_test_dir, '23 unfound pkgconfig') 263 self.init(testdir) 264 with open(os.path.join(self.privatedir, 'somename.pc'), encoding='utf-8') as f: 265 pcfile = f.read() 266 self.assertFalse('blub_blob_blib' in pcfile) 267 268 def test_symlink_builddir(self) -> None: 269 ''' 270 Test using a symlink as either the builddir for "setup" or 271 the argument for "-C". 272 ''' 273 testdir = os.path.join(self.common_test_dir, '1 trivial') 274 275 symdir = f'{self.builddir}-symlink' 276 os.symlink(self.builddir, symdir) 277 self.addCleanup(os.unlink, symdir) 278 self.change_builddir(symdir) 279 280 self.init(testdir) 281 self.build() 282 self._run(self.mtest_command) 283 284 def test_vala_c_warnings(self): 285 ''' 286 Test that no warnings are emitted for C code generated by Vala. This 287 can't be an ordinary test case because we need to inspect the compiler 288 database. 289 https://github.com/mesonbuild/meson/issues/864 290 ''' 291 if not shutil.which('valac'): 292 raise SkipTest('valac not installed.') 293 testdir = os.path.join(self.vala_test_dir, '5 target glib') 294 self.init(testdir) 295 compdb = self.get_compdb() 296 vala_command = None 297 c_command = None 298 for each in compdb: 299 if each['file'].endswith('GLib.Thread.c'): 300 vala_command = each['command'] 301 elif each['file'].endswith('GLib.Thread.vala'): 302 continue 303 elif each['file'].endswith('retcode.c'): 304 c_command = each['command'] 305 else: 306 m = 'Unknown file {!r} in vala_c_warnings test'.format(each['file']) 307 raise AssertionError(m) 308 self.assertIsNotNone(vala_command) 309 self.assertIsNotNone(c_command) 310 # -w suppresses all warnings, should be there in Vala but not in C 311 self.assertIn(" -w ", vala_command) 312 self.assertNotIn(" -w ", c_command) 313 # -Wall enables all warnings, should be there in C but not in Vala 314 self.assertNotIn(" -Wall ", vala_command) 315 self.assertIn(" -Wall ", c_command) 316 # -Werror converts warnings to errors, should always be there since it's 317 # injected by an unrelated piece of code and the project has werror=true 318 self.assertIn(" -Werror ", vala_command) 319 self.assertIn(" -Werror ", c_command) 320 321 @skipIfNoPkgconfig 322 def test_qtdependency_pkgconfig_detection(self): 323 ''' 324 Test that qt4 and qt5 detection with pkgconfig works. 325 ''' 326 # Verify Qt4 or Qt5 can be found with pkg-config 327 qt4 = subprocess.call(['pkg-config', '--exists', 'QtCore']) 328 qt5 = subprocess.call(['pkg-config', '--exists', 'Qt5Core']) 329 testdir = os.path.join(self.framework_test_dir, '4 qt') 330 self.init(testdir, extra_args=['-Dmethod=pkg-config']) 331 # Confirm that the dependency was found with pkg-config 332 mesonlog = self.get_meson_log() 333 if qt4 == 0: 334 self.assertRegex('\n'.join(mesonlog), 335 r'Run-time dependency qt4 \(modules: Core\) found: YES 4.* \(pkg-config\)') 336 if qt5 == 0: 337 self.assertRegex('\n'.join(mesonlog), 338 r'Run-time dependency qt5 \(modules: Core\) found: YES 5.* \(pkg-config\)') 339 340 @skip_if_not_base_option('b_sanitize') 341 def test_generate_gir_with_address_sanitizer(self): 342 if is_cygwin(): 343 raise SkipTest('asan not available on Cygwin') 344 if is_openbsd(): 345 raise SkipTest('-fsanitize=address is not supported on OpenBSD') 346 347 testdir = os.path.join(self.framework_test_dir, '7 gnome') 348 self.init(testdir, extra_args=['-Db_sanitize=address', '-Db_lundef=false']) 349 self.build() 350 351 def test_qt5dependency_qmake_detection(self): 352 ''' 353 Test that qt5 detection with qmake works. This can't be an ordinary 354 test case because it involves setting the environment. 355 ''' 356 # Verify that qmake is for Qt5 357 if not shutil.which('qmake-qt5'): 358 if not shutil.which('qmake'): 359 raise SkipTest('QMake not found') 360 output = subprocess.getoutput('qmake --version') 361 if 'Qt version 5' not in output: 362 raise SkipTest('Qmake found, but it is not for Qt 5.') 363 # Disable pkg-config codepath and force searching with qmake/qmake-qt5 364 testdir = os.path.join(self.framework_test_dir, '4 qt') 365 self.init(testdir, extra_args=['-Dmethod=qmake']) 366 # Confirm that the dependency was found with qmake 367 mesonlog = self.get_meson_log() 368 self.assertRegex('\n'.join(mesonlog), 369 r'Run-time dependency qt5 \(modules: Core\) found: YES .* \(qmake\)\n') 370 371 def test_qt6dependency_qmake_detection(self): 372 ''' 373 Test that qt6 detection with qmake works. This can't be an ordinary 374 test case because it involves setting the environment. 375 ''' 376 # Verify that qmake is for Qt5 377 if not shutil.which('qmake-qt6'): 378 if not shutil.which('qmake'): 379 raise SkipTest('QMake not found') 380 output = subprocess.getoutput('qmake --version') 381 if 'Qt version 6' not in output: 382 raise SkipTest('Qmake found, but it is not for Qt 6.') 383 # Disable pkg-config codepath and force searching with qmake/qmake-qt6 384 testdir = os.path.join(self.framework_test_dir, '4 qt') 385 self.init(testdir, extra_args=['-Dmethod=qmake']) 386 # Confirm that the dependency was found with qmake 387 mesonlog = self.get_meson_log() 388 self.assertRegex('\n'.join(mesonlog), 389 r'Run-time dependency qt6 \(modules: Core\) found: YES .* \(qmake\)\n') 390 391 def glob_sofiles_without_privdir(self, g): 392 files = glob(g) 393 return [f for f in files if not f.endswith('.p')] 394 395 def _test_soname_impl(self, libpath, install): 396 if is_cygwin() or is_osx(): 397 raise SkipTest('Test only applicable to ELF and linuxlike sonames') 398 399 testdir = os.path.join(self.unit_test_dir, '1 soname') 400 self.init(testdir) 401 self.build() 402 if install: 403 self.install() 404 405 # File without aliases set. 406 nover = os.path.join(libpath, 'libnover.so') 407 self.assertPathExists(nover) 408 self.assertFalse(os.path.islink(nover)) 409 self.assertEqual(get_soname(nover), 'libnover.so') 410 self.assertEqual(len(self.glob_sofiles_without_privdir(nover[:-3] + '*')), 1) 411 412 # File with version set 413 verset = os.path.join(libpath, 'libverset.so') 414 self.assertPathExists(verset + '.4.5.6') 415 self.assertEqual(os.readlink(verset), 'libverset.so.4') 416 self.assertEqual(get_soname(verset), 'libverset.so.4') 417 self.assertEqual(len(self.glob_sofiles_without_privdir(verset[:-3] + '*')), 3) 418 419 # File with soversion set 420 soverset = os.path.join(libpath, 'libsoverset.so') 421 self.assertPathExists(soverset + '.1.2.3') 422 self.assertEqual(os.readlink(soverset), 'libsoverset.so.1.2.3') 423 self.assertEqual(get_soname(soverset), 'libsoverset.so.1.2.3') 424 self.assertEqual(len(self.glob_sofiles_without_privdir(soverset[:-3] + '*')), 2) 425 426 # File with version and soversion set to same values 427 settosame = os.path.join(libpath, 'libsettosame.so') 428 self.assertPathExists(settosame + '.7.8.9') 429 self.assertEqual(os.readlink(settosame), 'libsettosame.so.7.8.9') 430 self.assertEqual(get_soname(settosame), 'libsettosame.so.7.8.9') 431 self.assertEqual(len(self.glob_sofiles_without_privdir(settosame[:-3] + '*')), 2) 432 433 # File with version and soversion set to different values 434 bothset = os.path.join(libpath, 'libbothset.so') 435 self.assertPathExists(bothset + '.1.2.3') 436 self.assertEqual(os.readlink(bothset), 'libbothset.so.1.2.3') 437 self.assertEqual(os.readlink(bothset + '.1.2.3'), 'libbothset.so.4.5.6') 438 self.assertEqual(get_soname(bothset), 'libbothset.so.1.2.3') 439 self.assertEqual(len(self.glob_sofiles_without_privdir(bothset[:-3] + '*')), 3) 440 441 # A shared_module that is not linked to anything 442 module = os.path.join(libpath, 'libsome_module.so') 443 self.assertPathExists(module) 444 self.assertFalse(os.path.islink(module)) 445 self.assertEqual(get_soname(module), None) 446 447 # A shared_module that is not linked to an executable with link_with: 448 module = os.path.join(libpath, 'liblinked_module1.so') 449 self.assertPathExists(module) 450 self.assertFalse(os.path.islink(module)) 451 self.assertEqual(get_soname(module), 'liblinked_module1.so') 452 453 # A shared_module that is not linked to an executable with dependencies: 454 module = os.path.join(libpath, 'liblinked_module2.so') 455 self.assertPathExists(module) 456 self.assertFalse(os.path.islink(module)) 457 self.assertEqual(get_soname(module), 'liblinked_module2.so') 458 459 def test_soname(self): 460 self._test_soname_impl(self.builddir, False) 461 462 def test_installed_soname(self): 463 libdir = self.installdir + os.path.join(self.prefix, self.libdir) 464 self._test_soname_impl(libdir, True) 465 466 def test_compiler_check_flags_order(self): 467 ''' 468 Test that compiler check flags override all other flags. This can't be 469 an ordinary test case because it needs the environment to be set. 470 ''' 471 testdir = os.path.join(self.common_test_dir, '36 has function') 472 env = get_fake_env(testdir, self.builddir, self.prefix) 473 cpp = detect_cpp_compiler(env, MachineChoice.HOST) 474 Oflag = '-O3' 475 OflagCPP = Oflag 476 if cpp.get_id() in ('clang', 'gcc'): 477 # prevent developers from adding "int main(int argc, char **argv)" 478 # to small Meson checks unless these parameters are actually used 479 OflagCPP += ' -Werror=unused-parameter' 480 env = {'CFLAGS': Oflag, 481 'CXXFLAGS': OflagCPP} 482 self.init(testdir, override_envvars=env) 483 cmds = self.get_meson_log_compiler_checks() 484 for cmd in cmds: 485 if cmd[0] == 'ccache': 486 cmd = cmd[1:] 487 # Verify that -I flags from the `args` kwarg are first 488 # This is set in the '36 has function' test case 489 self.assertEqual(cmd[1], '-I/tmp') 490 # Verify that -O3 set via the environment is overridden by -O0 491 Oargs = [arg for arg in cmd if arg.startswith('-O')] 492 self.assertEqual(Oargs, [Oflag, '-O0']) 493 494 def _test_stds_impl(self, testdir: str, compiler: 'Compiler') -> None: 495 has_cpp17 = (compiler.get_id() not in {'clang', 'gcc'} or 496 compiler.get_id() == 'clang' and _clang_at_least(compiler, '>=5.0.0', '>=9.1') or 497 compiler.get_id() == 'gcc' and version_compare(compiler.version, '>=5.0.0')) 498 has_cpp2a_c17 = (compiler.get_id() not in {'clang', 'gcc'} or 499 compiler.get_id() == 'clang' and _clang_at_least(compiler, '>=6.0.0', '>=10.0') or 500 compiler.get_id() == 'gcc' and version_compare(compiler.version, '>=8.0.0')) 501 has_cpp20 = (compiler.get_id() not in {'clang', 'gcc'} or 502 compiler.get_id() == 'clang' and _clang_at_least(compiler, '>=10.0.0', None) or 503 compiler.get_id() == 'gcc' and version_compare(compiler.version, '>=10.0.0')) 504 has_c18 = (compiler.get_id() not in {'clang', 'gcc'} or 505 compiler.get_id() == 'clang' and _clang_at_least(compiler, '>=8.0.0', '>=11.0') or 506 compiler.get_id() == 'gcc' and version_compare(compiler.version, '>=8.0.0')) 507 # Check that all the listed -std=xxx options for this compiler work just fine when used 508 # https://en.wikipedia.org/wiki/Xcode#Latest_versions 509 # https://www.gnu.org/software/gcc/projects/cxx-status.html 510 key = OptionKey('std', lang=compiler.language) 511 for v in compiler.get_options()[key].choices: 512 # we do it like this to handle gnu++17,c++17 and gnu17,c17 cleanly 513 # thus, C++ first 514 if '++17' in v and not has_cpp17: 515 continue 516 elif '++2a' in v and not has_cpp2a_c17: # https://en.cppreference.com/w/cpp/compiler_support 517 continue 518 elif '++20' in v and not has_cpp20: 519 continue 520 # now C 521 elif '17' in v and not has_cpp2a_c17: 522 continue 523 elif '18' in v and not has_c18: 524 continue 525 self.init(testdir, extra_args=[f'-D{key!s}={v}']) 526 cmd = self.get_compdb()[0]['command'] 527 # c++03 and gnu++03 are not understood by ICC, don't try to look for them 528 skiplist = frozenset([ 529 ('intel', 'c++03'), 530 ('intel', 'gnu++03')]) 531 if v != 'none' and not (compiler.get_id(), v) in skiplist: 532 cmd_std = f" -std={v} " 533 self.assertIn(cmd_std, cmd) 534 try: 535 self.build() 536 except Exception: 537 print(f'{key!s} was {v!r}') 538 raise 539 self.wipe() 540 # Check that an invalid std option in CFLAGS/CPPFLAGS fails 541 # Needed because by default ICC ignores invalid options 542 cmd_std = '-std=FAIL' 543 if compiler.language == 'c': 544 env_flag_name = 'CFLAGS' 545 elif compiler.language == 'cpp': 546 env_flag_name = 'CXXFLAGS' 547 else: 548 raise NotImplementedError(f'Language {compiler.language} not defined.') 549 env = {} 550 env[env_flag_name] = cmd_std 551 with self.assertRaises((subprocess.CalledProcessError, EnvironmentException), 552 msg='C compiler should have failed with -std=FAIL'): 553 self.init(testdir, override_envvars = env) 554 # ICC won't fail in the above because additional flags are needed to 555 # make unknown -std=... options errors. 556 self.build() 557 558 def test_compiler_c_stds(self): 559 ''' 560 Test that C stds specified for this compiler can all be used. Can't be 561 an ordinary test because it requires passing options to meson. 562 ''' 563 testdir = os.path.join(self.common_test_dir, '1 trivial') 564 env = get_fake_env(testdir, self.builddir, self.prefix) 565 cc = detect_c_compiler(env, MachineChoice.HOST) 566 self._test_stds_impl(testdir, cc) 567 568 def test_compiler_cpp_stds(self): 569 ''' 570 Test that C++ stds specified for this compiler can all be used. Can't 571 be an ordinary test because it requires passing options to meson. 572 ''' 573 testdir = os.path.join(self.common_test_dir, '2 cpp') 574 env = get_fake_env(testdir, self.builddir, self.prefix) 575 cpp = detect_cpp_compiler(env, MachineChoice.HOST) 576 self._test_stds_impl(testdir, cpp) 577 578 def test_unity_subproj(self): 579 testdir = os.path.join(self.common_test_dir, '42 subproject') 580 self.init(testdir, extra_args='--unity=subprojects') 581 pdirs = glob(os.path.join(self.builddir, 'subprojects/sublib/simpletest*.p')) 582 self.assertEqual(len(pdirs), 1) 583 self.assertPathExists(os.path.join(pdirs[0], 'simpletest-unity0.c')) 584 sdirs = glob(os.path.join(self.builddir, 'subprojects/sublib/*sublib*.p')) 585 self.assertEqual(len(sdirs), 1) 586 self.assertPathExists(os.path.join(sdirs[0], 'sublib-unity0.c')) 587 self.assertPathDoesNotExist(os.path.join(self.builddir, 'user@exe/user-unity.c')) 588 self.build() 589 590 def test_installed_modes(self): 591 ''' 592 Test that files installed by these tests have the correct permissions. 593 Can't be an ordinary test because our installed_files.txt is very basic. 594 ''' 595 if is_cygwin(): 596 self.new_builddir_in_tempdir() 597 # Test file modes 598 testdir = os.path.join(self.common_test_dir, '12 data') 599 self.init(testdir) 600 self.install() 601 602 f = os.path.join(self.installdir, 'etc', 'etcfile.dat') 603 found_mode = stat.filemode(os.stat(f).st_mode) 604 want_mode = 'rw------T' 605 self.assertEqual(want_mode, found_mode[1:]) 606 607 f = os.path.join(self.installdir, 'usr', 'bin', 'runscript.sh') 608 statf = os.stat(f) 609 found_mode = stat.filemode(statf.st_mode) 610 want_mode = 'rwxr-sr-x' 611 self.assertEqual(want_mode, found_mode[1:]) 612 if os.getuid() == 0: 613 # The chown failed nonfatally if we're not root 614 self.assertEqual(0, statf.st_uid) 615 self.assertEqual(0, statf.st_gid) 616 617 f = os.path.join(self.installdir, 'usr', 'share', 'progname', 618 'fileobject_datafile.dat') 619 orig = os.path.join(testdir, 'fileobject_datafile.dat') 620 statf = os.stat(f) 621 statorig = os.stat(orig) 622 found_mode = stat.filemode(statf.st_mode) 623 orig_mode = stat.filemode(statorig.st_mode) 624 self.assertEqual(orig_mode[1:], found_mode[1:]) 625 self.assertEqual(os.getuid(), statf.st_uid) 626 if os.getuid() == 0: 627 # The chown failed nonfatally if we're not root 628 self.assertEqual(0, statf.st_gid) 629 630 self.wipe() 631 # Test directory modes 632 testdir = os.path.join(self.common_test_dir, '59 install subdir') 633 self.init(testdir) 634 self.install() 635 636 f = os.path.join(self.installdir, 'usr', 'share', 'sub1', 'second.dat') 637 statf = os.stat(f) 638 found_mode = stat.filemode(statf.st_mode) 639 want_mode = 'rwxr-x--t' 640 self.assertEqual(want_mode, found_mode[1:]) 641 if os.getuid() == 0: 642 # The chown failed nonfatally if we're not root 643 self.assertEqual(0, statf.st_uid) 644 645 def test_installed_modes_extended(self): 646 ''' 647 Test that files are installed with correct permissions using install_mode. 648 ''' 649 if is_cygwin(): 650 self.new_builddir_in_tempdir() 651 testdir = os.path.join(self.common_test_dir, '190 install_mode') 652 self.init(testdir) 653 self.build() 654 self.install() 655 656 for fsobj, want_mode in [ 657 ('bin', 'drwxr-x---'), 658 ('bin/runscript.sh', '-rwxr-sr-x'), 659 ('bin/trivialprog', '-rwxr-sr-x'), 660 ('include', 'drwxr-x---'), 661 ('include/config.h', '-rw-rwSr--'), 662 ('include/rootdir.h', '-r--r--r-T'), 663 ('lib', 'drwxr-x---'), 664 ('lib/libstat.a', '-rw---Sr--'), 665 ('share', 'drwxr-x---'), 666 ('share/man', 'drwxr-x---'), 667 ('share/man/man1', 'drwxr-x---'), 668 ('share/man/man1/foo.1', '-r--r--r-T'), 669 ('share/sub1', 'drwxr-x---'), 670 ('share/sub1/second.dat', '-rwxr-x--t'), 671 ('subdir', 'drwxr-x---'), 672 ('subdir/data.dat', '-rw-rwSr--'), 673 ]: 674 f = os.path.join(self.installdir, 'usr', *fsobj.split('/')) 675 found_mode = stat.filemode(os.stat(f).st_mode) 676 self.assertEqual(want_mode, found_mode, 677 msg=('Expected file %s to have mode %s but found %s instead.' % 678 (fsobj, want_mode, found_mode))) 679 # Ensure that introspect --installed works on all types of files 680 # FIXME: also verify the files list 681 self.introspect('--installed') 682 683 def test_install_umask(self): 684 ''' 685 Test that files are installed with correct permissions using default 686 install umask of 022, regardless of the umask at time the worktree 687 was checked out or the build was executed. 688 ''' 689 if is_cygwin(): 690 self.new_builddir_in_tempdir() 691 # Copy source tree to a temporary directory and change permissions 692 # there to simulate a checkout with umask 002. 693 orig_testdir = os.path.join(self.unit_test_dir, '26 install umask') 694 # Create a new testdir under tmpdir. 695 tmpdir = os.path.realpath(tempfile.mkdtemp()) 696 self.addCleanup(windows_proof_rmtree, tmpdir) 697 testdir = os.path.join(tmpdir, '26 install umask') 698 # Copy the tree using shutil.copyfile, which will use the current umask 699 # instead of preserving permissions of the old tree. 700 save_umask = os.umask(0o002) 701 self.addCleanup(os.umask, save_umask) 702 shutil.copytree(orig_testdir, testdir, copy_function=shutil.copyfile) 703 # Preserve the executable status of subdir/sayhello though. 704 os.chmod(os.path.join(testdir, 'subdir', 'sayhello'), 0o775) 705 self.init(testdir) 706 # Run the build under a 027 umask now. 707 os.umask(0o027) 708 self.build() 709 # And keep umask 027 for the install step too. 710 self.install() 711 712 for executable in [ 713 'bin/prog', 714 'share/subdir/sayhello', 715 ]: 716 f = os.path.join(self.installdir, 'usr', *executable.split('/')) 717 found_mode = stat.filemode(os.stat(f).st_mode) 718 want_mode = '-rwxr-xr-x' 719 self.assertEqual(want_mode, found_mode, 720 msg=('Expected file %s to have mode %s but found %s instead.' % 721 (executable, want_mode, found_mode))) 722 723 for directory in [ 724 'usr', 725 'usr/bin', 726 'usr/include', 727 'usr/share', 728 'usr/share/man', 729 'usr/share/man/man1', 730 'usr/share/subdir', 731 ]: 732 f = os.path.join(self.installdir, *directory.split('/')) 733 found_mode = stat.filemode(os.stat(f).st_mode) 734 want_mode = 'drwxr-xr-x' 735 self.assertEqual(want_mode, found_mode, 736 msg=('Expected directory %s to have mode %s but found %s instead.' % 737 (directory, want_mode, found_mode))) 738 739 for datafile in [ 740 'include/sample.h', 741 'share/datafile.cat', 742 'share/file.dat', 743 'share/man/man1/prog.1', 744 'share/subdir/datafile.dog', 745 ]: 746 f = os.path.join(self.installdir, 'usr', *datafile.split('/')) 747 found_mode = stat.filemode(os.stat(f).st_mode) 748 want_mode = '-rw-r--r--' 749 self.assertEqual(want_mode, found_mode, 750 msg=('Expected file %s to have mode %s but found %s instead.' % 751 (datafile, want_mode, found_mode))) 752 753 def test_cpp_std_override(self): 754 testdir = os.path.join(self.unit_test_dir, '6 std override') 755 self.init(testdir) 756 compdb = self.get_compdb() 757 # Don't try to use -std=c++03 as a check for the 758 # presence of a compiler flag, as ICC does not 759 # support it. 760 for i in compdb: 761 if 'prog98' in i['file']: 762 c98_comp = i['command'] 763 if 'prog11' in i['file']: 764 c11_comp = i['command'] 765 if 'progp' in i['file']: 766 plain_comp = i['command'] 767 self.assertNotEqual(len(plain_comp), 0) 768 self.assertIn('-std=c++98', c98_comp) 769 self.assertNotIn('-std=c++11', c98_comp) 770 self.assertIn('-std=c++11', c11_comp) 771 self.assertNotIn('-std=c++98', c11_comp) 772 self.assertNotIn('-std=c++98', plain_comp) 773 self.assertNotIn('-std=c++11', plain_comp) 774 # Now werror 775 self.assertIn('-Werror', plain_comp) 776 self.assertNotIn('-Werror', c98_comp) 777 778 def test_run_installed(self): 779 if is_cygwin() or is_osx(): 780 raise SkipTest('LD_LIBRARY_PATH and RPATH not applicable') 781 782 testdir = os.path.join(self.unit_test_dir, '7 run installed') 783 self.init(testdir) 784 self.build() 785 self.install() 786 installed_exe = os.path.join(self.installdir, 'usr/bin/prog') 787 installed_libdir = os.path.join(self.installdir, 'usr/foo') 788 installed_lib = os.path.join(installed_libdir, 'libfoo.so') 789 self.assertTrue(os.path.isfile(installed_exe)) 790 self.assertTrue(os.path.isdir(installed_libdir)) 791 self.assertTrue(os.path.isfile(installed_lib)) 792 # Must fail when run without LD_LIBRARY_PATH to ensure that 793 # rpath has been properly stripped rather than pointing to the builddir. 794 self.assertNotEqual(subprocess.call(installed_exe, stderr=subprocess.DEVNULL), 0) 795 # When LD_LIBRARY_PATH is set it should start working. 796 # For some reason setting LD_LIBRARY_PATH in os.environ fails 797 # when all tests are run (but works when only this test is run), 798 # but doing this explicitly works. 799 env = os.environ.copy() 800 env['LD_LIBRARY_PATH'] = ':'.join([installed_libdir, env.get('LD_LIBRARY_PATH', '')]) 801 self.assertEqual(subprocess.call(installed_exe, env=env), 0) 802 # Ensure that introspect --installed works 803 installed = self.introspect('--installed') 804 for v in installed.values(): 805 self.assertTrue('prog' in v or 'foo' in v) 806 807 @skipIfNoPkgconfig 808 def test_order_of_l_arguments(self): 809 testdir = os.path.join(self.unit_test_dir, '8 -L -l order') 810 self.init(testdir, override_envvars={'PKG_CONFIG_PATH': testdir}) 811 # NOTE: .pc file has -Lfoo -lfoo -Lbar -lbar but pkg-config reorders 812 # the flags before returning them to -Lfoo -Lbar -lfoo -lbar 813 # but pkgconf seems to not do that. Sigh. Support both. 814 expected_order = [('-L/me/first', '-lfoo1'), 815 ('-L/me/second', '-lfoo2'), 816 ('-L/me/first', '-L/me/second'), 817 ('-lfoo1', '-lfoo2'), 818 ('-L/me/second', '-L/me/third'), 819 ('-L/me/third', '-L/me/fourth',), 820 ('-L/me/third', '-lfoo3'), 821 ('-L/me/fourth', '-lfoo4'), 822 ('-lfoo3', '-lfoo4'), 823 ] 824 with open(os.path.join(self.builddir, 'build.ninja'), encoding='utf-8') as ifile: 825 for line in ifile: 826 if expected_order[0][0] in line: 827 for first, second in expected_order: 828 self.assertLess(line.index(first), line.index(second)) 829 return 830 raise RuntimeError('Linker entries not found in the Ninja file.') 831 832 def test_introspect_dependencies(self): 833 ''' 834 Tests that mesonintrospect --dependencies returns expected output. 835 ''' 836 testdir = os.path.join(self.framework_test_dir, '7 gnome') 837 self.init(testdir) 838 glib_found = False 839 gobject_found = False 840 deps = self.introspect('--dependencies') 841 self.assertIsInstance(deps, list) 842 for dep in deps: 843 self.assertIsInstance(dep, dict) 844 self.assertIn('name', dep) 845 self.assertIn('compile_args', dep) 846 self.assertIn('link_args', dep) 847 if dep['name'] == 'glib-2.0': 848 glib_found = True 849 elif dep['name'] == 'gobject-2.0': 850 gobject_found = True 851 self.assertTrue(glib_found) 852 self.assertTrue(gobject_found) 853 if subprocess.call(['pkg-config', '--exists', 'glib-2.0 >= 2.56.2']) != 0: 854 raise SkipTest('glib >= 2.56.2 needed for the rest') 855 targets = self.introspect('--targets') 856 docbook_target = None 857 for t in targets: 858 if t['name'] == 'generated-gdbus-docbook': 859 docbook_target = t 860 break 861 self.assertIsInstance(docbook_target, dict) 862 self.assertEqual(os.path.basename(t['filename'][0]), 'generated-gdbus-doc-' + os.path.basename(t['target_sources'][0]['sources'][0])) 863 864 def test_introspect_installed(self): 865 testdir = os.path.join(self.linuxlike_test_dir, '7 library versions') 866 self.init(testdir) 867 868 install = self.introspect('--installed') 869 install = {os.path.basename(k): v for k, v in install.items()} 870 print(install) 871 if is_osx(): 872 the_truth = { 873 'libmodule.dylib': '/usr/lib/libmodule.dylib', 874 'libnoversion.dylib': '/usr/lib/libnoversion.dylib', 875 'libonlysoversion.5.dylib': '/usr/lib/libonlysoversion.5.dylib', 876 'libonlysoversion.dylib': '/usr/lib/libonlysoversion.dylib', 877 'libonlyversion.1.dylib': '/usr/lib/libonlyversion.1.dylib', 878 'libonlyversion.dylib': '/usr/lib/libonlyversion.dylib', 879 'libsome.0.dylib': '/usr/lib/libsome.0.dylib', 880 'libsome.dylib': '/usr/lib/libsome.dylib', 881 } 882 the_truth_2 = {'/usr/lib/libsome.dylib', 883 '/usr/lib/libsome.0.dylib', 884 } 885 else: 886 the_truth = { 887 'libmodule.so': '/usr/lib/libmodule.so', 888 'libnoversion.so': '/usr/lib/libnoversion.so', 889 'libonlysoversion.so': '/usr/lib/libonlysoversion.so', 890 'libonlysoversion.so.5': '/usr/lib/libonlysoversion.so.5', 891 'libonlyversion.so': '/usr/lib/libonlyversion.so', 892 'libonlyversion.so.1': '/usr/lib/libonlyversion.so.1', 893 'libonlyversion.so.1.4.5': '/usr/lib/libonlyversion.so.1.4.5', 894 'libsome.so': '/usr/lib/libsome.so', 895 'libsome.so.0': '/usr/lib/libsome.so.0', 896 'libsome.so.1.2.3': '/usr/lib/libsome.so.1.2.3', 897 } 898 the_truth_2 = {'/usr/lib/libsome.so', 899 '/usr/lib/libsome.so.0', 900 '/usr/lib/libsome.so.1.2.3'} 901 self.assertDictEqual(install, the_truth) 902 903 targets = self.introspect('--targets') 904 for t in targets: 905 if t['name'] != 'some': 906 continue 907 self.assertSetEqual(the_truth_2, set(t['install_filename'])) 908 909 def test_build_rpath(self): 910 if is_cygwin(): 911 raise SkipTest('Windows PE/COFF binaries do not use RPATH') 912 testdir = os.path.join(self.unit_test_dir, '10 build_rpath') 913 self.init(testdir) 914 self.build() 915 build_rpath = get_rpath(os.path.join(self.builddir, 'prog')) 916 self.assertEqual(build_rpath, '$ORIGIN/sub:/foo/bar') 917 build_rpath = get_rpath(os.path.join(self.builddir, 'progcxx')) 918 self.assertEqual(build_rpath, '$ORIGIN/sub:/foo/bar') 919 self.install() 920 install_rpath = get_rpath(os.path.join(self.installdir, 'usr/bin/prog')) 921 self.assertEqual(install_rpath, '/baz') 922 install_rpath = get_rpath(os.path.join(self.installdir, 'usr/bin/progcxx')) 923 self.assertEqual(install_rpath, 'baz') 924 925 @skipIfNoPkgconfig 926 def test_build_rpath_pkgconfig(self): 927 ''' 928 Test that current build artefacts (libs) are found first on the rpath, 929 manually specified rpath comes second and additional rpath elements (from 930 pkg-config files) come last 931 ''' 932 if is_cygwin(): 933 raise SkipTest('Windows PE/COFF binaries do not use RPATH') 934 testdir = os.path.join(self.unit_test_dir, '90 pkgconfig build rpath order') 935 self.init(testdir, override_envvars={'PKG_CONFIG_PATH': testdir}) 936 self.build() 937 build_rpath = get_rpath(os.path.join(self.builddir, 'prog')) 938 self.assertEqual(build_rpath, '$ORIGIN/sub:/foo/bar:/foo/dummy') 939 build_rpath = get_rpath(os.path.join(self.builddir, 'progcxx')) 940 self.assertEqual(build_rpath, '$ORIGIN/sub:/foo/bar:/foo/dummy') 941 self.install() 942 install_rpath = get_rpath(os.path.join(self.installdir, 'usr/bin/prog')) 943 self.assertEqual(install_rpath, '/baz:/foo/dummy') 944 install_rpath = get_rpath(os.path.join(self.installdir, 'usr/bin/progcxx')) 945 self.assertEqual(install_rpath, 'baz:/foo/dummy') 946 947 def test_global_rpath(self): 948 if is_cygwin(): 949 raise SkipTest('Windows PE/COFF binaries do not use RPATH') 950 if is_osx(): 951 raise SkipTest('Global RPATHs via LDFLAGS not yet supported on MacOS (does anybody need it?)') 952 953 testdir = os.path.join(self.unit_test_dir, '80 global-rpath') 954 oldinstalldir = self.installdir 955 956 # Build and install an external library without DESTDIR. 957 # The external library generates a .pc file without an rpath. 958 yonder_dir = os.path.join(testdir, 'yonder') 959 yonder_prefix = os.path.join(oldinstalldir, 'yonder') 960 yonder_libdir = os.path.join(yonder_prefix, self.libdir) 961 self.prefix = yonder_prefix 962 self.installdir = yonder_prefix 963 self.init(yonder_dir) 964 self.build() 965 self.install(use_destdir=False) 966 967 # Since rpath has multiple valid formats we need to 968 # test that they are all properly used. 969 rpath_formats = [ 970 ('-Wl,-rpath=', False), 971 ('-Wl,-rpath,', False), 972 ('-Wl,--just-symbols=', True), 973 ('-Wl,--just-symbols,', True), 974 ('-Wl,-R', False), 975 ('-Wl,-R,', False) 976 ] 977 for rpath_format, exception in rpath_formats: 978 # Build an app that uses that installed library. 979 # Supply the rpath to the installed library via LDFLAGS 980 # (as systems like buildroot and guix are wont to do) 981 # and verify install preserves that rpath. 982 self.new_builddir() 983 env = {'LDFLAGS': rpath_format + yonder_libdir, 984 'PKG_CONFIG_PATH': os.path.join(yonder_libdir, 'pkgconfig')} 985 if exception: 986 with self.assertRaises(subprocess.CalledProcessError): 987 self.init(testdir, override_envvars=env) 988 continue 989 self.init(testdir, override_envvars=env) 990 self.build() 991 self.install(use_destdir=False) 992 got_rpath = get_rpath(os.path.join(yonder_prefix, 'bin/rpathified')) 993 self.assertEqual(got_rpath, yonder_libdir, rpath_format) 994 995 @skip_if_not_base_option('b_sanitize') 996 def test_pch_with_address_sanitizer(self): 997 if is_cygwin(): 998 raise SkipTest('asan not available on Cygwin') 999 if is_openbsd(): 1000 raise SkipTest('-fsanitize=address is not supported on OpenBSD') 1001 1002 testdir = os.path.join(self.common_test_dir, '13 pch') 1003 self.init(testdir, extra_args=['-Db_sanitize=address', '-Db_lundef=false']) 1004 self.build() 1005 compdb = self.get_compdb() 1006 for i in compdb: 1007 self.assertIn("-fsanitize=address", i["command"]) 1008 1009 def test_cross_find_program(self): 1010 testdir = os.path.join(self.unit_test_dir, '11 cross prog') 1011 crossfile = tempfile.NamedTemporaryFile(mode='w') 1012 print(os.path.join(testdir, 'some_cross_tool.py')) 1013 1014 tool_path = os.path.join(testdir, 'some_cross_tool.py') 1015 1016 crossfile.write(textwrap.dedent(f'''\ 1017 [binaries] 1018 c = '{shutil.which('gcc' if is_sunos() else 'cc')}' 1019 ar = '{shutil.which('ar')}' 1020 strip = '{shutil.which('strip')}' 1021 sometool.py = ['{tool_path}'] 1022 someothertool.py = '{tool_path}' 1023 1024 [properties] 1025 1026 [host_machine] 1027 system = 'linux' 1028 cpu_family = 'arm' 1029 cpu = 'armv7' # Not sure if correct. 1030 endian = 'little' 1031 ''')) 1032 crossfile.flush() 1033 self.meson_cross_file = crossfile.name 1034 self.init(testdir) 1035 1036 def test_reconfigure(self): 1037 testdir = os.path.join(self.unit_test_dir, '13 reconfigure') 1038 self.init(testdir, extra_args=['-Db_coverage=true'], default_args=False) 1039 self.build('reconfigure') 1040 1041 def test_vala_generated_source_buildir_inside_source_tree(self): 1042 ''' 1043 Test that valac outputs generated C files in the expected location when 1044 the builddir is a subdir of the source tree. 1045 ''' 1046 if not shutil.which('valac'): 1047 raise SkipTest('valac not installed.') 1048 1049 testdir = os.path.join(self.vala_test_dir, '8 generated sources') 1050 newdir = os.path.join(self.builddir, 'srctree') 1051 shutil.copytree(testdir, newdir) 1052 testdir = newdir 1053 # New builddir 1054 builddir = os.path.join(testdir, 'subdir/_build') 1055 os.makedirs(builddir, exist_ok=True) 1056 self.change_builddir(builddir) 1057 self.init(testdir) 1058 self.build() 1059 1060 def test_old_gnome_module_codepaths(self): 1061 ''' 1062 A lot of code in the GNOME module is conditional on the version of the 1063 glib tools that are installed, and breakages in the old code can slip 1064 by once the CI has a newer glib version. So we force the GNOME module 1065 to pretend that it's running on an ancient glib so the fallback code is 1066 also tested. 1067 ''' 1068 testdir = os.path.join(self.framework_test_dir, '7 gnome') 1069 mesonbuild.modules.gnome.native_glib_version = '2.20' 1070 env = {'MESON_UNIT_TEST_PRETEND_GLIB_OLD': "1"} 1071 try: 1072 self.init(testdir, 1073 inprocess=True, 1074 override_envvars=env) 1075 self.build(override_envvars=env) 1076 finally: 1077 mesonbuild.modules.gnome.native_glib_version = None 1078 1079 @skipIfNoPkgconfig 1080 def test_pkgconfig_usage(self): 1081 testdir1 = os.path.join(self.unit_test_dir, '27 pkgconfig usage/dependency') 1082 testdir2 = os.path.join(self.unit_test_dir, '27 pkgconfig usage/dependee') 1083 if subprocess.call(['pkg-config', '--cflags', 'glib-2.0'], 1084 stdout=subprocess.DEVNULL, 1085 stderr=subprocess.DEVNULL) != 0: 1086 raise SkipTest('Glib 2.0 dependency not available.') 1087 with tempfile.TemporaryDirectory() as tempdirname: 1088 self.init(testdir1, extra_args=['--prefix=' + tempdirname, '--libdir=lib'], default_args=False) 1089 self.install(use_destdir=False) 1090 shutil.rmtree(self.builddir) 1091 os.mkdir(self.builddir) 1092 pkg_dir = os.path.join(tempdirname, 'lib/pkgconfig') 1093 self.assertTrue(os.path.exists(os.path.join(pkg_dir, 'libpkgdep.pc'))) 1094 lib_dir = os.path.join(tempdirname, 'lib') 1095 myenv = os.environ.copy() 1096 myenv['PKG_CONFIG_PATH'] = pkg_dir 1097 # Private internal libraries must not leak out. 1098 pkg_out = subprocess.check_output(['pkg-config', '--static', '--libs', 'libpkgdep'], env=myenv) 1099 self.assertFalse(b'libpkgdep-int' in pkg_out, 'Internal library leaked out.') 1100 # Dependencies must not leak to cflags when building only a shared library. 1101 pkg_out = subprocess.check_output(['pkg-config', '--cflags', 'libpkgdep'], env=myenv) 1102 self.assertFalse(b'glib' in pkg_out, 'Internal dependency leaked to headers.') 1103 # Test that the result is usable. 1104 self.init(testdir2, override_envvars=myenv) 1105 self.build(override_envvars=myenv) 1106 myenv = os.environ.copy() 1107 myenv['LD_LIBRARY_PATH'] = ':'.join([lib_dir, myenv.get('LD_LIBRARY_PATH', '')]) 1108 if is_cygwin(): 1109 bin_dir = os.path.join(tempdirname, 'bin') 1110 myenv['PATH'] = bin_dir + os.pathsep + myenv['PATH'] 1111 self.assertTrue(os.path.isdir(lib_dir)) 1112 test_exe = os.path.join(self.builddir, 'pkguser') 1113 self.assertTrue(os.path.isfile(test_exe)) 1114 subprocess.check_call(test_exe, env=myenv) 1115 1116 @skipIfNoPkgconfig 1117 def test_pkgconfig_relative_paths(self): 1118 testdir = os.path.join(self.unit_test_dir, '62 pkgconfig relative paths') 1119 pkg_dir = os.path.join(testdir, 'pkgconfig') 1120 self.assertTrue(os.path.exists(os.path.join(pkg_dir, 'librelativepath.pc'))) 1121 1122 env = get_fake_env(testdir, self.builddir, self.prefix) 1123 env.coredata.set_options({OptionKey('pkg_config_path'): pkg_dir}, subproject='') 1124 kwargs = {'required': True, 'silent': True} 1125 relative_path_dep = PkgConfigDependency('librelativepath', env, kwargs) 1126 self.assertTrue(relative_path_dep.found()) 1127 1128 # Ensure link_args are properly quoted 1129 libpath = Path(self.builddir) / '../relativepath/lib' 1130 link_args = ['-L' + libpath.as_posix(), '-lrelativepath'] 1131 self.assertEqual(relative_path_dep.get_link_args(), link_args) 1132 1133 @skipIfNoPkgconfig 1134 def test_pkgconfig_duplicate_path_entries(self): 1135 testdir = os.path.join(self.unit_test_dir, '111 pkgconfig duplicate path entries') 1136 pkg_dir = os.path.join(testdir, 'pkgconfig') 1137 1138 env = get_fake_env(testdir, self.builddir, self.prefix) 1139 env.coredata.set_options({OptionKey('pkg_config_path'): pkg_dir}, subproject='') 1140 1141 PkgConfigDependency.setup_env({}, env, MachineChoice.HOST, pkg_dir) 1142 pkg_config_path = env.coredata.options[OptionKey('pkg_config_path')].value 1143 self.assertTrue(len(pkg_config_path) == 1) 1144 1145 @skipIfNoPkgconfig 1146 def test_pkgconfig_internal_libraries(self): 1147 ''' 1148 ''' 1149 with tempfile.TemporaryDirectory() as tempdirname: 1150 # build library 1151 testdirbase = os.path.join(self.unit_test_dir, '32 pkgconfig use libraries') 1152 testdirlib = os.path.join(testdirbase, 'lib') 1153 self.init(testdirlib, extra_args=['--prefix=' + tempdirname, 1154 '--libdir=lib', 1155 '--default-library=static'], default_args=False) 1156 self.build() 1157 self.install(use_destdir=False) 1158 1159 # build user of library 1160 pkg_dir = os.path.join(tempdirname, 'lib/pkgconfig') 1161 self.new_builddir() 1162 self.init(os.path.join(testdirbase, 'app'), 1163 override_envvars={'PKG_CONFIG_PATH': pkg_dir}) 1164 self.build() 1165 1166 @skipIfNoPkgconfig 1167 def test_static_archive_stripping(self): 1168 ''' 1169 Check that Meson produces valid static archives with --strip enabled 1170 ''' 1171 with tempfile.TemporaryDirectory() as tempdirname: 1172 testdirbase = os.path.join(self.unit_test_dir, '66 static archive stripping') 1173 1174 # build lib 1175 self.new_builddir() 1176 testdirlib = os.path.join(testdirbase, 'lib') 1177 testlibprefix = os.path.join(tempdirname, 'libprefix') 1178 self.init(testdirlib, extra_args=['--prefix=' + testlibprefix, 1179 '--libdir=lib', 1180 '--default-library=static', 1181 '--buildtype=debug', 1182 '--strip'], default_args=False) 1183 self.build() 1184 self.install(use_destdir=False) 1185 1186 # build executable (uses lib, fails if static archive has been stripped incorrectly) 1187 pkg_dir = os.path.join(testlibprefix, 'lib/pkgconfig') 1188 self.new_builddir() 1189 self.init(os.path.join(testdirbase, 'app'), 1190 override_envvars={'PKG_CONFIG_PATH': pkg_dir}) 1191 self.build() 1192 1193 @skipIfNoPkgconfig 1194 def test_pkgconfig_formatting(self): 1195 testdir = os.path.join(self.unit_test_dir, '38 pkgconfig format') 1196 self.init(testdir) 1197 myenv = os.environ.copy() 1198 myenv['PKG_CONFIG_PATH'] = self.privatedir 1199 stdo = subprocess.check_output(['pkg-config', '--libs-only-l', 'libsomething'], env=myenv) 1200 deps = [b'-lgobject-2.0', b'-lgio-2.0', b'-lglib-2.0', b'-lsomething'] 1201 if is_windows() or is_cygwin() or is_osx() or is_openbsd(): 1202 # On Windows, libintl is a separate library 1203 deps.append(b'-lintl') 1204 self.assertEqual(set(deps), set(stdo.split())) 1205 1206 @skipIfNoPkgconfig 1207 @skip_if_not_language('cs') 1208 def test_pkgconfig_csharp_library(self): 1209 testdir = os.path.join(self.unit_test_dir, '50 pkgconfig csharp library') 1210 self.init(testdir) 1211 myenv = os.environ.copy() 1212 myenv['PKG_CONFIG_PATH'] = self.privatedir 1213 stdo = subprocess.check_output(['pkg-config', '--libs', 'libsomething'], env=myenv) 1214 1215 self.assertEqual("-r/usr/lib/libsomething.dll", str(stdo.decode('ascii')).strip()) 1216 1217 @skipIfNoPkgconfig 1218 def test_pkgconfig_link_order(self): 1219 ''' 1220 Test that libraries are listed before their dependencies. 1221 ''' 1222 testdir = os.path.join(self.unit_test_dir, '53 pkgconfig static link order') 1223 self.init(testdir) 1224 myenv = os.environ.copy() 1225 myenv['PKG_CONFIG_PATH'] = self.privatedir 1226 stdo = subprocess.check_output(['pkg-config', '--libs', 'libsomething'], env=myenv) 1227 deps = stdo.split() 1228 self.assertTrue(deps.index(b'-lsomething') < deps.index(b'-ldependency')) 1229 1230 def test_deterministic_dep_order(self): 1231 ''' 1232 Test that the dependencies are always listed in a deterministic order. 1233 ''' 1234 testdir = os.path.join(self.unit_test_dir, '43 dep order') 1235 self.init(testdir) 1236 with open(os.path.join(self.builddir, 'build.ninja'), encoding='utf-8') as bfile: 1237 for line in bfile: 1238 if 'build myexe:' in line or 'build myexe.exe:' in line: 1239 self.assertIn('liblib1.a liblib2.a', line) 1240 return 1241 raise RuntimeError('Could not find the build rule') 1242 1243 def test_deterministic_rpath_order(self): 1244 ''' 1245 Test that the rpaths are always listed in a deterministic order. 1246 ''' 1247 if is_cygwin(): 1248 raise SkipTest('rpath are not used on Cygwin') 1249 testdir = os.path.join(self.unit_test_dir, '42 rpath order') 1250 self.init(testdir) 1251 if is_osx(): 1252 rpathre = re.compile(r'-rpath,.*/subprojects/sub1.*-rpath,.*/subprojects/sub2') 1253 else: 1254 rpathre = re.compile(r'-rpath,\$\$ORIGIN/subprojects/sub1:\$\$ORIGIN/subprojects/sub2') 1255 with open(os.path.join(self.builddir, 'build.ninja'), encoding='utf-8') as bfile: 1256 for line in bfile: 1257 if '-rpath' in line: 1258 self.assertRegex(line, rpathre) 1259 return 1260 raise RuntimeError('Could not find the rpath') 1261 1262 def test_override_with_exe_dep(self): 1263 ''' 1264 Test that we produce the correct dependencies when a program is overridden with an executable. 1265 ''' 1266 testdir = os.path.join(self.src_root, 'test cases', 'native', '9 override with exe') 1267 self.init(testdir) 1268 with open(os.path.join(self.builddir, 'build.ninja'), encoding='utf-8') as bfile: 1269 for line in bfile: 1270 if 'main1.c:' in line or 'main2.c:' in line: 1271 self.assertIn('| subprojects/sub/foobar', line) 1272 1273 @skipIfNoPkgconfig 1274 def test_usage_external_library(self): 1275 ''' 1276 Test that uninstalled usage of an external library (from the system or 1277 PkgConfigDependency) works. On macOS, this workflow works out of the 1278 box. On Linux, BSDs, Windows, etc, you need to set extra arguments such 1279 as LD_LIBRARY_PATH, etc, so this test is skipped. 1280 1281 The system library is found with cc.find_library() and pkg-config deps. 1282 ''' 1283 oldprefix = self.prefix 1284 # Install external library so we can find it 1285 testdir = os.path.join(self.unit_test_dir, '40 external, internal library rpath', 'external library') 1286 # install into installdir without using DESTDIR 1287 installdir = self.installdir 1288 self.prefix = installdir 1289 self.init(testdir) 1290 self.prefix = oldprefix 1291 self.build() 1292 self.install(use_destdir=False) 1293 ## New builddir for the consumer 1294 self.new_builddir() 1295 env = {'LIBRARY_PATH': os.path.join(installdir, self.libdir), 1296 'PKG_CONFIG_PATH': os.path.join(installdir, self.libdir, 'pkgconfig')} 1297 testdir = os.path.join(self.unit_test_dir, '40 external, internal library rpath', 'built library') 1298 # install into installdir without using DESTDIR 1299 self.prefix = self.installdir 1300 self.init(testdir, override_envvars=env) 1301 self.prefix = oldprefix 1302 self.build(override_envvars=env) 1303 # test uninstalled 1304 self.run_tests(override_envvars=env) 1305 if not (is_osx() or is_linux()): 1306 return 1307 # test running after installation 1308 self.install(use_destdir=False) 1309 prog = os.path.join(self.installdir, 'bin', 'prog') 1310 self._run([prog]) 1311 if not is_osx(): 1312 # Rest of the workflow only works on macOS 1313 return 1314 out = self._run(['otool', '-L', prog]) 1315 self.assertNotIn('@rpath', out) 1316 ## New builddir for testing that DESTDIR is not added to install_name 1317 self.new_builddir() 1318 # install into installdir with DESTDIR 1319 self.init(testdir, override_envvars=env) 1320 self.build(override_envvars=env) 1321 # test running after installation 1322 self.install(override_envvars=env) 1323 prog = self.installdir + os.path.join(self.prefix, 'bin', 'prog') 1324 lib = self.installdir + os.path.join(self.prefix, 'lib', 'libbar_built.dylib') 1325 for f in prog, lib: 1326 out = self._run(['otool', '-L', f]) 1327 # Ensure that the otool output does not contain self.installdir 1328 self.assertNotRegex(out, self.installdir + '.*dylib ') 1329 1330 @skipIfNoPkgconfig 1331 def test_link_arg_fullname(self): 1332 ''' 1333 Test for support of -l:libfullname.a 1334 see: https://github.com/mesonbuild/meson/issues/9000 1335 https://stackoverflow.com/questions/48532868/gcc-library-option-with-a-colon-llibevent-a 1336 ''' 1337 testdir = os.path.join(self.unit_test_dir, '97 link full name','libtestprovider') 1338 oldprefix = self.prefix 1339 # install into installdir without using DESTDIR 1340 installdir = self.installdir 1341 self.prefix = installdir 1342 self.init(testdir) 1343 self.prefix=oldprefix 1344 self.build() 1345 self.install(use_destdir=False) 1346 1347 self.new_builddir() 1348 env = {'LIBRARY_PATH': os.path.join(installdir, self.libdir), 1349 'PKG_CONFIG_PATH': os.path.join(installdir, self.libdir, 'pkgconfig')} 1350 testdir = os.path.join(self.unit_test_dir, '97 link full name','proguser') 1351 self.init(testdir,override_envvars=env) 1352 1353 # test for link with full path 1354 with open(os.path.join(self.builddir, 'build.ninja'), encoding='utf-8') as bfile: 1355 for line in bfile: 1356 if 'build dprovidertest:' in line: 1357 self.assertIn('/libtestprovider.a', line) 1358 1359 if is_osx(): 1360 # macOS's ld do not supports `--whole-archive`, skip build & run 1361 return 1362 1363 self.build(override_envvars=env) 1364 1365 # skip test if pkg-config is too old. 1366 # before v0.28, Libs flags like -Wl will not kept in context order with -l flags. 1367 # see https://gitlab.freedesktop.org/pkg-config/pkg-config/-/blob/master/NEWS 1368 pkgconfigver = subprocess.check_output(['pkg-config', '--version']) 1369 if b'0.28' > pkgconfigver: 1370 raise SkipTest('pkg-config is too old to be correctly done this.') 1371 self.run_tests() 1372 1373 @skipIfNoPkgconfig 1374 def test_usage_pkgconfig_prefixes(self): 1375 ''' 1376 Build and install two external libraries, to different prefixes, 1377 then build and install a client program that finds them via pkgconfig, 1378 and verify the installed client program runs. 1379 ''' 1380 oldinstalldir = self.installdir 1381 1382 # Build and install both external libraries without DESTDIR 1383 val1dir = os.path.join(self.unit_test_dir, '75 pkgconfig prefixes', 'val1') 1384 val1prefix = os.path.join(oldinstalldir, 'val1') 1385 self.prefix = val1prefix 1386 self.installdir = val1prefix 1387 self.init(val1dir) 1388 self.build() 1389 self.install(use_destdir=False) 1390 self.new_builddir() 1391 1392 env1 = {} 1393 env1['PKG_CONFIG_PATH'] = os.path.join(val1prefix, self.libdir, 'pkgconfig') 1394 val2dir = os.path.join(self.unit_test_dir, '75 pkgconfig prefixes', 'val2') 1395 val2prefix = os.path.join(oldinstalldir, 'val2') 1396 self.prefix = val2prefix 1397 self.installdir = val2prefix 1398 self.init(val2dir, override_envvars=env1) 1399 self.build() 1400 self.install(use_destdir=False) 1401 self.new_builddir() 1402 1403 # Build, install, and run the client program 1404 env2 = {} 1405 env2['PKG_CONFIG_PATH'] = os.path.join(val2prefix, self.libdir, 'pkgconfig') 1406 testdir = os.path.join(self.unit_test_dir, '75 pkgconfig prefixes', 'client') 1407 testprefix = os.path.join(oldinstalldir, 'client') 1408 self.prefix = testprefix 1409 self.installdir = testprefix 1410 self.init(testdir, override_envvars=env2) 1411 self.build() 1412 self.install(use_destdir=False) 1413 prog = os.path.join(self.installdir, 'bin', 'client') 1414 env3 = {} 1415 if is_cygwin(): 1416 env3['PATH'] = os.path.join(val1prefix, 'bin') + \ 1417 os.pathsep + \ 1418 os.path.join(val2prefix, 'bin') + \ 1419 os.pathsep + os.environ['PATH'] 1420 out = self._run([prog], override_envvars=env3).strip() 1421 # Expected output is val1 + val2 = 3 1422 self.assertEqual(out, '3') 1423 1424 def install_subdir_invalid_symlinks(self, testdir, subdir_path): 1425 ''' 1426 Test that installation of broken symlinks works fine. 1427 https://github.com/mesonbuild/meson/issues/3914 1428 ''' 1429 testdir = os.path.join(self.common_test_dir, testdir) 1430 subdir = os.path.join(testdir, subdir_path) 1431 with chdir(subdir): 1432 # Can't distribute broken symlinks in the source tree because it breaks 1433 # the creation of zipapps. Create it dynamically and run the test by 1434 # hand. 1435 src = '../../nonexistent.txt' 1436 os.symlink(src, 'invalid-symlink.txt') 1437 try: 1438 self.init(testdir) 1439 self.build() 1440 self.install() 1441 install_path = subdir_path.split(os.path.sep)[-1] 1442 link = os.path.join(self.installdir, 'usr', 'share', install_path, 'invalid-symlink.txt') 1443 self.assertTrue(os.path.islink(link), msg=link) 1444 self.assertEqual(src, os.readlink(link)) 1445 self.assertFalse(os.path.isfile(link), msg=link) 1446 finally: 1447 os.remove(os.path.join(subdir, 'invalid-symlink.txt')) 1448 1449 def test_install_subdir_symlinks(self): 1450 self.install_subdir_invalid_symlinks('59 install subdir', os.path.join('sub', 'sub1')) 1451 1452 def test_install_subdir_symlinks_with_default_umask(self): 1453 self.install_subdir_invalid_symlinks('190 install_mode', 'sub2') 1454 1455 def test_install_subdir_symlinks_with_default_umask_and_mode(self): 1456 self.install_subdir_invalid_symlinks('190 install_mode', 'sub1') 1457 1458 @skipIfNoPkgconfigDep('gmodule-2.0') 1459 def test_ldflag_dedup(self): 1460 testdir = os.path.join(self.unit_test_dir, '52 ldflagdedup') 1461 if is_cygwin() or is_osx(): 1462 raise SkipTest('Not applicable on Cygwin or OSX.') 1463 env = get_fake_env() 1464 cc = detect_c_compiler(env, MachineChoice.HOST) 1465 linker = cc.linker 1466 if not linker.export_dynamic_args(env): 1467 raise SkipTest('Not applicable for linkers without --export-dynamic') 1468 self.init(testdir) 1469 build_ninja = os.path.join(self.builddir, 'build.ninja') 1470 max_count = 0 1471 search_term = '-Wl,--export-dynamic' 1472 with open(build_ninja, encoding='utf-8') as f: 1473 for line in f: 1474 max_count = max(max_count, line.count(search_term)) 1475 self.assertEqual(max_count, 1, 'Export dynamic incorrectly deduplicated.') 1476 1477 def test_compiler_libs_static_dedup(self): 1478 testdir = os.path.join(self.unit_test_dir, '56 dedup compiler libs') 1479 self.init(testdir) 1480 build_ninja = os.path.join(self.builddir, 'build.ninja') 1481 with open(build_ninja, encoding='utf-8') as f: 1482 lines = f.readlines() 1483 for lib in ('-ldl', '-lm', '-lc', '-lrt'): 1484 for line in lines: 1485 if lib not in line: 1486 continue 1487 # Assert that 1488 self.assertEqual(len(line.split(lib)), 2, msg=(lib, line)) 1489 1490 @skipIfNoPkgconfig 1491 def test_noncross_options(self): 1492 # C_std defined in project options must be in effect also when native compiling. 1493 testdir = os.path.join(self.unit_test_dir, '51 noncross options') 1494 self.init(testdir, extra_args=['-Dpkg_config_path=' + testdir]) 1495 compdb = self.get_compdb() 1496 self.assertEqual(len(compdb), 2) 1497 self.assertRegex(compdb[0]['command'], '-std=c99') 1498 self.assertRegex(compdb[1]['command'], '-std=c99') 1499 self.build() 1500 1501 def test_identity_cross(self): 1502 testdir = os.path.join(self.unit_test_dir, '61 identity cross') 1503 1504 nativefile = tempfile.NamedTemporaryFile(mode='w') 1505 nativefile.write(textwrap.dedent('''\ 1506 [binaries] 1507 c = ['{}'] 1508 '''.format(os.path.join(testdir, 'build_wrapper.py')))) 1509 nativefile.flush() 1510 self.meson_native_file = nativefile.name 1511 1512 crossfile = tempfile.NamedTemporaryFile(mode='w') 1513 crossfile.write(textwrap.dedent('''\ 1514 [binaries] 1515 c = ['{}'] 1516 '''.format(os.path.join(testdir, 'host_wrapper.py')))) 1517 crossfile.flush() 1518 self.meson_cross_file = crossfile.name 1519 1520 # TODO should someday be explicit about build platform only here 1521 self.init(testdir) 1522 1523 def test_identity_cross_env(self): 1524 testdir = os.path.join(self.unit_test_dir, '61 identity cross') 1525 env = { 1526 'CC_FOR_BUILD': '"' + os.path.join(testdir, 'build_wrapper.py') + '"', 1527 } 1528 crossfile = tempfile.NamedTemporaryFile(mode='w') 1529 crossfile.write(textwrap.dedent('''\ 1530 [binaries] 1531 c = ['{}'] 1532 '''.format(os.path.join(testdir, 'host_wrapper.py')))) 1533 crossfile.flush() 1534 self.meson_cross_file = crossfile.name 1535 # TODO should someday be explicit about build platform only here 1536 self.init(testdir, override_envvars=env) 1537 1538 @skipIfNoPkgconfig 1539 def test_static_link(self): 1540 if is_cygwin(): 1541 raise SkipTest("Cygwin doesn't support LD_LIBRARY_PATH.") 1542 1543 # Build some libraries and install them 1544 testdir = os.path.join(self.unit_test_dir, '67 static link/lib') 1545 libdir = os.path.join(self.installdir, self.libdir) 1546 oldprefix = self.prefix 1547 self.prefix = self.installdir 1548 self.init(testdir) 1549 self.install(use_destdir=False) 1550 1551 # Test that installed libraries works 1552 self.new_builddir() 1553 self.prefix = oldprefix 1554 meson_args = [f'-Dc_link_args=-L{libdir}', 1555 '--fatal-meson-warnings'] 1556 testdir = os.path.join(self.unit_test_dir, '67 static link') 1557 env = {'PKG_CONFIG_LIBDIR': os.path.join(libdir, 'pkgconfig')} 1558 self.init(testdir, extra_args=meson_args, override_envvars=env) 1559 self.build() 1560 self.run_tests() 1561 1562 def _check_ld(self, check: str, name: str, lang: str, expected: str) -> None: 1563 if is_sunos(): 1564 raise SkipTest('Solaris currently cannot override the linker.') 1565 if not shutil.which(check): 1566 raise SkipTest(f'Could not find {check}.') 1567 envvars = [mesonbuild.envconfig.ENV_VAR_PROG_MAP[f'{lang}_ld']] 1568 1569 # Also test a deprecated variable if there is one. 1570 if f'{lang}_ld' in mesonbuild.envconfig.DEPRECATED_ENV_PROG_MAP: 1571 envvars.append( 1572 mesonbuild.envconfig.DEPRECATED_ENV_PROG_MAP[f'{lang}_ld']) 1573 1574 for envvar in envvars: 1575 with mock.patch.dict(os.environ, {envvar: name}): 1576 env = get_fake_env() 1577 comp = compiler_from_language(env, lang, MachineChoice.HOST) 1578 if isinstance(comp, (AppleClangCCompiler, AppleClangCPPCompiler, 1579 AppleClangObjCCompiler, AppleClangObjCPPCompiler)): 1580 raise SkipTest('AppleClang is currently only supported with ld64') 1581 if lang != 'rust' and comp.use_linker_args('bfd') == []: 1582 raise SkipTest( 1583 f'Compiler {comp.id} does not support using alternative linkers') 1584 self.assertEqual(comp.linker.id, expected) 1585 1586 def test_ld_environment_variable_bfd(self): 1587 self._check_ld('ld.bfd', 'bfd', 'c', 'ld.bfd') 1588 1589 def test_ld_environment_variable_gold(self): 1590 self._check_ld('ld.gold', 'gold', 'c', 'ld.gold') 1591 1592 def test_ld_environment_variable_lld(self): 1593 self._check_ld('ld.lld', 'lld', 'c', 'ld.lld') 1594 1595 @skip_if_not_language('rust') 1596 @skipIfNoExecutable('ld.gold') # need an additional check here because _check_ld checks for gcc 1597 def test_ld_environment_variable_rust(self): 1598 self._check_ld('gcc', 'gcc -fuse-ld=gold', 'rust', 'ld.gold') 1599 1600 def test_ld_environment_variable_cpp(self): 1601 self._check_ld('ld.gold', 'gold', 'cpp', 'ld.gold') 1602 1603 @skip_if_not_language('objc') 1604 def test_ld_environment_variable_objc(self): 1605 self._check_ld('ld.gold', 'gold', 'objc', 'ld.gold') 1606 1607 @skip_if_not_language('objcpp') 1608 def test_ld_environment_variable_objcpp(self): 1609 self._check_ld('ld.gold', 'gold', 'objcpp', 'ld.gold') 1610 1611 @skip_if_not_language('fortran') 1612 def test_ld_environment_variable_fortran(self): 1613 self._check_ld('ld.gold', 'gold', 'fortran', 'ld.gold') 1614 1615 @skip_if_not_language('d') 1616 def test_ld_environment_variable_d(self): 1617 # At least for me, ldc defaults to gold, and gdc defaults to bfd, so 1618 # let's pick lld, which isn't the default for either (currently) 1619 if is_osx(): 1620 expected = 'ld64' 1621 else: 1622 expected = 'ld.lld' 1623 self._check_ld('ld.lld', 'lld', 'd', expected) 1624 1625 def compute_sha256(self, filename): 1626 with open(filename, 'rb') as f: 1627 return hashlib.sha256(f.read()).hexdigest() 1628 1629 def test_wrap_with_file_url(self): 1630 testdir = os.path.join(self.unit_test_dir, '73 wrap file url') 1631 source_filename = os.path.join(testdir, 'subprojects', 'foo.tar.xz') 1632 patch_filename = os.path.join(testdir, 'subprojects', 'foo-patch.tar.xz') 1633 wrap_filename = os.path.join(testdir, 'subprojects', 'foo.wrap') 1634 source_hash = self.compute_sha256(source_filename) 1635 patch_hash = self.compute_sha256(patch_filename) 1636 wrap = textwrap.dedent("""\ 1637 [wrap-file] 1638 directory = foo 1639 1640 source_url = http://server.invalid/foo 1641 source_fallback_url = file://{} 1642 source_filename = foo.tar.xz 1643 source_hash = {} 1644 1645 patch_url = http://server.invalid/foo 1646 patch_fallback_url = file://{} 1647 patch_filename = foo-patch.tar.xz 1648 patch_hash = {} 1649 """.format(source_filename, source_hash, patch_filename, patch_hash)) 1650 with open(wrap_filename, 'w', encoding='utf-8') as f: 1651 f.write(wrap) 1652 self.init(testdir) 1653 self.build() 1654 self.run_tests() 1655 1656 windows_proof_rmtree(os.path.join(testdir, 'subprojects', 'packagecache')) 1657 windows_proof_rmtree(os.path.join(testdir, 'subprojects', 'foo')) 1658 os.unlink(wrap_filename) 1659 1660 def test_no_rpath_for_static(self): 1661 testdir = os.path.join(self.common_test_dir, '5 linkstatic') 1662 self.init(testdir) 1663 self.build() 1664 build_rpath = get_rpath(os.path.join(self.builddir, 'prog')) 1665 self.assertIsNone(build_rpath) 1666 1667 def test_lookup_system_after_broken_fallback(self): 1668 # Just to generate libfoo.pc so we can test system dependency lookup. 1669 testdir = os.path.join(self.common_test_dir, '44 pkgconfig-gen') 1670 self.init(testdir) 1671 privatedir = self.privatedir 1672 1673 # Write test project where the first dependency() returns not-found 1674 # because 'broken' subproject does not exit, but that should not prevent 1675 # the 2nd dependency() to lookup on system. 1676 self.new_builddir() 1677 with tempfile.TemporaryDirectory() as d: 1678 with open(os.path.join(d, 'meson.build'), 'w', encoding='utf-8') as f: 1679 f.write(textwrap.dedent('''\ 1680 project('test') 1681 dependency('notfound', fallback: 'broken', required: false) 1682 dependency('libfoo', fallback: 'broken', required: true) 1683 ''')) 1684 self.init(d, override_envvars={'PKG_CONFIG_LIBDIR': privatedir}) 1685 1686 def test_as_link_whole(self): 1687 testdir = os.path.join(self.unit_test_dir, '77 as link whole') 1688 self.init(testdir) 1689 with open(os.path.join(self.privatedir, 'bar1.pc'), encoding='utf-8') as f: 1690 content = f.read() 1691 self.assertIn('-lfoo', content) 1692 with open(os.path.join(self.privatedir, 'bar2.pc'), encoding='utf-8') as f: 1693 content = f.read() 1694 self.assertNotIn('-lfoo', content) 1695 1696 def test_prelinking(self): 1697 # Prelinking currently only works on recently new GNU toolchains. 1698 # Skip everything else. When support for other toolchains is added, 1699 # remove limitations as necessary. 1700 if is_osx(): 1701 raise SkipTest('Prelinking not supported on Darwin.') 1702 if 'clang' in os.environ.get('CC', 'dummy'): 1703 raise SkipTest('Prelinking not supported with Clang.') 1704 testdir = os.path.join(self.unit_test_dir, '87 prelinking') 1705 env = get_fake_env(testdir, self.builddir, self.prefix) 1706 cc = detect_c_compiler(env, MachineChoice.HOST) 1707 if cc.id == "gcc": 1708 gccver = subprocess.check_output(['gcc', '--version']) 1709 if b'7.5.0' in gccver: 1710 raise SkipTest('GCC on Bionic is too old to be supported.') 1711 self.init(testdir) 1712 self.build() 1713 outlib = os.path.join(self.builddir, 'libprelinked.a') 1714 ar = shutil.which('ar') 1715 self.assertTrue(os.path.exists(outlib)) 1716 self.assertTrue(ar is not None) 1717 p = subprocess.run([ar, 't', outlib], 1718 stdout=subprocess.PIPE, 1719 stderr=subprocess.DEVNULL, 1720 universal_newlines=True, timeout=1) 1721 obj_files = p.stdout.strip().split('\n') 1722 self.assertEqual(len(obj_files), 1) 1723 self.assertTrue(obj_files[0].endswith('-prelink.o')) 1724 1725 def do_one_test_with_nativefile(self, testdir, args): 1726 testdir = os.path.join(self.common_test_dir, testdir) 1727 with tempfile.TemporaryDirectory() as d: 1728 p = Path(d) / 'nativefile' 1729 with p.open('wt', encoding='utf-8') as f: 1730 f.write(f'''[binaries] 1731 c = {args} 1732 ''') 1733 self.init(testdir, extra_args=['--native-file=' + str(p)]) 1734 self.build() 1735 1736 def test_cmake_multilib(self): 1737 ''' 1738 Test that the cmake module handles multilib paths correctly. 1739 ''' 1740 # Verify that "gcc -m32" works 1741 try: 1742 self.do_one_test_with_nativefile('1 trivial', "['gcc', '-m32']") 1743 except subprocess.CalledProcessError as e: 1744 raise SkipTest('Not GCC, or GCC does not have the -m32 option') 1745 self.wipe() 1746 1747 # Verify that cmake works 1748 try: 1749 self.do_one_test_with_nativefile('../cmake/1 basic', "['gcc']") 1750 except subprocess.CalledProcessError as e: 1751 raise SkipTest('Could not build basic cmake project') 1752 self.wipe() 1753 1754 # If so, we can test that cmake works with "gcc -m32" 1755 self.do_one_test_with_nativefile('../cmake/1 basic', "['gcc', '-m32']") 1756