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 subprocess 16import re 17import json 18import tempfile 19import textwrap 20import os 21import shutil 22import platform 23import pickle 24import zipfile, tarfile 25import sys 26from unittest import mock, SkipTest, skipIf, skipUnless 27from contextlib import contextmanager 28from glob import glob 29from pathlib import (PurePath, Path) 30import typing as T 31 32import mesonbuild.mlog 33import mesonbuild.depfile 34import mesonbuild.dependencies.base 35import mesonbuild.dependencies.factory 36import mesonbuild.envconfig 37import mesonbuild.environment 38import mesonbuild.coredata 39import mesonbuild.modules.gnome 40from mesonbuild.mesonlib import ( 41 BuildDirLock, MachineChoice, is_windows, is_osx, is_cygwin, is_dragonflybsd, 42 is_sunos, windows_proof_rmtree, python_command, version_compare, split_args, quote_arg, 43 relpath, is_linux, git, search_version, do_conf_file, do_conf_str, default_prefix, 44 MesonException, EnvironmentException, OptionKey 45) 46 47from mesonbuild.compilers import ( 48 GnuCompiler, ClangCompiler, IntelGnuLikeCompiler, VisualStudioCCompiler, 49 VisualStudioCPPCompiler, ClangClCCompiler, ClangClCPPCompiler, 50 detect_static_linker, detect_c_compiler, compiler_from_language, 51 detect_compiler_for 52) 53 54from mesonbuild.dependencies import PkgConfigDependency 55from mesonbuild.build import Target, ConfigurationData, Executable, SharedLibrary, StaticLibrary 56import mesonbuild.modules.pkgconfig 57from mesonbuild.scripts import destdir_join 58 59from mesonbuild.wrap.wrap import PackageDefinition, WrapException 60 61from run_tests import ( 62 Backend, exe_suffix, get_fake_env 63) 64 65from .baseplatformtests import BasePlatformTests 66from .helpers import * 67 68@contextmanager 69def temp_filename(): 70 '''A context manager which provides a filename to an empty temporary file. 71 72 On exit the file will be deleted. 73 ''' 74 75 fd, filename = tempfile.mkstemp() 76 os.close(fd) 77 try: 78 yield filename 79 finally: 80 try: 81 os.remove(filename) 82 except OSError: 83 pass 84 85def _git_init(project_dir): 86 # If a user has git configuration init.defaultBranch set we want to override that 87 with tempfile.TemporaryDirectory() as d: 88 out = git(['--version'], str(d))[1] 89 if version_compare(search_version(out), '>= 2.28'): 90 extra_cmd = ['--initial-branch', 'master'] 91 else: 92 extra_cmd = [] 93 94 subprocess.check_call(['git', 'init'] + extra_cmd, cwd=project_dir, stdout=subprocess.DEVNULL) 95 subprocess.check_call(['git', 'config', 96 'user.name', 'Author Person'], cwd=project_dir) 97 subprocess.check_call(['git', 'config', 98 'user.email', 'teh_coderz@example.com'], cwd=project_dir) 99 _git_add_all(project_dir) 100 101def _git_add_all(project_dir): 102 subprocess.check_call('git add *', cwd=project_dir, shell=True, 103 stdout=subprocess.DEVNULL) 104 subprocess.check_call(['git', 'commit', '--no-gpg-sign', '-a', '-m', 'I am a project'], cwd=project_dir, 105 stdout=subprocess.DEVNULL) 106 107class AllPlatformTests(BasePlatformTests): 108 ''' 109 Tests that should run on all platforms 110 ''' 111 112 def test_default_options_prefix(self): 113 ''' 114 Tests that setting a prefix in default_options in project() works. 115 Can't be an ordinary test because we pass --prefix to meson there. 116 https://github.com/mesonbuild/meson/issues/1349 117 ''' 118 testdir = os.path.join(self.common_test_dir, '87 default options') 119 self.init(testdir, default_args=False, inprocess=True) 120 opts = self.introspect('--buildoptions') 121 for opt in opts: 122 if opt['name'] == 'prefix': 123 prefix = opt['value'] 124 break 125 else: 126 raise self.fail('Did not find option "prefix"') 127 self.assertEqual(prefix, '/absoluteprefix') 128 129 def test_do_conf_file_preserve_newlines(self): 130 131 def conf_file(in_data, confdata): 132 with temp_filename() as fin: 133 with open(fin, 'wb') as fobj: 134 fobj.write(in_data.encode('utf-8')) 135 with temp_filename() as fout: 136 do_conf_file(fin, fout, confdata, 'meson') 137 with open(fout, 'rb') as fobj: 138 return fobj.read().decode('utf-8') 139 140 confdata = {'VAR': ('foo', 'bar')} 141 self.assertEqual(conf_file('@VAR@\n@VAR@\n', confdata), 'foo\nfoo\n') 142 self.assertEqual(conf_file('@VAR@\r\n@VAR@\r\n', confdata), 'foo\r\nfoo\r\n') 143 144 def test_do_conf_file_by_format(self): 145 def conf_str(in_data, confdata, vformat): 146 (result, missing_variables, confdata_useless) = do_conf_str('configuration_file', in_data, confdata, variable_format = vformat) 147 return '\n'.join(result) 148 149 def check_formats(confdata, result): 150 self.assertEqual(conf_str(['#mesondefine VAR'], confdata, 'meson'), result) 151 self.assertEqual(conf_str(['#cmakedefine VAR ${VAR}'], confdata, 'cmake'), result) 152 self.assertEqual(conf_str(['#cmakedefine VAR @VAR@'], confdata, 'cmake@'), result) 153 154 confdata = ConfigurationData() 155 # Key error as they do not exists 156 check_formats(confdata, '/* #undef VAR */\n') 157 158 # Check boolean 159 confdata.values = {'VAR': (False, 'description')} 160 check_formats(confdata, '#undef VAR\n') 161 confdata.values = {'VAR': (True, 'description')} 162 check_formats(confdata, '#define VAR\n') 163 164 # Check string 165 confdata.values = {'VAR': ('value', 'description')} 166 check_formats(confdata, '#define VAR value\n') 167 168 # Check integer 169 confdata.values = {'VAR': (10, 'description')} 170 check_formats(confdata, '#define VAR 10\n') 171 172 # Check multiple string with cmake formats 173 confdata.values = {'VAR': ('value', 'description')} 174 self.assertEqual(conf_str(['#cmakedefine VAR xxx @VAR@ yyy @VAR@'], confdata, 'cmake@'), '#define VAR xxx value yyy value\n') 175 self.assertEqual(conf_str(['#define VAR xxx @VAR@ yyy @VAR@'], confdata, 'cmake@'), '#define VAR xxx value yyy value') 176 self.assertEqual(conf_str(['#cmakedefine VAR xxx ${VAR} yyy ${VAR}'], confdata, 'cmake'), '#define VAR xxx value yyy value\n') 177 self.assertEqual(conf_str(['#define VAR xxx ${VAR} yyy ${VAR}'], confdata, 'cmake'), '#define VAR xxx value yyy value') 178 179 # Handles meson format exceptions 180 # Unknown format 181 self.assertRaises(MesonException, conf_str, ['#mesondefine VAR xxx'], confdata, 'unknown_format') 182 # More than 2 params in mesondefine 183 self.assertRaises(MesonException, conf_str, ['#mesondefine VAR xxx'], confdata, 'meson') 184 # Mismatched line with format 185 self.assertRaises(MesonException, conf_str, ['#cmakedefine VAR'], confdata, 'meson') 186 self.assertRaises(MesonException, conf_str, ['#mesondefine VAR'], confdata, 'cmake') 187 self.assertRaises(MesonException, conf_str, ['#mesondefine VAR'], confdata, 'cmake@') 188 # Dict value in confdata 189 confdata.values = {'VAR': (['value'], 'description')} 190 self.assertRaises(MesonException, conf_str, ['#mesondefine VAR'], confdata, 'meson') 191 192 def test_absolute_prefix_libdir(self): 193 ''' 194 Tests that setting absolute paths for --prefix and --libdir work. Can't 195 be an ordinary test because these are set via the command-line. 196 https://github.com/mesonbuild/meson/issues/1341 197 https://github.com/mesonbuild/meson/issues/1345 198 ''' 199 testdir = os.path.join(self.common_test_dir, '87 default options') 200 # on Windows, /someabs is *not* an absolute path 201 prefix = 'x:/someabs' if is_windows() else '/someabs' 202 libdir = 'libdir' 203 extra_args = ['--prefix=' + prefix, 204 # This can just be a relative path, but we want to test 205 # that passing this as an absolute path also works 206 '--libdir=' + prefix + '/' + libdir] 207 self.init(testdir, extra_args=extra_args, default_args=False) 208 opts = self.introspect('--buildoptions') 209 for opt in opts: 210 if opt['name'] == 'prefix': 211 self.assertEqual(prefix, opt['value']) 212 elif opt['name'] == 'libdir': 213 self.assertEqual(libdir, opt['value']) 214 215 def test_libdir_must_be_inside_prefix(self): 216 ''' 217 Tests that libdir is forced to be inside prefix no matter how it is set. 218 Must be a unit test for obvious reasons. 219 ''' 220 testdir = os.path.join(self.common_test_dir, '1 trivial') 221 # libdir being inside prefix is ok 222 if is_windows(): 223 args = ['--prefix', 'x:/opt', '--libdir', 'x:/opt/lib32'] 224 else: 225 args = ['--prefix', '/opt', '--libdir', '/opt/lib32'] 226 self.init(testdir, extra_args=args) 227 self.wipe() 228 # libdir not being inside prefix is not ok 229 if is_windows(): 230 args = ['--prefix', 'x:/usr', '--libdir', 'x:/opt/lib32'] 231 else: 232 args = ['--prefix', '/usr', '--libdir', '/opt/lib32'] 233 self.assertRaises(subprocess.CalledProcessError, self.init, testdir, extra_args=args) 234 self.wipe() 235 # libdir must be inside prefix even when set via mesonconf 236 self.init(testdir) 237 if is_windows(): 238 self.assertRaises(subprocess.CalledProcessError, self.setconf, '-Dlibdir=x:/opt', False) 239 else: 240 self.assertRaises(subprocess.CalledProcessError, self.setconf, '-Dlibdir=/opt', False) 241 242 def test_prefix_dependent_defaults(self): 243 ''' 244 Tests that configured directory paths are set to prefix dependent 245 defaults. 246 ''' 247 testdir = os.path.join(self.common_test_dir, '1 trivial') 248 expected = { 249 '/opt': {'prefix': '/opt', 250 'bindir': 'bin', 'datadir': 'share', 'includedir': 'include', 251 'infodir': 'share/info', 252 'libexecdir': 'libexec', 'localedir': 'share/locale', 253 'localstatedir': 'var', 'mandir': 'share/man', 254 'sbindir': 'sbin', 'sharedstatedir': 'com', 255 'sysconfdir': 'etc'}, 256 '/usr': {'prefix': '/usr', 257 'bindir': 'bin', 'datadir': 'share', 'includedir': 'include', 258 'infodir': 'share/info', 259 'libexecdir': 'libexec', 'localedir': 'share/locale', 260 'localstatedir': '/var', 'mandir': 'share/man', 261 'sbindir': 'sbin', 'sharedstatedir': '/var/lib', 262 'sysconfdir': '/etc'}, 263 '/usr/local': {'prefix': '/usr/local', 264 'bindir': 'bin', 'datadir': 'share', 265 'includedir': 'include', 'infodir': 'share/info', 266 'libexecdir': 'libexec', 267 'localedir': 'share/locale', 268 'localstatedir': '/var/local', 'mandir': 'share/man', 269 'sbindir': 'sbin', 'sharedstatedir': '/var/local/lib', 270 'sysconfdir': 'etc'}, 271 # N.B. We don't check 'libdir' as it's platform dependent, see 272 # default_libdir(): 273 } 274 275 if default_prefix() == '/usr/local': 276 expected[None] = expected['/usr/local'] 277 278 for prefix in expected: 279 args = [] 280 if prefix: 281 args += ['--prefix', prefix] 282 self.init(testdir, extra_args=args, default_args=False) 283 opts = self.introspect('--buildoptions') 284 for opt in opts: 285 name = opt['name'] 286 value = opt['value'] 287 if name in expected[prefix]: 288 self.assertEqual(value, expected[prefix][name]) 289 self.wipe() 290 291 def test_default_options_prefix_dependent_defaults(self): 292 ''' 293 Tests that setting a prefix in default_options in project() sets prefix 294 dependent defaults for other options, and that those defaults can 295 be overridden in default_options or by the command line. 296 ''' 297 testdir = os.path.join(self.common_test_dir, '163 default options prefix dependent defaults') 298 expected = { 299 '': 300 {'prefix': '/usr', 301 'sysconfdir': '/etc', 302 'localstatedir': '/var', 303 'sharedstatedir': '/sharedstate'}, 304 '--prefix=/usr': 305 {'prefix': '/usr', 306 'sysconfdir': '/etc', 307 'localstatedir': '/var', 308 'sharedstatedir': '/sharedstate'}, 309 '--sharedstatedir=/var/state': 310 {'prefix': '/usr', 311 'sysconfdir': '/etc', 312 'localstatedir': '/var', 313 'sharedstatedir': '/var/state'}, 314 '--sharedstatedir=/var/state --prefix=/usr --sysconfdir=sysconf': 315 {'prefix': '/usr', 316 'sysconfdir': 'sysconf', 317 'localstatedir': '/var', 318 'sharedstatedir': '/var/state'}, 319 } 320 for args in expected: 321 self.init(testdir, extra_args=args.split(), default_args=False) 322 opts = self.introspect('--buildoptions') 323 for opt in opts: 324 name = opt['name'] 325 value = opt['value'] 326 if name in expected[args]: 327 self.assertEqual(value, expected[args][name]) 328 self.wipe() 329 330 def test_clike_get_library_dirs(self): 331 env = get_fake_env() 332 cc = detect_c_compiler(env, MachineChoice.HOST) 333 for d in cc.get_library_dirs(env): 334 self.assertTrue(os.path.exists(d)) 335 self.assertTrue(os.path.isdir(d)) 336 self.assertTrue(os.path.isabs(d)) 337 338 def test_static_library_overwrite(self): 339 ''' 340 Tests that static libraries are never appended to, always overwritten. 341 Has to be a unit test because this involves building a project, 342 reconfiguring, and building it again so that `ar` is run twice on the 343 same static library. 344 https://github.com/mesonbuild/meson/issues/1355 345 ''' 346 testdir = os.path.join(self.common_test_dir, '3 static') 347 env = get_fake_env(testdir, self.builddir, self.prefix) 348 cc = detect_c_compiler(env, MachineChoice.HOST) 349 static_linker = detect_static_linker(env, cc) 350 if is_windows(): 351 raise SkipTest('https://github.com/mesonbuild/meson/issues/1526') 352 if not isinstance(static_linker, mesonbuild.linkers.ArLinker): 353 raise SkipTest('static linker is not `ar`') 354 # Configure 355 self.init(testdir) 356 # Get name of static library 357 targets = self.introspect('--targets') 358 self.assertEqual(len(targets), 1) 359 libname = targets[0]['filename'][0] 360 # Build and get contents of static library 361 self.build() 362 before = self._run(['ar', 't', os.path.join(self.builddir, libname)]).split() 363 # Filter out non-object-file contents 364 before = [f for f in before if f.endswith(('.o', '.obj'))] 365 # Static library should contain only one object 366 self.assertEqual(len(before), 1, msg=before) 367 # Change the source to be built into the static library 368 self.setconf('-Dsource=libfile2.c') 369 self.build() 370 after = self._run(['ar', 't', os.path.join(self.builddir, libname)]).split() 371 # Filter out non-object-file contents 372 after = [f for f in after if f.endswith(('.o', '.obj'))] 373 # Static library should contain only one object 374 self.assertEqual(len(after), 1, msg=after) 375 # and the object must have changed 376 self.assertNotEqual(before, after) 377 378 def test_static_compile_order(self): 379 ''' 380 Test that the order of files in a compiler command-line while compiling 381 and linking statically is deterministic. This can't be an ordinary test 382 case because we need to inspect the compiler database. 383 https://github.com/mesonbuild/meson/pull/951 384 ''' 385 testdir = os.path.join(self.common_test_dir, '5 linkstatic') 386 self.init(testdir) 387 compdb = self.get_compdb() 388 # Rules will get written out in this order 389 self.assertTrue(compdb[0]['file'].endswith("libfile.c")) 390 self.assertTrue(compdb[1]['file'].endswith("libfile2.c")) 391 self.assertTrue(compdb[2]['file'].endswith("libfile3.c")) 392 self.assertTrue(compdb[3]['file'].endswith("libfile4.c")) 393 # FIXME: We don't have access to the linker command 394 395 def test_run_target_files_path(self): 396 ''' 397 Test that run_targets are run from the correct directory 398 https://github.com/mesonbuild/meson/issues/957 399 ''' 400 testdir = os.path.join(self.common_test_dir, '51 run target') 401 self.init(testdir) 402 self.run_target('check_exists') 403 self.run_target('check-env') 404 self.run_target('check-env-ct') 405 406 def test_run_target_subdir(self): 407 ''' 408 Test that run_targets are run from the correct directory 409 https://github.com/mesonbuild/meson/issues/957 410 ''' 411 testdir = os.path.join(self.common_test_dir, '51 run target') 412 self.init(testdir) 413 self.run_target('textprinter') 414 415 def test_install_introspection(self): 416 ''' 417 Tests that the Meson introspection API exposes install filenames correctly 418 https://github.com/mesonbuild/meson/issues/829 419 ''' 420 if self.backend is not Backend.ninja: 421 raise SkipTest(f'{self.backend.name!r} backend can\'t install files') 422 testdir = os.path.join(self.common_test_dir, '8 install') 423 self.init(testdir) 424 intro = self.introspect('--targets') 425 if intro[0]['type'] == 'executable': 426 intro = intro[::-1] 427 self.assertPathListEqual(intro[0]['install_filename'], ['/usr/lib/libstat.a']) 428 self.assertPathListEqual(intro[1]['install_filename'], ['/usr/bin/prog' + exe_suffix]) 429 430 def test_install_subdir_introspection(self): 431 ''' 432 Test that the Meson introspection API also contains subdir install information 433 https://github.com/mesonbuild/meson/issues/5556 434 ''' 435 testdir = os.path.join(self.common_test_dir, '59 install subdir') 436 self.init(testdir) 437 intro = self.introspect('--installed') 438 expected = { 439 'sub2': 'share/sub2', 440 'subdir/sub1': 'share/sub1', 441 'subdir/sub_elided': 'share', 442 'sub1': 'share/sub1', 443 'sub/sub1': 'share/sub1', 444 'sub_elided': 'share', 445 'nested_elided/sub': 'share', 446 'new_directory': 'share/new_directory', 447 } 448 449 self.assertEqual(len(intro), len(expected)) 450 451 # Convert expected to PurePath 452 expected_converted = {PurePath(os.path.join(testdir, key)): PurePath(os.path.join(self.prefix, val)) for key, val in expected.items()} 453 intro_converted = {PurePath(key): PurePath(val) for key, val in intro.items()} 454 455 for src, dst in expected_converted.items(): 456 self.assertIn(src, intro_converted) 457 self.assertEqual(dst, intro_converted[src]) 458 459 def test_install_introspection_multiple_outputs(self): 460 ''' 461 Tests that the Meson introspection API exposes multiple install filenames correctly without crashing 462 https://github.com/mesonbuild/meson/pull/4555 463 464 Reverted to the first file only because of https://github.com/mesonbuild/meson/pull/4547#discussion_r244173438 465 TODO Change the format to a list officially in a followup PR 466 ''' 467 if self.backend is not Backend.ninja: 468 raise SkipTest(f'{self.backend.name!r} backend can\'t install files') 469 testdir = os.path.join(self.common_test_dir, '140 custom target multiple outputs') 470 self.init(testdir) 471 intro = self.introspect('--targets') 472 if intro[0]['type'] == 'executable': 473 intro = intro[::-1] 474 self.assertPathListEqual(intro[0]['install_filename'], ['/usr/include/diff.h', '/usr/bin/diff.sh']) 475 self.assertPathListEqual(intro[1]['install_filename'], ['/opt/same.h', '/opt/same.sh']) 476 self.assertPathListEqual(intro[2]['install_filename'], ['/usr/include/first.h', None]) 477 self.assertPathListEqual(intro[3]['install_filename'], [None, '/usr/bin/second.sh']) 478 479 def read_install_logs(self): 480 # Find logged files and directories 481 with Path(self.builddir, 'meson-logs', 'install-log.txt').open(encoding='utf-8') as f: 482 return list(map(lambda l: Path(l.strip()), 483 filter(lambda l: not l.startswith('#'), 484 f.readlines()))) 485 486 def test_install_log_content(self): 487 ''' 488 Tests that the install-log.txt is consistent with the installed files and directories. 489 Specifically checks that the log file only contains one entry per file/directory. 490 https://github.com/mesonbuild/meson/issues/4499 491 ''' 492 testdir = os.path.join(self.common_test_dir, '59 install subdir') 493 self.init(testdir) 494 self.install() 495 installpath = Path(self.installdir) 496 # Find installed files and directories 497 expected = {installpath: 0} 498 for name in installpath.rglob('*'): 499 expected[name] = 0 500 logged = self.read_install_logs() 501 for name in logged: 502 self.assertTrue(name in expected, f'Log contains extra entry {name}') 503 expected[name] += 1 504 505 for name, count in expected.items(): 506 self.assertGreater(count, 0, f'Log is missing entry for {name}') 507 self.assertLess(count, 2, f'Log has multiple entries for {name}') 508 509 # Verify that with --dry-run we obtain the same logs but with nothing 510 # actually installed 511 windows_proof_rmtree(self.installdir) 512 self._run(self.meson_command + ['install', '--dry-run', '--destdir', self.installdir], workdir=self.builddir) 513 self.assertEqual(logged, self.read_install_logs()) 514 self.assertFalse(os.path.exists(self.installdir)) 515 516 # If destdir is relative to build directory it should install 517 # exactly the same files. 518 rel_installpath = os.path.relpath(self.installdir, self.builddir) 519 self._run(self.meson_command + ['install', '--dry-run', '--destdir', rel_installpath, '-C', self.builddir]) 520 self.assertEqual(logged, self.read_install_logs()) 521 522 def test_uninstall(self): 523 exename = os.path.join(self.installdir, 'usr/bin/prog' + exe_suffix) 524 dirname = os.path.join(self.installdir, 'usr/share/dir') 525 testdir = os.path.join(self.common_test_dir, '8 install') 526 self.init(testdir) 527 self.assertPathDoesNotExist(exename) 528 self.install() 529 self.assertPathExists(exename) 530 self.uninstall() 531 self.assertPathDoesNotExist(exename) 532 self.assertPathDoesNotExist(dirname) 533 534 def test_forcefallback(self): 535 testdir = os.path.join(self.unit_test_dir, '31 forcefallback') 536 self.init(testdir, extra_args=['--wrap-mode=forcefallback']) 537 self.build() 538 self.run_tests() 539 540 def test_implicit_forcefallback(self): 541 testdir = os.path.join(self.unit_test_dir, '96 implicit force fallback') 542 with self.assertRaises(subprocess.CalledProcessError): 543 self.init(testdir) 544 self.init(testdir, extra_args=['--wrap-mode=forcefallback']) 545 self.new_builddir() 546 self.init(testdir, extra_args=['--force-fallback-for=something']) 547 548 def test_nopromote(self): 549 testdir = os.path.join(self.common_test_dir, '98 subproject subdir') 550 with self.assertRaises(subprocess.CalledProcessError) as cm: 551 self.init(testdir, extra_args=['--wrap-mode=nopromote']) 552 self.assertIn('dependency subsub found: NO', cm.exception.stdout) 553 554 def test_force_fallback_for(self): 555 testdir = os.path.join(self.unit_test_dir, '31 forcefallback') 556 self.init(testdir, extra_args=['--force-fallback-for=zlib,foo']) 557 self.build() 558 self.run_tests() 559 560 def test_force_fallback_for_nofallback(self): 561 testdir = os.path.join(self.unit_test_dir, '31 forcefallback') 562 self.init(testdir, extra_args=['--force-fallback-for=zlib,foo', '--wrap-mode=nofallback']) 563 self.build() 564 self.run_tests() 565 566 def test_testrepeat(self): 567 testdir = os.path.join(self.common_test_dir, '206 tap tests') 568 self.init(testdir) 569 self.build() 570 self._run(self.mtest_command + ['--repeat=2']) 571 572 def test_testsetups(self): 573 if not shutil.which('valgrind'): 574 raise SkipTest('Valgrind not installed.') 575 testdir = os.path.join(self.unit_test_dir, '2 testsetups') 576 self.init(testdir) 577 self.build() 578 # Run tests without setup 579 self.run_tests() 580 with open(os.path.join(self.logdir, 'testlog.txt'), encoding='utf-8') as f: 581 basic_log = f.read() 582 # Run buggy test with setup that has env that will make it fail 583 self.assertRaises(subprocess.CalledProcessError, 584 self._run, self.mtest_command + ['--setup=valgrind']) 585 with open(os.path.join(self.logdir, 'testlog-valgrind.txt'), encoding='utf-8') as f: 586 vg_log = f.read() 587 self.assertFalse('TEST_ENV is set' in basic_log) 588 self.assertFalse('Memcheck' in basic_log) 589 self.assertTrue('TEST_ENV is set' in vg_log) 590 self.assertTrue('Memcheck' in vg_log) 591 # Run buggy test with setup without env that will pass 592 self._run(self.mtest_command + ['--setup=wrapper']) 593 # Setup with no properties works 594 self._run(self.mtest_command + ['--setup=empty']) 595 # Setup with only env works 596 self._run(self.mtest_command + ['--setup=onlyenv']) 597 self._run(self.mtest_command + ['--setup=onlyenv2']) 598 self._run(self.mtest_command + ['--setup=onlyenv3']) 599 # Setup with only a timeout works 600 self._run(self.mtest_command + ['--setup=timeout']) 601 # Setup that does not define a wrapper works with --wrapper 602 self._run(self.mtest_command + ['--setup=timeout', '--wrapper', shutil.which('valgrind')]) 603 # Setup that skips test works 604 self._run(self.mtest_command + ['--setup=good']) 605 with open(os.path.join(self.logdir, 'testlog-good.txt'), encoding='utf-8') as f: 606 exclude_suites_log = f.read() 607 self.assertFalse('buggy' in exclude_suites_log) 608 # --suite overrides add_test_setup(xclude_suites) 609 self._run(self.mtest_command + ['--setup=good', '--suite', 'buggy']) 610 with open(os.path.join(self.logdir, 'testlog-good.txt'), encoding='utf-8') as f: 611 include_suites_log = f.read() 612 self.assertTrue('buggy' in include_suites_log) 613 614 def test_testsetup_selection(self): 615 testdir = os.path.join(self.unit_test_dir, '14 testsetup selection') 616 self.init(testdir) 617 self.build() 618 619 # Run tests without setup 620 self.run_tests() 621 622 self.assertRaises(subprocess.CalledProcessError, self._run, self.mtest_command + ['--setup=missingfromfoo']) 623 self._run(self.mtest_command + ['--setup=missingfromfoo', '--no-suite=foo:']) 624 625 self._run(self.mtest_command + ['--setup=worksforall']) 626 self._run(self.mtest_command + ['--setup=main:worksforall']) 627 628 self.assertRaises(subprocess.CalledProcessError, self._run, 629 self.mtest_command + ['--setup=onlyinbar']) 630 self.assertRaises(subprocess.CalledProcessError, self._run, 631 self.mtest_command + ['--setup=onlyinbar', '--no-suite=main:']) 632 self._run(self.mtest_command + ['--setup=onlyinbar', '--no-suite=main:', '--no-suite=foo:']) 633 self._run(self.mtest_command + ['--setup=bar:onlyinbar']) 634 self.assertRaises(subprocess.CalledProcessError, self._run, 635 self.mtest_command + ['--setup=foo:onlyinbar']) 636 self.assertRaises(subprocess.CalledProcessError, self._run, 637 self.mtest_command + ['--setup=main:onlyinbar']) 638 639 def test_testsetup_default(self): 640 testdir = os.path.join(self.unit_test_dir, '49 testsetup default') 641 self.init(testdir) 642 self.build() 643 644 # Run tests without --setup will cause the default setup to be used 645 self.run_tests() 646 with open(os.path.join(self.logdir, 'testlog.txt'), encoding='utf-8') as f: 647 default_log = f.read() 648 649 # Run tests with explicitly using the same setup that is set as default 650 self._run(self.mtest_command + ['--setup=mydefault']) 651 with open(os.path.join(self.logdir, 'testlog-mydefault.txt'), encoding='utf-8') as f: 652 mydefault_log = f.read() 653 654 # Run tests with another setup 655 self._run(self.mtest_command + ['--setup=other']) 656 with open(os.path.join(self.logdir, 'testlog-other.txt'), encoding='utf-8') as f: 657 other_log = f.read() 658 659 self.assertTrue('ENV_A is 1' in default_log) 660 self.assertTrue('ENV_B is 2' in default_log) 661 self.assertTrue('ENV_C is 2' in default_log) 662 663 self.assertTrue('ENV_A is 1' in mydefault_log) 664 self.assertTrue('ENV_B is 2' in mydefault_log) 665 self.assertTrue('ENV_C is 2' in mydefault_log) 666 667 self.assertTrue('ENV_A is 1' in other_log) 668 self.assertTrue('ENV_B is 3' in other_log) 669 self.assertTrue('ENV_C is 2' in other_log) 670 671 def assertFailedTestCount(self, failure_count, command): 672 try: 673 self._run(command) 674 self.assertEqual(0, failure_count, 'Expected %d tests to fail.' % failure_count) 675 except subprocess.CalledProcessError as e: 676 self.assertEqual(e.returncode, failure_count) 677 678 def test_suite_selection(self): 679 testdir = os.path.join(self.unit_test_dir, '4 suite selection') 680 self.init(testdir) 681 self.build() 682 683 self.assertFailedTestCount(4, self.mtest_command) 684 685 self.assertFailedTestCount(0, self.mtest_command + ['--suite', ':success']) 686 self.assertFailedTestCount(3, self.mtest_command + ['--suite', ':fail']) 687 self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', ':success']) 688 self.assertFailedTestCount(1, self.mtest_command + ['--no-suite', ':fail']) 689 690 self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'mainprj']) 691 self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc']) 692 self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail']) 693 self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjmix']) 694 self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'mainprj']) 695 self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'subprjsucc']) 696 self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjfail']) 697 self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjmix']) 698 699 self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'mainprj:fail']) 700 self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'mainprj:success']) 701 self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'mainprj:fail']) 702 self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'mainprj:success']) 703 704 self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail:fail']) 705 self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjfail:success']) 706 self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjfail:fail']) 707 self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'subprjfail:success']) 708 709 self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc:fail']) 710 self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc:success']) 711 self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'subprjsucc:fail']) 712 self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'subprjsucc:success']) 713 714 self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjmix:fail']) 715 self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjmix:success']) 716 self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjmix:fail']) 717 self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'subprjmix:success']) 718 719 self.assertFailedTestCount(2, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix:fail']) 720 self.assertFailedTestCount(3, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj']) 721 self.assertFailedTestCount(2, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj', '--no-suite', 'subprjmix:fail']) 722 self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj', '--no-suite', 'subprjmix:fail', 'mainprj-failing_test']) 723 724 self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjfail:fail', '--no-suite', 'subprjmix:fail']) 725 726 def test_build_by_default(self): 727 testdir = os.path.join(self.common_test_dir, '129 build by default') 728 self.init(testdir) 729 self.build() 730 genfile1 = os.path.join(self.builddir, 'generated1.dat') 731 genfile2 = os.path.join(self.builddir, 'generated2.dat') 732 exe1 = os.path.join(self.builddir, 'fooprog' + exe_suffix) 733 exe2 = os.path.join(self.builddir, 'barprog' + exe_suffix) 734 self.assertPathExists(genfile1) 735 self.assertPathExists(genfile2) 736 self.assertPathDoesNotExist(exe1) 737 self.assertPathDoesNotExist(exe2) 738 self.build(target=('fooprog' + exe_suffix)) 739 self.assertPathExists(exe1) 740 self.build(target=('barprog' + exe_suffix)) 741 self.assertPathExists(exe2) 742 743 def test_internal_include_order(self): 744 if mesonbuild.environment.detect_msys2_arch() and ('MESON_RSP_THRESHOLD' in os.environ): 745 raise SkipTest('Test does not yet support gcc rsp files on msys2') 746 747 testdir = os.path.join(self.common_test_dir, '130 include order') 748 self.init(testdir) 749 execmd = fxecmd = None 750 for cmd in self.get_compdb(): 751 if 'someexe' in cmd['command']: 752 execmd = cmd['command'] 753 continue 754 if 'somefxe' in cmd['command']: 755 fxecmd = cmd['command'] 756 continue 757 if not execmd or not fxecmd: 758 raise Exception('Could not find someexe and somfxe commands') 759 # Check include order for 'someexe' 760 incs = [a for a in split_args(execmd) if a.startswith("-I")] 761 self.assertEqual(len(incs), 9) 762 # Need to run the build so the private dir is created. 763 self.build() 764 pdirs = glob(os.path.join(self.builddir, 'sub4/someexe*.p')) 765 self.assertEqual(len(pdirs), 1) 766 privdir = pdirs[0][len(self.builddir)+1:] 767 self.assertPathEqual(incs[0], "-I" + privdir) 768 # target build subdir 769 self.assertPathEqual(incs[1], "-Isub4") 770 # target source subdir 771 self.assertPathBasenameEqual(incs[2], 'sub4') 772 # include paths added via per-target c_args: ['-I'...] 773 self.assertPathBasenameEqual(incs[3], 'sub3') 774 # target include_directories: build dir 775 self.assertPathEqual(incs[4], "-Isub2") 776 # target include_directories: source dir 777 self.assertPathBasenameEqual(incs[5], 'sub2') 778 # target internal dependency include_directories: build dir 779 self.assertPathEqual(incs[6], "-Isub1") 780 # target internal dependency include_directories: source dir 781 self.assertPathBasenameEqual(incs[7], 'sub1') 782 # custom target include dir 783 self.assertPathEqual(incs[8], '-Ictsub') 784 # Check include order for 'somefxe' 785 incs = [a for a in split_args(fxecmd) if a.startswith('-I')] 786 self.assertEqual(len(incs), 9) 787 # target private dir 788 pdirs = glob(os.path.join(self.builddir, 'somefxe*.p')) 789 self.assertEqual(len(pdirs), 1) 790 privdir = pdirs[0][len(self.builddir)+1:] 791 self.assertPathEqual(incs[0], '-I' + privdir) 792 # target build dir 793 self.assertPathEqual(incs[1], '-I.') 794 # target source dir 795 self.assertPathBasenameEqual(incs[2], os.path.basename(testdir)) 796 # target internal dependency correct include_directories: build dir 797 self.assertPathEqual(incs[3], "-Isub4") 798 # target internal dependency correct include_directories: source dir 799 self.assertPathBasenameEqual(incs[4], 'sub4') 800 # target internal dependency dep include_directories: build dir 801 self.assertPathEqual(incs[5], "-Isub1") 802 # target internal dependency dep include_directories: source dir 803 self.assertPathBasenameEqual(incs[6], 'sub1') 804 # target internal dependency wrong include_directories: build dir 805 self.assertPathEqual(incs[7], "-Isub2") 806 # target internal dependency wrong include_directories: source dir 807 self.assertPathBasenameEqual(incs[8], 'sub2') 808 809 def test_compiler_detection(self): 810 ''' 811 Test that automatic compiler detection and setting from the environment 812 both work just fine. This is needed because while running project tests 813 and other unit tests, we always read CC/CXX/etc from the environment. 814 ''' 815 gnu = GnuCompiler 816 clang = ClangCompiler 817 intel = IntelGnuLikeCompiler 818 msvc = (VisualStudioCCompiler, VisualStudioCPPCompiler) 819 clangcl = (ClangClCCompiler, ClangClCPPCompiler) 820 ar = mesonbuild.linkers.ArLinker 821 lib = mesonbuild.linkers.VisualStudioLinker 822 langs = [('c', 'CC'), ('cpp', 'CXX')] 823 if not is_windows() and platform.machine().lower() != 'e2k': 824 langs += [('objc', 'OBJC'), ('objcpp', 'OBJCXX')] 825 testdir = os.path.join(self.unit_test_dir, '5 compiler detection') 826 env = get_fake_env(testdir, self.builddir, self.prefix) 827 for lang, evar in langs: 828 # Detect with evar and do sanity checks on that 829 if evar in os.environ: 830 ecc = compiler_from_language(env, lang, MachineChoice.HOST) 831 self.assertTrue(ecc.version) 832 elinker = detect_static_linker(env, ecc) 833 # Pop it so we don't use it for the next detection 834 evalue = os.environ.pop(evar) 835 # Very rough/strict heuristics. Would never work for actual 836 # compiler detection, but should be ok for the tests. 837 ebase = os.path.basename(evalue) 838 if ebase.startswith('g') or ebase.endswith(('-gcc', '-g++')): 839 self.assertIsInstance(ecc, gnu) 840 self.assertIsInstance(elinker, ar) 841 elif 'clang-cl' in ebase: 842 self.assertIsInstance(ecc, clangcl) 843 self.assertIsInstance(elinker, lib) 844 elif 'clang' in ebase: 845 self.assertIsInstance(ecc, clang) 846 self.assertIsInstance(elinker, ar) 847 elif ebase.startswith('ic'): 848 self.assertIsInstance(ecc, intel) 849 self.assertIsInstance(elinker, ar) 850 elif ebase.startswith('cl'): 851 self.assertIsInstance(ecc, msvc) 852 self.assertIsInstance(elinker, lib) 853 else: 854 raise AssertionError(f'Unknown compiler {evalue!r}') 855 # Check that we actually used the evalue correctly as the compiler 856 self.assertEqual(ecc.get_exelist(), split_args(evalue)) 857 # Do auto-detection of compiler based on platform, PATH, etc. 858 cc = compiler_from_language(env, lang, MachineChoice.HOST) 859 self.assertTrue(cc.version) 860 linker = detect_static_linker(env, cc) 861 # Check compiler type 862 if isinstance(cc, gnu): 863 self.assertIsInstance(linker, ar) 864 if is_osx(): 865 self.assertIsInstance(cc.linker, mesonbuild.linkers.AppleDynamicLinker) 866 elif is_sunos(): 867 self.assertIsInstance(cc.linker, (mesonbuild.linkers.SolarisDynamicLinker, mesonbuild.linkers.GnuLikeDynamicLinkerMixin)) 868 else: 869 self.assertIsInstance(cc.linker, mesonbuild.linkers.GnuLikeDynamicLinkerMixin) 870 if isinstance(cc, clangcl): 871 self.assertIsInstance(linker, lib) 872 self.assertIsInstance(cc.linker, mesonbuild.linkers.ClangClDynamicLinker) 873 if isinstance(cc, clang): 874 self.assertIsInstance(linker, ar) 875 if is_osx(): 876 self.assertIsInstance(cc.linker, mesonbuild.linkers.AppleDynamicLinker) 877 elif is_windows(): 878 # This is clang, not clang-cl. This can be either an 879 # ld-like linker of link.exe-like linker (usually the 880 # former for msys2, the latter otherwise) 881 self.assertIsInstance(cc.linker, (mesonbuild.linkers.MSVCDynamicLinker, mesonbuild.linkers.GnuLikeDynamicLinkerMixin)) 882 else: 883 self.assertIsInstance(cc.linker, mesonbuild.linkers.GnuLikeDynamicLinkerMixin) 884 if isinstance(cc, intel): 885 self.assertIsInstance(linker, ar) 886 if is_osx(): 887 self.assertIsInstance(cc.linker, mesonbuild.linkers.AppleDynamicLinker) 888 elif is_windows(): 889 self.assertIsInstance(cc.linker, mesonbuild.linkers.XilinkDynamicLinker) 890 else: 891 self.assertIsInstance(cc.linker, mesonbuild.linkers.GnuDynamicLinker) 892 if isinstance(cc, msvc): 893 self.assertTrue(is_windows()) 894 self.assertIsInstance(linker, lib) 895 self.assertEqual(cc.id, 'msvc') 896 self.assertTrue(hasattr(cc, 'is_64')) 897 self.assertIsInstance(cc.linker, mesonbuild.linkers.MSVCDynamicLinker) 898 # If we're on Windows CI, we know what the compiler will be 899 if 'arch' in os.environ: 900 if os.environ['arch'] == 'x64': 901 self.assertTrue(cc.is_64) 902 else: 903 self.assertFalse(cc.is_64) 904 # Set evar ourselves to a wrapper script that just calls the same 905 # exelist + some argument. This is meant to test that setting 906 # something like `ccache gcc -pipe` or `distcc ccache gcc` works. 907 wrapper = os.path.join(testdir, 'compiler wrapper.py') 908 wrappercc = python_command + [wrapper] + cc.get_exelist() + ['-DSOME_ARG'] 909 os.environ[evar] = ' '.join(quote_arg(w) for w in wrappercc) 910 911 # Check static linker too 912 wrapperlinker = python_command + [wrapper] + linker.get_exelist() + linker.get_always_args() 913 os.environ['AR'] = ' '.join(quote_arg(w) for w in wrapperlinker) 914 915 # Need a new env to re-run environment loading 916 env = get_fake_env(testdir, self.builddir, self.prefix) 917 918 wcc = compiler_from_language(env, lang, MachineChoice.HOST) 919 wlinker = detect_static_linker(env, wcc) 920 # Pop it so we don't use it for the next detection 921 evalue = os.environ.pop('AR') 922 # Must be the same type since it's a wrapper around the same exelist 923 self.assertIs(type(cc), type(wcc)) 924 self.assertIs(type(linker), type(wlinker)) 925 # Ensure that the exelist is correct 926 self.assertEqual(wcc.get_exelist(), wrappercc) 927 self.assertEqual(wlinker.get_exelist(), wrapperlinker) 928 # Ensure that the version detection worked correctly 929 self.assertEqual(cc.version, wcc.version) 930 if hasattr(cc, 'is_64'): 931 self.assertEqual(cc.is_64, wcc.is_64) 932 933 def test_always_prefer_c_compiler_for_asm(self): 934 testdir = os.path.join(self.common_test_dir, '133 c cpp and asm') 935 # Skip if building with MSVC 936 env = get_fake_env(testdir, self.builddir, self.prefix) 937 if detect_c_compiler(env, MachineChoice.HOST).get_id() == 'msvc': 938 raise SkipTest('MSVC can\'t compile assembly') 939 self.init(testdir) 940 commands = {'c-asm': {}, 'cpp-asm': {}, 'cpp-c-asm': {}, 'c-cpp-asm': {}} 941 for cmd in self.get_compdb(): 942 # Get compiler 943 split = split_args(cmd['command']) 944 if split[0] == 'ccache': 945 compiler = split[1] 946 else: 947 compiler = split[0] 948 # Classify commands 949 if 'Ic-asm' in cmd['command']: 950 if cmd['file'].endswith('.S'): 951 commands['c-asm']['asm'] = compiler 952 elif cmd['file'].endswith('.c'): 953 commands['c-asm']['c'] = compiler 954 else: 955 raise AssertionError('{!r} found in cpp-asm?'.format(cmd['command'])) 956 elif 'Icpp-asm' in cmd['command']: 957 if cmd['file'].endswith('.S'): 958 commands['cpp-asm']['asm'] = compiler 959 elif cmd['file'].endswith('.cpp'): 960 commands['cpp-asm']['cpp'] = compiler 961 else: 962 raise AssertionError('{!r} found in cpp-asm?'.format(cmd['command'])) 963 elif 'Ic-cpp-asm' in cmd['command']: 964 if cmd['file'].endswith('.S'): 965 commands['c-cpp-asm']['asm'] = compiler 966 elif cmd['file'].endswith('.c'): 967 commands['c-cpp-asm']['c'] = compiler 968 elif cmd['file'].endswith('.cpp'): 969 commands['c-cpp-asm']['cpp'] = compiler 970 else: 971 raise AssertionError('{!r} found in c-cpp-asm?'.format(cmd['command'])) 972 elif 'Icpp-c-asm' in cmd['command']: 973 if cmd['file'].endswith('.S'): 974 commands['cpp-c-asm']['asm'] = compiler 975 elif cmd['file'].endswith('.c'): 976 commands['cpp-c-asm']['c'] = compiler 977 elif cmd['file'].endswith('.cpp'): 978 commands['cpp-c-asm']['cpp'] = compiler 979 else: 980 raise AssertionError('{!r} found in cpp-c-asm?'.format(cmd['command'])) 981 else: 982 raise AssertionError('Unknown command {!r} found'.format(cmd['command'])) 983 # Check that .S files are always built with the C compiler 984 self.assertEqual(commands['c-asm']['asm'], commands['c-asm']['c']) 985 self.assertEqual(commands['c-asm']['asm'], commands['cpp-asm']['asm']) 986 self.assertEqual(commands['cpp-asm']['asm'], commands['c-cpp-asm']['c']) 987 self.assertEqual(commands['c-cpp-asm']['asm'], commands['c-cpp-asm']['c']) 988 self.assertEqual(commands['cpp-c-asm']['asm'], commands['cpp-c-asm']['c']) 989 self.assertNotEqual(commands['cpp-asm']['asm'], commands['cpp-asm']['cpp']) 990 self.assertNotEqual(commands['c-cpp-asm']['c'], commands['c-cpp-asm']['cpp']) 991 self.assertNotEqual(commands['cpp-c-asm']['c'], commands['cpp-c-asm']['cpp']) 992 # Check that the c-asm target is always linked with the C linker 993 build_ninja = os.path.join(self.builddir, 'build.ninja') 994 with open(build_ninja, encoding='utf-8') as f: 995 contents = f.read() 996 m = re.search('build c-asm.*: c_LINKER', contents) 997 self.assertIsNotNone(m, msg=contents) 998 999 def test_preprocessor_checks_CPPFLAGS(self): 1000 ''' 1001 Test that preprocessor compiler checks read CPPFLAGS and also CFLAGS but 1002 not LDFLAGS. 1003 ''' 1004 testdir = os.path.join(self.common_test_dir, '132 get define') 1005 define = 'MESON_TEST_DEFINE_VALUE' 1006 # NOTE: this list can't have \n, ' or " 1007 # \n is never substituted by the GNU pre-processor via a -D define 1008 # ' and " confuse split_args() even when they are escaped 1009 # % and # confuse the MSVC preprocessor 1010 # !, ^, *, and < confuse lcc preprocessor 1011 value = 'spaces and fun@$&()-=_+{}[]:;>?,./~`' 1012 for env_var in ['CPPFLAGS', 'CFLAGS']: 1013 env = {} 1014 env[env_var] = f'-D{define}="{value}"' 1015 env['LDFLAGS'] = '-DMESON_FAIL_VALUE=cflags-read' 1016 self.init(testdir, extra_args=[f'-D{define}={value}'], override_envvars=env) 1017 1018 def test_custom_target_exe_data_deterministic(self): 1019 testdir = os.path.join(self.common_test_dir, '109 custom target capture') 1020 self.init(testdir) 1021 meson_exe_dat1 = glob(os.path.join(self.privatedir, 'meson_exe*.dat')) 1022 self.wipe() 1023 self.init(testdir) 1024 meson_exe_dat2 = glob(os.path.join(self.privatedir, 'meson_exe*.dat')) 1025 self.assertListEqual(meson_exe_dat1, meson_exe_dat2) 1026 1027 def test_noop_changes_cause_no_rebuilds(self): 1028 ''' 1029 Test that no-op changes to the build files such as mtime do not cause 1030 a rebuild of anything. 1031 ''' 1032 testdir = os.path.join(self.common_test_dir, '6 linkshared') 1033 self.init(testdir) 1034 self.build() 1035 # Immediately rebuilding should not do anything 1036 self.assertBuildIsNoop() 1037 # Changing mtime of meson.build should not rebuild anything 1038 self.utime(os.path.join(testdir, 'meson.build')) 1039 self.assertReconfiguredBuildIsNoop() 1040 # Changing mtime of libefile.c should rebuild the library, but not relink the executable 1041 self.utime(os.path.join(testdir, 'libfile.c')) 1042 self.assertBuildRelinkedOnlyTarget('mylib') 1043 1044 def test_source_changes_cause_rebuild(self): 1045 ''' 1046 Test that changes to sources and headers cause rebuilds, but not 1047 changes to unused files (as determined by the dependency file) in the 1048 input files list. 1049 ''' 1050 testdir = os.path.join(self.common_test_dir, '19 header in file list') 1051 self.init(testdir) 1052 self.build() 1053 # Immediately rebuilding should not do anything 1054 self.assertBuildIsNoop() 1055 # Changing mtime of header.h should rebuild everything 1056 self.utime(os.path.join(testdir, 'header.h')) 1057 self.assertBuildRelinkedOnlyTarget('prog') 1058 1059 def test_custom_target_changes_cause_rebuild(self): 1060 ''' 1061 Test that in a custom target, changes to the input files, the 1062 ExternalProgram, and any File objects on the command-line cause 1063 a rebuild. 1064 ''' 1065 testdir = os.path.join(self.common_test_dir, '57 custom header generator') 1066 self.init(testdir) 1067 self.build() 1068 # Immediately rebuilding should not do anything 1069 self.assertBuildIsNoop() 1070 # Changing mtime of these should rebuild everything 1071 for f in ('input.def', 'makeheader.py', 'somefile.txt'): 1072 self.utime(os.path.join(testdir, f)) 1073 self.assertBuildRelinkedOnlyTarget('prog') 1074 1075 def test_source_generator_program_cause_rebuild(self): 1076 ''' 1077 Test that changes to generator programs in the source tree cause 1078 a rebuild. 1079 ''' 1080 testdir = os.path.join(self.common_test_dir, '90 gen extra') 1081 self.init(testdir) 1082 self.build() 1083 # Immediately rebuilding should not do anything 1084 self.assertBuildIsNoop() 1085 # Changing mtime of generator should rebuild the executable 1086 self.utime(os.path.join(testdir, 'srcgen.py')) 1087 self.assertRebuiltTarget('basic') 1088 1089 def test_static_library_lto(self): 1090 ''' 1091 Test that static libraries can be built with LTO and linked to 1092 executables. On Linux, this requires the use of gcc-ar. 1093 https://github.com/mesonbuild/meson/issues/1646 1094 ''' 1095 testdir = os.path.join(self.common_test_dir, '5 linkstatic') 1096 1097 env = get_fake_env(testdir, self.builddir, self.prefix) 1098 if detect_c_compiler(env, MachineChoice.HOST).get_id() == 'clang' and is_windows(): 1099 raise SkipTest('LTO not (yet) supported by windows clang') 1100 1101 self.init(testdir, extra_args='-Db_lto=true') 1102 self.build() 1103 self.run_tests() 1104 1105 @skip_if_not_base_option('b_lto_threads') 1106 def test_lto_threads(self): 1107 testdir = os.path.join(self.common_test_dir, '6 linkshared') 1108 1109 env = get_fake_env(testdir, self.builddir, self.prefix) 1110 cc = detect_c_compiler(env, MachineChoice.HOST) 1111 extra_args: T.List[str] = [] 1112 if cc.get_id() == 'clang': 1113 if is_windows(): 1114 raise SkipTest('LTO not (yet) supported by windows clang') 1115 1116 self.init(testdir, extra_args=['-Db_lto=true', '-Db_lto_threads=8'] + extra_args) 1117 self.build() 1118 self.run_tests() 1119 1120 expected = set(cc.get_lto_compile_args(threads=8)) 1121 targets = self.introspect('--targets') 1122 # This assumes all of the targets support lto 1123 for t in targets: 1124 for s in t['target_sources']: 1125 for e in expected: 1126 self.assertIn(e, s['parameters']) 1127 1128 @skip_if_not_base_option('b_lto_mode') 1129 @skip_if_not_base_option('b_lto_threads') 1130 def test_lto_mode(self): 1131 testdir = os.path.join(self.common_test_dir, '6 linkshared') 1132 1133 env = get_fake_env(testdir, self.builddir, self.prefix) 1134 cc = detect_c_compiler(env, MachineChoice.HOST) 1135 if cc.get_id() != 'clang': 1136 raise SkipTest('Only clang currently supports thinLTO') 1137 if cc.linker.id not in {'ld.lld', 'ld.gold', 'ld64', 'lld-link'}: 1138 raise SkipTest('thinLTO requires ld.lld, ld.gold, ld64, or lld-link') 1139 elif is_windows(): 1140 raise SkipTest('LTO not (yet) supported by windows clang') 1141 1142 self.init(testdir, extra_args=['-Db_lto=true', '-Db_lto_mode=thin', '-Db_lto_threads=8', '-Dc_args=-Werror=unused-command-line-argument']) 1143 self.build() 1144 self.run_tests() 1145 1146 expected = set(cc.get_lto_compile_args(threads=8, mode='thin')) 1147 targets = self.introspect('--targets') 1148 # This assumes all of the targets support lto 1149 for t in targets: 1150 for s in t['target_sources']: 1151 self.assertTrue(expected.issubset(set(s['parameters'])), f'Incorrect values for {t["name"]}') 1152 1153 def test_dist_git(self): 1154 if not shutil.which('git'): 1155 raise SkipTest('Git not found') 1156 if self.backend is not Backend.ninja: 1157 raise SkipTest('Dist is only supported with Ninja') 1158 1159 try: 1160 self.dist_impl(_git_init, _git_add_all) 1161 except PermissionError: 1162 # When run under Windows CI, something (virus scanner?) 1163 # holds on to the git files so cleaning up the dir 1164 # fails sometimes. 1165 pass 1166 1167 def has_working_hg(self): 1168 if not shutil.which('hg'): 1169 return False 1170 try: 1171 # This check should not be necessary, but 1172 # CI under macOS passes the above test even 1173 # though Mercurial is not installed. 1174 if subprocess.call(['hg', '--version'], 1175 stdout=subprocess.DEVNULL, 1176 stderr=subprocess.DEVNULL) != 0: 1177 return False 1178 return True 1179 except FileNotFoundError: 1180 return False 1181 1182 def test_dist_hg(self): 1183 if not self.has_working_hg(): 1184 raise SkipTest('Mercurial not found or broken.') 1185 if self.backend is not Backend.ninja: 1186 raise SkipTest('Dist is only supported with Ninja') 1187 1188 def hg_init(project_dir): 1189 subprocess.check_call(['hg', 'init'], cwd=project_dir) 1190 with open(os.path.join(project_dir, '.hg', 'hgrc'), 'w', encoding='utf-8') as f: 1191 print('[ui]', file=f) 1192 print('username=Author Person <teh_coderz@example.com>', file=f) 1193 subprocess.check_call(['hg', 'add', 'meson.build', 'distexe.c'], cwd=project_dir) 1194 subprocess.check_call(['hg', 'commit', '-m', 'I am a project'], cwd=project_dir) 1195 1196 try: 1197 self.dist_impl(hg_init, include_subprojects=False) 1198 except PermissionError: 1199 # When run under Windows CI, something (virus scanner?) 1200 # holds on to the hg files so cleaning up the dir 1201 # fails sometimes. 1202 pass 1203 1204 def test_dist_git_script(self): 1205 if not shutil.which('git'): 1206 raise SkipTest('Git not found') 1207 if self.backend is not Backend.ninja: 1208 raise SkipTest('Dist is only supported with Ninja') 1209 1210 try: 1211 with tempfile.TemporaryDirectory() as tmpdir: 1212 project_dir = os.path.join(tmpdir, 'a') 1213 shutil.copytree(os.path.join(self.unit_test_dir, '35 dist script'), 1214 project_dir) 1215 _git_init(project_dir) 1216 self.init(project_dir) 1217 self.build('dist') 1218 1219 self.new_builddir() 1220 self.init(project_dir, extra_args=['-Dsub:broken_dist_script=false']) 1221 self._run(self.meson_command + ['dist', '--include-subprojects'], workdir=self.builddir) 1222 except PermissionError: 1223 # When run under Windows CI, something (virus scanner?) 1224 # holds on to the git files so cleaning up the dir 1225 # fails sometimes. 1226 pass 1227 1228 def create_dummy_subproject(self, project_dir, name): 1229 path = os.path.join(project_dir, 'subprojects', name) 1230 os.makedirs(path) 1231 with open(os.path.join(path, 'meson.build'), 'w', encoding='utf-8') as ofile: 1232 ofile.write(f"project('{name}', version: '1.0')") 1233 return path 1234 1235 def dist_impl(self, vcs_init, vcs_add_all=None, include_subprojects=True): 1236 # Create this on the fly because having rogue .git directories inside 1237 # the source tree leads to all kinds of trouble. 1238 with tempfile.TemporaryDirectory() as project_dir: 1239 with open(os.path.join(project_dir, 'meson.build'), 'w', encoding='utf-8') as ofile: 1240 ofile.write(textwrap.dedent('''\ 1241 project('disttest', 'c', version : '1.4.3') 1242 e = executable('distexe', 'distexe.c') 1243 test('dist test', e) 1244 subproject('vcssub', required : false) 1245 subproject('tarballsub', required : false) 1246 subproject('samerepo', required : false) 1247 ''')) 1248 with open(os.path.join(project_dir, 'distexe.c'), 'w', encoding='utf-8') as ofile: 1249 ofile.write(textwrap.dedent('''\ 1250 #include<stdio.h> 1251 1252 int main(int argc, char **argv) { 1253 printf("I am a distribution test.\\n"); 1254 return 0; 1255 } 1256 ''')) 1257 xz_distfile = os.path.join(self.distdir, 'disttest-1.4.3.tar.xz') 1258 xz_checksumfile = xz_distfile + '.sha256sum' 1259 gz_distfile = os.path.join(self.distdir, 'disttest-1.4.3.tar.gz') 1260 gz_checksumfile = gz_distfile + '.sha256sum' 1261 zip_distfile = os.path.join(self.distdir, 'disttest-1.4.3.zip') 1262 zip_checksumfile = zip_distfile + '.sha256sum' 1263 vcs_init(project_dir) 1264 if include_subprojects: 1265 vcs_init(self.create_dummy_subproject(project_dir, 'vcssub')) 1266 self.create_dummy_subproject(project_dir, 'tarballsub') 1267 self.create_dummy_subproject(project_dir, 'unusedsub') 1268 if vcs_add_all: 1269 vcs_add_all(self.create_dummy_subproject(project_dir, 'samerepo')) 1270 self.init(project_dir) 1271 self.build('dist') 1272 self.assertPathExists(xz_distfile) 1273 self.assertPathExists(xz_checksumfile) 1274 self.assertPathDoesNotExist(gz_distfile) 1275 self.assertPathDoesNotExist(gz_checksumfile) 1276 self.assertPathDoesNotExist(zip_distfile) 1277 self.assertPathDoesNotExist(zip_checksumfile) 1278 self._run(self.meson_command + ['dist', '--formats', 'gztar'], 1279 workdir=self.builddir) 1280 self.assertPathExists(gz_distfile) 1281 self.assertPathExists(gz_checksumfile) 1282 self._run(self.meson_command + ['dist', '--formats', 'zip'], 1283 workdir=self.builddir) 1284 self.assertPathExists(zip_distfile) 1285 self.assertPathExists(zip_checksumfile) 1286 os.remove(xz_distfile) 1287 os.remove(xz_checksumfile) 1288 os.remove(gz_distfile) 1289 os.remove(gz_checksumfile) 1290 os.remove(zip_distfile) 1291 os.remove(zip_checksumfile) 1292 self._run(self.meson_command + ['dist', '--formats', 'xztar,gztar,zip'], 1293 workdir=self.builddir) 1294 self.assertPathExists(xz_distfile) 1295 self.assertPathExists(xz_checksumfile) 1296 self.assertPathExists(gz_distfile) 1297 self.assertPathExists(gz_checksumfile) 1298 self.assertPathExists(zip_distfile) 1299 self.assertPathExists(zip_checksumfile) 1300 1301 if include_subprojects: 1302 # Verify that without --include-subprojects we have files from 1303 # the main project and also files from subprojects part of the 1304 # main vcs repository. 1305 z = zipfile.ZipFile(zip_distfile) 1306 expected = ['disttest-1.4.3/', 1307 'disttest-1.4.3/meson.build', 1308 'disttest-1.4.3/distexe.c'] 1309 if vcs_add_all: 1310 expected += ['disttest-1.4.3/subprojects/', 1311 'disttest-1.4.3/subprojects/samerepo/', 1312 'disttest-1.4.3/subprojects/samerepo/meson.build'] 1313 self.assertEqual(sorted(expected), 1314 sorted(z.namelist())) 1315 # Verify that with --include-subprojects we now also have files 1316 # from tarball and separate vcs subprojects. But not files from 1317 # unused subprojects. 1318 self._run(self.meson_command + ['dist', '--formats', 'zip', '--include-subprojects'], 1319 workdir=self.builddir) 1320 z = zipfile.ZipFile(zip_distfile) 1321 expected += ['disttest-1.4.3/subprojects/tarballsub/', 1322 'disttest-1.4.3/subprojects/tarballsub/meson.build', 1323 'disttest-1.4.3/subprojects/vcssub/', 1324 'disttest-1.4.3/subprojects/vcssub/meson.build'] 1325 self.assertEqual(sorted(expected), 1326 sorted(z.namelist())) 1327 if vcs_add_all: 1328 # Verify we can distribute separately subprojects in the same vcs 1329 # repository as the main project. 1330 subproject_dir = os.path.join(project_dir, 'subprojects', 'samerepo') 1331 self.new_builddir() 1332 self.init(subproject_dir) 1333 self.build('dist') 1334 xz_distfile = os.path.join(self.distdir, 'samerepo-1.0.tar.xz') 1335 xz_checksumfile = xz_distfile + '.sha256sum' 1336 self.assertPathExists(xz_distfile) 1337 self.assertPathExists(xz_checksumfile) 1338 tar = tarfile.open(xz_distfile, "r:xz") # [ignore encoding] 1339 self.assertEqual(sorted(['samerepo-1.0', 1340 'samerepo-1.0/meson.build']), 1341 sorted([i.name for i in tar])) 1342 1343 def test_rpath_uses_ORIGIN(self): 1344 ''' 1345 Test that built targets use $ORIGIN in rpath, which ensures that they 1346 are relocatable and ensures that builds are reproducible since the 1347 build directory won't get embedded into the built binaries. 1348 ''' 1349 if is_windows() or is_cygwin(): 1350 raise SkipTest('Windows PE/COFF binaries do not use RPATH') 1351 testdir = os.path.join(self.common_test_dir, '39 library chain') 1352 self.init(testdir) 1353 self.build() 1354 for each in ('prog', 'subdir/liblib1.so', ): 1355 rpath = get_rpath(os.path.join(self.builddir, each)) 1356 self.assertTrue(rpath, f'Rpath could not be determined for {each}.') 1357 if is_dragonflybsd(): 1358 # DragonflyBSD will prepend /usr/lib/gccVERSION to the rpath, 1359 # so ignore that. 1360 self.assertTrue(rpath.startswith('/usr/lib/gcc')) 1361 rpaths = rpath.split(':')[1:] 1362 else: 1363 rpaths = rpath.split(':') 1364 for path in rpaths: 1365 self.assertTrue(path.startswith('$ORIGIN'), msg=(each, path)) 1366 # These two don't link to anything else, so they do not need an rpath entry. 1367 for each in ('subdir/subdir2/liblib2.so', 'subdir/subdir3/liblib3.so'): 1368 rpath = get_rpath(os.path.join(self.builddir, each)) 1369 if is_dragonflybsd(): 1370 # The rpath should be equal to /usr/lib/gccVERSION 1371 self.assertTrue(rpath.startswith('/usr/lib/gcc')) 1372 self.assertEqual(len(rpath.split(':')), 1) 1373 else: 1374 self.assertTrue(rpath is None) 1375 1376 def test_dash_d_dedup(self): 1377 testdir = os.path.join(self.unit_test_dir, '9 d dedup') 1378 self.init(testdir) 1379 cmd = self.get_compdb()[0]['command'] 1380 self.assertTrue('-D FOO -D BAR' in cmd or 1381 '"-D" "FOO" "-D" "BAR"' in cmd or 1382 '/D FOO /D BAR' in cmd or 1383 '"/D" "FOO" "/D" "BAR"' in cmd) 1384 1385 def test_all_forbidden_targets_tested(self): 1386 ''' 1387 Test that all forbidden targets are tested in the '150 reserved targets' 1388 test. Needs to be a unit test because it accesses Meson internals. 1389 ''' 1390 testdir = os.path.join(self.common_test_dir, '150 reserved targets') 1391 targets = mesonbuild.coredata.FORBIDDEN_TARGET_NAMES 1392 # We don't actually define a target with this name 1393 targets.pop('build.ninja') 1394 # Remove this to avoid multiple entries with the same name 1395 # but different case. 1396 targets.pop('PHONY') 1397 for i in targets: 1398 self.assertPathExists(os.path.join(testdir, i)) 1399 1400 def detect_prebuild_env(self): 1401 env = get_fake_env() 1402 cc = detect_c_compiler(env, MachineChoice.HOST) 1403 stlinker = detect_static_linker(env, cc) 1404 if is_windows(): 1405 object_suffix = 'obj' 1406 shared_suffix = 'dll' 1407 elif is_cygwin(): 1408 object_suffix = 'o' 1409 shared_suffix = 'dll' 1410 elif is_osx(): 1411 object_suffix = 'o' 1412 shared_suffix = 'dylib' 1413 else: 1414 object_suffix = 'o' 1415 shared_suffix = 'so' 1416 return (cc, stlinker, object_suffix, shared_suffix) 1417 1418 def pbcompile(self, compiler, source, objectfile, extra_args=None): 1419 cmd = compiler.get_exelist() 1420 extra_args = extra_args or [] 1421 if compiler.get_argument_syntax() == 'msvc': 1422 cmd += ['/nologo', '/Fo' + objectfile, '/c', source] + extra_args 1423 else: 1424 cmd += ['-c', source, '-o', objectfile] + extra_args 1425 subprocess.check_call(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) 1426 1427 def test_prebuilt_object(self): 1428 (compiler, _, object_suffix, _) = self.detect_prebuild_env() 1429 tdir = os.path.join(self.unit_test_dir, '15 prebuilt object') 1430 source = os.path.join(tdir, 'source.c') 1431 objectfile = os.path.join(tdir, 'prebuilt.' + object_suffix) 1432 self.pbcompile(compiler, source, objectfile) 1433 try: 1434 self.init(tdir) 1435 self.build() 1436 self.run_tests() 1437 finally: 1438 os.unlink(objectfile) 1439 1440 def build_static_lib(self, compiler, linker, source, objectfile, outfile, extra_args=None): 1441 if extra_args is None: 1442 extra_args = [] 1443 if compiler.get_argument_syntax() == 'msvc': 1444 link_cmd = ['lib', '/NOLOGO', '/OUT:' + outfile, objectfile] 1445 else: 1446 link_cmd = ['ar', 'csr', outfile, objectfile] 1447 link_cmd = linker.get_exelist() 1448 link_cmd += linker.get_always_args() 1449 link_cmd += linker.get_std_link_args() 1450 link_cmd += linker.get_output_args(outfile) 1451 link_cmd += [objectfile] 1452 self.pbcompile(compiler, source, objectfile, extra_args=extra_args) 1453 try: 1454 subprocess.check_call(link_cmd) 1455 finally: 1456 os.unlink(objectfile) 1457 1458 def test_prebuilt_static_lib(self): 1459 (cc, stlinker, object_suffix, _) = self.detect_prebuild_env() 1460 tdir = os.path.join(self.unit_test_dir, '16 prebuilt static') 1461 source = os.path.join(tdir, 'libdir/best.c') 1462 objectfile = os.path.join(tdir, 'libdir/best.' + object_suffix) 1463 stlibfile = os.path.join(tdir, 'libdir/libbest.a') 1464 self.build_static_lib(cc, stlinker, source, objectfile, stlibfile) 1465 # Run the test 1466 try: 1467 self.init(tdir) 1468 self.build() 1469 self.run_tests() 1470 finally: 1471 os.unlink(stlibfile) 1472 1473 def build_shared_lib(self, compiler, source, objectfile, outfile, impfile, extra_args=None): 1474 if extra_args is None: 1475 extra_args = [] 1476 if compiler.get_argument_syntax() == 'msvc': 1477 link_cmd = compiler.get_linker_exelist() + [ 1478 '/NOLOGO', '/DLL', '/DEBUG', '/IMPLIB:' + impfile, 1479 '/OUT:' + outfile, objectfile] 1480 else: 1481 if not (compiler.info.is_windows() or compiler.info.is_cygwin() or compiler.info.is_darwin()): 1482 extra_args += ['-fPIC'] 1483 link_cmd = compiler.get_exelist() + ['-shared', '-o', outfile, objectfile] 1484 if not is_osx(): 1485 link_cmd += ['-Wl,-soname=' + os.path.basename(outfile)] 1486 self.pbcompile(compiler, source, objectfile, extra_args=extra_args) 1487 try: 1488 subprocess.check_call(link_cmd) 1489 finally: 1490 os.unlink(objectfile) 1491 1492 def test_prebuilt_shared_lib(self): 1493 (cc, _, object_suffix, shared_suffix) = self.detect_prebuild_env() 1494 tdir = os.path.join(self.unit_test_dir, '17 prebuilt shared') 1495 source = os.path.join(tdir, 'alexandria.c') 1496 objectfile = os.path.join(tdir, 'alexandria.' + object_suffix) 1497 impfile = os.path.join(tdir, 'alexandria.lib') 1498 if cc.get_argument_syntax() == 'msvc': 1499 shlibfile = os.path.join(tdir, 'alexandria.' + shared_suffix) 1500 elif is_cygwin(): 1501 shlibfile = os.path.join(tdir, 'cygalexandria.' + shared_suffix) 1502 else: 1503 shlibfile = os.path.join(tdir, 'libalexandria.' + shared_suffix) 1504 self.build_shared_lib(cc, source, objectfile, shlibfile, impfile) 1505 # Run the test 1506 try: 1507 self.init(tdir) 1508 self.build() 1509 self.run_tests() 1510 finally: 1511 os.unlink(shlibfile) 1512 if is_windows(): 1513 # Clean up all the garbage MSVC writes in the 1514 # source tree. 1515 for fname in glob(os.path.join(tdir, 'alexandria.*')): 1516 if os.path.splitext(fname)[1] not in ['.c', '.h']: 1517 os.unlink(fname) 1518 1519 @skipIfNoPkgconfig 1520 def test_pkgconfig_static(self): 1521 ''' 1522 Test that the we prefer static libraries when `static: true` is 1523 passed to dependency() with pkg-config. Can't be an ordinary test 1524 because we need to build libs and try to find them from meson.build 1525 1526 Also test that it's not a hard error to have unsatisfiable library deps 1527 since system libraries -lm will never be found statically. 1528 https://github.com/mesonbuild/meson/issues/2785 1529 ''' 1530 (cc, stlinker, objext, shext) = self.detect_prebuild_env() 1531 testdir = os.path.join(self.unit_test_dir, '18 pkgconfig static') 1532 source = os.path.join(testdir, 'foo.c') 1533 objectfile = os.path.join(testdir, 'foo.' + objext) 1534 stlibfile = os.path.join(testdir, 'libfoo.a') 1535 impfile = os.path.join(testdir, 'foo.lib') 1536 if cc.get_argument_syntax() == 'msvc': 1537 shlibfile = os.path.join(testdir, 'foo.' + shext) 1538 elif is_cygwin(): 1539 shlibfile = os.path.join(testdir, 'cygfoo.' + shext) 1540 else: 1541 shlibfile = os.path.join(testdir, 'libfoo.' + shext) 1542 # Build libs 1543 self.build_static_lib(cc, stlinker, source, objectfile, stlibfile, extra_args=['-DFOO_STATIC']) 1544 self.build_shared_lib(cc, source, objectfile, shlibfile, impfile) 1545 # Run test 1546 try: 1547 self.init(testdir, override_envvars={'PKG_CONFIG_LIBDIR': self.builddir}) 1548 self.build() 1549 self.run_tests() 1550 finally: 1551 os.unlink(stlibfile) 1552 os.unlink(shlibfile) 1553 if is_windows(): 1554 # Clean up all the garbage MSVC writes in the 1555 # source tree. 1556 for fname in glob(os.path.join(testdir, 'foo.*')): 1557 if os.path.splitext(fname)[1] not in ['.c', '.h', '.in']: 1558 os.unlink(fname) 1559 1560 @skipIfNoPkgconfig 1561 @mock.patch.dict(os.environ) 1562 def test_pkgconfig_gen_escaping(self): 1563 testdir = os.path.join(self.common_test_dir, '44 pkgconfig-gen') 1564 prefix = '/usr/with spaces' 1565 libdir = 'lib' 1566 self.init(testdir, extra_args=['--prefix=' + prefix, 1567 '--libdir=' + libdir]) 1568 # Find foo dependency 1569 os.environ['PKG_CONFIG_LIBDIR'] = self.privatedir 1570 env = get_fake_env(testdir, self.builddir, self.prefix) 1571 kwargs = {'required': True, 'silent': True} 1572 foo_dep = PkgConfigDependency('libfoo', env, kwargs) 1573 # Ensure link_args are properly quoted 1574 libdir = PurePath(prefix) / PurePath(libdir) 1575 link_args = ['-L' + libdir.as_posix(), '-lfoo'] 1576 self.assertEqual(foo_dep.get_link_args(), link_args) 1577 # Ensure include args are properly quoted 1578 incdir = PurePath(prefix) / PurePath('include') 1579 cargs = ['-I' + incdir.as_posix(), '-DLIBFOO'] 1580 # pkg-config and pkgconf does not respect the same order 1581 self.assertEqual(sorted(foo_dep.get_compile_args()), sorted(cargs)) 1582 1583 def test_array_option_change(self): 1584 def get_opt(): 1585 opts = self.introspect('--buildoptions') 1586 for x in opts: 1587 if x.get('name') == 'list': 1588 return x 1589 raise Exception(opts) 1590 1591 expected = { 1592 'name': 'list', 1593 'description': 'list', 1594 'section': 'user', 1595 'type': 'array', 1596 'value': ['foo', 'bar'], 1597 'machine': 'any', 1598 } 1599 tdir = os.path.join(self.unit_test_dir, '19 array option') 1600 self.init(tdir) 1601 original = get_opt() 1602 self.assertDictEqual(original, expected) 1603 1604 expected['value'] = ['oink', 'boink'] 1605 self.setconf('-Dlist=oink,boink') 1606 changed = get_opt() 1607 self.assertEqual(changed, expected) 1608 1609 def test_array_option_bad_change(self): 1610 def get_opt(): 1611 opts = self.introspect('--buildoptions') 1612 for x in opts: 1613 if x.get('name') == 'list': 1614 return x 1615 raise Exception(opts) 1616 1617 expected = { 1618 'name': 'list', 1619 'description': 'list', 1620 'section': 'user', 1621 'type': 'array', 1622 'value': ['foo', 'bar'], 1623 'machine': 'any', 1624 } 1625 tdir = os.path.join(self.unit_test_dir, '19 array option') 1626 self.init(tdir) 1627 original = get_opt() 1628 self.assertDictEqual(original, expected) 1629 with self.assertRaises(subprocess.CalledProcessError): 1630 self.setconf('-Dlist=bad') 1631 changed = get_opt() 1632 self.assertDictEqual(changed, expected) 1633 1634 def test_array_option_empty_equivalents(self): 1635 """Array options treat -Dopt=[] and -Dopt= as equivalent.""" 1636 def get_opt(): 1637 opts = self.introspect('--buildoptions') 1638 for x in opts: 1639 if x.get('name') == 'list': 1640 return x 1641 raise Exception(opts) 1642 1643 expected = { 1644 'name': 'list', 1645 'description': 'list', 1646 'section': 'user', 1647 'type': 'array', 1648 'value': [], 1649 'machine': 'any', 1650 } 1651 tdir = os.path.join(self.unit_test_dir, '19 array option') 1652 self.init(tdir, extra_args='-Dlist=') 1653 original = get_opt() 1654 self.assertDictEqual(original, expected) 1655 1656 def opt_has(self, name, value): 1657 res = self.introspect('--buildoptions') 1658 found = False 1659 for i in res: 1660 if i['name'] == name: 1661 self.assertEqual(i['value'], value) 1662 found = True 1663 break 1664 self.assertTrue(found, "Array option not found in introspect data.") 1665 1666 def test_free_stringarray_setting(self): 1667 testdir = os.path.join(self.common_test_dir, '40 options') 1668 self.init(testdir) 1669 self.opt_has('free_array_opt', []) 1670 self.setconf('-Dfree_array_opt=foo,bar', will_build=False) 1671 self.opt_has('free_array_opt', ['foo', 'bar']) 1672 self.setconf("-Dfree_array_opt=['a,b', 'c,d']", will_build=False) 1673 self.opt_has('free_array_opt', ['a,b', 'c,d']) 1674 1675 # When running under Travis Mac CI, the file updates seem to happen 1676 # too fast so the timestamps do not get properly updated. 1677 # Call this method before file operations in appropriate places 1678 # to make things work. 1679 def mac_ci_delay(self): 1680 if is_osx() and is_ci(): 1681 import time 1682 time.sleep(1) 1683 1684 def test_options_with_choices_changing(self) -> None: 1685 """Detect when options like arrays or combos have their choices change.""" 1686 testdir = Path(os.path.join(self.unit_test_dir, '84 change option choices')) 1687 options1 = str(testdir / 'meson_options.1.txt') 1688 options2 = str(testdir / 'meson_options.2.txt') 1689 1690 # Test that old options are changed to the new defaults if they are not valid 1691 real_options = str(testdir / 'meson_options.txt') 1692 self.addCleanup(os.unlink, real_options) 1693 1694 shutil.copy(options1, real_options) 1695 self.init(str(testdir)) 1696 self.mac_ci_delay() 1697 shutil.copy(options2, real_options) 1698 1699 self.build() 1700 opts = self.introspect('--buildoptions') 1701 for item in opts: 1702 if item['name'] == 'combo': 1703 self.assertEqual(item['value'], 'b') 1704 self.assertEqual(item['choices'], ['b', 'c', 'd']) 1705 elif item['name'] == 'arr': 1706 self.assertEqual(item['value'], ['b']) 1707 self.assertEqual(item['choices'], ['b', 'c', 'd']) 1708 1709 self.wipe() 1710 self.mac_ci_delay() 1711 1712 # When the old options are valid they should remain 1713 shutil.copy(options1, real_options) 1714 self.init(str(testdir), extra_args=['-Dcombo=c', '-Darray=b,c']) 1715 self.mac_ci_delay() 1716 shutil.copy(options2, real_options) 1717 self.build() 1718 opts = self.introspect('--buildoptions') 1719 for item in opts: 1720 if item['name'] == 'combo': 1721 self.assertEqual(item['value'], 'c') 1722 self.assertEqual(item['choices'], ['b', 'c', 'd']) 1723 elif item['name'] == 'arr': 1724 self.assertEqual(item['value'], ['b', 'c']) 1725 self.assertEqual(item['choices'], ['b', 'c', 'd']) 1726 1727 def test_subproject_promotion(self): 1728 testdir = os.path.join(self.unit_test_dir, '12 promote') 1729 workdir = os.path.join(self.builddir, 'work') 1730 shutil.copytree(testdir, workdir) 1731 spdir = os.path.join(workdir, 'subprojects') 1732 s3dir = os.path.join(spdir, 's3') 1733 scommondir = os.path.join(spdir, 'scommon') 1734 self.assertFalse(os.path.isdir(s3dir)) 1735 subprocess.check_call(self.wrap_command + ['promote', 's3'], 1736 cwd=workdir, 1737 stdout=subprocess.DEVNULL) 1738 self.assertTrue(os.path.isdir(s3dir)) 1739 self.assertFalse(os.path.isdir(scommondir)) 1740 self.assertNotEqual(subprocess.call(self.wrap_command + ['promote', 'scommon'], 1741 cwd=workdir, 1742 stderr=subprocess.DEVNULL), 0) 1743 self.assertNotEqual(subprocess.call(self.wrap_command + ['promote', 'invalid/path/to/scommon'], 1744 cwd=workdir, 1745 stderr=subprocess.DEVNULL), 0) 1746 self.assertFalse(os.path.isdir(scommondir)) 1747 subprocess.check_call(self.wrap_command + ['promote', 'subprojects/s2/subprojects/scommon'], cwd=workdir) 1748 self.assertTrue(os.path.isdir(scommondir)) 1749 promoted_wrap = os.path.join(spdir, 'athing.wrap') 1750 self.assertFalse(os.path.isfile(promoted_wrap)) 1751 subprocess.check_call(self.wrap_command + ['promote', 'athing'], cwd=workdir) 1752 self.assertTrue(os.path.isfile(promoted_wrap)) 1753 self.init(workdir) 1754 self.build() 1755 1756 def test_subproject_promotion_wrap(self): 1757 testdir = os.path.join(self.unit_test_dir, '44 promote wrap') 1758 workdir = os.path.join(self.builddir, 'work') 1759 shutil.copytree(testdir, workdir) 1760 spdir = os.path.join(workdir, 'subprojects') 1761 1762 ambiguous_wrap = os.path.join(spdir, 'ambiguous.wrap') 1763 self.assertNotEqual(subprocess.call(self.wrap_command + ['promote', 'ambiguous'], 1764 cwd=workdir, 1765 stderr=subprocess.DEVNULL), 0) 1766 self.assertFalse(os.path.isfile(ambiguous_wrap)) 1767 subprocess.check_call(self.wrap_command + ['promote', 'subprojects/s2/subprojects/ambiguous.wrap'], cwd=workdir) 1768 self.assertTrue(os.path.isfile(ambiguous_wrap)) 1769 1770 def test_warning_location(self): 1771 tdir = os.path.join(self.unit_test_dir, '22 warning location') 1772 out = self.init(tdir) 1773 for expected in [ 1774 r'meson.build:4: WARNING: Keyword argument "link_with" defined multiple times.', 1775 r'sub' + os.path.sep + r'meson.build:3: WARNING: Keyword argument "link_with" defined multiple times.', 1776 r'meson.build:6: WARNING: a warning of some sort', 1777 r'sub' + os.path.sep + r'meson.build:4: WARNING: subdir warning', 1778 r'meson.build:7: WARNING: Module unstable-simd has no backwards or forwards compatibility and might not exist in future releases.', 1779 r"meson.build:11: WARNING: The variable(s) 'MISSING' in the input file 'conf.in' are not present in the given configuration data.", 1780 ]: 1781 self.assertRegex(out, re.escape(expected)) 1782 1783 for wd in [ 1784 self.src_root, 1785 self.builddir, 1786 os.getcwd(), 1787 ]: 1788 self.new_builddir() 1789 out = self.init(tdir, workdir=wd) 1790 expected = os.path.join(relpath(tdir, self.src_root), 'meson.build') 1791 relwd = relpath(self.src_root, wd) 1792 if relwd != '.': 1793 expected = os.path.join(relwd, expected) 1794 expected = '\n' + expected + ':' 1795 self.assertIn(expected, out) 1796 1797 def test_error_location_path(self): 1798 '''Test locations in meson errors contain correct paths''' 1799 # this list contains errors from all the different steps in the 1800 # lexer/parser/interpreter we have tests for. 1801 for (t, f) in [ 1802 ('10 out of bounds', 'meson.build'), 1803 ('18 wrong plusassign', 'meson.build'), 1804 ('60 bad option argument', 'meson_options.txt'), 1805 ('98 subdir parse error', os.path.join('subdir', 'meson.build')), 1806 ('99 invalid option file', 'meson_options.txt'), 1807 ]: 1808 tdir = os.path.join(self.src_root, 'test cases', 'failing', t) 1809 1810 for wd in [ 1811 self.src_root, 1812 self.builddir, 1813 os.getcwd(), 1814 ]: 1815 try: 1816 self.init(tdir, workdir=wd) 1817 except subprocess.CalledProcessError as e: 1818 expected = os.path.join('test cases', 'failing', t, f) 1819 relwd = relpath(self.src_root, wd) 1820 if relwd != '.': 1821 expected = os.path.join(relwd, expected) 1822 expected = '\n' + expected + ':' 1823 self.assertIn(expected, e.output) 1824 else: 1825 self.fail('configure unexpectedly succeeded') 1826 1827 def test_permitted_method_kwargs(self): 1828 tdir = os.path.join(self.unit_test_dir, '25 non-permitted kwargs') 1829 with self.assertRaises(subprocess.CalledProcessError) as cm: 1830 self.init(tdir) 1831 self.assertIn('ERROR: compiler.has_header_symbol got unknown keyword arguments "prefixxx"', cm.exception.output) 1832 1833 def test_templates(self): 1834 ninja = mesonbuild.environment.detect_ninja() 1835 if ninja is None: 1836 raise SkipTest('This test currently requires ninja. Fix this once "meson build" works.') 1837 1838 langs = ['c'] 1839 env = get_fake_env() 1840 for l in ['cpp', 'cs', 'd', 'java', 'cuda', 'fortran', 'objc', 'objcpp', 'rust']: 1841 try: 1842 comp = detect_compiler_for(env, l, MachineChoice.HOST) 1843 with tempfile.TemporaryDirectory() as d: 1844 comp.sanity_check(d, env) 1845 langs.append(l) 1846 except EnvironmentException: 1847 pass 1848 1849 # The D template fails under mac CI and we don't know why. 1850 # Patches welcome 1851 if is_osx(): 1852 langs = [l for l in langs if l != 'd'] 1853 1854 for lang in langs: 1855 for target_type in ('executable', 'library'): 1856 # test empty directory 1857 with tempfile.TemporaryDirectory() as tmpdir: 1858 self._run(self.meson_command + ['init', '--language', lang, '--type', target_type], 1859 workdir=tmpdir) 1860 self._run(self.setup_command + ['--backend=ninja', 'builddir'], 1861 workdir=tmpdir) 1862 self._run(ninja, 1863 workdir=os.path.join(tmpdir, 'builddir')) 1864 # test directory with existing code file 1865 if lang in {'c', 'cpp', 'd'}: 1866 with tempfile.TemporaryDirectory() as tmpdir: 1867 with open(os.path.join(tmpdir, 'foo.' + lang), 'w', encoding='utf-8') as f: 1868 f.write('int main(void) {}') 1869 self._run(self.meson_command + ['init', '-b'], workdir=tmpdir) 1870 elif lang in {'java'}: 1871 with tempfile.TemporaryDirectory() as tmpdir: 1872 with open(os.path.join(tmpdir, 'Foo.' + lang), 'w', encoding='utf-8') as f: 1873 f.write('public class Foo { public static void main() {} }') 1874 self._run(self.meson_command + ['init', '-b'], workdir=tmpdir) 1875 1876 def test_compiler_run_command(self): 1877 ''' 1878 The test checks that the compiler object can be passed to 1879 run_command(). 1880 ''' 1881 testdir = os.path.join(self.unit_test_dir, '24 compiler run_command') 1882 self.init(testdir) 1883 1884 def test_identical_target_name_in_subproject_flat_layout(self): 1885 ''' 1886 Test that identical targets in different subprojects do not collide 1887 if layout is flat. 1888 ''' 1889 testdir = os.path.join(self.common_test_dir, '172 identical target name in subproject flat layout') 1890 self.init(testdir, extra_args=['--layout=flat']) 1891 self.build() 1892 1893 def test_identical_target_name_in_subdir_flat_layout(self): 1894 ''' 1895 Test that identical targets in different subdirs do not collide 1896 if layout is flat. 1897 ''' 1898 testdir = os.path.join(self.common_test_dir, '181 same target name flat layout') 1899 self.init(testdir, extra_args=['--layout=flat']) 1900 self.build() 1901 1902 def test_flock(self): 1903 exception_raised = False 1904 with tempfile.TemporaryDirectory() as tdir: 1905 os.mkdir(os.path.join(tdir, 'meson-private')) 1906 with BuildDirLock(tdir): 1907 try: 1908 with BuildDirLock(tdir): 1909 pass 1910 except MesonException: 1911 exception_raised = True 1912 self.assertTrue(exception_raised, 'Double locking did not raise exception.') 1913 1914 @skipIf(is_osx(), 'Test not applicable to OSX') 1915 def test_check_module_linking(self): 1916 """ 1917 Test that link_with: a shared module issues a warning 1918 https://github.com/mesonbuild/meson/issues/2865 1919 (That an error is raised on OSX is exercised by test failing/78) 1920 """ 1921 tdir = os.path.join(self.unit_test_dir, '30 shared_mod linking') 1922 out = self.init(tdir) 1923 msg = ('WARNING: target links against shared modules. This is not ' 1924 'recommended as it is not supported on some platforms') 1925 self.assertIn(msg, out) 1926 1927 def test_ndebug_if_release_disabled(self): 1928 testdir = os.path.join(self.unit_test_dir, '28 ndebug if-release') 1929 self.init(testdir, extra_args=['--buildtype=release', '-Db_ndebug=if-release']) 1930 self.build() 1931 exe = os.path.join(self.builddir, 'main') 1932 self.assertEqual(b'NDEBUG=1', subprocess.check_output(exe).strip()) 1933 1934 def test_ndebug_if_release_enabled(self): 1935 testdir = os.path.join(self.unit_test_dir, '28 ndebug if-release') 1936 self.init(testdir, extra_args=['--buildtype=debugoptimized', '-Db_ndebug=if-release']) 1937 self.build() 1938 exe = os.path.join(self.builddir, 'main') 1939 self.assertEqual(b'NDEBUG=0', subprocess.check_output(exe).strip()) 1940 1941 def test_guessed_linker_dependencies(self): 1942 ''' 1943 Test that meson adds dependencies for libraries based on the final 1944 linker command line. 1945 ''' 1946 testdirbase = os.path.join(self.unit_test_dir, '29 guessed linker dependencies') 1947 testdirlib = os.path.join(testdirbase, 'lib') 1948 1949 extra_args = None 1950 libdir_flags = ['-L'] 1951 env = get_fake_env(testdirlib, self.builddir, self.prefix) 1952 if detect_c_compiler(env, MachineChoice.HOST).get_id() in {'msvc', 'clang-cl', 'intel-cl'}: 1953 # msvc-like compiler, also test it with msvc-specific flags 1954 libdir_flags += ['/LIBPATH:', '-LIBPATH:'] 1955 else: 1956 # static libraries are not linkable with -l with msvc because meson installs them 1957 # as .a files which unix_args_to_native will not know as it expects libraries to use 1958 # .lib as extension. For a DLL the import library is installed as .lib. Thus for msvc 1959 # this tests needs to use shared libraries to test the path resolving logic in the 1960 # dependency generation code path. 1961 extra_args = ['--default-library', 'static'] 1962 1963 initial_builddir = self.builddir 1964 initial_installdir = self.installdir 1965 1966 for libdir_flag in libdir_flags: 1967 # build library 1968 self.new_builddir() 1969 self.init(testdirlib, extra_args=extra_args) 1970 self.build() 1971 self.install() 1972 libbuilddir = self.builddir 1973 installdir = self.installdir 1974 libdir = os.path.join(self.installdir, self.prefix.lstrip('/').lstrip('\\'), 'lib') 1975 1976 # build user of library 1977 self.new_builddir() 1978 # replace is needed because meson mangles platform paths passed via LDFLAGS 1979 self.init(os.path.join(testdirbase, 'exe'), 1980 override_envvars={"LDFLAGS": '{}{}'.format(libdir_flag, libdir.replace('\\', '/'))}) 1981 self.build() 1982 self.assertBuildIsNoop() 1983 1984 # rebuild library 1985 exebuilddir = self.builddir 1986 self.installdir = installdir 1987 self.builddir = libbuilddir 1988 # Microsoft's compiler is quite smart about touching import libs on changes, 1989 # so ensure that there is actually a change in symbols. 1990 self.setconf('-Dmore_exports=true') 1991 self.build() 1992 self.install() 1993 # no ensure_backend_detects_changes needed because self.setconf did that already 1994 1995 # assert user of library will be rebuild 1996 self.builddir = exebuilddir 1997 self.assertRebuiltTarget('app') 1998 1999 # restore dirs for the next test case 2000 self.installdir = initial_builddir 2001 self.builddir = initial_installdir 2002 2003 def test_conflicting_d_dash_option(self): 2004 testdir = os.path.join(self.unit_test_dir, '37 mixed command line args') 2005 with self.assertRaises((subprocess.CalledProcessError, RuntimeError)) as e: 2006 self.init(testdir, extra_args=['-Dbindir=foo', '--bindir=bar']) 2007 # Just to ensure that we caught the correct error 2008 self.assertIn('as both', e.stderr) 2009 2010 def _test_same_option_twice(self, arg, args): 2011 testdir = os.path.join(self.unit_test_dir, '37 mixed command line args') 2012 self.init(testdir, extra_args=args) 2013 opts = self.introspect('--buildoptions') 2014 for item in opts: 2015 if item['name'] == arg: 2016 self.assertEqual(item['value'], 'bar') 2017 return 2018 raise Exception(f'Missing {arg} value?') 2019 2020 def test_same_dash_option_twice(self): 2021 self._test_same_option_twice('bindir', ['--bindir=foo', '--bindir=bar']) 2022 2023 def test_same_d_option_twice(self): 2024 self._test_same_option_twice('bindir', ['-Dbindir=foo', '-Dbindir=bar']) 2025 2026 def test_same_project_d_option_twice(self): 2027 self._test_same_option_twice('one', ['-Done=foo', '-Done=bar']) 2028 2029 def _test_same_option_twice_configure(self, arg, args): 2030 testdir = os.path.join(self.unit_test_dir, '37 mixed command line args') 2031 self.init(testdir) 2032 self.setconf(args) 2033 opts = self.introspect('--buildoptions') 2034 for item in opts: 2035 if item['name'] == arg: 2036 self.assertEqual(item['value'], 'bar') 2037 return 2038 raise Exception(f'Missing {arg} value?') 2039 2040 def test_same_dash_option_twice_configure(self): 2041 self._test_same_option_twice_configure( 2042 'bindir', ['--bindir=foo', '--bindir=bar']) 2043 2044 def test_same_d_option_twice_configure(self): 2045 self._test_same_option_twice_configure( 2046 'bindir', ['-Dbindir=foo', '-Dbindir=bar']) 2047 2048 def test_same_project_d_option_twice_configure(self): 2049 self._test_same_option_twice_configure( 2050 'one', ['-Done=foo', '-Done=bar']) 2051 2052 def test_command_line(self): 2053 testdir = os.path.join(self.unit_test_dir, '34 command line') 2054 2055 # Verify default values when passing no args that affect the 2056 # configuration, and as a bonus, test that --profile-self works. 2057 out = self.init(testdir, extra_args=['--profile-self', '--fatal-meson-warnings']) 2058 self.assertNotIn('[default: true]', out) 2059 obj = mesonbuild.coredata.load(self.builddir) 2060 self.assertEqual(obj.options[OptionKey('default_library')].value, 'static') 2061 self.assertEqual(obj.options[OptionKey('warning_level')].value, '1') 2062 self.assertEqual(obj.options[OptionKey('set_sub_opt')].value, True) 2063 self.assertEqual(obj.options[OptionKey('subp_opt', 'subp')].value, 'default3') 2064 self.wipe() 2065 2066 # warning_level is special, it's --warnlevel instead of --warning-level 2067 # for historical reasons 2068 self.init(testdir, extra_args=['--warnlevel=2', '--fatal-meson-warnings']) 2069 obj = mesonbuild.coredata.load(self.builddir) 2070 self.assertEqual(obj.options[OptionKey('warning_level')].value, '2') 2071 self.setconf('--warnlevel=3') 2072 obj = mesonbuild.coredata.load(self.builddir) 2073 self.assertEqual(obj.options[OptionKey('warning_level')].value, '3') 2074 self.wipe() 2075 2076 # But when using -D syntax, it should be 'warning_level' 2077 self.init(testdir, extra_args=['-Dwarning_level=2', '--fatal-meson-warnings']) 2078 obj = mesonbuild.coredata.load(self.builddir) 2079 self.assertEqual(obj.options[OptionKey('warning_level')].value, '2') 2080 self.setconf('-Dwarning_level=3') 2081 obj = mesonbuild.coredata.load(self.builddir) 2082 self.assertEqual(obj.options[OptionKey('warning_level')].value, '3') 2083 self.wipe() 2084 2085 # Mixing --option and -Doption is forbidden 2086 with self.assertRaises((subprocess.CalledProcessError, RuntimeError)) as cm: 2087 self.init(testdir, extra_args=['--warnlevel=1', '-Dwarning_level=3']) 2088 if isinstance(cm.exception, subprocess.CalledProcessError): 2089 self.assertNotEqual(0, cm.exception.returncode) 2090 self.assertIn('as both', cm.exception.output) 2091 else: 2092 self.assertIn('as both', str(cm.exception)) 2093 self.init(testdir) 2094 with self.assertRaises((subprocess.CalledProcessError, RuntimeError)) as cm: 2095 self.setconf(['--warnlevel=1', '-Dwarning_level=3']) 2096 if isinstance(cm.exception, subprocess.CalledProcessError): 2097 self.assertNotEqual(0, cm.exception.returncode) 2098 self.assertIn('as both', cm.exception.output) 2099 else: 2100 self.assertIn('as both', str(cm.exception)) 2101 self.wipe() 2102 2103 # --default-library should override default value from project() 2104 self.init(testdir, extra_args=['--default-library=both', '--fatal-meson-warnings']) 2105 obj = mesonbuild.coredata.load(self.builddir) 2106 self.assertEqual(obj.options[OptionKey('default_library')].value, 'both') 2107 self.setconf('--default-library=shared') 2108 obj = mesonbuild.coredata.load(self.builddir) 2109 self.assertEqual(obj.options[OptionKey('default_library')].value, 'shared') 2110 if self.backend is Backend.ninja: 2111 # reconfigure target works only with ninja backend 2112 self.build('reconfigure') 2113 obj = mesonbuild.coredata.load(self.builddir) 2114 self.assertEqual(obj.options[OptionKey('default_library')].value, 'shared') 2115 self.wipe() 2116 2117 # Should fail on unknown options 2118 with self.assertRaises((subprocess.CalledProcessError, RuntimeError)) as cm: 2119 self.init(testdir, extra_args=['-Dbad=1', '-Dfoo=2', '-Dwrong_link_args=foo']) 2120 self.assertNotEqual(0, cm.exception.returncode) 2121 self.assertIn(msg, cm.exception.output) 2122 self.wipe() 2123 2124 # Should fail on malformed option 2125 msg = "Option 'foo' must have a value separated by equals sign." 2126 with self.assertRaises((subprocess.CalledProcessError, RuntimeError)) as cm: 2127 self.init(testdir, extra_args=['-Dfoo']) 2128 if isinstance(cm.exception, subprocess.CalledProcessError): 2129 self.assertNotEqual(0, cm.exception.returncode) 2130 self.assertIn(msg, cm.exception.output) 2131 else: 2132 self.assertIn(msg, str(cm.exception)) 2133 self.init(testdir) 2134 with self.assertRaises((subprocess.CalledProcessError, RuntimeError)) as cm: 2135 self.setconf('-Dfoo') 2136 if isinstance(cm.exception, subprocess.CalledProcessError): 2137 self.assertNotEqual(0, cm.exception.returncode) 2138 self.assertIn(msg, cm.exception.output) 2139 else: 2140 self.assertIn(msg, str(cm.exception)) 2141 self.wipe() 2142 2143 # It is not an error to set wrong option for unknown subprojects or 2144 # language because we don't have control on which one will be selected. 2145 self.init(testdir, extra_args=['-Dc_wrong=1', '-Dwrong:bad=1', '-Db_wrong=1']) 2146 self.wipe() 2147 2148 # Test we can set subproject option 2149 self.init(testdir, extra_args=['-Dsubp:subp_opt=foo', '--fatal-meson-warnings']) 2150 obj = mesonbuild.coredata.load(self.builddir) 2151 self.assertEqual(obj.options[OptionKey('subp_opt', 'subp')].value, 'foo') 2152 self.wipe() 2153 2154 # c_args value should be parsed with split_args 2155 self.init(testdir, extra_args=['-Dc_args=-Dfoo -Dbar "-Dthird=one two"', '--fatal-meson-warnings']) 2156 obj = mesonbuild.coredata.load(self.builddir) 2157 self.assertEqual(obj.options[OptionKey('args', lang='c')].value, ['-Dfoo', '-Dbar', '-Dthird=one two']) 2158 2159 self.setconf('-Dc_args="foo bar" one two') 2160 obj = mesonbuild.coredata.load(self.builddir) 2161 self.assertEqual(obj.options[OptionKey('args', lang='c')].value, ['foo bar', 'one', 'two']) 2162 self.wipe() 2163 2164 self.init(testdir, extra_args=['-Dset_percent_opt=myoption%', '--fatal-meson-warnings']) 2165 obj = mesonbuild.coredata.load(self.builddir) 2166 self.assertEqual(obj.options[OptionKey('set_percent_opt')].value, 'myoption%') 2167 self.wipe() 2168 2169 # Setting a 2nd time the same option should override the first value 2170 try: 2171 self.init(testdir, extra_args=['--bindir=foo', '--bindir=bar', 2172 '-Dbuildtype=plain', '-Dbuildtype=release', 2173 '-Db_sanitize=address', '-Db_sanitize=thread', 2174 '-Dc_args=-Dfoo', '-Dc_args=-Dbar', 2175 '-Db_lundef=false', '--fatal-meson-warnings']) 2176 obj = mesonbuild.coredata.load(self.builddir) 2177 self.assertEqual(obj.options[OptionKey('bindir')].value, 'bar') 2178 self.assertEqual(obj.options[OptionKey('buildtype')].value, 'release') 2179 self.assertEqual(obj.options[OptionKey('b_sanitize')].value, 'thread') 2180 self.assertEqual(obj.options[OptionKey('args', lang='c')].value, ['-Dbar']) 2181 self.setconf(['--bindir=bar', '--bindir=foo', 2182 '-Dbuildtype=release', '-Dbuildtype=plain', 2183 '-Db_sanitize=thread', '-Db_sanitize=address', 2184 '-Dc_args=-Dbar', '-Dc_args=-Dfoo']) 2185 obj = mesonbuild.coredata.load(self.builddir) 2186 self.assertEqual(obj.options[OptionKey('bindir')].value, 'foo') 2187 self.assertEqual(obj.options[OptionKey('buildtype')].value, 'plain') 2188 self.assertEqual(obj.options[OptionKey('b_sanitize')].value, 'address') 2189 self.assertEqual(obj.options[OptionKey('args', lang='c')].value, ['-Dfoo']) 2190 self.wipe() 2191 except KeyError: 2192 # Ignore KeyError, it happens on CI for compilers that does not 2193 # support b_sanitize. We have to test with a base option because 2194 # they used to fail this test with Meson 0.46 an earlier versions. 2195 pass 2196 2197 def test_warning_level_0(self): 2198 testdir = os.path.join(self.common_test_dir, '207 warning level 0') 2199 2200 # Verify default values when passing no args 2201 self.init(testdir) 2202 obj = mesonbuild.coredata.load(self.builddir) 2203 self.assertEqual(obj.options[OptionKey('warning_level')].value, '0') 2204 self.wipe() 2205 2206 # verify we can override w/ --warnlevel 2207 self.init(testdir, extra_args=['--warnlevel=1']) 2208 obj = mesonbuild.coredata.load(self.builddir) 2209 self.assertEqual(obj.options[OptionKey('warning_level')].value, '1') 2210 self.setconf('--warnlevel=0') 2211 obj = mesonbuild.coredata.load(self.builddir) 2212 self.assertEqual(obj.options[OptionKey('warning_level')].value, '0') 2213 self.wipe() 2214 2215 # verify we can override w/ -Dwarning_level 2216 self.init(testdir, extra_args=['-Dwarning_level=1']) 2217 obj = mesonbuild.coredata.load(self.builddir) 2218 self.assertEqual(obj.options[OptionKey('warning_level')].value, '1') 2219 self.setconf('-Dwarning_level=0') 2220 obj = mesonbuild.coredata.load(self.builddir) 2221 self.assertEqual(obj.options[OptionKey('warning_level')].value, '0') 2222 self.wipe() 2223 2224 def test_feature_check_usage_subprojects(self): 2225 testdir = os.path.join(self.unit_test_dir, '41 featurenew subprojects') 2226 out = self.init(testdir) 2227 # Parent project warns correctly 2228 self.assertRegex(out, "WARNING: Project targeting '>=0.45'.*'0.47.0': dict") 2229 # Subprojects warn correctly 2230 self.assertRegex(out, r"\| WARNING: Project targeting '>=0.40'.*'0.44.0': disabler") 2231 self.assertRegex(out, r"\| WARNING: Project targeting '!=0.40'.*'0.44.0': disabler") 2232 # Subproject has a new-enough meson_version, no warning 2233 self.assertNotRegex(out, "WARNING: Project targeting.*Python") 2234 # Ensure a summary is printed in the subproject and the outer project 2235 self.assertRegex(out, r"\| WARNING: Project specifies a minimum meson_version '>=0.40'") 2236 self.assertRegex(out, r"\| \* 0.44.0: {'disabler'}") 2237 self.assertRegex(out, "WARNING: Project specifies a minimum meson_version '>=0.45'") 2238 self.assertRegex(out, " * 0.47.0: {'dict'}") 2239 2240 def test_configure_file_warnings(self): 2241 testdir = os.path.join(self.common_test_dir, "14 configure file") 2242 out = self.init(testdir) 2243 self.assertRegex(out, "WARNING:.*'empty'.*config.h.in.*not present.*") 2244 self.assertRegex(out, "WARNING:.*'FOO_BAR'.*nosubst-nocopy2.txt.in.*not present.*") 2245 self.assertRegex(out, "WARNING:.*'empty'.*config.h.in.*not present.*") 2246 self.assertRegex(out, "WARNING:.*empty configuration_data.*test.py.in") 2247 # Warnings for configuration files that are overwritten. 2248 self.assertRegex(out, "WARNING:.*\"double_output.txt\".*overwrites") 2249 self.assertRegex(out, "WARNING:.*\"subdir.double_output2.txt\".*overwrites") 2250 self.assertNotRegex(out, "WARNING:.*no_write_conflict.txt.*overwrites") 2251 self.assertNotRegex(out, "WARNING:.*@BASENAME@.*overwrites") 2252 self.assertRegex(out, "WARNING:.*\"sameafterbasename\".*overwrites") 2253 # No warnings about empty configuration data objects passed to files with substitutions 2254 self.assertNotRegex(out, "WARNING:.*empty configuration_data.*nosubst-nocopy1.txt.in") 2255 self.assertNotRegex(out, "WARNING:.*empty configuration_data.*nosubst-nocopy2.txt.in") 2256 with open(os.path.join(self.builddir, 'nosubst-nocopy1.txt'), 'rb') as f: 2257 self.assertEqual(f.read().strip(), b'/* #undef FOO_BAR */') 2258 with open(os.path.join(self.builddir, 'nosubst-nocopy2.txt'), 'rb') as f: 2259 self.assertEqual(f.read().strip(), b'') 2260 self.assertRegex(out, r"DEPRECATION:.*\['array'\] is invalid.*dict") 2261 2262 def test_dirs(self): 2263 with tempfile.TemporaryDirectory() as containing: 2264 with tempfile.TemporaryDirectory(dir=containing) as srcdir: 2265 mfile = os.path.join(srcdir, 'meson.build') 2266 of = open(mfile, 'w', encoding='utf-8') 2267 of.write("project('foobar', 'c')\n") 2268 of.close() 2269 pc = subprocess.run(self.setup_command, 2270 cwd=srcdir, 2271 stdout=subprocess.PIPE, 2272 stderr=subprocess.DEVNULL) 2273 self.assertIn(b'Must specify at least one directory name', pc.stdout) 2274 with tempfile.TemporaryDirectory(dir=srcdir) as builddir: 2275 subprocess.run(self.setup_command, 2276 check=True, 2277 cwd=builddir, 2278 stdout=subprocess.DEVNULL, 2279 stderr=subprocess.DEVNULL) 2280 2281 def get_opts_as_dict(self): 2282 result = {} 2283 for i in self.introspect('--buildoptions'): 2284 result[i['name']] = i['value'] 2285 return result 2286 2287 def test_buildtype_setting(self): 2288 testdir = os.path.join(self.common_test_dir, '1 trivial') 2289 self.init(testdir) 2290 opts = self.get_opts_as_dict() 2291 self.assertEqual(opts['buildtype'], 'debug') 2292 self.assertEqual(opts['debug'], True) 2293 self.setconf('-Ddebug=false') 2294 opts = self.get_opts_as_dict() 2295 self.assertEqual(opts['debug'], False) 2296 self.assertEqual(opts['buildtype'], 'debug') 2297 self.assertEqual(opts['optimization'], '0') 2298 self.setconf('-Doptimization=g') 2299 opts = self.get_opts_as_dict() 2300 self.assertEqual(opts['debug'], False) 2301 self.assertEqual(opts['buildtype'], 'debug') 2302 self.assertEqual(opts['optimization'], 'g') 2303 2304 @skipIfNoPkgconfig 2305 @skipIf(is_windows(), 'Help needed with fixing this test on windows') 2306 def test_native_dep_pkgconfig(self): 2307 testdir = os.path.join(self.unit_test_dir, 2308 '46 native dep pkgconfig var') 2309 with tempfile.NamedTemporaryFile(mode='w', delete=False) as crossfile: 2310 crossfile.write(textwrap.dedent( 2311 '''[binaries] 2312 pkgconfig = '{}' 2313 2314 [properties] 2315 2316 [host_machine] 2317 system = 'linux' 2318 cpu_family = 'arm' 2319 cpu = 'armv7' 2320 endian = 'little' 2321 '''.format(os.path.join(testdir, 'cross_pkgconfig.py')))) 2322 crossfile.flush() 2323 self.meson_cross_file = crossfile.name 2324 2325 env = {'PKG_CONFIG_LIBDIR': os.path.join(testdir, 2326 'native_pkgconfig')} 2327 self.init(testdir, extra_args=['-Dstart_native=false'], override_envvars=env) 2328 self.wipe() 2329 self.init(testdir, extra_args=['-Dstart_native=true'], override_envvars=env) 2330 2331 @skipIfNoPkgconfig 2332 @skipIf(is_windows(), 'Help needed with fixing this test on windows') 2333 def test_pkg_config_libdir(self): 2334 testdir = os.path.join(self.unit_test_dir, 2335 '46 native dep pkgconfig var') 2336 with tempfile.NamedTemporaryFile(mode='w', delete=False) as crossfile: 2337 crossfile.write(textwrap.dedent( 2338 '''[binaries] 2339 pkgconfig = 'pkg-config' 2340 2341 [properties] 2342 pkg_config_libdir = ['{}'] 2343 2344 [host_machine] 2345 system = 'linux' 2346 cpu_family = 'arm' 2347 cpu = 'armv7' 2348 endian = 'little' 2349 '''.format(os.path.join(testdir, 'cross_pkgconfig')))) 2350 crossfile.flush() 2351 self.meson_cross_file = crossfile.name 2352 2353 env = {'PKG_CONFIG_LIBDIR': os.path.join(testdir, 2354 'native_pkgconfig')} 2355 self.init(testdir, extra_args=['-Dstart_native=false'], override_envvars=env) 2356 self.wipe() 2357 self.init(testdir, extra_args=['-Dstart_native=true'], override_envvars=env) 2358 2359 def __reconfigure(self): 2360 # Set an older version to force a reconfigure from scratch 2361 filename = os.path.join(self.privatedir, 'coredata.dat') 2362 with open(filename, 'rb') as f: 2363 obj = pickle.load(f) 2364 obj.version = '0.47.0' 2365 with open(filename, 'wb') as f: 2366 pickle.dump(obj, f) 2367 2368 def test_reconfigure(self): 2369 testdir = os.path.join(self.unit_test_dir, '48 reconfigure') 2370 self.init(testdir, extra_args=['-Dopt1=val1', '-Dsub1:werror=true']) 2371 self.setconf('-Dopt2=val2') 2372 2373 self.__reconfigure() 2374 2375 out = self.init(testdir, extra_args=['--reconfigure', '-Dopt3=val3']) 2376 self.assertRegex(out, 'Regenerating configuration from scratch') 2377 self.assertRegex(out, 'opt1 val1') 2378 self.assertRegex(out, 'opt2 val2') 2379 self.assertRegex(out, 'opt3 val3') 2380 self.assertRegex(out, 'opt4 default4') 2381 self.assertRegex(out, 'sub1:werror true') 2382 self.build() 2383 self.run_tests() 2384 2385 # Create a file in builddir and verify wipe command removes it 2386 filename = os.path.join(self.builddir, 'something') 2387 open(filename, 'w', encoding='utf-8').close() 2388 self.assertTrue(os.path.exists(filename)) 2389 out = self.init(testdir, extra_args=['--wipe', '-Dopt4=val4']) 2390 self.assertFalse(os.path.exists(filename)) 2391 self.assertRegex(out, 'opt1 val1') 2392 self.assertRegex(out, 'opt2 val2') 2393 self.assertRegex(out, 'opt3 val3') 2394 self.assertRegex(out, 'opt4 val4') 2395 self.assertRegex(out, 'sub1:werror true') 2396 self.assertTrue(Path(self.builddir, '.gitignore').exists()) 2397 self.build() 2398 self.run_tests() 2399 2400 def test_wipe_from_builddir(self): 2401 testdir = os.path.join(self.common_test_dir, '157 custom target subdir depend files') 2402 self.init(testdir) 2403 self.__reconfigure() 2404 self.init(testdir, extra_args=['--wipe'], workdir=self.builddir) 2405 2406 def test_target_construct_id_from_path(self): 2407 # This id is stable but not guessable. 2408 # The test is supposed to prevent unintentional 2409 # changes of target ID generation. 2410 target_id = Target.construct_id_from_path('some/obscure/subdir', 2411 'target-id', '@suffix') 2412 self.assertEqual('5e002d3@@target-id@suffix', target_id) 2413 target_id = Target.construct_id_from_path('subproject/foo/subdir/bar', 2414 'target2-id', '@other') 2415 self.assertEqual('81d46d1@@target2-id@other', target_id) 2416 2417 def test_introspect_projectinfo_without_configured_build(self): 2418 testfile = os.path.join(self.common_test_dir, '33 run program', 'meson.build') 2419 res = self.introspect_directory(testfile, '--projectinfo') 2420 self.assertEqual(set(res['buildsystem_files']), {'meson.build'}) 2421 self.assertEqual(res['version'], 'undefined') 2422 self.assertEqual(res['descriptive_name'], 'run command') 2423 self.assertEqual(res['subprojects'], []) 2424 2425 testfile = os.path.join(self.common_test_dir, '40 options', 'meson.build') 2426 res = self.introspect_directory(testfile, '--projectinfo') 2427 self.assertEqual(set(res['buildsystem_files']), {'meson_options.txt', 'meson.build'}) 2428 self.assertEqual(res['version'], 'undefined') 2429 self.assertEqual(res['descriptive_name'], 'options') 2430 self.assertEqual(res['subprojects'], []) 2431 2432 testfile = os.path.join(self.common_test_dir, '43 subproject options', 'meson.build') 2433 res = self.introspect_directory(testfile, '--projectinfo') 2434 self.assertEqual(set(res['buildsystem_files']), {'meson_options.txt', 'meson.build'}) 2435 self.assertEqual(res['version'], 'undefined') 2436 self.assertEqual(res['descriptive_name'], 'suboptions') 2437 self.assertEqual(len(res['subprojects']), 1) 2438 subproject_files = {f.replace('\\', '/') for f in res['subprojects'][0]['buildsystem_files']} 2439 self.assertEqual(subproject_files, {'subprojects/subproject/meson_options.txt', 'subprojects/subproject/meson.build'}) 2440 self.assertEqual(res['subprojects'][0]['name'], 'subproject') 2441 self.assertEqual(res['subprojects'][0]['version'], 'undefined') 2442 self.assertEqual(res['subprojects'][0]['descriptive_name'], 'subproject') 2443 2444 def test_introspect_projectinfo_subprojects(self): 2445 testdir = os.path.join(self.common_test_dir, '98 subproject subdir') 2446 self.init(testdir) 2447 res = self.introspect('--projectinfo') 2448 expected = { 2449 'descriptive_name': 'proj', 2450 'version': 'undefined', 2451 'subproject_dir': 'subprojects', 2452 'subprojects': [ 2453 { 2454 'descriptive_name': 'sub', 2455 'name': 'sub', 2456 'version': '1.0' 2457 }, 2458 { 2459 'descriptive_name': 'sub_implicit', 2460 'name': 'sub_implicit', 2461 'version': '1.0', 2462 }, 2463 { 2464 'descriptive_name': 'sub-novar', 2465 'name': 'sub_novar', 2466 'version': '1.0', 2467 }, 2468 { 2469 'descriptive_name': 'sub_static', 2470 'name': 'sub_static', 2471 'version': 'undefined' 2472 }, 2473 { 2474 'descriptive_name': 'subsub', 2475 'name': 'subsub', 2476 'version': 'undefined' 2477 }, 2478 { 2479 'descriptive_name': 'subsubsub', 2480 'name': 'subsubsub', 2481 'version': 'undefined' 2482 }, 2483 ] 2484 } 2485 res['subprojects'] = sorted(res['subprojects'], key=lambda i: i['name']) 2486 self.assertDictEqual(expected, res) 2487 2488 def test_introspection_target_subproject(self): 2489 testdir = os.path.join(self.common_test_dir, '42 subproject') 2490 self.init(testdir) 2491 res = self.introspect('--targets') 2492 2493 expected = { 2494 'sublib': 'sublib', 2495 'simpletest': 'sublib', 2496 'user': None 2497 } 2498 2499 for entry in res: 2500 name = entry['name'] 2501 self.assertEqual(entry['subproject'], expected[name]) 2502 2503 def test_introspect_projectinfo_subproject_dir(self): 2504 testdir = os.path.join(self.common_test_dir, '75 custom subproject dir') 2505 self.init(testdir) 2506 res = self.introspect('--projectinfo') 2507 2508 self.assertEqual(res['subproject_dir'], 'custom_subproject_dir') 2509 2510 def test_introspect_projectinfo_subproject_dir_from_source(self): 2511 testfile = os.path.join(self.common_test_dir, '75 custom subproject dir', 'meson.build') 2512 res = self.introspect_directory(testfile, '--projectinfo') 2513 2514 self.assertEqual(res['subproject_dir'], 'custom_subproject_dir') 2515 2516 @skipIfNoExecutable('clang-format') 2517 def test_clang_format(self): 2518 if self.backend is not Backend.ninja: 2519 raise SkipTest(f'Clang-format is for now only supported on Ninja, not {self.backend.name}') 2520 testdir = os.path.join(self.unit_test_dir, '54 clang-format') 2521 testfile = os.path.join(testdir, 'prog.c') 2522 badfile = os.path.join(testdir, 'prog_orig_c') 2523 goodfile = os.path.join(testdir, 'prog_expected_c') 2524 testheader = os.path.join(testdir, 'header.h') 2525 badheader = os.path.join(testdir, 'header_orig_h') 2526 goodheader = os.path.join(testdir, 'header_expected_h') 2527 try: 2528 shutil.copyfile(badfile, testfile) 2529 shutil.copyfile(badheader, testheader) 2530 self.init(testdir) 2531 self.assertNotEqual(Path(testfile).read_text(encoding='utf-8'), 2532 Path(goodfile).read_text(encoding='utf-8')) 2533 self.assertNotEqual(Path(testheader).read_text(encoding='utf-8'), 2534 Path(goodheader).read_text(encoding='utf-8')) 2535 self.run_target('clang-format') 2536 self.assertEqual(Path(testheader).read_text(encoding='utf-8'), 2537 Path(goodheader).read_text(encoding='utf-8')) 2538 finally: 2539 if os.path.exists(testfile): 2540 os.unlink(testfile) 2541 if os.path.exists(testheader): 2542 os.unlink(testheader) 2543 2544 @skipIfNoExecutable('clang-tidy') 2545 def test_clang_tidy(self): 2546 if self.backend is not Backend.ninja: 2547 raise SkipTest(f'Clang-tidy is for now only supported on Ninja, not {self.backend.name}') 2548 if shutil.which('c++') is None: 2549 raise SkipTest('Clang-tidy breaks when ccache is used and "c++" not in path.') 2550 if is_osx(): 2551 raise SkipTest('Apple ships a broken clang-tidy that chokes on -pipe.') 2552 testdir = os.path.join(self.unit_test_dir, '69 clang-tidy') 2553 dummydir = os.path.join(testdir, 'dummydir.h') 2554 self.init(testdir, override_envvars={'CXX': 'c++'}) 2555 out = self.run_target('clang-tidy') 2556 self.assertIn('cttest.cpp:4:20', out) 2557 self.assertNotIn(dummydir, out) 2558 2559 def test_identity_cross(self): 2560 testdir = os.path.join(self.unit_test_dir, '70 cross') 2561 # Do a build to generate a cross file where the host is this target 2562 self.init(testdir, extra_args=['-Dgenerate=true']) 2563 self.meson_cross_file = os.path.join(self.builddir, "crossfile") 2564 self.assertTrue(os.path.exists(self.meson_cross_file)) 2565 # Now verify that this is detected as cross 2566 self.new_builddir() 2567 self.init(testdir) 2568 2569 def test_introspect_buildoptions_without_configured_build(self): 2570 testdir = os.path.join(self.unit_test_dir, '59 introspect buildoptions') 2571 testfile = os.path.join(testdir, 'meson.build') 2572 res_nb = self.introspect_directory(testfile, ['--buildoptions'] + self.meson_args) 2573 self.init(testdir, default_args=False) 2574 res_wb = self.introspect('--buildoptions') 2575 self.maxDiff = None 2576 # XXX: These now generate in a different order, is that okay? 2577 self.assertListEqual(sorted(res_nb, key=lambda x: x['name']), sorted(res_wb, key=lambda x: x['name'])) 2578 2579 def test_meson_configure_from_source_does_not_crash(self): 2580 testdir = os.path.join(self.unit_test_dir, '59 introspect buildoptions') 2581 self._run(self.mconf_command + [testdir]) 2582 2583 def test_introspect_buildoptions_cross_only(self): 2584 testdir = os.path.join(self.unit_test_dir, '83 cross only introspect') 2585 testfile = os.path.join(testdir, 'meson.build') 2586 res = self.introspect_directory(testfile, ['--buildoptions'] + self.meson_args) 2587 optnames = [o['name'] for o in res] 2588 self.assertIn('c_args', optnames) 2589 self.assertNotIn('build.c_args', optnames) 2590 2591 def test_introspect_json_flat(self): 2592 testdir = os.path.join(self.unit_test_dir, '57 introspection') 2593 out = self.init(testdir, extra_args=['-Dlayout=flat']) 2594 infodir = os.path.join(self.builddir, 'meson-info') 2595 self.assertPathExists(infodir) 2596 2597 with open(os.path.join(infodir, 'intro-targets.json'), encoding='utf-8') as fp: 2598 targets = json.load(fp) 2599 2600 for i in targets: 2601 for out in i['filename']: 2602 assert os.path.relpath(out, self.builddir).startswith('meson-out') 2603 2604 def test_introspect_json_dump(self): 2605 testdir = os.path.join(self.unit_test_dir, '57 introspection') 2606 self.init(testdir) 2607 infodir = os.path.join(self.builddir, 'meson-info') 2608 self.assertPathExists(infodir) 2609 2610 def assertKeyTypes(key_type_list, obj, strict: bool = True): 2611 for i in key_type_list: 2612 if isinstance(i[1], (list, tuple)) and None in i[1]: 2613 i = (i[0], tuple([x for x in i[1] if x is not None])) 2614 if i[0] not in obj or obj[i[0]] is None: 2615 continue 2616 self.assertIn(i[0], obj) 2617 self.assertIsInstance(obj[i[0]], i[1]) 2618 if strict: 2619 for k in obj.keys(): 2620 found = False 2621 for i in key_type_list: 2622 if k == i[0]: 2623 found = True 2624 break 2625 self.assertTrue(found, f'Key "{k}" not in expected list') 2626 2627 root_keylist = [ 2628 ('benchmarks', list), 2629 ('buildoptions', list), 2630 ('buildsystem_files', list), 2631 ('dependencies', list), 2632 ('installed', dict), 2633 ('projectinfo', dict), 2634 ('targets', list), 2635 ('tests', list), 2636 ] 2637 2638 test_keylist = [ 2639 ('cmd', list), 2640 ('env', dict), 2641 ('name', str), 2642 ('timeout', int), 2643 ('suite', list), 2644 ('is_parallel', bool), 2645 ('protocol', str), 2646 ('depends', list), 2647 ('workdir', (str, None)), 2648 ('priority', int), 2649 ] 2650 2651 buildoptions_keylist = [ 2652 ('name', str), 2653 ('section', str), 2654 ('type', str), 2655 ('description', str), 2656 ('machine', str), 2657 ('choices', (list, None)), 2658 ('value', (str, int, bool, list)), 2659 ] 2660 2661 buildoptions_typelist = [ 2662 ('combo', str, [('choices', list)]), 2663 ('string', str, []), 2664 ('boolean', bool, []), 2665 ('integer', int, []), 2666 ('array', list, []), 2667 ] 2668 2669 buildoptions_sections = ['core', 'backend', 'base', 'compiler', 'directory', 'user', 'test'] 2670 buildoptions_machines = ['any', 'build', 'host'] 2671 2672 dependencies_typelist = [ 2673 ('name', str), 2674 ('version', str), 2675 ('compile_args', list), 2676 ('link_args', list), 2677 ] 2678 2679 targets_typelist = [ 2680 ('name', str), 2681 ('id', str), 2682 ('type', str), 2683 ('defined_in', str), 2684 ('filename', list), 2685 ('build_by_default', bool), 2686 ('target_sources', list), 2687 ('extra_files', list), 2688 ('subproject', (str, None)), 2689 ('install_filename', (list, None)), 2690 ('installed', bool), 2691 ] 2692 2693 targets_sources_typelist = [ 2694 ('language', str), 2695 ('compiler', list), 2696 ('parameters', list), 2697 ('sources', list), 2698 ('generated_sources', list), 2699 ] 2700 2701 # First load all files 2702 res = {} 2703 for i in root_keylist: 2704 curr = os.path.join(infodir, 'intro-{}.json'.format(i[0])) 2705 self.assertPathExists(curr) 2706 with open(curr, encoding='utf-8') as fp: 2707 res[i[0]] = json.load(fp) 2708 2709 assertKeyTypes(root_keylist, res) 2710 2711 # Match target ids to input and output files for ease of reference 2712 src_to_id = {} 2713 out_to_id = {} 2714 name_to_out = {} 2715 for i in res['targets']: 2716 print(json.dump(i, sys.stdout)) 2717 out_to_id.update({os.path.relpath(out, self.builddir): i['id'] 2718 for out in i['filename']}) 2719 name_to_out.update({i['name']: i['filename']}) 2720 for group in i['target_sources']: 2721 src_to_id.update({os.path.relpath(src, testdir): i['id'] 2722 for src in group['sources']}) 2723 2724 # Check Tests and benchmarks 2725 tests_to_find = ['test case 1', 'test case 2', 'benchmark 1'] 2726 deps_to_find = {'test case 1': [src_to_id['t1.cpp']], 2727 'test case 2': [src_to_id['t2.cpp'], src_to_id['t3.cpp']], 2728 'benchmark 1': [out_to_id['file2'], out_to_id['file3'], out_to_id['file4'], src_to_id['t3.cpp']]} 2729 for i in res['benchmarks'] + res['tests']: 2730 assertKeyTypes(test_keylist, i) 2731 if i['name'] in tests_to_find: 2732 tests_to_find.remove(i['name']) 2733 self.assertEqual(sorted(i['depends']), 2734 sorted(deps_to_find[i['name']])) 2735 self.assertListEqual(tests_to_find, []) 2736 2737 # Check buildoptions 2738 buildopts_to_find = {'cpp_std': 'c++11'} 2739 for i in res['buildoptions']: 2740 assertKeyTypes(buildoptions_keylist, i) 2741 valid_type = False 2742 for j in buildoptions_typelist: 2743 if i['type'] == j[0]: 2744 self.assertIsInstance(i['value'], j[1]) 2745 assertKeyTypes(j[2], i, strict=False) 2746 valid_type = True 2747 break 2748 2749 self.assertIn(i['section'], buildoptions_sections) 2750 self.assertIn(i['machine'], buildoptions_machines) 2751 self.assertTrue(valid_type) 2752 if i['name'] in buildopts_to_find: 2753 self.assertEqual(i['value'], buildopts_to_find[i['name']]) 2754 buildopts_to_find.pop(i['name'], None) 2755 self.assertDictEqual(buildopts_to_find, {}) 2756 2757 # Check buildsystem_files 2758 bs_files = ['meson.build', 'meson_options.txt', 'sharedlib/meson.build', 'staticlib/meson.build'] 2759 bs_files = [os.path.join(testdir, x) for x in bs_files] 2760 self.assertPathListEqual(list(sorted(res['buildsystem_files'])), list(sorted(bs_files))) 2761 2762 # Check dependencies 2763 dependencies_to_find = ['threads'] 2764 for i in res['dependencies']: 2765 assertKeyTypes(dependencies_typelist, i) 2766 if i['name'] in dependencies_to_find: 2767 dependencies_to_find.remove(i['name']) 2768 self.assertListEqual(dependencies_to_find, []) 2769 2770 # Check projectinfo 2771 self.assertDictEqual(res['projectinfo'], {'version': '1.2.3', 'descriptive_name': 'introspection', 'subproject_dir': 'subprojects', 'subprojects': []}) 2772 2773 # Check targets 2774 targets_to_find = { 2775 'sharedTestLib': ('shared library', True, False, 'sharedlib/meson.build', 2776 [os.path.join(testdir, 'sharedlib', 'shared.cpp')]), 2777 'staticTestLib': ('static library', True, False, 'staticlib/meson.build', 2778 [os.path.join(testdir, 'staticlib', 'static.c')]), 2779 'custom target test 1': ('custom', False, False, 'meson.build', 2780 [os.path.join(testdir, 'cp.py')]), 2781 'custom target test 2': ('custom', False, False, 'meson.build', 2782 name_to_out['custom target test 1']), 2783 'test1': ('executable', True, True, 'meson.build', 2784 [os.path.join(testdir, 't1.cpp')]), 2785 'test2': ('executable', True, False, 'meson.build', 2786 [os.path.join(testdir, 't2.cpp')]), 2787 'test3': ('executable', True, False, 'meson.build', 2788 [os.path.join(testdir, 't3.cpp')]), 2789 'custom target test 3': ('custom', False, False, 'meson.build', 2790 name_to_out['test3']), 2791 } 2792 for i in res['targets']: 2793 assertKeyTypes(targets_typelist, i) 2794 if i['name'] in targets_to_find: 2795 tgt = targets_to_find[i['name']] 2796 self.assertEqual(i['type'], tgt[0]) 2797 self.assertEqual(i['build_by_default'], tgt[1]) 2798 self.assertEqual(i['installed'], tgt[2]) 2799 self.assertPathEqual(i['defined_in'], os.path.join(testdir, tgt[3])) 2800 targets_to_find.pop(i['name'], None) 2801 for j in i['target_sources']: 2802 assertKeyTypes(targets_sources_typelist, j) 2803 self.assertEqual(j['sources'], [os.path.normpath(f) for f in tgt[4]]) 2804 self.assertDictEqual(targets_to_find, {}) 2805 2806 def test_introspect_file_dump_equals_all(self): 2807 testdir = os.path.join(self.unit_test_dir, '57 introspection') 2808 self.init(testdir) 2809 res_all = self.introspect('--all') 2810 res_file = {} 2811 2812 root_keylist = [ 2813 'benchmarks', 2814 'buildoptions', 2815 'buildsystem_files', 2816 'dependencies', 2817 'installed', 2818 'install_plan', 2819 'projectinfo', 2820 'targets', 2821 'tests', 2822 ] 2823 2824 infodir = os.path.join(self.builddir, 'meson-info') 2825 self.assertPathExists(infodir) 2826 for i in root_keylist: 2827 curr = os.path.join(infodir, f'intro-{i}.json') 2828 self.assertPathExists(curr) 2829 with open(curr, encoding='utf-8') as fp: 2830 res_file[i] = json.load(fp) 2831 2832 self.assertEqual(res_all, res_file) 2833 2834 def test_introspect_meson_info(self): 2835 testdir = os.path.join(self.unit_test_dir, '57 introspection') 2836 introfile = os.path.join(self.builddir, 'meson-info', 'meson-info.json') 2837 self.init(testdir) 2838 self.assertPathExists(introfile) 2839 with open(introfile, encoding='utf-8') as fp: 2840 res1 = json.load(fp) 2841 2842 for i in ['meson_version', 'directories', 'introspection', 'build_files_updated', 'error']: 2843 self.assertIn(i, res1) 2844 2845 self.assertEqual(res1['error'], False) 2846 self.assertEqual(res1['build_files_updated'], True) 2847 2848 def test_introspect_config_update(self): 2849 testdir = os.path.join(self.unit_test_dir, '57 introspection') 2850 introfile = os.path.join(self.builddir, 'meson-info', 'intro-buildoptions.json') 2851 self.init(testdir) 2852 self.assertPathExists(introfile) 2853 with open(introfile, encoding='utf-8') as fp: 2854 res1 = json.load(fp) 2855 2856 for i in res1: 2857 if i['name'] == 'cpp_std': 2858 i['value'] = 'c++14' 2859 if i['name'] == 'build.cpp_std': 2860 i['value'] = 'c++14' 2861 if i['name'] == 'buildtype': 2862 i['value'] = 'release' 2863 if i['name'] == 'optimization': 2864 i['value'] = '3' 2865 if i['name'] == 'debug': 2866 i['value'] = False 2867 2868 self.setconf('-Dcpp_std=c++14') 2869 self.setconf('-Dbuildtype=release') 2870 2871 with open(introfile, encoding='utf-8') as fp: 2872 res2 = json.load(fp) 2873 2874 self.assertListEqual(res1, res2) 2875 2876 def test_introspect_targets_from_source(self): 2877 testdir = os.path.join(self.unit_test_dir, '57 introspection') 2878 testfile = os.path.join(testdir, 'meson.build') 2879 introfile = os.path.join(self.builddir, 'meson-info', 'intro-targets.json') 2880 self.init(testdir) 2881 self.assertPathExists(introfile) 2882 with open(introfile, encoding='utf-8') as fp: 2883 res_wb = json.load(fp) 2884 2885 res_nb = self.introspect_directory(testfile, ['--targets'] + self.meson_args) 2886 2887 # Account for differences in output 2888 res_wb = [i for i in res_wb if i['type'] != 'custom'] 2889 for i in res_wb: 2890 i['filename'] = [os.path.relpath(x, self.builddir) for x in i['filename']] 2891 if 'install_filename' in i: 2892 del i['install_filename'] 2893 2894 sources = [] 2895 for j in i['target_sources']: 2896 sources += j['sources'] 2897 i['target_sources'] = [{ 2898 'language': 'unknown', 2899 'compiler': [], 2900 'parameters': [], 2901 'sources': sources, 2902 'generated_sources': [] 2903 }] 2904 2905 self.maxDiff = None 2906 self.assertListEqual(res_nb, res_wb) 2907 2908 def test_introspect_ast_source(self): 2909 testdir = os.path.join(self.unit_test_dir, '57 introspection') 2910 testfile = os.path.join(testdir, 'meson.build') 2911 res_nb = self.introspect_directory(testfile, ['--ast'] + self.meson_args) 2912 2913 node_counter = {} 2914 2915 def accept_node(json_node): 2916 self.assertIsInstance(json_node, dict) 2917 for i in ['lineno', 'colno', 'end_lineno', 'end_colno']: 2918 self.assertIn(i, json_node) 2919 self.assertIsInstance(json_node[i], int) 2920 self.assertIn('node', json_node) 2921 n = json_node['node'] 2922 self.assertIsInstance(n, str) 2923 self.assertIn(n, nodes) 2924 if n not in node_counter: 2925 node_counter[n] = 0 2926 node_counter[n] = node_counter[n] + 1 2927 for nodeDesc in nodes[n]: 2928 key = nodeDesc[0] 2929 func = nodeDesc[1] 2930 self.assertIn(key, json_node) 2931 if func is None: 2932 tp = nodeDesc[2] 2933 self.assertIsInstance(json_node[key], tp) 2934 continue 2935 func(json_node[key]) 2936 2937 def accept_node_list(node_list): 2938 self.assertIsInstance(node_list, list) 2939 for i in node_list: 2940 accept_node(i) 2941 2942 def accept_kwargs(kwargs): 2943 self.assertIsInstance(kwargs, list) 2944 for i in kwargs: 2945 self.assertIn('key', i) 2946 self.assertIn('val', i) 2947 accept_node(i['key']) 2948 accept_node(i['val']) 2949 2950 nodes = { 2951 'BooleanNode': [('value', None, bool)], 2952 'IdNode': [('value', None, str)], 2953 'NumberNode': [('value', None, int)], 2954 'StringNode': [('value', None, str)], 2955 'FormatStringNode': [('value', None, str)], 2956 'ContinueNode': [], 2957 'BreakNode': [], 2958 'ArgumentNode': [('positional', accept_node_list), ('kwargs', accept_kwargs)], 2959 'ArrayNode': [('args', accept_node)], 2960 'DictNode': [('args', accept_node)], 2961 'EmptyNode': [], 2962 'OrNode': [('left', accept_node), ('right', accept_node)], 2963 'AndNode': [('left', accept_node), ('right', accept_node)], 2964 'ComparisonNode': [('left', accept_node), ('right', accept_node), ('ctype', None, str)], 2965 'ArithmeticNode': [('left', accept_node), ('right', accept_node), ('op', None, str)], 2966 'NotNode': [('right', accept_node)], 2967 'CodeBlockNode': [('lines', accept_node_list)], 2968 'IndexNode': [('object', accept_node), ('index', accept_node)], 2969 'MethodNode': [('object', accept_node), ('args', accept_node), ('name', None, str)], 2970 'FunctionNode': [('args', accept_node), ('name', None, str)], 2971 'AssignmentNode': [('value', accept_node), ('var_name', None, str)], 2972 'PlusAssignmentNode': [('value', accept_node), ('var_name', None, str)], 2973 'ForeachClauseNode': [('items', accept_node), ('block', accept_node), ('varnames', None, list)], 2974 'IfClauseNode': [('ifs', accept_node_list), ('else', accept_node)], 2975 'IfNode': [('condition', accept_node), ('block', accept_node)], 2976 'UMinusNode': [('right', accept_node)], 2977 'TernaryNode': [('condition', accept_node), ('true', accept_node), ('false', accept_node)], 2978 } 2979 2980 accept_node(res_nb) 2981 2982 for n, c in [('ContinueNode', 2), ('BreakNode', 1), ('NotNode', 3)]: 2983 self.assertIn(n, node_counter) 2984 self.assertEqual(node_counter[n], c) 2985 2986 def test_introspect_dependencies_from_source(self): 2987 testdir = os.path.join(self.unit_test_dir, '57 introspection') 2988 testfile = os.path.join(testdir, 'meson.build') 2989 res_nb = self.introspect_directory(testfile, ['--scan-dependencies'] + self.meson_args) 2990 expected = [ 2991 { 2992 'name': 'threads', 2993 'required': True, 2994 'version': [], 2995 'has_fallback': False, 2996 'conditional': False 2997 }, 2998 { 2999 'name': 'zlib', 3000 'required': False, 3001 'version': [], 3002 'has_fallback': False, 3003 'conditional': False 3004 }, 3005 { 3006 'name': 'bugDep1', 3007 'required': True, 3008 'version': [], 3009 'has_fallback': False, 3010 'conditional': False 3011 }, 3012 { 3013 'name': 'somethingthatdoesnotexist', 3014 'required': True, 3015 'version': ['>=1.2.3'], 3016 'has_fallback': False, 3017 'conditional': True 3018 }, 3019 { 3020 'name': 'look_i_have_a_fallback', 3021 'required': True, 3022 'version': ['>=1.0.0', '<=99.9.9'], 3023 'has_fallback': True, 3024 'conditional': True 3025 } 3026 ] 3027 self.maxDiff = None 3028 self.assertListEqual(res_nb, expected) 3029 3030 def test_unstable_coredata(self): 3031 testdir = os.path.join(self.common_test_dir, '1 trivial') 3032 self.init(testdir) 3033 # just test that the command does not fail (e.g. because it throws an exception) 3034 self._run([*self.meson_command, 'unstable-coredata', self.builddir]) 3035 3036 @skip_if_no_cmake 3037 def test_cmake_prefix_path(self): 3038 testdir = os.path.join(self.unit_test_dir, '63 cmake_prefix_path') 3039 self.init(testdir, extra_args=['-Dcmake_prefix_path=' + os.path.join(testdir, 'prefix')]) 3040 3041 @skip_if_no_cmake 3042 def test_cmake_parser(self): 3043 testdir = os.path.join(self.unit_test_dir, '64 cmake parser') 3044 self.init(testdir, extra_args=['-Dcmake_prefix_path=' + os.path.join(testdir, 'prefix')]) 3045 3046 def test_alias_target(self): 3047 testdir = os.path.join(self.unit_test_dir, '65 alias target') 3048 self.init(testdir) 3049 self.build() 3050 self.assertPathDoesNotExist(os.path.join(self.builddir, 'prog' + exe_suffix)) 3051 self.assertPathDoesNotExist(os.path.join(self.builddir, 'hello.txt')) 3052 self.run_target('build-all') 3053 self.assertPathExists(os.path.join(self.builddir, 'prog' + exe_suffix)) 3054 self.assertPathExists(os.path.join(self.builddir, 'hello.txt')) 3055 out = self.run_target('aliased-run') 3056 self.assertIn('a run target was here', out) 3057 3058 def test_configure(self): 3059 testdir = os.path.join(self.common_test_dir, '2 cpp') 3060 self.init(testdir) 3061 self._run(self.mconf_command + [self.builddir]) 3062 3063 def test_summary(self): 3064 testdir = os.path.join(self.unit_test_dir, '72 summary') 3065 out = self.init(testdir, extra_args=['-Denabled_opt=enabled']) 3066 expected = textwrap.dedent(r''' 3067 Some Subproject 2.0 3068 3069 string : bar 3070 integer: 1 3071 boolean: True 3072 3073 subsub undefined 3074 3075 Something: Some value 3076 3077 My Project 1.0 3078 3079 Configuration 3080 Some boolean : False 3081 Another boolean: True 3082 Some string : Hello World 3083 A list : string 3084 1 3085 True 3086 empty list : 3087 enabled_opt : enabled 3088 A number : 1 3089 yes : YES 3090 no : NO 3091 coma list : a, b, c 3092 3093 Stuff 3094 missing prog : NO 3095 existing prog : ''' + sys.executable + ''' 3096 missing dep : NO 3097 external dep : YES 1.2.3 3098 internal dep : YES 3099 3100 Plugins 3101 long coma list : alpha, alphacolor, apetag, audiofx, audioparsers, auparse, 3102 autodetect, avi 3103 3104 Subprojects 3105 sub : YES 3106 sub2 : NO Problem encountered: This subproject failed 3107 subsub : YES 3108 3109 User defined options 3110 backend : ''' + self.backend.name + ''' 3111 libdir : lib 3112 prefix : /usr 3113 enabled_opt : enabled 3114 ''') 3115 expected_lines = expected.split('\n')[1:] 3116 out_start = out.find(expected_lines[0]) 3117 out_lines = out[out_start:].split('\n')[:len(expected_lines)] 3118 if sys.version_info < (3, 7, 0): 3119 # Dictionary order is not stable in Python <3.7, so sort the lines 3120 # while comparing 3121 expected_lines = sorted(expected_lines) 3122 out_lines = sorted(out_lines) 3123 for e, o in zip(expected_lines, out_lines): 3124 if e.startswith(' external dep'): 3125 self.assertRegex(o, r'^ external dep : (YES [0-9.]*|NO)$') 3126 else: 3127 self.assertEqual(o, e) 3128 3129 def test_meson_compile(self): 3130 """Test the meson compile command.""" 3131 3132 def get_exe_name(basename: str) -> str: 3133 if is_windows(): 3134 return f'{basename}.exe' 3135 else: 3136 return basename 3137 3138 def get_shared_lib_name(basename: str) -> str: 3139 if mesonbuild.environment.detect_msys2_arch(): 3140 return f'lib{basename}.dll' 3141 elif is_windows(): 3142 return f'{basename}.dll' 3143 elif is_cygwin(): 3144 return f'cyg{basename}.dll' 3145 elif is_osx(): 3146 return f'lib{basename}.dylib' 3147 else: 3148 return f'lib{basename}.so' 3149 3150 def get_static_lib_name(basename: str) -> str: 3151 return f'lib{basename}.a' 3152 3153 # Base case (no targets or additional arguments) 3154 3155 testdir = os.path.join(self.common_test_dir, '1 trivial') 3156 self.init(testdir) 3157 3158 self._run([*self.meson_command, 'compile', '-C', self.builddir]) 3159 self.assertPathExists(os.path.join(self.builddir, get_exe_name('trivialprog'))) 3160 3161 # `--clean` 3162 3163 self._run([*self.meson_command, 'compile', '-C', self.builddir, '--clean']) 3164 self.assertPathDoesNotExist(os.path.join(self.builddir, get_exe_name('trivialprog'))) 3165 3166 # Target specified in a project with unique names 3167 3168 testdir = os.path.join(self.common_test_dir, '6 linkshared') 3169 self.init(testdir, extra_args=['--wipe']) 3170 # Multiple targets and target type specified 3171 self._run([*self.meson_command, 'compile', '-C', self.builddir, 'mylib', 'mycpplib:shared_library']) 3172 # Check that we have a shared lib, but not an executable, i.e. check that target actually worked 3173 self.assertPathExists(os.path.join(self.builddir, get_shared_lib_name('mylib'))) 3174 self.assertPathDoesNotExist(os.path.join(self.builddir, get_exe_name('prog'))) 3175 self.assertPathExists(os.path.join(self.builddir, get_shared_lib_name('mycpplib'))) 3176 self.assertPathDoesNotExist(os.path.join(self.builddir, get_exe_name('cppprog'))) 3177 3178 # Target specified in a project with non unique names 3179 3180 testdir = os.path.join(self.common_test_dir, '185 same target name') 3181 self.init(testdir, extra_args=['--wipe']) 3182 self._run([*self.meson_command, 'compile', '-C', self.builddir, './foo']) 3183 self.assertPathExists(os.path.join(self.builddir, get_static_lib_name('foo'))) 3184 self._run([*self.meson_command, 'compile', '-C', self.builddir, 'sub/foo']) 3185 self.assertPathExists(os.path.join(self.builddir, 'sub', get_static_lib_name('foo'))) 3186 3187 # run_target 3188 3189 testdir = os.path.join(self.common_test_dir, '51 run target') 3190 self.init(testdir, extra_args=['--wipe']) 3191 out = self._run([*self.meson_command, 'compile', '-C', self.builddir, 'py3hi']) 3192 self.assertIn('I am Python3.', out) 3193 3194 # `--$BACKEND-args` 3195 3196 testdir = os.path.join(self.common_test_dir, '1 trivial') 3197 if self.backend is Backend.ninja: 3198 self.init(testdir, extra_args=['--wipe']) 3199 # Dry run - should not create a program 3200 self._run([*self.meson_command, 'compile', '-C', self.builddir, '--ninja-args=-n']) 3201 self.assertPathDoesNotExist(os.path.join(self.builddir, get_exe_name('trivialprog'))) 3202 elif self.backend is Backend.vs: 3203 self.init(testdir, extra_args=['--wipe']) 3204 self._run([*self.meson_command, 'compile', '-C', self.builddir]) 3205 # Explicitly clean the target through msbuild interface 3206 self._run([*self.meson_command, 'compile', '-C', self.builddir, '--vs-args=-t:{}:Clean'.format(re.sub(r'[\%\$\@\;\.\(\)\']', '_', get_exe_name('trivialprog')))]) 3207 self.assertPathDoesNotExist(os.path.join(self.builddir, get_exe_name('trivialprog'))) 3208 3209 def test_spurious_reconfigure_built_dep_file(self): 3210 testdir = os.path.join(self.unit_test_dir, '74 dep files') 3211 3212 # Regression test: Spurious reconfigure was happening when build 3213 # directory is inside source directory. 3214 # See https://gitlab.freedesktop.org/gstreamer/gst-build/-/issues/85. 3215 srcdir = os.path.join(self.builddir, 'srctree') 3216 shutil.copytree(testdir, srcdir) 3217 builddir = os.path.join(srcdir, '_build') 3218 self.change_builddir(builddir) 3219 3220 self.init(srcdir) 3221 self.build() 3222 3223 # During first configure the file did not exist so no dependency should 3224 # have been set. A rebuild should not trigger a reconfigure. 3225 self.clean() 3226 out = self.build() 3227 self.assertNotIn('Project configured', out) 3228 3229 self.init(srcdir, extra_args=['--reconfigure']) 3230 3231 # During the reconfigure the file did exist, but is inside build 3232 # directory, so no dependency should have been set. A rebuild should not 3233 # trigger a reconfigure. 3234 self.clean() 3235 out = self.build() 3236 self.assertNotIn('Project configured', out) 3237 3238 def _test_junit(self, case: str) -> None: 3239 try: 3240 import lxml.etree as et 3241 except ImportError: 3242 raise SkipTest('lxml required, but not found.') 3243 3244 schema = et.XMLSchema(et.parse(str(Path(self.src_root) / 'data' / 'schema.xsd'))) 3245 3246 self.init(case) 3247 self.run_tests() 3248 3249 junit = et.parse(str(Path(self.builddir) / 'meson-logs' / 'testlog.junit.xml')) 3250 try: 3251 schema.assertValid(junit) 3252 except et.DocumentInvalid as e: 3253 self.fail(e.error_log) 3254 3255 def test_junit_valid_tap(self): 3256 self._test_junit(os.path.join(self.common_test_dir, '206 tap tests')) 3257 3258 def test_junit_valid_exitcode(self): 3259 self._test_junit(os.path.join(self.common_test_dir, '41 test args')) 3260 3261 def test_junit_valid_gtest(self): 3262 self._test_junit(os.path.join(self.framework_test_dir, '2 gtest')) 3263 3264 def test_link_language_linker(self): 3265 # TODO: there should be some way to query how we're linking things 3266 # without resorting to reading the ninja.build file 3267 if self.backend is not Backend.ninja: 3268 raise SkipTest('This test reads the ninja file') 3269 3270 testdir = os.path.join(self.common_test_dir, '225 link language') 3271 self.init(testdir) 3272 3273 build_ninja = os.path.join(self.builddir, 'build.ninja') 3274 with open(build_ninja, encoding='utf-8') as f: 3275 contents = f.read() 3276 3277 self.assertRegex(contents, r'build main(\.exe)?.*: c_LINKER') 3278 self.assertRegex(contents, r'build (lib|cyg)?mylib.*: c_LINKER') 3279 3280 def test_commands_documented(self): 3281 ''' 3282 Test that all listed meson commands are documented in Commands.md. 3283 ''' 3284 3285 # The docs directory is not in release tarballs. 3286 if not os.path.isdir('docs'): 3287 raise SkipTest('Doc directory does not exist.') 3288 doc_path = 'docs/markdown/Commands.md' 3289 3290 md = None 3291 with open(doc_path, encoding='utf-8') as f: 3292 md = f.read() 3293 self.assertIsNotNone(md) 3294 3295 ## Get command sections 3296 3297 section_pattern = re.compile(r'^### (.+)$', re.MULTILINE) 3298 md_command_section_matches = [i for i in section_pattern.finditer(md)] 3299 md_command_sections = dict() 3300 for i, s in enumerate(md_command_section_matches): 3301 section_end = len(md) if i == len(md_command_section_matches) - 1 else md_command_section_matches[i + 1].start() 3302 md_command_sections[s.group(1)] = (s.start(), section_end) 3303 3304 ## Validate commands 3305 3306 md_commands = {k for k,v in md_command_sections.items()} 3307 3308 help_output = self._run(self.meson_command + ['--help']) 3309 help_commands = {c.strip() for c in re.findall(r'usage:(?:.+)?{((?:[a-z]+,*)+?)}', help_output, re.MULTILINE|re.DOTALL)[0].split(',')} 3310 3311 self.assertEqual(md_commands | {'help'}, help_commands, f'Doc file: `{doc_path}`') 3312 3313 ## Validate that each section has proper placeholders 3314 3315 def get_data_pattern(command): 3316 return re.compile( 3317 r'{{ ' + command + r'_usage.inc }}[\r\n]' 3318 r'.*?' 3319 r'{{ ' + command + r'_arguments.inc }}[\r\n]', 3320 flags = re.MULTILINE|re.DOTALL) 3321 3322 for command in md_commands: 3323 m = get_data_pattern(command).search(md, pos=md_command_sections[command][0], endpos=md_command_sections[command][1]) 3324 self.assertIsNotNone(m, f'Command `{command}` is missing placeholders for dynamic data. Doc file: `{doc_path}`') 3325 3326 def _check_coverage_files(self, types=('text', 'xml', 'html')): 3327 covdir = Path(self.builddir) / 'meson-logs' 3328 files = [] 3329 if 'text' in types: 3330 files.append('coverage.txt') 3331 if 'xml' in types: 3332 files.append('coverage.xml') 3333 if 'html' in types: 3334 files.append('coveragereport/index.html') 3335 for f in files: 3336 self.assertTrue((covdir / f).is_file(), msg=f'{f} is not a file') 3337 3338 def test_coverage(self): 3339 if mesonbuild.environment.detect_msys2_arch(): 3340 raise SkipTest('Skipped due to problems with coverage on MSYS2') 3341 gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr() 3342 if not gcovr_exe: 3343 raise SkipTest('gcovr not found, or too old') 3344 testdir = os.path.join(self.common_test_dir, '1 trivial') 3345 env = get_fake_env(testdir, self.builddir, self.prefix) 3346 cc = detect_c_compiler(env, MachineChoice.HOST) 3347 if cc.get_id() == 'clang': 3348 if not mesonbuild.environment.detect_llvm_cov(): 3349 raise SkipTest('llvm-cov not found') 3350 if cc.get_id() == 'msvc': 3351 raise SkipTest('Test only applies to non-MSVC compilers') 3352 self.init(testdir, extra_args=['-Db_coverage=true']) 3353 self.build() 3354 self.run_tests() 3355 self.run_target('coverage') 3356 self._check_coverage_files() 3357 3358 def test_coverage_complex(self): 3359 if mesonbuild.environment.detect_msys2_arch(): 3360 raise SkipTest('Skipped due to problems with coverage on MSYS2') 3361 gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr() 3362 if not gcovr_exe: 3363 raise SkipTest('gcovr not found, or too old') 3364 testdir = os.path.join(self.common_test_dir, '105 generatorcustom') 3365 env = get_fake_env(testdir, self.builddir, self.prefix) 3366 cc = detect_c_compiler(env, MachineChoice.HOST) 3367 if cc.get_id() == 'clang': 3368 if not mesonbuild.environment.detect_llvm_cov(): 3369 raise SkipTest('llvm-cov not found') 3370 if cc.get_id() == 'msvc': 3371 raise SkipTest('Test only applies to non-MSVC compilers') 3372 self.init(testdir, extra_args=['-Db_coverage=true']) 3373 self.build() 3374 self.run_tests() 3375 self.run_target('coverage') 3376 self._check_coverage_files() 3377 3378 def test_coverage_html(self): 3379 if mesonbuild.environment.detect_msys2_arch(): 3380 raise SkipTest('Skipped due to problems with coverage on MSYS2') 3381 gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr() 3382 if not gcovr_exe: 3383 raise SkipTest('gcovr not found, or too old') 3384 testdir = os.path.join(self.common_test_dir, '1 trivial') 3385 env = get_fake_env(testdir, self.builddir, self.prefix) 3386 cc = detect_c_compiler(env, MachineChoice.HOST) 3387 if cc.get_id() == 'clang': 3388 if not mesonbuild.environment.detect_llvm_cov(): 3389 raise SkipTest('llvm-cov not found') 3390 if cc.get_id() == 'msvc': 3391 raise SkipTest('Test only applies to non-MSVC compilers') 3392 self.init(testdir, extra_args=['-Db_coverage=true']) 3393 self.build() 3394 self.run_tests() 3395 self.run_target('coverage-html') 3396 self._check_coverage_files(['html']) 3397 3398 def test_coverage_text(self): 3399 if mesonbuild.environment.detect_msys2_arch(): 3400 raise SkipTest('Skipped due to problems with coverage on MSYS2') 3401 gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr() 3402 if not gcovr_exe: 3403 raise SkipTest('gcovr not found, or too old') 3404 testdir = os.path.join(self.common_test_dir, '1 trivial') 3405 env = get_fake_env(testdir, self.builddir, self.prefix) 3406 cc = detect_c_compiler(env, MachineChoice.HOST) 3407 if cc.get_id() == 'clang': 3408 if not mesonbuild.environment.detect_llvm_cov(): 3409 raise SkipTest('llvm-cov not found') 3410 if cc.get_id() == 'msvc': 3411 raise SkipTest('Test only applies to non-MSVC compilers') 3412 self.init(testdir, extra_args=['-Db_coverage=true']) 3413 self.build() 3414 self.run_tests() 3415 self.run_target('coverage-text') 3416 self._check_coverage_files(['text']) 3417 3418 def test_coverage_xml(self): 3419 if mesonbuild.environment.detect_msys2_arch(): 3420 raise SkipTest('Skipped due to problems with coverage on MSYS2') 3421 gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr() 3422 if not gcovr_exe: 3423 raise SkipTest('gcovr not found, or too old') 3424 testdir = os.path.join(self.common_test_dir, '1 trivial') 3425 env = get_fake_env(testdir, self.builddir, self.prefix) 3426 cc = detect_c_compiler(env, MachineChoice.HOST) 3427 if cc.get_id() == 'clang': 3428 if not mesonbuild.environment.detect_llvm_cov(): 3429 raise SkipTest('llvm-cov not found') 3430 if cc.get_id() == 'msvc': 3431 raise SkipTest('Test only applies to non-MSVC compilers') 3432 self.init(testdir, extra_args=['-Db_coverage=true']) 3433 self.build() 3434 self.run_tests() 3435 self.run_target('coverage-xml') 3436 self._check_coverage_files(['xml']) 3437 3438 def test_coverage_escaping(self): 3439 if mesonbuild.environment.detect_msys2_arch(): 3440 raise SkipTest('Skipped due to problems with coverage on MSYS2') 3441 gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr() 3442 if not gcovr_exe: 3443 raise SkipTest('gcovr not found, or too old') 3444 testdir = os.path.join(self.common_test_dir, '243 escape++') 3445 env = get_fake_env(testdir, self.builddir, self.prefix) 3446 cc = detect_c_compiler(env, MachineChoice.HOST) 3447 if cc.get_id() == 'clang': 3448 if not mesonbuild.environment.detect_llvm_cov(): 3449 raise SkipTest('llvm-cov not found') 3450 if cc.get_id() == 'msvc': 3451 raise SkipTest('Test only applies to non-MSVC compilers') 3452 self.init(testdir, extra_args=['-Db_coverage=true']) 3453 self.build() 3454 self.run_tests() 3455 self.run_target('coverage') 3456 self._check_coverage_files() 3457 3458 def test_cross_file_constants(self): 3459 with temp_filename() as crossfile1, temp_filename() as crossfile2: 3460 with open(crossfile1, 'w', encoding='utf-8') as f: 3461 f.write(textwrap.dedent( 3462 ''' 3463 [constants] 3464 compiler = 'gcc' 3465 ''')) 3466 with open(crossfile2, 'w', encoding='utf-8') as f: 3467 f.write(textwrap.dedent( 3468 ''' 3469 [constants] 3470 toolchain = '/toolchain/' 3471 common_flags = ['--sysroot=' + toolchain / 'sysroot'] 3472 3473 [properties] 3474 c_args = common_flags + ['-DSOMETHING'] 3475 cpp_args = c_args + ['-DSOMETHING_ELSE'] 3476 3477 [binaries] 3478 c = toolchain / compiler 3479 ''')) 3480 3481 values = mesonbuild.coredata.parse_machine_files([crossfile1, crossfile2]) 3482 self.assertEqual(values['binaries']['c'], '/toolchain/gcc') 3483 self.assertEqual(values['properties']['c_args'], 3484 ['--sysroot=/toolchain/sysroot', '-DSOMETHING']) 3485 self.assertEqual(values['properties']['cpp_args'], 3486 ['--sysroot=/toolchain/sysroot', '-DSOMETHING', '-DSOMETHING_ELSE']) 3487 3488 @skipIf(is_windows(), 'Directory cleanup fails for some reason') 3489 def test_wrap_git(self): 3490 with tempfile.TemporaryDirectory() as tmpdir: 3491 srcdir = os.path.join(tmpdir, 'src') 3492 shutil.copytree(os.path.join(self.unit_test_dir, '81 wrap-git'), srcdir) 3493 upstream = os.path.join(srcdir, 'subprojects', 'wrap_git_upstream') 3494 upstream_uri = Path(upstream).as_uri() 3495 _git_init(upstream) 3496 with open(os.path.join(srcdir, 'subprojects', 'wrap_git.wrap'), 'w', encoding='utf-8') as f: 3497 f.write(textwrap.dedent(''' 3498 [wrap-git] 3499 url = {} 3500 patch_directory = wrap_git_builddef 3501 revision = master 3502 '''.format(upstream_uri))) 3503 self.init(srcdir) 3504 self.build() 3505 self.run_tests() 3506 3507 def test_multi_output_custom_target_no_warning(self): 3508 testdir = os.path.join(self.common_test_dir, '228 custom_target source') 3509 3510 out = self.init(testdir) 3511 self.assertNotRegex(out, 'WARNING:.*Using the first one.') 3512 self.build() 3513 self.run_tests() 3514 3515 @skipUnless(is_linux() and (re.search('^i.86$|^x86$|^x64$|^x86_64$|^amd64$', platform.processor()) is not None), 3516 'Requires ASM compiler for x86 or x86_64 platform currently only available on Linux CI runners') 3517 def test_nostdlib(self): 3518 testdir = os.path.join(self.unit_test_dir, '78 nostdlib') 3519 machinefile = os.path.join(self.builddir, 'machine.txt') 3520 with open(machinefile, 'w', encoding='utf-8') as f: 3521 f.write(textwrap.dedent(''' 3522 [properties] 3523 c_stdlib = 'mylibc' 3524 ''')) 3525 3526 # Test native C stdlib 3527 self.meson_native_file = machinefile 3528 self.init(testdir) 3529 self.build() 3530 3531 # Test cross C stdlib 3532 self.new_builddir() 3533 self.meson_native_file = None 3534 self.meson_cross_file = machinefile 3535 self.init(testdir) 3536 self.build() 3537 3538 def test_meson_version_compare(self): 3539 testdir = os.path.join(self.unit_test_dir, '82 meson version compare') 3540 out = self.init(testdir) 3541 self.assertNotRegex(out, r'WARNING') 3542 3543 def test_wrap_redirect(self): 3544 redirect_wrap = os.path.join(self.builddir, 'redirect.wrap') 3545 real_wrap = os.path.join(self.builddir, 'foo/subprojects/real.wrap') 3546 os.makedirs(os.path.dirname(real_wrap)) 3547 3548 # Invalid redirect, filename must have .wrap extension 3549 with open(redirect_wrap, 'w', encoding='utf-8') as f: 3550 f.write(textwrap.dedent(''' 3551 [wrap-redirect] 3552 filename = foo/subprojects/real.wrapper 3553 ''')) 3554 with self.assertRaisesRegex(WrapException, 'wrap-redirect filename must be a .wrap file'): 3555 PackageDefinition(redirect_wrap) 3556 3557 # Invalid redirect, filename cannot be in parent directory 3558 with open(redirect_wrap, 'w', encoding='utf-8') as f: 3559 f.write(textwrap.dedent(''' 3560 [wrap-redirect] 3561 filename = ../real.wrap 3562 ''')) 3563 with self.assertRaisesRegex(WrapException, 'wrap-redirect filename cannot contain ".."'): 3564 PackageDefinition(redirect_wrap) 3565 3566 # Invalid redirect, filename must be in foo/subprojects/real.wrap 3567 with open(redirect_wrap, 'w', encoding='utf-8') as f: 3568 f.write(textwrap.dedent(''' 3569 [wrap-redirect] 3570 filename = foo/real.wrap 3571 ''')) 3572 with self.assertRaisesRegex(WrapException, 'wrap-redirect filename must be in the form foo/subprojects/bar.wrap'): 3573 wrap = PackageDefinition(redirect_wrap) 3574 3575 # Correct redirect 3576 with open(redirect_wrap, 'w', encoding='utf-8') as f: 3577 f.write(textwrap.dedent(''' 3578 [wrap-redirect] 3579 filename = foo/subprojects/real.wrap 3580 ''')) 3581 with open(real_wrap, 'w', encoding='utf-8') as f: 3582 f.write(textwrap.dedent(''' 3583 [wrap-git] 3584 url = http://invalid 3585 ''')) 3586 wrap = PackageDefinition(redirect_wrap) 3587 self.assertEqual(wrap.get('url'), 'http://invalid') 3588 3589 @skip_if_no_cmake 3590 def test_nested_cmake_rebuild(self) -> None: 3591 # This checks a bug where if a non-meson project is used as a third 3592 # level (or deeper) subproject it doesn't cause a rebuild if the build 3593 # files for that project are changed 3594 testdir = os.path.join(self.unit_test_dir, '85 nested subproject regenerate depends') 3595 cmakefile = Path(testdir) / 'subprojects' / 'sub2' / 'CMakeLists.txt' 3596 self.init(testdir) 3597 self.build() 3598 with cmakefile.open('a', encoding='utf-8'): 3599 os.utime(str(cmakefile)) 3600 self.assertReconfiguredBuildIsNoop() 3601 3602 def test_version_file(self): 3603 srcdir = os.path.join(self.common_test_dir, '2 cpp') 3604 self.init(srcdir) 3605 projinfo = self.introspect('--projectinfo') 3606 self.assertEqual(projinfo['version'], '1.0.0') 3607 3608 def test_cflags_cppflags(self): 3609 envs = {'CPPFLAGS': '-DCPPFLAG', 3610 'CFLAGS': '-DCFLAG', 3611 'CXXFLAGS': '-DCXXFLAG'} 3612 srcdir = os.path.join(self.unit_test_dir, '89 multiple envvars') 3613 self.init(srcdir, override_envvars=envs) 3614 self.build() 3615 3616 def test_build_b_options(self) -> None: 3617 # Currently (0.57) these do nothing, but they've always been allowed 3618 srcdir = os.path.join(self.common_test_dir, '2 cpp') 3619 self.init(srcdir, extra_args=['-Dbuild.b_lto=true']) 3620 3621 def test_install_skip_subprojects(self): 3622 testdir = os.path.join(self.unit_test_dir, '92 install skip subprojects') 3623 self.init(testdir) 3624 self.build() 3625 3626 main_expected = [ 3627 '', 3628 'share', 3629 'include', 3630 'foo', 3631 'bin', 3632 'share/foo', 3633 'share/foo/foo.dat', 3634 'include/foo.h', 3635 'foo/foofile', 3636 'bin/foo' + exe_suffix, 3637 ] 3638 bar_expected = [ 3639 'bar', 3640 'share/foo/bar.dat', 3641 'include/bar.h', 3642 'bin/bar' + exe_suffix, 3643 'bar/barfile' 3644 ] 3645 env = get_fake_env(testdir, self.builddir, self.prefix) 3646 cc = detect_c_compiler(env, MachineChoice.HOST) 3647 if cc.get_argument_syntax() == 'msvc': 3648 main_expected.append('bin/foo.pdb') 3649 bar_expected.append('bin/bar.pdb') 3650 prefix = destdir_join(self.installdir, self.prefix) 3651 main_expected = [Path(prefix, p) for p in main_expected] 3652 bar_expected = [Path(prefix, p) for p in bar_expected] 3653 all_expected = main_expected + bar_expected 3654 3655 def check_installed_files(extra_args, expected): 3656 args = ['install', '--destdir', self.installdir] + extra_args 3657 self._run(self.meson_command + args, workdir=self.builddir) 3658 all_files = [p for p in Path(self.installdir).rglob('*')] 3659 self.assertEqual(sorted(expected), sorted(all_files)) 3660 windows_proof_rmtree(self.installdir) 3661 3662 check_installed_files([], all_expected) 3663 check_installed_files(['--skip-subprojects'], main_expected) 3664 check_installed_files(['--skip-subprojects', 'bar'], main_expected) 3665 check_installed_files(['--skip-subprojects', 'another'], all_expected) 3666 3667 def test_adding_subproject_to_configure_project(self) -> None: 3668 srcdir = os.path.join(self.unit_test_dir, '93 new subproject in configured project') 3669 self.init(srcdir) 3670 self.build() 3671 self.setconf('-Duse-sub=true') 3672 self.build() 3673 3674 def test_devenv(self): 3675 testdir = os.path.join(self.unit_test_dir, '91 devenv') 3676 self.init(testdir) 3677 self.build() 3678 3679 cmd = self.meson_command + ['devenv', '-C', self.builddir] 3680 script = os.path.join(testdir, 'test-devenv.py') 3681 app = os.path.join(self.builddir, 'app') 3682 self._run(cmd + python_command + [script]) 3683 self.assertEqual('This is text.', self._run(cmd + [app]).strip()) 3684 3685 def test_clang_format_check(self): 3686 if self.backend is not Backend.ninja: 3687 raise SkipTest(f'Skipping clang-format tests with {self.backend.name} backend') 3688 if not shutil.which('clang-format'): 3689 raise SkipTest('clang-format not found') 3690 3691 testdir = os.path.join(self.unit_test_dir, '94 clangformat') 3692 newdir = os.path.join(self.builddir, 'testdir') 3693 shutil.copytree(testdir, newdir) 3694 self.new_builddir() 3695 self.init(newdir) 3696 3697 # Should reformat 1 file but not return error 3698 output = self.build('clang-format') 3699 self.assertEqual(1, output.count('File reformatted:')) 3700 3701 # Reset source tree then try again with clang-format-check, it should 3702 # return an error code this time. 3703 windows_proof_rmtree(newdir) 3704 shutil.copytree(testdir, newdir) 3705 with self.assertRaises(subprocess.CalledProcessError): 3706 output = self.build('clang-format-check') 3707 self.assertEqual(1, output.count('File reformatted:')) 3708 3709 # The check format should not touch any files. Thus 3710 # running format again has some work to do. 3711 output = self.build('clang-format') 3712 self.assertEqual(1, output.count('File reformatted:')) 3713 self.build('clang-format-check') 3714 3715 def test_custom_target_implicit_include(self): 3716 testdir = os.path.join(self.unit_test_dir, '95 custominc') 3717 self.init(testdir) 3718 self.build() 3719 compdb = self.get_compdb() 3720 matches = 0 3721 for c in compdb: 3722 if 'prog.c' in c['file']: 3723 self.assertNotIn('easytogrepfor', c['command']) 3724 matches += 1 3725 self.assertEqual(matches, 1) 3726 matches = 0 3727 for c in compdb: 3728 if 'prog2.c' in c['file']: 3729 self.assertIn('easytogrepfor', c['command']) 3730 matches += 1 3731 self.assertEqual(matches, 1) 3732 3733 def test_env_flags_to_linker(self) -> None: 3734 # Compilers that act as drivers should add their compiler flags to the 3735 # linker, those that do not shouldn't 3736 with mock.patch.dict(os.environ, {'CFLAGS': '-DCFLAG', 'LDFLAGS': '-flto'}): 3737 env = get_fake_env() 3738 3739 # Get the compiler so we know which compiler class to mock. 3740 cc = detect_compiler_for(env, 'c', MachineChoice.HOST) 3741 cc_type = type(cc) 3742 3743 # Test a compiler that acts as a linker 3744 with mock.patch.object(cc_type, 'INVOKES_LINKER', True): 3745 cc = detect_compiler_for(env, 'c', MachineChoice.HOST) 3746 link_args = env.coredata.get_external_link_args(cc.for_machine, cc.language) 3747 self.assertEqual(sorted(link_args), sorted(['-DCFLAG', '-flto'])) 3748 3749 # And one that doesn't 3750 with mock.patch.object(cc_type, 'INVOKES_LINKER', False): 3751 cc = detect_compiler_for(env, 'c', MachineChoice.HOST) 3752 link_args = env.coredata.get_external_link_args(cc.for_machine, cc.language) 3753 self.assertEqual(sorted(link_args), sorted(['-flto'])) 3754 3755 def test_install_tag(self) -> None: 3756 testdir = os.path.join(self.unit_test_dir, '98 install all targets') 3757 self.init(testdir) 3758 self.build() 3759 3760 env = get_fake_env(testdir, self.builddir, self.prefix) 3761 cc = detect_c_compiler(env, MachineChoice.HOST) 3762 3763 def shared_lib_name(name): 3764 if cc.get_id() in {'msvc', 'clang-cl'}: 3765 return f'bin/{name}.dll' 3766 elif is_windows(): 3767 return f'bin/lib{name}.dll' 3768 elif is_cygwin(): 3769 return f'bin/cyg{name}.dll' 3770 elif is_osx(): 3771 return f'lib/lib{name}.dylib' 3772 return f'lib/lib{name}.so' 3773 3774 def exe_name(name): 3775 if is_windows() or is_cygwin(): 3776 return f'{name}.exe' 3777 return name 3778 3779 installpath = Path(self.installdir) 3780 3781 expected_common = { 3782 installpath, 3783 Path(installpath, 'usr'), 3784 } 3785 3786 expected_devel = expected_common | { 3787 Path(installpath, 'usr/include'), 3788 Path(installpath, 'usr/include/bar-devel.h'), 3789 Path(installpath, 'usr/include/bar2-devel.h'), 3790 Path(installpath, 'usr/include/foo1-devel.h'), 3791 Path(installpath, 'usr/include/foo2-devel.h'), 3792 Path(installpath, 'usr/include/foo3-devel.h'), 3793 Path(installpath, 'usr/include/out-devel.h'), 3794 Path(installpath, 'usr/lib'), 3795 Path(installpath, 'usr/lib/libstatic.a'), 3796 Path(installpath, 'usr/lib/libboth.a'), 3797 Path(installpath, 'usr/lib/libboth2.a'), 3798 } 3799 3800 if cc.get_id() in {'msvc', 'clang-cl'}: 3801 expected_devel |= { 3802 Path(installpath, 'usr/bin'), 3803 Path(installpath, 'usr/bin/app.pdb'), 3804 Path(installpath, 'usr/bin/app2.pdb'), 3805 Path(installpath, 'usr/bin/both.pdb'), 3806 Path(installpath, 'usr/bin/both2.pdb'), 3807 Path(installpath, 'usr/bin/bothcustom.pdb'), 3808 Path(installpath, 'usr/bin/shared.pdb'), 3809 Path(installpath, 'usr/lib/both.lib'), 3810 Path(installpath, 'usr/lib/both2.lib'), 3811 Path(installpath, 'usr/lib/bothcustom.lib'), 3812 Path(installpath, 'usr/lib/shared.lib'), 3813 } 3814 elif is_windows() or is_cygwin(): 3815 expected_devel |= { 3816 Path(installpath, 'usr/lib/libboth.dll.a'), 3817 Path(installpath, 'usr/lib/libboth2.dll.a'), 3818 Path(installpath, 'usr/lib/libshared.dll.a'), 3819 Path(installpath, 'usr/lib/libbothcustom.dll.a'), 3820 } 3821 3822 expected_runtime = expected_common | { 3823 Path(installpath, 'usr/bin'), 3824 Path(installpath, 'usr/bin/' + exe_name('app')), 3825 Path(installpath, 'usr/bin/' + exe_name('app2')), 3826 Path(installpath, 'usr/' + shared_lib_name('shared')), 3827 Path(installpath, 'usr/' + shared_lib_name('both')), 3828 Path(installpath, 'usr/' + shared_lib_name('both2')), 3829 } 3830 3831 expected_custom = expected_common | { 3832 Path(installpath, 'usr/share'), 3833 Path(installpath, 'usr/share/bar-custom.txt'), 3834 Path(installpath, 'usr/share/foo-custom.h'), 3835 Path(installpath, 'usr/share/out1-custom.txt'), 3836 Path(installpath, 'usr/share/out2-custom.txt'), 3837 Path(installpath, 'usr/share/out3-custom.txt'), 3838 Path(installpath, 'usr/share/custom_files'), 3839 Path(installpath, 'usr/share/custom_files/data.txt'), 3840 Path(installpath, 'usr/lib'), 3841 Path(installpath, 'usr/lib/libbothcustom.a'), 3842 Path(installpath, 'usr/' + shared_lib_name('bothcustom')), 3843 } 3844 3845 if is_windows() or is_cygwin(): 3846 expected_custom |= {Path(installpath, 'usr/bin')} 3847 else: 3848 expected_runtime |= {Path(installpath, 'usr/lib')} 3849 3850 expected_runtime_custom = expected_runtime | expected_custom 3851 3852 expected_all = expected_devel | expected_runtime | expected_custom | { 3853 Path(installpath, 'usr/share/foo-notag.h'), 3854 Path(installpath, 'usr/share/bar-notag.txt'), 3855 Path(installpath, 'usr/share/out1-notag.txt'), 3856 Path(installpath, 'usr/share/out2-notag.txt'), 3857 Path(installpath, 'usr/share/out3-notag.txt'), 3858 Path(installpath, 'usr/share/foo2.h'), 3859 Path(installpath, 'usr/share/out1.txt'), 3860 Path(installpath, 'usr/share/out2.txt'), 3861 } 3862 3863 def do_install(tags=None): 3864 extra_args = ['--tags', tags] if tags else [] 3865 self._run(self.meson_command + ['install', '--dry-run', '--destdir', self.installdir] + extra_args, workdir=self.builddir) 3866 installed = self.read_install_logs() 3867 return sorted(installed) 3868 3869 self.assertEqual(sorted(expected_devel), do_install('devel')) 3870 self.assertEqual(sorted(expected_runtime), do_install('runtime')) 3871 self.assertEqual(sorted(expected_custom), do_install('custom')) 3872 self.assertEqual(sorted(expected_runtime_custom), do_install('runtime,custom')) 3873 self.assertEqual(sorted(expected_all), do_install()) 3874 3875 def test_introspect_install_plan(self): 3876 testdir = os.path.join(self.unit_test_dir, '98 install all targets') 3877 introfile = os.path.join(self.builddir, 'meson-info', 'intro-install_plan.json') 3878 self.init(testdir) 3879 self.assertPathExists(introfile) 3880 with open(introfile, encoding='utf-8') as fp: 3881 res = json.load(fp) 3882 3883 env = get_fake_env(testdir, self.builddir, self.prefix) 3884 3885 def output_name(name, type_): 3886 return type_(name=name, subdir=None, subproject=None, 3887 for_machine=MachineChoice.HOST, sources=[], 3888 objects=[], environment=env, kwargs={}).filename 3889 3890 shared_lib_name = lambda name: output_name(name, SharedLibrary) 3891 static_lib_name = lambda name: output_name(name, StaticLibrary) 3892 exe_name = lambda name: output_name(name, Executable) 3893 3894 expected = { 3895 'targets': { 3896 f'{self.builddir}/out1-notag.txt': { 3897 'destination': '{prefix}/share/out1-notag.txt', 3898 'tag': None, 3899 }, 3900 f'{self.builddir}/out2-notag.txt': { 3901 'destination': '{prefix}/share/out2-notag.txt', 3902 'tag': None, 3903 }, 3904 f'{self.builddir}/libstatic.a': { 3905 'destination': '{libdir_static}/libstatic.a', 3906 'tag': 'devel', 3907 }, 3908 f'{self.builddir}/' + exe_name('app'): { 3909 'destination': '{bindir}/' + exe_name('app'), 3910 'tag': 'runtime', 3911 }, 3912 f'{self.builddir}/subdir/' + exe_name('app2'): { 3913 'destination': '{bindir}/' + exe_name('app2'), 3914 'tag': 'runtime', 3915 }, 3916 f'{self.builddir}/' + shared_lib_name('shared'): { 3917 'destination': '{libdir_shared}/' + shared_lib_name('shared'), 3918 'tag': 'runtime', 3919 }, 3920 f'{self.builddir}/' + shared_lib_name('both'): { 3921 'destination': '{libdir_shared}/' + shared_lib_name('both'), 3922 'tag': 'runtime', 3923 }, 3924 f'{self.builddir}/' + static_lib_name('both'): { 3925 'destination': '{libdir_static}/' + static_lib_name('both'), 3926 'tag': 'devel', 3927 }, 3928 f'{self.builddir}/' + shared_lib_name('bothcustom'): { 3929 'destination': '{libdir_shared}/' + shared_lib_name('bothcustom'), 3930 'tag': 'custom', 3931 }, 3932 f'{self.builddir}/' + static_lib_name('bothcustom'): { 3933 'destination': '{libdir_static}/' + static_lib_name('bothcustom'), 3934 'tag': 'custom', 3935 }, 3936 f'{self.builddir}/subdir/' + shared_lib_name('both2'): { 3937 'destination': '{libdir_shared}/' + shared_lib_name('both2'), 3938 'tag': 'runtime', 3939 }, 3940 f'{self.builddir}/subdir/' + static_lib_name('both2'): { 3941 'destination': '{libdir_static}/' + static_lib_name('both2'), 3942 'tag': 'devel', 3943 }, 3944 f'{self.builddir}/out1-custom.txt': { 3945 'destination': '{prefix}/share/out1-custom.txt', 3946 'tag': 'custom', 3947 }, 3948 f'{self.builddir}/out2-custom.txt': { 3949 'destination': '{prefix}/share/out2-custom.txt', 3950 'tag': 'custom', 3951 }, 3952 f'{self.builddir}/out3-custom.txt': { 3953 'destination': '{prefix}/share/out3-custom.txt', 3954 'tag': 'custom', 3955 }, 3956 f'{self.builddir}/subdir/out1.txt': { 3957 'destination': '{prefix}/share/out1.txt', 3958 'tag': None, 3959 }, 3960 f'{self.builddir}/subdir/out2.txt': { 3961 'destination': '{prefix}/share/out2.txt', 3962 'tag': None, 3963 }, 3964 f'{self.builddir}/out-devel.h': { 3965 'destination': '{prefix}/include/out-devel.h', 3966 'tag': 'devel', 3967 }, 3968 f'{self.builddir}/out3-notag.txt': { 3969 'destination': '{prefix}/share/out3-notag.txt', 3970 'tag': None, 3971 }, 3972 }, 3973 'configure': { 3974 f'{self.builddir}/foo-notag.h': { 3975 'destination': '{prefix}/share/foo-notag.h', 3976 'tag': None, 3977 }, 3978 f'{self.builddir}/foo2-devel.h': { 3979 'destination': '{prefix}/include/foo2-devel.h', 3980 'tag': 'devel', 3981 }, 3982 f'{self.builddir}/foo-custom.h': { 3983 'destination': '{prefix}/share/foo-custom.h', 3984 'tag': 'custom', 3985 }, 3986 f'{self.builddir}/subdir/foo2.h': { 3987 'destination': '{prefix}/share/foo2.h', 3988 'tag': None, 3989 }, 3990 }, 3991 'data': { 3992 f'{testdir}/bar-notag.txt': { 3993 'destination': '{datadir}/share/bar-notag.txt', 3994 'tag': None, 3995 }, 3996 f'{testdir}/bar-devel.h': { 3997 'destination': '{datadir}/include/bar-devel.h', 3998 'tag': 'devel', 3999 }, 4000 f'{testdir}/bar-custom.txt': { 4001 'destination': '{datadir}/share/bar-custom.txt', 4002 'tag': 'custom', 4003 }, 4004 f'{testdir}/subdir/bar2-devel.h': { 4005 'destination': '{datadir}/include/bar2-devel.h', 4006 'tag': 'devel', 4007 }, 4008 }, 4009 'headers': { 4010 f'{testdir}/foo1-devel.h': { 4011 'destination': '{includedir}/foo1-devel.h', 4012 'tag': 'devel', 4013 }, 4014 f'{testdir}/subdir/foo3-devel.h': { 4015 'destination': '{includedir}/foo3-devel.h', 4016 'tag': 'devel', 4017 }, 4018 } 4019 } 4020 4021 fix_path = lambda path: os.path.sep.join(path.split('/')) 4022 expected_fixed = { 4023 data_type: { 4024 fix_path(source): { 4025 key: fix_path(value) if key == 'destination' else value 4026 for key, value in attributes.items() 4027 } 4028 for source, attributes in files.items() 4029 } 4030 for data_type, files in expected.items() 4031 } 4032 4033 for data_type, files in expected_fixed.items(): 4034 for file, details in files.items(): 4035 with self.subTest(key='{}.{}'.format(data_type, file)): 4036 self.assertEqual(res[data_type][file], details) 4037 4038 @skip_if_not_language('rust') 4039 @unittest.skipIf(not shutil.which('clippy-driver'), 'Test requires clippy-driver') 4040 def test_rust_clippy(self) -> None: 4041 if self.backend is not Backend.ninja: 4042 raise unittest.SkipTest('Rust is only supported with ninja currently') 4043 # Wehn clippy is used, we should get an exception since a variable named 4044 # "foo" is used, but is on our denylist 4045 testdir = os.path.join(self.rust_test_dir, '1 basic') 4046 self.init(testdir, extra_args=['--werror'], override_envvars={'RUSTC': 'clippy-driver'}) 4047 with self.assertRaises(subprocess.CalledProcessError) as cm: 4048 self.build() 4049 self.assertIn('error: use of a blacklisted/placeholder name `foo`', cm.exception.stdout) 4050