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