summaryrefslogtreecommitdiffstats
path: root/unittests/allplatformstests.py
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-29 04:41:38 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-29 04:41:38 +0000
commit7b6e527f440cd7e6f8be2b07cee320ee6ca18786 (patch)
tree4a2738d69fa2814659fdadddf5826282e73d81f4 /unittests/allplatformstests.py
parentInitial commit. (diff)
downloadmeson-upstream.tar.xz
meson-upstream.zip
Adding upstream version 1.0.1.upstream/1.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--unittests/allplatformstests.py4499
1 files changed, 4499 insertions, 0 deletions
diff --git a/unittests/allplatformstests.py b/unittests/allplatformstests.py
new file mode 100644
index 0000000..d8c74e6
--- /dev/null
+++ b/unittests/allplatformstests.py
@@ -0,0 +1,4499 @@
+# Copyright 2016-2021 The Meson development team
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import subprocess
+import re
+import json
+import tempfile
+import textwrap
+import os
+import shutil
+import platform
+import pickle
+import zipfile, tarfile
+import sys
+from unittest import mock, SkipTest, skipIf, skipUnless
+from contextlib import contextmanager
+from glob import glob
+from pathlib import (PurePath, Path)
+import typing as T
+
+import mesonbuild.mlog
+import mesonbuild.depfile
+import mesonbuild.dependencies.base
+import mesonbuild.dependencies.factory
+import mesonbuild.envconfig
+import mesonbuild.environment
+import mesonbuild.coredata
+import mesonbuild.modules.gnome
+from mesonbuild.mesonlib import (
+ BuildDirLock, MachineChoice, is_windows, is_osx, is_cygwin, is_dragonflybsd,
+ is_sunos, windows_proof_rmtree, python_command, version_compare, split_args, quote_arg,
+ relpath, is_linux, git, search_version, do_conf_file, do_conf_str, default_prefix,
+ MesonException, EnvironmentException, OptionKey, ExecutableSerialisation, EnvironmentVariables,
+ windows_proof_rm
+)
+
+from mesonbuild.compilers.mixins.clang import ClangCompiler
+from mesonbuild.compilers.mixins.gnu import GnuCompiler
+from mesonbuild.compilers.mixins.intel import IntelGnuLikeCompiler
+from mesonbuild.compilers.c import VisualStudioCCompiler, ClangClCCompiler
+from mesonbuild.compilers.cpp import VisualStudioCPPCompiler, ClangClCPPCompiler
+from mesonbuild.compilers import (
+ detect_static_linker, detect_c_compiler, compiler_from_language,
+ detect_compiler_for
+)
+
+from mesonbuild.dependencies import PkgConfigDependency
+from mesonbuild.build import Target, ConfigurationData, Executable, SharedLibrary, StaticLibrary
+import mesonbuild.modules.pkgconfig
+from mesonbuild.scripts import destdir_join
+
+from mesonbuild.wrap.wrap import PackageDefinition, WrapException
+
+from run_tests import (
+ Backend, exe_suffix, get_fake_env, get_convincing_fake_env_and_cc
+)
+
+from .baseplatformtests import BasePlatformTests
+from .helpers import *
+
+@contextmanager
+def temp_filename():
+ '''A context manager which provides a filename to an empty temporary file.
+
+ On exit the file will be deleted.
+ '''
+
+ fd, filename = tempfile.mkstemp()
+ os.close(fd)
+ try:
+ yield filename
+ finally:
+ try:
+ os.remove(filename)
+ except OSError:
+ pass
+
+def git_init(project_dir):
+ # If a user has git configuration init.defaultBranch set we want to override that
+ with tempfile.TemporaryDirectory() as d:
+ out = git(['--version'], str(d))[1]
+ if version_compare(search_version(out), '>= 2.28'):
+ extra_cmd = ['--initial-branch', 'master']
+ else:
+ extra_cmd = []
+
+ subprocess.check_call(['git', 'init'] + extra_cmd, cwd=project_dir, stdout=subprocess.DEVNULL)
+ subprocess.check_call(['git', 'config',
+ 'user.name', 'Author Person'], cwd=project_dir)
+ subprocess.check_call(['git', 'config',
+ 'user.email', 'teh_coderz@example.com'], cwd=project_dir)
+ _git_add_all(project_dir)
+
+def _git_add_all(project_dir):
+ subprocess.check_call('git add *', cwd=project_dir, shell=True,
+ stdout=subprocess.DEVNULL)
+ subprocess.check_call(['git', 'commit', '--no-gpg-sign', '-a', '-m', 'I am a project'], cwd=project_dir,
+ stdout=subprocess.DEVNULL)
+
+class AllPlatformTests(BasePlatformTests):
+ '''
+ Tests that should run on all platforms
+ '''
+
+ def test_default_options_prefix(self):
+ '''
+ Tests that setting a prefix in default_options in project() works.
+ Can't be an ordinary test because we pass --prefix to meson there.
+ https://github.com/mesonbuild/meson/issues/1349
+ '''
+ testdir = os.path.join(self.common_test_dir, '87 default options')
+ self.init(testdir, default_args=False, inprocess=True)
+ opts = self.introspect('--buildoptions')
+ for opt in opts:
+ if opt['name'] == 'prefix':
+ prefix = opt['value']
+ break
+ else:
+ raise self.fail('Did not find option "prefix"')
+ self.assertEqual(prefix, '/absoluteprefix')
+
+ def test_do_conf_file_preserve_newlines(self):
+
+ def conf_file(in_data, confdata):
+ with temp_filename() as fin:
+ with open(fin, 'wb') as fobj:
+ fobj.write(in_data.encode('utf-8'))
+ with temp_filename() as fout:
+ do_conf_file(fin, fout, confdata, 'meson')
+ with open(fout, 'rb') as fobj:
+ return fobj.read().decode('utf-8')
+
+ confdata = {'VAR': ('foo', 'bar')}
+ self.assertEqual(conf_file('@VAR@\n@VAR@\n', confdata), 'foo\nfoo\n')
+ self.assertEqual(conf_file('@VAR@\r\n@VAR@\r\n', confdata), 'foo\r\nfoo\r\n')
+
+ def test_do_conf_file_by_format(self):
+ def conf_str(in_data, confdata, vformat):
+ (result, missing_variables, confdata_useless) = do_conf_str('configuration_file', in_data, confdata, variable_format = vformat)
+ return '\n'.join(result)
+
+ def check_formats(confdata, result):
+ self.assertEqual(conf_str(['#mesondefine VAR'], confdata, 'meson'), result)
+ self.assertEqual(conf_str(['#cmakedefine VAR ${VAR}'], confdata, 'cmake'), result)
+ self.assertEqual(conf_str(['#cmakedefine VAR @VAR@'], confdata, 'cmake@'), result)
+
+ confdata = ConfigurationData()
+ # Key error as they do not exists
+ check_formats(confdata, '/* #undef VAR */\n')
+
+ # Check boolean
+ confdata.values = {'VAR': (False, 'description')}
+ check_formats(confdata, '#undef VAR\n')
+ confdata.values = {'VAR': (True, 'description')}
+ check_formats(confdata, '#define VAR\n')
+
+ # Check string
+ confdata.values = {'VAR': ('value', 'description')}
+ check_formats(confdata, '#define VAR value\n')
+
+ # Check integer
+ confdata.values = {'VAR': (10, 'description')}
+ check_formats(confdata, '#define VAR 10\n')
+
+ # Check multiple string with cmake formats
+ confdata.values = {'VAR': ('value', 'description')}
+ self.assertEqual(conf_str(['#cmakedefine VAR xxx @VAR@ yyy @VAR@'], confdata, 'cmake@'), '#define VAR xxx value yyy value\n')
+ self.assertEqual(conf_str(['#define VAR xxx @VAR@ yyy @VAR@'], confdata, 'cmake@'), '#define VAR xxx value yyy value')
+ self.assertEqual(conf_str(['#cmakedefine VAR xxx ${VAR} yyy ${VAR}'], confdata, 'cmake'), '#define VAR xxx value yyy value\n')
+ self.assertEqual(conf_str(['#define VAR xxx ${VAR} yyy ${VAR}'], confdata, 'cmake'), '#define VAR xxx value yyy value')
+
+ # Handles meson format exceptions
+ # Unknown format
+ self.assertRaises(MesonException, conf_str, ['#mesondefine VAR xxx'], confdata, 'unknown_format')
+ # More than 2 params in mesondefine
+ self.assertRaises(MesonException, conf_str, ['#mesondefine VAR xxx'], confdata, 'meson')
+ # Mismatched line with format
+ self.assertRaises(MesonException, conf_str, ['#cmakedefine VAR'], confdata, 'meson')
+ self.assertRaises(MesonException, conf_str, ['#mesondefine VAR'], confdata, 'cmake')
+ self.assertRaises(MesonException, conf_str, ['#mesondefine VAR'], confdata, 'cmake@')
+ # Dict value in confdata
+ confdata.values = {'VAR': (['value'], 'description')}
+ self.assertRaises(MesonException, conf_str, ['#mesondefine VAR'], confdata, 'meson')
+
+ def test_absolute_prefix_libdir(self):
+ '''
+ Tests that setting absolute paths for --prefix and --libdir work. Can't
+ be an ordinary test because these are set via the command-line.
+ https://github.com/mesonbuild/meson/issues/1341
+ https://github.com/mesonbuild/meson/issues/1345
+ '''
+ testdir = os.path.join(self.common_test_dir, '87 default options')
+ # on Windows, /someabs is *not* an absolute path
+ prefix = 'x:/someabs' if is_windows() else '/someabs'
+ libdir = 'libdir'
+ extra_args = ['--prefix=' + prefix,
+ # This can just be a relative path, but we want to test
+ # that passing this as an absolute path also works
+ '--libdir=' + prefix + '/' + libdir]
+ self.init(testdir, extra_args=extra_args, default_args=False)
+ opts = self.introspect('--buildoptions')
+ for opt in opts:
+ if opt['name'] == 'prefix':
+ self.assertEqual(prefix, opt['value'])
+ elif opt['name'] == 'libdir':
+ self.assertEqual(libdir, opt['value'])
+
+ def test_libdir_can_be_outside_prefix(self):
+ '''
+ Tests that libdir is allowed to be outside prefix.
+ Must be a unit test for obvious reasons.
+ '''
+ testdir = os.path.join(self.common_test_dir, '1 trivial')
+ # libdir being inside prefix is ok
+ if is_windows():
+ args = ['--prefix', 'x:/opt', '--libdir', 'x:/opt/lib32']
+ else:
+ args = ['--prefix', '/opt', '--libdir', '/opt/lib32']
+ self.init(testdir, extra_args=args)
+ self.wipe()
+ # libdir not being inside prefix is ok too
+ if is_windows():
+ args = ['--prefix', 'x:/usr', '--libdir', 'x:/opt/lib32']
+ else:
+ args = ['--prefix', '/usr', '--libdir', '/opt/lib32']
+ self.init(testdir, extra_args=args)
+ self.wipe()
+ # libdir can be outside prefix even when set via mesonconf
+ self.init(testdir)
+ if is_windows():
+ self.setconf('-Dlibdir=x:/opt', will_build=False)
+ else:
+ self.setconf('-Dlibdir=/opt', will_build=False)
+
+ def test_prefix_dependent_defaults(self):
+ '''
+ Tests that configured directory paths are set to prefix dependent
+ defaults.
+ '''
+ testdir = os.path.join(self.common_test_dir, '1 trivial')
+ expected = {
+ '/opt': {'prefix': '/opt',
+ 'bindir': 'bin', 'datadir': 'share', 'includedir': 'include',
+ 'infodir': 'share/info',
+ 'libexecdir': 'libexec', 'localedir': 'share/locale',
+ 'localstatedir': 'var', 'mandir': 'share/man',
+ 'sbindir': 'sbin', 'sharedstatedir': 'com',
+ 'sysconfdir': 'etc'},
+ '/usr': {'prefix': '/usr',
+ 'bindir': 'bin', 'datadir': 'share', 'includedir': 'include',
+ 'infodir': 'share/info',
+ 'libexecdir': 'libexec', 'localedir': 'share/locale',
+ 'localstatedir': '/var', 'mandir': 'share/man',
+ 'sbindir': 'sbin', 'sharedstatedir': '/var/lib',
+ 'sysconfdir': '/etc'},
+ '/usr/local': {'prefix': '/usr/local',
+ 'bindir': 'bin', 'datadir': 'share',
+ 'includedir': 'include', 'infodir': 'share/info',
+ 'libexecdir': 'libexec',
+ 'localedir': 'share/locale',
+ 'localstatedir': '/var/local', 'mandir': 'share/man',
+ 'sbindir': 'sbin', 'sharedstatedir': '/var/local/lib',
+ 'sysconfdir': 'etc'},
+ # N.B. We don't check 'libdir' as it's platform dependent, see
+ # default_libdir():
+ }
+
+ if default_prefix() == '/usr/local':
+ expected[None] = expected['/usr/local']
+
+ for prefix in expected:
+ args = []
+ if prefix:
+ args += ['--prefix', prefix]
+ self.init(testdir, extra_args=args, default_args=False)
+ opts = self.introspect('--buildoptions')
+ for opt in opts:
+ name = opt['name']
+ value = opt['value']
+ if name in expected[prefix]:
+ self.assertEqual(value, expected[prefix][name])
+ self.wipe()
+
+ def test_default_options_prefix_dependent_defaults(self):
+ '''
+ Tests that setting a prefix in default_options in project() sets prefix
+ dependent defaults for other options, and that those defaults can
+ be overridden in default_options or by the command line.
+ '''
+ testdir = os.path.join(self.common_test_dir, '163 default options prefix dependent defaults')
+ expected = {
+ '':
+ {'prefix': '/usr',
+ 'sysconfdir': '/etc',
+ 'localstatedir': '/var',
+ 'sharedstatedir': '/sharedstate'},
+ '--prefix=/usr':
+ {'prefix': '/usr',
+ 'sysconfdir': '/etc',
+ 'localstatedir': '/var',
+ 'sharedstatedir': '/sharedstate'},
+ '--sharedstatedir=/var/state':
+ {'prefix': '/usr',
+ 'sysconfdir': '/etc',
+ 'localstatedir': '/var',
+ 'sharedstatedir': '/var/state'},
+ '--sharedstatedir=/var/state --prefix=/usr --sysconfdir=sysconf':
+ {'prefix': '/usr',
+ 'sysconfdir': 'sysconf',
+ 'localstatedir': '/var',
+ 'sharedstatedir': '/var/state'},
+ }
+ for args in expected:
+ self.init(testdir, extra_args=args.split(), default_args=False)
+ opts = self.introspect('--buildoptions')
+ for opt in opts:
+ name = opt['name']
+ value = opt['value']
+ if name in expected[args]:
+ self.assertEqual(value, expected[args][name])
+ self.wipe()
+
+ def test_clike_get_library_dirs(self):
+ env = get_fake_env()
+ cc = detect_c_compiler(env, MachineChoice.HOST)
+ for d in cc.get_library_dirs(env):
+ self.assertTrue(os.path.exists(d))
+ self.assertTrue(os.path.isdir(d))
+ self.assertTrue(os.path.isabs(d))
+
+ def test_static_library_overwrite(self):
+ '''
+ Tests that static libraries are never appended to, always overwritten.
+ Has to be a unit test because this involves building a project,
+ reconfiguring, and building it again so that `ar` is run twice on the
+ same static library.
+ https://github.com/mesonbuild/meson/issues/1355
+ '''
+ testdir = os.path.join(self.common_test_dir, '3 static')
+ env = get_fake_env(testdir, self.builddir, self.prefix)
+ cc = detect_c_compiler(env, MachineChoice.HOST)
+ static_linker = detect_static_linker(env, cc)
+ if is_windows():
+ raise SkipTest('https://github.com/mesonbuild/meson/issues/1526')
+ if not isinstance(static_linker, mesonbuild.linkers.ArLinker):
+ raise SkipTest('static linker is not `ar`')
+ # Configure
+ self.init(testdir)
+ # Get name of static library
+ targets = self.introspect('--targets')
+ self.assertEqual(len(targets), 1)
+ libname = targets[0]['filename'][0]
+ # Build and get contents of static library
+ self.build()
+ before = self._run(['ar', 't', os.path.join(self.builddir, libname)]).split()
+ # Filter out non-object-file contents
+ before = [f for f in before if f.endswith(('.o', '.obj'))]
+ # Static library should contain only one object
+ self.assertEqual(len(before), 1, msg=before)
+ # Change the source to be built into the static library
+ self.setconf('-Dsource=libfile2.c')
+ self.build()
+ after = self._run(['ar', 't', os.path.join(self.builddir, libname)]).split()
+ # Filter out non-object-file contents
+ after = [f for f in after if f.endswith(('.o', '.obj'))]
+ # Static library should contain only one object
+ self.assertEqual(len(after), 1, msg=after)
+ # and the object must have changed
+ self.assertNotEqual(before, after)
+
+ def test_static_compile_order(self):
+ '''
+ Test that the order of files in a compiler command-line while compiling
+ and linking statically is deterministic. This can't be an ordinary test
+ case because we need to inspect the compiler database.
+ https://github.com/mesonbuild/meson/pull/951
+ '''
+ testdir = os.path.join(self.common_test_dir, '5 linkstatic')
+ self.init(testdir)
+ compdb = self.get_compdb()
+ # Rules will get written out in this order
+ self.assertTrue(compdb[0]['file'].endswith("libfile.c"))
+ self.assertTrue(compdb[1]['file'].endswith("libfile2.c"))
+ self.assertTrue(compdb[2]['file'].endswith("libfile3.c"))
+ self.assertTrue(compdb[3]['file'].endswith("libfile4.c"))
+ # FIXME: We don't have access to the linker command
+
+ def test_run_target_files_path(self):
+ '''
+ Test that run_targets are run from the correct directory
+ https://github.com/mesonbuild/meson/issues/957
+ '''
+ testdir = os.path.join(self.common_test_dir, '51 run target')
+ self.init(testdir)
+ self.run_target('check_exists')
+ self.run_target('check-env')
+ self.run_target('check-env-ct')
+
+ def test_run_target_subdir(self):
+ '''
+ Test that run_targets are run from the correct directory
+ https://github.com/mesonbuild/meson/issues/957
+ '''
+ testdir = os.path.join(self.common_test_dir, '51 run target')
+ self.init(testdir)
+ self.run_target('textprinter')
+
+ def test_install_introspection(self):
+ '''
+ Tests that the Meson introspection API exposes install filenames correctly
+ https://github.com/mesonbuild/meson/issues/829
+ '''
+ if self.backend is not Backend.ninja:
+ raise SkipTest(f'{self.backend.name!r} backend can\'t install files')
+ testdir = os.path.join(self.common_test_dir, '8 install')
+ self.init(testdir)
+ intro = self.introspect('--targets')
+ if intro[0]['type'] == 'executable':
+ intro = intro[::-1]
+ self.assertPathListEqual(intro[0]['install_filename'], ['/usr/lib/libstat.a'])
+ self.assertPathListEqual(intro[1]['install_filename'], ['/usr/bin/prog' + exe_suffix])
+
+ def test_install_subdir_introspection(self):
+ '''
+ Test that the Meson introspection API also contains subdir install information
+ https://github.com/mesonbuild/meson/issues/5556
+ '''
+ testdir = os.path.join(self.common_test_dir, '59 install subdir')
+ self.init(testdir)
+ intro = self.introspect('--installed')
+ expected = {
+ 'sub2': 'share/sub2',
+ 'subdir/sub1': 'share/sub1',
+ 'subdir/sub_elided': 'share',
+ 'sub1': 'share/sub1',
+ 'sub/sub1': 'share/sub1',
+ 'sub_elided': 'share',
+ 'nested_elided/sub': 'share',
+ 'new_directory': 'share/new_directory',
+ }
+
+ self.assertEqual(len(intro), len(expected))
+
+ # Convert expected to PurePath
+ expected_converted = {PurePath(os.path.join(testdir, key)): PurePath(os.path.join(self.prefix, val)) for key, val in expected.items()}
+ intro_converted = {PurePath(key): PurePath(val) for key, val in intro.items()}
+
+ for src, dst in expected_converted.items():
+ self.assertIn(src, intro_converted)
+ self.assertEqual(dst, intro_converted[src])
+
+ def test_install_introspection_multiple_outputs(self):
+ '''
+ Tests that the Meson introspection API exposes multiple install filenames correctly without crashing
+ https://github.com/mesonbuild/meson/pull/4555
+
+ Reverted to the first file only because of https://github.com/mesonbuild/meson/pull/4547#discussion_r244173438
+ TODO Change the format to a list officially in a followup PR
+ '''
+ if self.backend is not Backend.ninja:
+ raise SkipTest(f'{self.backend.name!r} backend can\'t install files')
+ testdir = os.path.join(self.common_test_dir, '140 custom target multiple outputs')
+ self.init(testdir)
+ intro = self.introspect('--targets')
+ if intro[0]['type'] == 'executable':
+ intro = intro[::-1]
+ self.assertPathListEqual(intro[0]['install_filename'], ['/usr/include/diff.h', '/usr/bin/diff.sh'])
+ self.assertPathListEqual(intro[1]['install_filename'], ['/opt/same.h', '/opt/same.sh'])
+ self.assertPathListEqual(intro[2]['install_filename'], ['/usr/include/first.h', None])
+ self.assertPathListEqual(intro[3]['install_filename'], [None, '/usr/bin/second.sh'])
+
+ def read_install_logs(self):
+ # Find logged files and directories
+ with Path(self.builddir, 'meson-logs', 'install-log.txt').open(encoding='utf-8') as f:
+ return list(map(lambda l: Path(l.strip()),
+ filter(lambda l: not l.startswith('#'),
+ f.readlines())))
+
+ def test_install_log_content(self):
+ '''
+ Tests that the install-log.txt is consistent with the installed files and directories.
+ Specifically checks that the log file only contains one entry per file/directory.
+ https://github.com/mesonbuild/meson/issues/4499
+ '''
+ testdir = os.path.join(self.common_test_dir, '59 install subdir')
+ self.init(testdir)
+ self.install()
+ installpath = Path(self.installdir)
+ # Find installed files and directories
+ expected = {installpath: 0}
+ for name in installpath.rglob('*'):
+ expected[name] = 0
+ logged = self.read_install_logs()
+ for name in logged:
+ self.assertTrue(name in expected, f'Log contains extra entry {name}')
+ expected[name] += 1
+
+ for name, count in expected.items():
+ self.assertGreater(count, 0, f'Log is missing entry for {name}')
+ self.assertLess(count, 2, f'Log has multiple entries for {name}')
+
+ # Verify that with --dry-run we obtain the same logs but with nothing
+ # actually installed
+ windows_proof_rmtree(self.installdir)
+ self._run(self.meson_command + ['install', '--dry-run', '--destdir', self.installdir], workdir=self.builddir)
+ self.assertEqual(logged, self.read_install_logs())
+ self.assertFalse(os.path.exists(self.installdir))
+
+ # If destdir is relative to build directory it should install
+ # exactly the same files.
+ rel_installpath = os.path.relpath(self.installdir, self.builddir)
+ self._run(self.meson_command + ['install', '--dry-run', '--destdir', rel_installpath, '-C', self.builddir])
+ self.assertEqual(logged, self.read_install_logs())
+
+ def test_uninstall(self):
+ exename = os.path.join(self.installdir, 'usr/bin/prog' + exe_suffix)
+ dirname = os.path.join(self.installdir, 'usr/share/dir')
+ testdir = os.path.join(self.common_test_dir, '8 install')
+ self.init(testdir)
+ self.assertPathDoesNotExist(exename)
+ self.install()
+ self.assertPathExists(exename)
+ self.uninstall()
+ self.assertPathDoesNotExist(exename)
+ self.assertPathDoesNotExist(dirname)
+
+ def test_forcefallback(self):
+ testdir = os.path.join(self.unit_test_dir, '31 forcefallback')
+ self.init(testdir, extra_args=['--wrap-mode=forcefallback'])
+ self.build()
+ self.run_tests()
+
+ def test_implicit_forcefallback(self):
+ testdir = os.path.join(self.unit_test_dir, '95 implicit force fallback')
+ with self.assertRaises(subprocess.CalledProcessError):
+ self.init(testdir)
+ self.init(testdir, extra_args=['--wrap-mode=forcefallback'])
+ self.new_builddir()
+ self.init(testdir, extra_args=['--force-fallback-for=something'])
+
+ def test_nopromote(self):
+ testdir = os.path.join(self.common_test_dir, '98 subproject subdir')
+ with self.assertRaises(subprocess.CalledProcessError) as cm:
+ self.init(testdir, extra_args=['--wrap-mode=nopromote'])
+ self.assertIn('dependency subsub found: NO', cm.exception.stdout)
+
+ def test_force_fallback_for(self):
+ testdir = os.path.join(self.unit_test_dir, '31 forcefallback')
+ self.init(testdir, extra_args=['--force-fallback-for=zlib,foo'])
+ self.build()
+ self.run_tests()
+
+ def test_force_fallback_for_nofallback(self):
+ testdir = os.path.join(self.unit_test_dir, '31 forcefallback')
+ self.init(testdir, extra_args=['--force-fallback-for=zlib,foo', '--wrap-mode=nofallback'])
+ self.build()
+ self.run_tests()
+
+ def test_testrepeat(self):
+ testdir = os.path.join(self.common_test_dir, '206 tap tests')
+ self.init(testdir)
+ self.build()
+ self._run(self.mtest_command + ['--repeat=2'])
+
+ def test_verbose(self):
+ testdir = os.path.join(self.common_test_dir, '206 tap tests')
+ self.init(testdir)
+ self.build()
+ out = self._run(self.mtest_command + ['--suite', 'verbose'])
+ self.assertIn('1/1 subtest 1', out)
+
+ def test_long_output(self):
+ testdir = os.path.join(self.common_test_dir, '254 long output')
+ self.init(testdir)
+ self.build()
+ self.run_tests()
+
+ # Ensure lines are found from testlog.txt when not being verbose.
+
+ i = 1
+ with open(os.path.join(self.logdir, 'testlog.txt'), encoding='utf-8') as f:
+ line = f.readline()
+ while line and i < 100001:
+ if f'# Iteration {i} to stdout' in line:
+ i += 1
+ line = f.readline()
+ self.assertEqual(i, 100001)
+
+ i = 1
+ while line:
+ if f'# Iteration {i} to stderr' in line:
+ i += 1
+ line = f.readline()
+ self.assertEqual(i, 100001)
+
+ # Ensure lines are found from both testlog.txt and console when being verbose.
+
+ out = self._run(self.mtest_command + ['-v'])
+ i = 1
+ with open(os.path.join(self.logdir, 'testlog.txt'), encoding='utf-8') as f:
+ line = f.readline()
+ while line and i < 100001:
+ if f'# Iteration {i} to stdout' in line:
+ i += 1
+ line = f.readline()
+ self.assertEqual(i, 100001)
+
+ i = 1
+ while line:
+ if f'# Iteration {i} to stderr' in line:
+ i += 1
+ line = f.readline()
+ self.assertEqual(i, 100001)
+
+ lines = out.split('\n')
+ line_number = 0
+ i = 1
+ while line_number < len(lines) and i < 100001:
+ print('---> %s' % lines[line_number])
+ if f'# Iteration {i} to stdout' in lines[line_number]:
+ i += 1
+ line_number += 1
+ self.assertEqual(i, 100001)
+
+ line_number = 0
+ i = 1
+ while line_number < len(lines):
+ if f'# Iteration {i} to stderr' in lines[line_number]:
+ i += 1
+ line_number += 1
+ self.assertEqual(i, 100001)
+
+
+ def test_testsetups(self):
+ if not shutil.which('valgrind'):
+ raise SkipTest('Valgrind not installed.')
+ testdir = os.path.join(self.unit_test_dir, '2 testsetups')
+ self.init(testdir)
+ self.build()
+ # Run tests without setup
+ self.run_tests()
+ with open(os.path.join(self.logdir, 'testlog.txt'), encoding='utf-8') as f:
+ basic_log = f.read()
+ # Run buggy test with setup that has env that will make it fail
+ self.assertRaises(subprocess.CalledProcessError,
+ self._run, self.mtest_command + ['--setup=valgrind'])
+ with open(os.path.join(self.logdir, 'testlog-valgrind.txt'), encoding='utf-8') as f:
+ vg_log = f.read()
+ self.assertNotIn('TEST_ENV is set', basic_log)
+ self.assertNotIn('Memcheck', basic_log)
+ self.assertIn('TEST_ENV is set', vg_log)
+ self.assertIn('Memcheck', vg_log)
+ # Run buggy test with setup without env that will pass
+ self._run(self.mtest_command + ['--setup=wrapper'])
+ # Setup with no properties works
+ self._run(self.mtest_command + ['--setup=empty'])
+ # Setup with only env works
+ self._run(self.mtest_command + ['--setup=onlyenv'])
+ self._run(self.mtest_command + ['--setup=onlyenv2'])
+ self._run(self.mtest_command + ['--setup=onlyenv3'])
+ # Setup with only a timeout works
+ self._run(self.mtest_command + ['--setup=timeout'])
+ # Setup that does not define a wrapper works with --wrapper
+ self._run(self.mtest_command + ['--setup=timeout', '--wrapper', shutil.which('valgrind')])
+ # Setup that skips test works
+ self._run(self.mtest_command + ['--setup=good'])
+ with open(os.path.join(self.logdir, 'testlog-good.txt'), encoding='utf-8') as f:
+ exclude_suites_log = f.read()
+ self.assertNotIn('buggy', exclude_suites_log)
+ # --suite overrides add_test_setup(xclude_suites)
+ self._run(self.mtest_command + ['--setup=good', '--suite', 'buggy'])
+ with open(os.path.join(self.logdir, 'testlog-good.txt'), encoding='utf-8') as f:
+ include_suites_log = f.read()
+ self.assertIn('buggy', include_suites_log)
+
+ def test_testsetup_selection(self):
+ testdir = os.path.join(self.unit_test_dir, '14 testsetup selection')
+ self.init(testdir)
+ self.build()
+
+ # Run tests without setup
+ self.run_tests()
+
+ self.assertRaises(subprocess.CalledProcessError, self._run, self.mtest_command + ['--setup=missingfromfoo'])
+ self._run(self.mtest_command + ['--setup=missingfromfoo', '--no-suite=foo:'])
+
+ self._run(self.mtest_command + ['--setup=worksforall'])
+ self._run(self.mtest_command + ['--setup=main:worksforall'])
+
+ self.assertRaises(subprocess.CalledProcessError, self._run,
+ self.mtest_command + ['--setup=onlyinbar'])
+ self.assertRaises(subprocess.CalledProcessError, self._run,
+ self.mtest_command + ['--setup=onlyinbar', '--no-suite=main:'])
+ self._run(self.mtest_command + ['--setup=onlyinbar', '--no-suite=main:', '--no-suite=foo:'])
+ self._run(self.mtest_command + ['--setup=bar:onlyinbar'])
+ self.assertRaises(subprocess.CalledProcessError, self._run,
+ self.mtest_command + ['--setup=foo:onlyinbar'])
+ self.assertRaises(subprocess.CalledProcessError, self._run,
+ self.mtest_command + ['--setup=main:onlyinbar'])
+
+ def test_testsetup_default(self):
+ testdir = os.path.join(self.unit_test_dir, '48 testsetup default')
+ self.init(testdir)
+ self.build()
+
+ # Run tests without --setup will cause the default setup to be used
+ self.run_tests()
+ with open(os.path.join(self.logdir, 'testlog.txt'), encoding='utf-8') as f:
+ default_log = f.read()
+
+ # Run tests with explicitly using the same setup that is set as default
+ self._run(self.mtest_command + ['--setup=mydefault'])
+ with open(os.path.join(self.logdir, 'testlog-mydefault.txt'), encoding='utf-8') as f:
+ mydefault_log = f.read()
+
+ # Run tests with another setup
+ self._run(self.mtest_command + ['--setup=other'])
+ with open(os.path.join(self.logdir, 'testlog-other.txt'), encoding='utf-8') as f:
+ other_log = f.read()
+
+ self.assertIn('ENV_A is 1', default_log)
+ self.assertIn('ENV_B is 2', default_log)
+ self.assertIn('ENV_C is 2', default_log)
+
+ self.assertIn('ENV_A is 1', mydefault_log)
+ self.assertIn('ENV_B is 2', mydefault_log)
+ self.assertIn('ENV_C is 2', mydefault_log)
+
+ self.assertIn('ENV_A is 1', other_log)
+ self.assertIn('ENV_B is 3', other_log)
+ self.assertIn('ENV_C is 2', other_log)
+
+ def assertFailedTestCount(self, failure_count, command):
+ try:
+ self._run(command)
+ self.assertEqual(0, failure_count, 'Expected %d tests to fail.' % failure_count)
+ except subprocess.CalledProcessError as e:
+ self.assertEqual(e.returncode, failure_count)
+
+ def test_suite_selection(self):
+ testdir = os.path.join(self.unit_test_dir, '4 suite selection')
+ self.init(testdir)
+ self.build()
+
+ self.assertFailedTestCount(4, self.mtest_command)
+
+ self.assertFailedTestCount(0, self.mtest_command + ['--suite', ':success'])
+ self.assertFailedTestCount(3, self.mtest_command + ['--suite', ':fail'])
+ self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', ':success'])
+ self.assertFailedTestCount(1, self.mtest_command + ['--no-suite', ':fail'])
+
+ self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'mainprj'])
+ self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc'])
+ self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail'])
+ self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjmix'])
+ self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'mainprj'])
+ self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'subprjsucc'])
+ self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjfail'])
+ self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjmix'])
+
+ self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'mainprj:fail'])
+ self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'mainprj:success'])
+ self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'mainprj:fail'])
+ self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'mainprj:success'])
+
+ self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail:fail'])
+ self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjfail:success'])
+ self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjfail:fail'])
+ self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'subprjfail:success'])
+
+ self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc:fail'])
+ self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc:success'])
+ self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'subprjsucc:fail'])
+ self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'subprjsucc:success'])
+
+ self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjmix:fail'])
+ self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjmix:success'])
+ self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjmix:fail'])
+ self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'subprjmix:success'])
+
+ self.assertFailedTestCount(2, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix:fail'])
+ self.assertFailedTestCount(3, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj'])
+ self.assertFailedTestCount(2, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj', '--no-suite', 'subprjmix:fail'])
+ self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj', '--no-suite', 'subprjmix:fail', 'mainprj-failing_test'])
+
+ self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjfail:fail', '--no-suite', 'subprjmix:fail'])
+
+ def test_mtest_reconfigure(self):
+ if self.backend is not Backend.ninja:
+ raise SkipTest(f'mtest can\'t rebuild with {self.backend.name!r}')
+
+ testdir = os.path.join(self.common_test_dir, '206 tap tests')
+ self.init(testdir)
+ self.utime(os.path.join(testdir, 'meson.build'))
+ o = self._run(self.mtest_command + ['--list'])
+ self.assertIn('Regenerating build files.', o)
+ self.assertIn('test_features / xfail', o)
+ o = self._run(self.mtest_command + ['--list'])
+ self.assertNotIn('Regenerating build files.', o)
+ # no real targets should have been built
+ tester = os.path.join(self.builddir, 'tester' + exe_suffix)
+ self.assertPathDoesNotExist(tester)
+ # check that we don't reconfigure if --no-rebuild is passed
+ self.utime(os.path.join(testdir, 'meson.build'))
+ o = self._run(self.mtest_command + ['--list', '--no-rebuild'])
+ self.assertNotIn('Regenerating build files.', o)
+
+ def test_build_by_default(self):
+ testdir = os.path.join(self.common_test_dir, '129 build by default')
+ self.init(testdir)
+ self.build()
+ genfile1 = os.path.join(self.builddir, 'generated1.dat')
+ genfile2 = os.path.join(self.builddir, 'generated2.dat')
+ exe1 = os.path.join(self.builddir, 'fooprog' + exe_suffix)
+ exe2 = os.path.join(self.builddir, 'barprog' + exe_suffix)
+ self.assertPathExists(genfile1)
+ self.assertPathExists(genfile2)
+ self.assertPathDoesNotExist(exe1)
+ self.assertPathDoesNotExist(exe2)
+ self.build(target=('fooprog' + exe_suffix))
+ self.assertPathExists(exe1)
+ self.build(target=('barprog' + exe_suffix))
+ self.assertPathExists(exe2)
+
+ def test_build_generated_pyx_directly(self):
+ # Check that the transpile stage also includes
+ # dependencies for the compilation stage as dependencies
+ testdir = os.path.join("test cases/cython", '2 generated sources')
+ env = get_fake_env(testdir, self.builddir, self.prefix)
+ try:
+ detect_compiler_for(env, "cython", MachineChoice.HOST)
+ except EnvironmentException:
+ raise SkipTest("Cython is not installed")
+ self.init(testdir)
+ # Need to get the full target name of the pyx.c target
+ # (which is unfortunately not provided by introspection :( )
+ # We'll need to dig into the generated sources
+ targets = self.introspect('--targets')
+ name = None
+ for target in targets:
+ for target_sources in target["target_sources"]:
+ for generated_source in target_sources["generated_sources"]:
+ if "includestuff.pyx.c" in generated_source:
+ name = generated_source
+ break
+ # Split the path (we only want the includestuff.cpython-blahblahblah)
+ name = os.path.normpath(name).split("/")[-2:]
+ name = "/".join(name) # Glue list into a string
+ self.build(target=name)
+
+ def test_build_pyx_depfiles(self):
+ # building regularly and then touching a depfile dependency should rebuild
+ testdir = os.path.join("test cases/cython", '2 generated sources')
+ env = get_fake_env(testdir, self.builddir, self.prefix)
+ try:
+ cython = detect_compiler_for(env, "cython", MachineChoice.HOST)
+ if not version_compare(cython.version, '>=0.29.33'):
+ raise SkipTest('Cython is too old')
+ except EnvironmentException:
+ raise SkipTest("Cython is not installed")
+ self.init(testdir)
+
+ targets = self.introspect('--targets')
+ for target in targets:
+ if target['name'].startswith('simpleinclude'):
+ name = target['name']
+ self.build()
+ self.utime(os.path.join(testdir, 'simplestuff.pxi'))
+ self.assertBuildRelinkedOnlyTarget(name)
+
+
+ def test_internal_include_order(self):
+ if mesonbuild.environment.detect_msys2_arch() and ('MESON_RSP_THRESHOLD' in os.environ):
+ raise SkipTest('Test does not yet support gcc rsp files on msys2')
+
+ testdir = os.path.join(self.common_test_dir, '130 include order')
+ self.init(testdir)
+ execmd = fxecmd = None
+ for cmd in self.get_compdb():
+ if 'someexe' in cmd['command']:
+ execmd = cmd['command']
+ continue
+ if 'somefxe' in cmd['command']:
+ fxecmd = cmd['command']
+ continue
+ if not execmd or not fxecmd:
+ raise Exception('Could not find someexe and somfxe commands')
+ # Check include order for 'someexe'
+ incs = [a for a in split_args(execmd) if a.startswith("-I")]
+ self.assertEqual(len(incs), 9)
+ # Need to run the build so the private dir is created.
+ self.build()
+ pdirs = glob(os.path.join(self.builddir, 'sub4/someexe*.p'))
+ self.assertEqual(len(pdirs), 1)
+ privdir = pdirs[0][len(self.builddir)+1:]
+ self.assertPathEqual(incs[0], "-I" + privdir)
+ # target build subdir
+ self.assertPathEqual(incs[1], "-Isub4")
+ # target source subdir
+ self.assertPathBasenameEqual(incs[2], 'sub4')
+ # include paths added via per-target c_args: ['-I'...]
+ self.assertPathBasenameEqual(incs[3], 'sub3')
+ # target include_directories: build dir
+ self.assertPathEqual(incs[4], "-Isub2")
+ # target include_directories: source dir
+ self.assertPathBasenameEqual(incs[5], 'sub2')
+ # target internal dependency include_directories: build dir
+ self.assertPathEqual(incs[6], "-Isub1")
+ # target internal dependency include_directories: source dir
+ self.assertPathBasenameEqual(incs[7], 'sub1')
+ # custom target include dir
+ self.assertPathEqual(incs[8], '-Ictsub')
+ # Check include order for 'somefxe'
+ incs = [a for a in split_args(fxecmd) if a.startswith('-I')]
+ self.assertEqual(len(incs), 9)
+ # target private dir
+ pdirs = glob(os.path.join(self.builddir, 'somefxe*.p'))
+ self.assertEqual(len(pdirs), 1)
+ privdir = pdirs[0][len(self.builddir)+1:]
+ self.assertPathEqual(incs[0], '-I' + privdir)
+ # target build dir
+ self.assertPathEqual(incs[1], '-I.')
+ # target source dir
+ self.assertPathBasenameEqual(incs[2], os.path.basename(testdir))
+ # target internal dependency correct include_directories: build dir
+ self.assertPathEqual(incs[3], "-Isub4")
+ # target internal dependency correct include_directories: source dir
+ self.assertPathBasenameEqual(incs[4], 'sub4')
+ # target internal dependency dep include_directories: build dir
+ self.assertPathEqual(incs[5], "-Isub1")
+ # target internal dependency dep include_directories: source dir
+ self.assertPathBasenameEqual(incs[6], 'sub1')
+ # target internal dependency wrong include_directories: build dir
+ self.assertPathEqual(incs[7], "-Isub2")
+ # target internal dependency wrong include_directories: source dir
+ self.assertPathBasenameEqual(incs[8], 'sub2')
+
+ def test_compiler_detection(self):
+ '''
+ Test that automatic compiler detection and setting from the environment
+ both work just fine. This is needed because while running project tests
+ and other unit tests, we always read CC/CXX/etc from the environment.
+ '''
+ gnu = GnuCompiler
+ clang = ClangCompiler
+ intel = IntelGnuLikeCompiler
+ msvc = (VisualStudioCCompiler, VisualStudioCPPCompiler)
+ clangcl = (ClangClCCompiler, ClangClCPPCompiler)
+ ar = mesonbuild.linkers.ArLinker
+ lib = mesonbuild.linkers.VisualStudioLinker
+ langs = [('c', 'CC'), ('cpp', 'CXX')]
+ if not is_windows() and platform.machine().lower() != 'e2k':
+ langs += [('objc', 'OBJC'), ('objcpp', 'OBJCXX')]
+ testdir = os.path.join(self.unit_test_dir, '5 compiler detection')
+ env = get_fake_env(testdir, self.builddir, self.prefix)
+ for lang, evar in langs:
+ # Detect with evar and do sanity checks on that
+ if evar in os.environ:
+ ecc = compiler_from_language(env, lang, MachineChoice.HOST)
+ self.assertTrue(ecc.version)
+ elinker = detect_static_linker(env, ecc)
+ # Pop it so we don't use it for the next detection
+ evalue = os.environ.pop(evar)
+ # Very rough/strict heuristics. Would never work for actual
+ # compiler detection, but should be ok for the tests.
+ ebase = os.path.basename(evalue)
+ if ebase.startswith('g') or ebase.endswith(('-gcc', '-g++')):
+ self.assertIsInstance(ecc, gnu)
+ self.assertIsInstance(elinker, ar)
+ elif 'clang-cl' in ebase:
+ self.assertIsInstance(ecc, clangcl)
+ self.assertIsInstance(elinker, lib)
+ elif 'clang' in ebase:
+ self.assertIsInstance(ecc, clang)
+ self.assertIsInstance(elinker, ar)
+ elif ebase.startswith('ic'):
+ self.assertIsInstance(ecc, intel)
+ self.assertIsInstance(elinker, ar)
+ elif ebase.startswith('cl'):
+ self.assertIsInstance(ecc, msvc)
+ self.assertIsInstance(elinker, lib)
+ else:
+ raise AssertionError(f'Unknown compiler {evalue!r}')
+ # Check that we actually used the evalue correctly as the compiler
+ self.assertEqual(ecc.get_exelist(), split_args(evalue))
+ # Do auto-detection of compiler based on platform, PATH, etc.
+ cc = compiler_from_language(env, lang, MachineChoice.HOST)
+ self.assertTrue(cc.version)
+ linker = detect_static_linker(env, cc)
+ # Check compiler type
+ if isinstance(cc, gnu):
+ self.assertIsInstance(linker, ar)
+ if is_osx():
+ self.assertIsInstance(cc.linker, mesonbuild.linkers.AppleDynamicLinker)
+ elif is_sunos():
+ self.assertIsInstance(cc.linker, (mesonbuild.linkers.SolarisDynamicLinker, mesonbuild.linkers.GnuLikeDynamicLinkerMixin))
+ else:
+ self.assertIsInstance(cc.linker, mesonbuild.linkers.GnuLikeDynamicLinkerMixin)
+ if isinstance(cc, clangcl):
+ self.assertIsInstance(linker, lib)
+ self.assertIsInstance(cc.linker, mesonbuild.linkers.ClangClDynamicLinker)
+ if isinstance(cc, clang):
+ self.assertIsInstance(linker, ar)
+ if is_osx():
+ self.assertIsInstance(cc.linker, mesonbuild.linkers.AppleDynamicLinker)
+ elif is_windows():
+ # This is clang, not clang-cl. This can be either an
+ # ld-like linker of link.exe-like linker (usually the
+ # former for msys2, the latter otherwise)
+ self.assertIsInstance(cc.linker, (mesonbuild.linkers.MSVCDynamicLinker, mesonbuild.linkers.GnuLikeDynamicLinkerMixin))
+ else:
+ self.assertIsInstance(cc.linker, mesonbuild.linkers.GnuLikeDynamicLinkerMixin)
+ if isinstance(cc, intel):
+ self.assertIsInstance(linker, ar)
+ if is_osx():
+ self.assertIsInstance(cc.linker, mesonbuild.linkers.AppleDynamicLinker)
+ elif is_windows():
+ self.assertIsInstance(cc.linker, mesonbuild.linkers.XilinkDynamicLinker)
+ else:
+ self.assertIsInstance(cc.linker, mesonbuild.linkers.GnuDynamicLinker)
+ if isinstance(cc, msvc):
+ self.assertTrue(is_windows())
+ self.assertIsInstance(linker, lib)
+ self.assertEqual(cc.id, 'msvc')
+ self.assertTrue(hasattr(cc, 'is_64'))
+ self.assertIsInstance(cc.linker, mesonbuild.linkers.MSVCDynamicLinker)
+ # If we're on Windows CI, we know what the compiler will be
+ if 'arch' in os.environ:
+ if os.environ['arch'] == 'x64':
+ self.assertTrue(cc.is_64)
+ else:
+ self.assertFalse(cc.is_64)
+ # Set evar ourselves to a wrapper script that just calls the same
+ # exelist + some argument. This is meant to test that setting
+ # something like `ccache gcc -pipe` or `distcc ccache gcc` works.
+ wrapper = os.path.join(testdir, 'compiler wrapper.py')
+ wrappercc = python_command + [wrapper] + cc.get_exelist() + ['-DSOME_ARG']
+ os.environ[evar] = ' '.join(quote_arg(w) for w in wrappercc)
+
+ # Check static linker too
+ wrapperlinker = python_command + [wrapper] + linker.get_exelist() + linker.get_always_args()
+ os.environ['AR'] = ' '.join(quote_arg(w) for w in wrapperlinker)
+
+ # Need a new env to re-run environment loading
+ env = get_fake_env(testdir, self.builddir, self.prefix)
+
+ wcc = compiler_from_language(env, lang, MachineChoice.HOST)
+ wlinker = detect_static_linker(env, wcc)
+ # Pop it so we don't use it for the next detection
+ os.environ.pop('AR')
+ # Must be the same type since it's a wrapper around the same exelist
+ self.assertIs(type(cc), type(wcc))
+ self.assertIs(type(linker), type(wlinker))
+ # Ensure that the exelist is correct
+ self.assertEqual(wcc.get_exelist(), wrappercc)
+ self.assertEqual(wlinker.get_exelist(), wrapperlinker)
+ # Ensure that the version detection worked correctly
+ self.assertEqual(cc.version, wcc.version)
+ if hasattr(cc, 'is_64'):
+ self.assertEqual(cc.is_64, wcc.is_64)
+
+ def test_always_prefer_c_compiler_for_asm(self):
+ testdir = os.path.join(self.common_test_dir, '133 c cpp and asm')
+ # Skip if building with MSVC
+ env = get_fake_env(testdir, self.builddir, self.prefix)
+ if detect_c_compiler(env, MachineChoice.HOST).get_id() == 'msvc':
+ raise SkipTest('MSVC can\'t compile assembly')
+ self.init(testdir)
+ commands = {'c-asm': {}, 'cpp-asm': {}, 'cpp-c-asm': {}, 'c-cpp-asm': {}}
+ for cmd in self.get_compdb():
+ # Get compiler
+ split = split_args(cmd['command'])
+ if split[0] == 'ccache':
+ compiler = split[1]
+ else:
+ compiler = split[0]
+ # Classify commands
+ if 'Ic-asm' in cmd['command']:
+ if cmd['file'].endswith('.S'):
+ commands['c-asm']['asm'] = compiler
+ elif cmd['file'].endswith('.c'):
+ commands['c-asm']['c'] = compiler
+ else:
+ raise AssertionError('{!r} found in cpp-asm?'.format(cmd['command']))
+ elif 'Icpp-asm' in cmd['command']:
+ if cmd['file'].endswith('.S'):
+ commands['cpp-asm']['asm'] = compiler
+ elif cmd['file'].endswith('.cpp'):
+ commands['cpp-asm']['cpp'] = compiler
+ else:
+ raise AssertionError('{!r} found in cpp-asm?'.format(cmd['command']))
+ elif 'Ic-cpp-asm' in cmd['command']:
+ if cmd['file'].endswith('.S'):
+ commands['c-cpp-asm']['asm'] = compiler
+ elif cmd['file'].endswith('.c'):
+ commands['c-cpp-asm']['c'] = compiler
+ elif cmd['file'].endswith('.cpp'):
+ commands['c-cpp-asm']['cpp'] = compiler
+ else:
+ raise AssertionError('{!r} found in c-cpp-asm?'.format(cmd['command']))
+ elif 'Icpp-c-asm' in cmd['command']:
+ if cmd['file'].endswith('.S'):
+ commands['cpp-c-asm']['asm'] = compiler
+ elif cmd['file'].endswith('.c'):
+ commands['cpp-c-asm']['c'] = compiler
+ elif cmd['file'].endswith('.cpp'):
+ commands['cpp-c-asm']['cpp'] = compiler
+ else:
+ raise AssertionError('{!r} found in cpp-c-asm?'.format(cmd['command']))
+ else:
+ raise AssertionError('Unknown command {!r} found'.format(cmd['command']))
+ # Check that .S files are always built with the C compiler
+ self.assertEqual(commands['c-asm']['asm'], commands['c-asm']['c'])
+ self.assertEqual(commands['c-asm']['asm'], commands['cpp-asm']['asm'])
+ self.assertEqual(commands['cpp-asm']['asm'], commands['c-cpp-asm']['c'])
+ self.assertEqual(commands['c-cpp-asm']['asm'], commands['c-cpp-asm']['c'])
+ self.assertEqual(commands['cpp-c-asm']['asm'], commands['cpp-c-asm']['c'])
+ self.assertNotEqual(commands['cpp-asm']['asm'], commands['cpp-asm']['cpp'])
+ self.assertNotEqual(commands['c-cpp-asm']['c'], commands['c-cpp-asm']['cpp'])
+ self.assertNotEqual(commands['cpp-c-asm']['c'], commands['cpp-c-asm']['cpp'])
+ # Check that the c-asm target is always linked with the C linker
+ build_ninja = os.path.join(self.builddir, 'build.ninja')
+ with open(build_ninja, encoding='utf-8') as f:
+ contents = f.read()
+ m = re.search('build c-asm.*: c_LINKER', contents)
+ self.assertIsNotNone(m, msg=contents)
+
+ def test_preprocessor_checks_CPPFLAGS(self):
+ '''
+ Test that preprocessor compiler checks read CPPFLAGS and also CFLAGS but
+ not LDFLAGS.
+ '''
+ testdir = os.path.join(self.common_test_dir, '132 get define')
+ define = 'MESON_TEST_DEFINE_VALUE'
+ # NOTE: this list can't have \n, ' or "
+ # \n is never substituted by the GNU pre-processor via a -D define
+ # ' and " confuse split_args() even when they are escaped
+ # % and # confuse the MSVC preprocessor
+ # !, ^, *, and < confuse lcc preprocessor
+ value = 'spaces and fun@$&()-=_+{}[]:;>?,./~`'
+ for env_var in ['CPPFLAGS', 'CFLAGS']:
+ env = {}
+ env[env_var] = f'-D{define}="{value}"'
+ env['LDFLAGS'] = '-DMESON_FAIL_VALUE=cflags-read'
+ self.init(testdir, extra_args=[f'-D{define}={value}'], override_envvars=env)
+
+ def test_custom_target_exe_data_deterministic(self):
+ testdir = os.path.join(self.common_test_dir, '109 custom target capture')
+ self.init(testdir)
+ meson_exe_dat1 = glob(os.path.join(self.privatedir, 'meson_exe*.dat'))
+ self.wipe()
+ self.init(testdir)
+ meson_exe_dat2 = glob(os.path.join(self.privatedir, 'meson_exe*.dat'))
+ self.assertListEqual(meson_exe_dat1, meson_exe_dat2)
+
+ def test_noop_changes_cause_no_rebuilds(self):
+ '''
+ Test that no-op changes to the build files such as mtime do not cause
+ a rebuild of anything.
+ '''
+ testdir = os.path.join(self.common_test_dir, '6 linkshared')
+ self.init(testdir)
+ self.build()
+ # Immediately rebuilding should not do anything
+ self.assertBuildIsNoop()
+ # Changing mtime of meson.build should not rebuild anything
+ self.utime(os.path.join(testdir, 'meson.build'))
+ self.assertReconfiguredBuildIsNoop()
+ # Changing mtime of libefile.c should rebuild the library, but not relink the executable
+ self.utime(os.path.join(testdir, 'libfile.c'))
+ self.assertBuildRelinkedOnlyTarget('mylib')
+
+ def test_source_changes_cause_rebuild(self):
+ '''
+ Test that changes to sources and headers cause rebuilds, but not
+ changes to unused files (as determined by the dependency file) in the
+ input files list.
+ '''
+ testdir = os.path.join(self.common_test_dir, '19 header in file list')
+ self.init(testdir)
+ self.build()
+ # Immediately rebuilding should not do anything
+ self.assertBuildIsNoop()
+ # Changing mtime of header.h should rebuild everything
+ self.utime(os.path.join(testdir, 'header.h'))
+ self.assertBuildRelinkedOnlyTarget('prog')
+
+ def test_custom_target_changes_cause_rebuild(self):
+ '''
+ Test that in a custom target, changes to the input files, the
+ ExternalProgram, and any File objects on the command-line cause
+ a rebuild.
+ '''
+ testdir = os.path.join(self.common_test_dir, '57 custom header generator')
+ self.init(testdir)
+ self.build()
+ # Immediately rebuilding should not do anything
+ self.assertBuildIsNoop()
+ # Changing mtime of these should rebuild everything
+ for f in ('input.def', 'makeheader.py', 'somefile.txt'):
+ self.utime(os.path.join(testdir, f))
+ self.assertBuildRelinkedOnlyTarget('prog')
+
+ def test_source_generator_program_cause_rebuild(self):
+ '''
+ Test that changes to generator programs in the source tree cause
+ a rebuild.
+ '''
+ testdir = os.path.join(self.common_test_dir, '90 gen extra')
+ self.init(testdir)
+ self.build()
+ # Immediately rebuilding should not do anything
+ self.assertBuildIsNoop()
+ # Changing mtime of generator should rebuild the executable
+ self.utime(os.path.join(testdir, 'srcgen.py'))
+ self.assertRebuiltTarget('basic')
+
+ def test_static_library_lto(self):
+ '''
+ Test that static libraries can be built with LTO and linked to
+ executables. On Linux, this requires the use of gcc-ar.
+ https://github.com/mesonbuild/meson/issues/1646
+ '''
+ testdir = os.path.join(self.common_test_dir, '5 linkstatic')
+
+ env = get_fake_env(testdir, self.builddir, self.prefix)
+ if detect_c_compiler(env, MachineChoice.HOST).get_id() == 'clang' and is_windows():
+ raise SkipTest('LTO not (yet) supported by windows clang')
+
+ self.init(testdir, extra_args='-Db_lto=true')
+ self.build()
+ self.run_tests()
+
+ @skip_if_not_base_option('b_lto_threads')
+ def test_lto_threads(self):
+ testdir = os.path.join(self.common_test_dir, '6 linkshared')
+
+ env = get_fake_env(testdir, self.builddir, self.prefix)
+ cc = detect_c_compiler(env, MachineChoice.HOST)
+ extra_args: T.List[str] = []
+ if cc.get_id() == 'clang':
+ if is_windows():
+ raise SkipTest('LTO not (yet) supported by windows clang')
+
+ self.init(testdir, extra_args=['-Db_lto=true', '-Db_lto_threads=8'] + extra_args)
+ self.build()
+ self.run_tests()
+
+ expected = set(cc.get_lto_compile_args(threads=8))
+ targets = self.introspect('--targets')
+ # This assumes all of the targets support lto
+ for t in targets:
+ for s in t['target_sources']:
+ for e in expected:
+ self.assertIn(e, s['parameters'])
+
+ @skip_if_not_base_option('b_lto_mode')
+ @skip_if_not_base_option('b_lto_threads')
+ def test_lto_mode(self):
+ testdir = os.path.join(self.common_test_dir, '6 linkshared')
+
+ env = get_fake_env(testdir, self.builddir, self.prefix)
+ cc = detect_c_compiler(env, MachineChoice.HOST)
+ if cc.get_id() != 'clang':
+ raise SkipTest('Only clang currently supports thinLTO')
+ if cc.linker.id not in {'ld.lld', 'ld.gold', 'ld64', 'lld-link'}:
+ raise SkipTest('thinLTO requires ld.lld, ld.gold, ld64, or lld-link')
+ elif is_windows():
+ raise SkipTest('LTO not (yet) supported by windows clang')
+
+ self.init(testdir, extra_args=['-Db_lto=true', '-Db_lto_mode=thin', '-Db_lto_threads=8', '-Dc_args=-Werror=unused-command-line-argument'])
+ self.build()
+ self.run_tests()
+
+ expected = set(cc.get_lto_compile_args(threads=8, mode='thin'))
+ targets = self.introspect('--targets')
+ # This assumes all of the targets support lto
+ for t in targets:
+ for s in t['target_sources']:
+ self.assertTrue(expected.issubset(set(s['parameters'])), f'Incorrect values for {t["name"]}')
+
+ def test_dist_git(self):
+ if not shutil.which('git'):
+ raise SkipTest('Git not found')
+ if self.backend is not Backend.ninja:
+ raise SkipTest('Dist is only supported with Ninja')
+
+ try:
+ self.dist_impl(git_init, _git_add_all)
+ except PermissionError:
+ # When run under Windows CI, something (virus scanner?)
+ # holds on to the git files so cleaning up the dir
+ # fails sometimes.
+ pass
+
+ def has_working_hg(self):
+ if not shutil.which('hg'):
+ return False
+ try:
+ # This check should not be necessary, but
+ # CI under macOS passes the above test even
+ # though Mercurial is not installed.
+ if subprocess.call(['hg', '--version'],
+ stdout=subprocess.DEVNULL,
+ stderr=subprocess.DEVNULL) != 0:
+ return False
+ return True
+ except FileNotFoundError:
+ return False
+
+ def test_dist_hg(self):
+ if not self.has_working_hg():
+ raise SkipTest('Mercurial not found or broken.')
+ if self.backend is not Backend.ninja:
+ raise SkipTest('Dist is only supported with Ninja')
+
+ def hg_init(project_dir):
+ subprocess.check_call(['hg', 'init'], cwd=project_dir)
+ with open(os.path.join(project_dir, '.hg', 'hgrc'), 'w', encoding='utf-8') as f:
+ print('[ui]', file=f)
+ print('username=Author Person <teh_coderz@example.com>', file=f)
+ subprocess.check_call(['hg', 'add', 'meson.build', 'distexe.c'], cwd=project_dir)
+ subprocess.check_call(['hg', 'commit', '-m', 'I am a project'], cwd=project_dir)
+
+ try:
+ self.dist_impl(hg_init, include_subprojects=False)
+ except PermissionError:
+ # When run under Windows CI, something (virus scanner?)
+ # holds on to the hg files so cleaning up the dir
+ # fails sometimes.
+ pass
+
+ def test_dist_git_script(self):
+ if not shutil.which('git'):
+ raise SkipTest('Git not found')
+ if self.backend is not Backend.ninja:
+ raise SkipTest('Dist is only supported with Ninja')
+
+ try:
+ with tempfile.TemporaryDirectory() as tmpdir:
+ project_dir = os.path.join(tmpdir, 'a')
+ shutil.copytree(os.path.join(self.unit_test_dir, '35 dist script'),
+ project_dir)
+ git_init(project_dir)
+ self.init(project_dir)
+ self.build('dist')
+
+ self.new_builddir()
+ self.init(project_dir, extra_args=['-Dsub:broken_dist_script=false'])
+ self._run(self.meson_command + ['dist', '--include-subprojects'], workdir=self.builddir)
+ except PermissionError:
+ # When run under Windows CI, something (virus scanner?)
+ # holds on to the git files so cleaning up the dir
+ # fails sometimes.
+ pass
+
+ def create_dummy_subproject(self, project_dir, name):
+ path = os.path.join(project_dir, 'subprojects', name)
+ os.makedirs(path)
+ with open(os.path.join(path, 'meson.build'), 'w', encoding='utf-8') as ofile:
+ ofile.write(f"project('{name}', version: '1.0')")
+ return path
+
+ def dist_impl(self, vcs_init, vcs_add_all=None, include_subprojects=True):
+ # Create this on the fly because having rogue .git directories inside
+ # the source tree leads to all kinds of trouble.
+ with tempfile.TemporaryDirectory() as project_dir:
+ with open(os.path.join(project_dir, 'meson.build'), 'w', encoding='utf-8') as ofile:
+ ofile.write(textwrap.dedent('''\
+ project('disttest', 'c', version : '1.4.3')
+ e = executable('distexe', 'distexe.c')
+ test('dist test', e)
+ subproject('vcssub', required : false)
+ subproject('tarballsub', required : false)
+ subproject('samerepo', required : false)
+ '''))
+ with open(os.path.join(project_dir, 'distexe.c'), 'w', encoding='utf-8') as ofile:
+ ofile.write(textwrap.dedent('''\
+ #include<stdio.h>
+
+ int main(int argc, char **argv) {
+ printf("I am a distribution test.\\n");
+ return 0;
+ }
+ '''))
+ xz_distfile = os.path.join(self.distdir, 'disttest-1.4.3.tar.xz')
+ xz_checksumfile = xz_distfile + '.sha256sum'
+ gz_distfile = os.path.join(self.distdir, 'disttest-1.4.3.tar.gz')
+ gz_checksumfile = gz_distfile + '.sha256sum'
+ zip_distfile = os.path.join(self.distdir, 'disttest-1.4.3.zip')
+ zip_checksumfile = zip_distfile + '.sha256sum'
+ vcs_init(project_dir)
+ if include_subprojects:
+ vcs_init(self.create_dummy_subproject(project_dir, 'vcssub'))
+ self.create_dummy_subproject(project_dir, 'tarballsub')
+ self.create_dummy_subproject(project_dir, 'unusedsub')
+ if vcs_add_all:
+ vcs_add_all(self.create_dummy_subproject(project_dir, 'samerepo'))
+ self.init(project_dir)
+ self.build('dist')
+ self.assertPathExists(xz_distfile)
+ self.assertPathExists(xz_checksumfile)
+ self.assertPathDoesNotExist(gz_distfile)
+ self.assertPathDoesNotExist(gz_checksumfile)
+ self.assertPathDoesNotExist(zip_distfile)
+ self.assertPathDoesNotExist(zip_checksumfile)
+ self._run(self.meson_command + ['dist', '--formats', 'gztar'],
+ workdir=self.builddir)
+ self.assertPathExists(gz_distfile)
+ self.assertPathExists(gz_checksumfile)
+ self._run(self.meson_command + ['dist', '--formats', 'zip'],
+ workdir=self.builddir)
+ self.assertPathExists(zip_distfile)
+ self.assertPathExists(zip_checksumfile)
+ os.remove(xz_distfile)
+ os.remove(xz_checksumfile)
+ os.remove(gz_distfile)
+ os.remove(gz_checksumfile)
+ os.remove(zip_distfile)
+ os.remove(zip_checksumfile)
+ self._run(self.meson_command + ['dist', '--formats', 'xztar,gztar,zip'],
+ workdir=self.builddir)
+ self.assertPathExists(xz_distfile)
+ self.assertPathExists(xz_checksumfile)
+ self.assertPathExists(gz_distfile)
+ self.assertPathExists(gz_checksumfile)
+ self.assertPathExists(zip_distfile)
+ self.assertPathExists(zip_checksumfile)
+
+ if include_subprojects:
+ # Verify that without --include-subprojects we have files from
+ # the main project and also files from subprojects part of the
+ # main vcs repository.
+ z = zipfile.ZipFile(zip_distfile)
+ expected = ['disttest-1.4.3/',
+ 'disttest-1.4.3/meson.build',
+ 'disttest-1.4.3/distexe.c']
+ if vcs_add_all:
+ expected += ['disttest-1.4.3/subprojects/',
+ 'disttest-1.4.3/subprojects/samerepo/',
+ 'disttest-1.4.3/subprojects/samerepo/meson.build']
+ self.assertEqual(sorted(expected),
+ sorted(z.namelist()))
+ # Verify that with --include-subprojects we now also have files
+ # from tarball and separate vcs subprojects. But not files from
+ # unused subprojects.
+ self._run(self.meson_command + ['dist', '--formats', 'zip', '--include-subprojects'],
+ workdir=self.builddir)
+ z = zipfile.ZipFile(zip_distfile)
+ expected += ['disttest-1.4.3/subprojects/tarballsub/',
+ 'disttest-1.4.3/subprojects/tarballsub/meson.build',
+ 'disttest-1.4.3/subprojects/vcssub/',
+ 'disttest-1.4.3/subprojects/vcssub/meson.build']
+ self.assertEqual(sorted(expected),
+ sorted(z.namelist()))
+ if vcs_add_all:
+ # Verify we can distribute separately subprojects in the same vcs
+ # repository as the main project.
+ subproject_dir = os.path.join(project_dir, 'subprojects', 'samerepo')
+ self.new_builddir()
+ self.init(subproject_dir)
+ self.build('dist')
+ xz_distfile = os.path.join(self.distdir, 'samerepo-1.0.tar.xz')
+ xz_checksumfile = xz_distfile + '.sha256sum'
+ self.assertPathExists(xz_distfile)
+ self.assertPathExists(xz_checksumfile)
+ tar = tarfile.open(xz_distfile, "r:xz") # [ignore encoding]
+ self.assertEqual(sorted(['samerepo-1.0',
+ 'samerepo-1.0/meson.build']),
+ sorted(i.name for i in tar))
+
+ def test_rpath_uses_ORIGIN(self):
+ '''
+ Test that built targets use $ORIGIN in rpath, which ensures that they
+ are relocatable and ensures that builds are reproducible since the
+ build directory won't get embedded into the built binaries.
+ '''
+ if is_windows() or is_cygwin():
+ raise SkipTest('Windows PE/COFF binaries do not use RPATH')
+ testdir = os.path.join(self.common_test_dir, '39 library chain')
+ self.init(testdir)
+ self.build()
+ for each in ('prog', 'subdir/liblib1.so', ):
+ rpath = get_rpath(os.path.join(self.builddir, each))
+ self.assertTrue(rpath, f'Rpath could not be determined for {each}.')
+ if is_dragonflybsd():
+ # DragonflyBSD will prepend /usr/lib/gccVERSION to the rpath,
+ # so ignore that.
+ self.assertTrue(rpath.startswith('/usr/lib/gcc'))
+ rpaths = rpath.split(':')[1:]
+ else:
+ rpaths = rpath.split(':')
+ for path in rpaths:
+ self.assertTrue(path.startswith('$ORIGIN'), msg=(each, path))
+ # These two don't link to anything else, so they do not need an rpath entry.
+ for each in ('subdir/subdir2/liblib2.so', 'subdir/subdir3/liblib3.so'):
+ rpath = get_rpath(os.path.join(self.builddir, each))
+ if is_dragonflybsd():
+ # The rpath should be equal to /usr/lib/gccVERSION
+ self.assertTrue(rpath.startswith('/usr/lib/gcc'))
+ self.assertEqual(len(rpath.split(':')), 1)
+ else:
+ self.assertIsNone(rpath)
+
+ def test_dash_d_dedup(self):
+ testdir = os.path.join(self.unit_test_dir, '9 d dedup')
+ self.init(testdir)
+ cmd = self.get_compdb()[0]['command']
+ self.assertTrue('-D FOO -D BAR' in cmd or
+ '"-D" "FOO" "-D" "BAR"' in cmd or
+ '/D FOO /D BAR' in cmd or
+ '"/D" "FOO" "/D" "BAR"' in cmd)
+
+ def test_all_forbidden_targets_tested(self):
+ '''
+ Test that all forbidden targets are tested in the '150 reserved targets'
+ test. Needs to be a unit test because it accesses Meson internals.
+ '''
+ testdir = os.path.join(self.common_test_dir, '150 reserved targets')
+ targets = mesonbuild.coredata.FORBIDDEN_TARGET_NAMES
+ # We don't actually define a target with this name
+ targets.pop('build.ninja')
+ # Remove this to avoid multiple entries with the same name
+ # but different case.
+ targets.pop('PHONY')
+ for i in targets:
+ self.assertPathExists(os.path.join(testdir, i))
+
+ def detect_prebuild_env(self):
+ env = get_fake_env()
+ cc = detect_c_compiler(env, MachineChoice.HOST)
+ stlinker = detect_static_linker(env, cc)
+ if is_windows():
+ object_suffix = 'obj'
+ shared_suffix = 'dll'
+ elif is_cygwin():
+ object_suffix = 'o'
+ shared_suffix = 'dll'
+ elif is_osx():
+ object_suffix = 'o'
+ shared_suffix = 'dylib'
+ else:
+ object_suffix = 'o'
+ shared_suffix = 'so'
+ return (cc, stlinker, object_suffix, shared_suffix)
+
+ def pbcompile(self, compiler, source, objectfile, extra_args=None):
+ cmd = compiler.get_exelist()
+ extra_args = extra_args or []
+ if compiler.get_argument_syntax() == 'msvc':
+ cmd += ['/nologo', '/Fo' + objectfile, '/c', source] + extra_args
+ else:
+ cmd += ['-c', source, '-o', objectfile] + extra_args
+ subprocess.check_call(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
+
+ def test_prebuilt_object(self):
+ (compiler, _, object_suffix, _) = self.detect_prebuild_env()
+ tdir = os.path.join(self.unit_test_dir, '15 prebuilt object')
+ source = os.path.join(tdir, 'source.c')
+ objectfile = os.path.join(tdir, 'prebuilt.' + object_suffix)
+ self.pbcompile(compiler, source, objectfile)
+ try:
+ self.init(tdir)
+ self.build()
+ self.run_tests()
+ finally:
+ os.unlink(objectfile)
+
+ def build_static_lib(self, compiler, linker, source, objectfile, outfile, extra_args=None):
+ if extra_args is None:
+ extra_args = []
+ link_cmd = linker.get_exelist()
+ link_cmd += linker.get_always_args()
+ link_cmd += linker.get_std_link_args(get_fake_env(), False)
+ link_cmd += linker.get_output_args(outfile)
+ link_cmd += [objectfile]
+ self.pbcompile(compiler, source, objectfile, extra_args=extra_args)
+ try:
+ subprocess.check_call(link_cmd)
+ finally:
+ os.unlink(objectfile)
+
+ def test_prebuilt_static_lib(self):
+ (cc, stlinker, object_suffix, _) = self.detect_prebuild_env()
+ tdir = os.path.join(self.unit_test_dir, '16 prebuilt static')
+ source = os.path.join(tdir, 'libdir/best.c')
+ objectfile = os.path.join(tdir, 'libdir/best.' + object_suffix)
+ stlibfile = os.path.join(tdir, 'libdir/libbest.a')
+ self.build_static_lib(cc, stlinker, source, objectfile, stlibfile)
+ # Run the test
+ try:
+ self.init(tdir)
+ self.build()
+ self.run_tests()
+ finally:
+ os.unlink(stlibfile)
+
+ def build_shared_lib(self, compiler, source, objectfile, outfile, impfile, extra_args=None):
+ if extra_args is None:
+ extra_args = []
+ if compiler.get_argument_syntax() == 'msvc':
+ link_cmd = compiler.get_linker_exelist() + [
+ '/NOLOGO', '/DLL', '/DEBUG', '/IMPLIB:' + impfile,
+ '/OUT:' + outfile, objectfile]
+ else:
+ if not (compiler.info.is_windows() or compiler.info.is_cygwin() or compiler.info.is_darwin()):
+ extra_args += ['-fPIC']
+ link_cmd = compiler.get_exelist() + ['-shared', '-o', outfile, objectfile]
+ if not is_osx():
+ link_cmd += ['-Wl,-soname=' + os.path.basename(outfile)]
+ self.pbcompile(compiler, source, objectfile, extra_args=extra_args)
+ try:
+ subprocess.check_call(link_cmd)
+ finally:
+ os.unlink(objectfile)
+
+ def test_prebuilt_shared_lib(self):
+ (cc, _, object_suffix, shared_suffix) = self.detect_prebuild_env()
+ tdir = os.path.join(self.unit_test_dir, '17 prebuilt shared')
+ source = os.path.join(tdir, 'alexandria.c')
+ objectfile = os.path.join(tdir, 'alexandria.' + object_suffix)
+ impfile = os.path.join(tdir, 'alexandria.lib')
+ if cc.get_argument_syntax() == 'msvc':
+ shlibfile = os.path.join(tdir, 'alexandria.' + shared_suffix)
+ elif is_cygwin():
+ shlibfile = os.path.join(tdir, 'cygalexandria.' + shared_suffix)
+ else:
+ shlibfile = os.path.join(tdir, 'libalexandria.' + shared_suffix)
+ self.build_shared_lib(cc, source, objectfile, shlibfile, impfile)
+
+ if is_windows():
+ def cleanup() -> None:
+ """Clean up all the garbage MSVC writes in the source tree."""
+
+ for fname in glob(os.path.join(tdir, 'alexandria.*')):
+ if os.path.splitext(fname)[1] not in {'.c', '.h'}:
+ os.unlink(fname)
+ self.addCleanup(cleanup)
+ else:
+ self.addCleanup(os.unlink, shlibfile)
+
+ # Run the test
+ self.init(tdir)
+ self.build()
+ self.run_tests()
+
+ def test_prebuilt_shared_lib_rpath(self) -> None:
+ (cc, _, object_suffix, shared_suffix) = self.detect_prebuild_env()
+ tdir = os.path.join(self.unit_test_dir, '17 prebuilt shared')
+ with tempfile.TemporaryDirectory() as d:
+ source = os.path.join(tdir, 'alexandria.c')
+ objectfile = os.path.join(d, 'alexandria.' + object_suffix)
+ impfile = os.path.join(d, 'alexandria.lib')
+ if cc.get_argument_syntax() == 'msvc':
+ shlibfile = os.path.join(d, 'alexandria.' + shared_suffix)
+ elif is_cygwin():
+ shlibfile = os.path.join(d, 'cygalexandria.' + shared_suffix)
+ else:
+ shlibfile = os.path.join(d, 'libalexandria.' + shared_suffix)
+ # Ensure MSVC extra files end up in the directory that gets deleted
+ # at the end
+ with chdir(d):
+ self.build_shared_lib(cc, source, objectfile, shlibfile, impfile)
+
+ # Run the test
+ self.init(tdir, extra_args=[f'-Dsearch_dir={d}'])
+ self.build()
+ self.run_tests()
+
+ def test_underscore_prefix_detection_list(self) -> None:
+ '''
+ Test the underscore detection hardcoded lookup list
+ against what was detected in the binary.
+ '''
+ env, cc = get_convincing_fake_env_and_cc(self.builddir, self.prefix)
+ expected_uscore = cc._symbols_have_underscore_prefix_searchbin(env)
+ list_uscore = cc._symbols_have_underscore_prefix_list(env)
+ if list_uscore is not None:
+ self.assertEqual(list_uscore, expected_uscore)
+ else:
+ raise SkipTest('No match in underscore prefix list for this platform.')
+
+ def test_underscore_prefix_detection_define(self) -> None:
+ '''
+ Test the underscore detection based on compiler-defined preprocessor macro
+ against what was detected in the binary.
+ '''
+ env, cc = get_convincing_fake_env_and_cc(self.builddir, self.prefix)
+ expected_uscore = cc._symbols_have_underscore_prefix_searchbin(env)
+ define_uscore = cc._symbols_have_underscore_prefix_define(env)
+ if define_uscore is not None:
+ self.assertEqual(define_uscore, expected_uscore)
+ else:
+ raise SkipTest('Did not find the underscore prefix define __USER_LABEL_PREFIX__')
+
+ @skipIfNoPkgconfig
+ def test_pkgconfig_static(self):
+ '''
+ Test that the we prefer static libraries when `static: true` is
+ passed to dependency() with pkg-config. Can't be an ordinary test
+ because we need to build libs and try to find them from meson.build
+
+ Also test that it's not a hard error to have unsatisfiable library deps
+ since system libraries -lm will never be found statically.
+ https://github.com/mesonbuild/meson/issues/2785
+ '''
+ (cc, stlinker, objext, shext) = self.detect_prebuild_env()
+ testdir = os.path.join(self.unit_test_dir, '18 pkgconfig static')
+ source = os.path.join(testdir, 'foo.c')
+ objectfile = os.path.join(testdir, 'foo.' + objext)
+ stlibfile = os.path.join(testdir, 'libfoo.a')
+ impfile = os.path.join(testdir, 'foo.lib')
+ if cc.get_argument_syntax() == 'msvc':
+ shlibfile = os.path.join(testdir, 'foo.' + shext)
+ elif is_cygwin():
+ shlibfile = os.path.join(testdir, 'cygfoo.' + shext)
+ else:
+ shlibfile = os.path.join(testdir, 'libfoo.' + shext)
+ # Build libs
+ self.build_static_lib(cc, stlinker, source, objectfile, stlibfile, extra_args=['-DFOO_STATIC'])
+ self.build_shared_lib(cc, source, objectfile, shlibfile, impfile)
+ # Run test
+ try:
+ self.init(testdir, override_envvars={'PKG_CONFIG_LIBDIR': self.builddir})
+ self.build()
+ self.run_tests()
+ finally:
+ os.unlink(stlibfile)
+ os.unlink(shlibfile)
+ if is_windows():
+ # Clean up all the garbage MSVC writes in the
+ # source tree.
+ for fname in glob(os.path.join(testdir, 'foo.*')):
+ if os.path.splitext(fname)[1] not in ['.c', '.h', '.in']:
+ os.unlink(fname)
+
+ @skipIfNoPkgconfig
+ @mock.patch.dict(os.environ)
+ def test_pkgconfig_gen_escaping(self):
+ testdir = os.path.join(self.common_test_dir, '44 pkgconfig-gen')
+ prefix = '/usr/with spaces'
+ libdir = 'lib'
+ self.init(testdir, extra_args=['--prefix=' + prefix,
+ '--libdir=' + libdir])
+ # Find foo dependency
+ os.environ['PKG_CONFIG_LIBDIR'] = self.privatedir
+ env = get_fake_env(testdir, self.builddir, self.prefix)
+ kwargs = {'required': True, 'silent': True}
+ foo_dep = PkgConfigDependency('libanswer', env, kwargs)
+ # Ensure link_args are properly quoted
+ libdir = PurePath(prefix) / PurePath(libdir)
+ link_args = ['-L' + libdir.as_posix(), '-lanswer']
+ self.assertEqual(foo_dep.get_link_args(), link_args)
+ # Ensure include args are properly quoted
+ incdir = PurePath(prefix) / PurePath('include')
+ cargs = ['-I' + incdir.as_posix(), '-DLIBFOO']
+ # pkg-config and pkgconf does not respect the same order
+ self.assertEqual(sorted(foo_dep.get_compile_args()), sorted(cargs))
+
+ @skipIfNoPkgconfig
+ def test_pkgconfig_relocatable(self):
+ '''
+ Test that it generates relocatable pkgconfig when module
+ option pkgconfig.relocatable=true.
+ '''
+ testdir_rel = os.path.join(self.common_test_dir, '44 pkgconfig-gen')
+ self.init(testdir_rel, extra_args=['-Dpkgconfig.relocatable=true'])
+
+ def check_pcfile(name, *, relocatable, levels=2):
+ with open(os.path.join(self.privatedir, name), encoding='utf-8') as f:
+ pcfile = f.read()
+ # The pkgconfig module always uses posix path regardless of platform
+ prefix_rel = PurePath('${pcfiledir}', *(['..'] * levels)).as_posix()
+ (self.assertIn if relocatable else self.assertNotIn)(
+ f'prefix={prefix_rel}\n',
+ pcfile)
+
+ check_pcfile('libvartest.pc', relocatable=True)
+ check_pcfile('libvartest2.pc', relocatable=True)
+
+ self.wipe()
+ self.init(testdir_rel, extra_args=['-Dpkgconfig.relocatable=false'])
+
+ check_pcfile('libvartest.pc', relocatable=False)
+ check_pcfile('libvartest2.pc', relocatable=False)
+
+ self.wipe()
+ testdir_abs = os.path.join(self.unit_test_dir, '105 pkgconfig relocatable with absolute path')
+ self.init(testdir_abs)
+
+ check_pcfile('libsimple.pc', relocatable=True, levels=3)
+
+ def test_array_option_change(self):
+ def get_opt():
+ opts = self.introspect('--buildoptions')
+ for x in opts:
+ if x.get('name') == 'list':
+ return x
+ raise Exception(opts)
+
+ expected = {
+ 'name': 'list',
+ 'description': 'list',
+ 'section': 'user',
+ 'type': 'array',
+ 'value': ['foo', 'bar'],
+ 'choices': ['foo', 'bar', 'oink', 'boink'],
+ 'machine': 'any',
+ }
+ tdir = os.path.join(self.unit_test_dir, '19 array option')
+ self.init(tdir)
+ original = get_opt()
+ self.assertDictEqual(original, expected)
+
+ expected['value'] = ['oink', 'boink']
+ self.setconf('-Dlist=oink,boink')
+ changed = get_opt()
+ self.assertEqual(changed, expected)
+
+ def test_array_option_bad_change(self):
+ def get_opt():
+ opts = self.introspect('--buildoptions')
+ for x in opts:
+ if x.get('name') == 'list':
+ return x
+ raise Exception(opts)
+
+ expected = {
+ 'name': 'list',
+ 'description': 'list',
+ 'section': 'user',
+ 'type': 'array',
+ 'value': ['foo', 'bar'],
+ 'choices': ['foo', 'bar', 'oink', 'boink'],
+ 'machine': 'any',
+ }
+ tdir = os.path.join(self.unit_test_dir, '19 array option')
+ self.init(tdir)
+ original = get_opt()
+ self.assertDictEqual(original, expected)
+ with self.assertRaises(subprocess.CalledProcessError):
+ self.setconf('-Dlist=bad')
+ changed = get_opt()
+ self.assertDictEqual(changed, expected)
+
+ def test_array_option_empty_equivalents(self):
+ """Array options treat -Dopt=[] and -Dopt= as equivalent."""
+ def get_opt():
+ opts = self.introspect('--buildoptions')
+ for x in opts:
+ if x.get('name') == 'list':
+ return x
+ raise Exception(opts)
+
+ expected = {
+ 'name': 'list',
+ 'description': 'list',
+ 'section': 'user',
+ 'type': 'array',
+ 'value': [],
+ 'choices': ['foo', 'bar', 'oink', 'boink'],
+ 'machine': 'any',
+ }
+ tdir = os.path.join(self.unit_test_dir, '19 array option')
+ self.init(tdir, extra_args='-Dlist=')
+ original = get_opt()
+ self.assertDictEqual(original, expected)
+
+ def opt_has(self, name, value):
+ res = self.introspect('--buildoptions')
+ found = False
+ for i in res:
+ if i['name'] == name:
+ self.assertEqual(i['value'], value)
+ found = True
+ break
+ self.assertTrue(found, "Array option not found in introspect data.")
+
+ def test_free_stringarray_setting(self):
+ testdir = os.path.join(self.common_test_dir, '40 options')
+ self.init(testdir)
+ self.opt_has('free_array_opt', [])
+ self.setconf('-Dfree_array_opt=foo,bar', will_build=False)
+ self.opt_has('free_array_opt', ['foo', 'bar'])
+ self.setconf("-Dfree_array_opt=['a,b', 'c,d']", will_build=False)
+ self.opt_has('free_array_opt', ['a,b', 'c,d'])
+
+ # When running under Travis Mac CI, the file updates seem to happen
+ # too fast so the timestamps do not get properly updated.
+ # Call this method before file operations in appropriate places
+ # to make things work.
+ def mac_ci_delay(self):
+ if is_osx() and is_ci():
+ import time
+ time.sleep(1)
+
+ def test_options_with_choices_changing(self) -> None:
+ """Detect when options like arrays or combos have their choices change."""
+ testdir = Path(os.path.join(self.unit_test_dir, '83 change option choices'))
+ options1 = str(testdir / 'meson_options.1.txt')
+ options2 = str(testdir / 'meson_options.2.txt')
+
+ # Test that old options are changed to the new defaults if they are not valid
+ real_options = str(testdir / 'meson_options.txt')
+ self.addCleanup(os.unlink, real_options)
+
+ shutil.copy(options1, real_options)
+ self.init(str(testdir))
+ self.mac_ci_delay()
+ shutil.copy(options2, real_options)
+
+ self.build()
+ opts = self.introspect('--buildoptions')
+ for item in opts:
+ if item['name'] == 'combo':
+ self.assertEqual(item['value'], 'b')
+ self.assertEqual(item['choices'], ['b', 'c', 'd'])
+ elif item['name'] == 'array':
+ self.assertEqual(item['value'], ['b'])
+ self.assertEqual(item['choices'], ['b', 'c', 'd'])
+
+ self.wipe()
+ self.mac_ci_delay()
+
+ # When the old options are valid they should remain
+ shutil.copy(options1, real_options)
+ self.init(str(testdir), extra_args=['-Dcombo=c', '-Darray=b,c'])
+ self.mac_ci_delay()
+ shutil.copy(options2, real_options)
+ self.build()
+ opts = self.introspect('--buildoptions')
+ for item in opts:
+ if item['name'] == 'combo':
+ self.assertEqual(item['value'], 'c')
+ self.assertEqual(item['choices'], ['b', 'c', 'd'])
+ elif item['name'] == 'array':
+ self.assertEqual(item['value'], ['b', 'c'])
+ self.assertEqual(item['choices'], ['b', 'c', 'd'])
+
+ def test_subproject_promotion(self):
+ testdir = os.path.join(self.unit_test_dir, '12 promote')
+ workdir = os.path.join(self.builddir, 'work')
+ shutil.copytree(testdir, workdir)
+ spdir = os.path.join(workdir, 'subprojects')
+ s3dir = os.path.join(spdir, 's3')
+ scommondir = os.path.join(spdir, 'scommon')
+ self.assertFalse(os.path.isdir(s3dir))
+ subprocess.check_call(self.wrap_command + ['promote', 's3'],
+ cwd=workdir,
+ stdout=subprocess.DEVNULL)
+ self.assertTrue(os.path.isdir(s3dir))
+ self.assertFalse(os.path.isdir(scommondir))
+ self.assertNotEqual(subprocess.call(self.wrap_command + ['promote', 'scommon'],
+ cwd=workdir,
+ stderr=subprocess.DEVNULL), 0)
+ self.assertNotEqual(subprocess.call(self.wrap_command + ['promote', 'invalid/path/to/scommon'],
+ cwd=workdir,
+ stderr=subprocess.DEVNULL), 0)
+ self.assertFalse(os.path.isdir(scommondir))
+ subprocess.check_call(self.wrap_command + ['promote', 'subprojects/s2/subprojects/scommon'], cwd=workdir)
+ self.assertTrue(os.path.isdir(scommondir))
+ promoted_wrap = os.path.join(spdir, 'athing.wrap')
+ self.assertFalse(os.path.isfile(promoted_wrap))
+ subprocess.check_call(self.wrap_command + ['promote', 'athing'], cwd=workdir)
+ self.assertTrue(os.path.isfile(promoted_wrap))
+ self.init(workdir)
+ self.build()
+
+ def test_subproject_promotion_wrap(self):
+ testdir = os.path.join(self.unit_test_dir, '43 promote wrap')
+ workdir = os.path.join(self.builddir, 'work')
+ shutil.copytree(testdir, workdir)
+ spdir = os.path.join(workdir, 'subprojects')
+
+ ambiguous_wrap = os.path.join(spdir, 'ambiguous.wrap')
+ self.assertNotEqual(subprocess.call(self.wrap_command + ['promote', 'ambiguous'],
+ cwd=workdir,
+ stderr=subprocess.DEVNULL), 0)
+ self.assertFalse(os.path.isfile(ambiguous_wrap))
+ subprocess.check_call(self.wrap_command + ['promote', 'subprojects/s2/subprojects/ambiguous.wrap'], cwd=workdir)
+ self.assertTrue(os.path.isfile(ambiguous_wrap))
+
+ def test_warning_location(self):
+ tdir = os.path.join(self.unit_test_dir, '22 warning location')
+ out = self.init(tdir)
+ for expected in [
+ r'meson.build:4: WARNING: Keyword argument "link_with" defined multiple times.',
+ r'sub' + os.path.sep + r'meson.build:3: WARNING: Keyword argument "link_with" defined multiple times.',
+ r'meson.build:6: WARNING: a warning of some sort',
+ r'sub' + os.path.sep + r'meson.build:4: WARNING: subdir warning',
+ r'meson.build:7: WARNING: Module SIMD has no backwards or forwards compatibility and might not exist in future releases.',
+ r"meson.build:11: WARNING: The variable(s) 'MISSING' in the input file 'conf.in' are not present in the given configuration data.",
+ ]:
+ with self.subTest(expected):
+ self.assertRegex(out, re.escape(expected))
+
+ for wd in [
+ self.src_root,
+ self.builddir,
+ os.getcwd(),
+ ]:
+ with self.subTest(wd):
+ self.new_builddir()
+ out = self.init(tdir, workdir=wd)
+ expected = os.path.join(relpath(tdir, self.src_root), 'meson.build')
+ relwd = relpath(self.src_root, wd)
+ if relwd != '.':
+ expected = os.path.join(relwd, expected)
+ expected = '\n' + expected + ':'
+ self.assertIn(expected, out)
+
+ def test_error_location_path(self):
+ '''Test locations in meson errors contain correct paths'''
+ # this list contains errors from all the different steps in the
+ # lexer/parser/interpreter we have tests for.
+ for (t, f) in [
+ ('10 out of bounds', 'meson.build'),
+ ('18 wrong plusassign', 'meson.build'),
+ ('59 bad option argument', 'meson_options.txt'),
+ ('97 subdir parse error', os.path.join('subdir', 'meson.build')),
+ ('98 invalid option file', 'meson_options.txt'),
+ ]:
+ tdir = os.path.join(self.src_root, 'test cases', 'failing', t)
+
+ for wd in [
+ self.src_root,
+ self.builddir,
+ os.getcwd(),
+ ]:
+ try:
+ self.init(tdir, workdir=wd)
+ except subprocess.CalledProcessError as e:
+ expected = os.path.join('test cases', 'failing', t, f)
+ relwd = relpath(self.src_root, wd)
+ if relwd != '.':
+ expected = os.path.join(relwd, expected)
+ expected = '\n' + expected + ':'
+ self.assertIn(expected, e.output)
+ else:
+ self.fail('configure unexpectedly succeeded')
+
+ def test_permitted_method_kwargs(self):
+ tdir = os.path.join(self.unit_test_dir, '25 non-permitted kwargs')
+ with self.assertRaises(subprocess.CalledProcessError) as cm:
+ self.init(tdir)
+ self.assertIn('ERROR: compiler.has_header_symbol got unknown keyword arguments "prefixxx"', cm.exception.output)
+
+ def test_templates(self):
+ ninja = mesonbuild.environment.detect_ninja()
+ if ninja is None:
+ raise SkipTest('This test currently requires ninja. Fix this once "meson build" works.')
+
+ langs = ['c']
+ env = get_fake_env()
+ for l in ['cpp', 'cs', 'd', 'java', 'cuda', 'fortran', 'objc', 'objcpp', 'rust']:
+ try:
+ comp = detect_compiler_for(env, l, MachineChoice.HOST)
+ with tempfile.TemporaryDirectory() as d:
+ comp.sanity_check(d, env)
+ langs.append(l)
+ except EnvironmentException:
+ pass
+
+ # The D template fails under mac CI and we don't know why.
+ # Patches welcome
+ if is_osx():
+ langs = [l for l in langs if l != 'd']
+
+ for lang in langs:
+ for target_type in ('executable', 'library'):
+ if is_windows() and lang == 'fortran' and target_type == 'library':
+ # non-Gfortran Windows Fortran compilers do not do shared libraries in a Fortran standard way
+ # see "test cases/fortran/6 dynamic"
+ fc = detect_compiler_for(env, 'fortran', MachineChoice.HOST)
+ if fc.get_id() in {'intel-cl', 'pgi'}:
+ continue
+ # test empty directory
+ with tempfile.TemporaryDirectory() as tmpdir:
+ self._run(self.meson_command + ['init', '--language', lang, '--type', target_type],
+ workdir=tmpdir)
+ self._run(self.setup_command + ['--backend=ninja', 'builddir'],
+ workdir=tmpdir)
+ self._run(ninja,
+ workdir=os.path.join(tmpdir, 'builddir'))
+ # test directory with existing code file
+ if lang in {'c', 'cpp', 'd'}:
+ with tempfile.TemporaryDirectory() as tmpdir:
+ with open(os.path.join(tmpdir, 'foo.' + lang), 'w', encoding='utf-8') as f:
+ f.write('int main(void) {}')
+ self._run(self.meson_command + ['init', '-b'], workdir=tmpdir)
+ elif lang in {'java'}:
+ with tempfile.TemporaryDirectory() as tmpdir:
+ with open(os.path.join(tmpdir, 'Foo.' + lang), 'w', encoding='utf-8') as f:
+ f.write('public class Foo { public static void main() {} }')
+ self._run(self.meson_command + ['init', '-b'], workdir=tmpdir)
+
+ def test_compiler_run_command(self):
+ '''
+ The test checks that the compiler object can be passed to
+ run_command().
+ '''
+ testdir = os.path.join(self.unit_test_dir, '24 compiler run_command')
+ self.init(testdir)
+
+ def test_identical_target_name_in_subproject_flat_layout(self):
+ '''
+ Test that identical targets in different subprojects do not collide
+ if layout is flat.
+ '''
+ testdir = os.path.join(self.common_test_dir, '172 identical target name in subproject flat layout')
+ self.init(testdir, extra_args=['--layout=flat'])
+ self.build()
+
+ def test_identical_target_name_in_subdir_flat_layout(self):
+ '''
+ Test that identical targets in different subdirs do not collide
+ if layout is flat.
+ '''
+ testdir = os.path.join(self.common_test_dir, '181 same target name flat layout')
+ self.init(testdir, extra_args=['--layout=flat'])
+ self.build()
+
+ def test_flock(self):
+ exception_raised = False
+ with tempfile.TemporaryDirectory() as tdir:
+ os.mkdir(os.path.join(tdir, 'meson-private'))
+ with BuildDirLock(tdir):
+ try:
+ with BuildDirLock(tdir):
+ pass
+ except MesonException:
+ exception_raised = True
+ self.assertTrue(exception_raised, 'Double locking did not raise exception.')
+
+ @skipIf(is_osx(), 'Test not applicable to OSX')
+ def test_check_module_linking(self):
+ """
+ Test that link_with: a shared module issues a warning
+ https://github.com/mesonbuild/meson/issues/2865
+ (That an error is raised on OSX is exercised by test failing/78)
+ """
+ tdir = os.path.join(self.unit_test_dir, '30 shared_mod linking')
+ out = self.init(tdir)
+ msg = ('''DEPRECATION: target prog links against shared module mymod, which is incorrect.
+ This will be an error in the future, so please use shared_library() for mymod instead.
+ If shared_module() was used for mymod because it has references to undefined symbols,
+ use shared_libary() with `override_options: ['b_lundef=false']` instead.''')
+ self.assertIn(msg, out)
+
+ def test_mixed_language_linker_check(self):
+ testdir = os.path.join(self.unit_test_dir, '96 compiler.links file arg')
+ self.init(testdir)
+ cmds = self.get_meson_log_compiler_checks()
+ self.assertEqual(len(cmds), 5)
+ # Path to the compilers, gleaned from cc.compiles tests
+ cc = cmds[0][0]
+ cxx = cmds[1][0]
+ # cc.links
+ self.assertEqual(cmds[2][0], cc)
+ # cxx.links with C source
+ self.assertEqual(cmds[3][0], cc)
+ self.assertEqual(cmds[4][0], cxx)
+ if self.backend is Backend.ninja:
+ # updating the file to check causes a reconfigure
+ #
+ # only the ninja backend is competent enough to detect reconfigured
+ # no-op builds without build targets
+ self.utime(os.path.join(testdir, 'test.c'))
+ self.assertReconfiguredBuildIsNoop()
+
+ def test_ndebug_if_release_disabled(self):
+ testdir = os.path.join(self.unit_test_dir, '28 ndebug if-release')
+ self.init(testdir, extra_args=['--buildtype=release', '-Db_ndebug=if-release'])
+ self.build()
+ exe = os.path.join(self.builddir, 'main')
+ self.assertEqual(b'NDEBUG=1', subprocess.check_output(exe).strip())
+
+ def test_ndebug_if_release_enabled(self):
+ testdir = os.path.join(self.unit_test_dir, '28 ndebug if-release')
+ self.init(testdir, extra_args=['--buildtype=debugoptimized', '-Db_ndebug=if-release'])
+ self.build()
+ exe = os.path.join(self.builddir, 'main')
+ self.assertEqual(b'NDEBUG=0', subprocess.check_output(exe).strip())
+
+ def test_guessed_linker_dependencies(self):
+ '''
+ Test that meson adds dependencies for libraries based on the final
+ linker command line.
+ '''
+ testdirbase = os.path.join(self.unit_test_dir, '29 guessed linker dependencies')
+ testdirlib = os.path.join(testdirbase, 'lib')
+
+ extra_args = None
+ libdir_flags = ['-L']
+ env = get_fake_env(testdirlib, self.builddir, self.prefix)
+ if detect_c_compiler(env, MachineChoice.HOST).get_id() in {'msvc', 'clang-cl', 'intel-cl'}:
+ # msvc-like compiler, also test it with msvc-specific flags
+ libdir_flags += ['/LIBPATH:', '-LIBPATH:']
+ else:
+ # static libraries are not linkable with -l with msvc because meson installs them
+ # as .a files which unix_args_to_native will not know as it expects libraries to use
+ # .lib as extension. For a DLL the import library is installed as .lib. Thus for msvc
+ # this tests needs to use shared libraries to test the path resolving logic in the
+ # dependency generation code path.
+ extra_args = ['--default-library', 'static']
+
+ initial_builddir = self.builddir
+ initial_installdir = self.installdir
+
+ for libdir_flag in libdir_flags:
+ # build library
+ self.new_builddir()
+ self.init(testdirlib, extra_args=extra_args)
+ self.build()
+ self.install()
+ libbuilddir = self.builddir
+ installdir = self.installdir
+ libdir = os.path.join(self.installdir, self.prefix.lstrip('/').lstrip('\\'), 'lib')
+
+ # build user of library
+ self.new_builddir()
+ # replace is needed because meson mangles platform paths passed via LDFLAGS
+ self.init(os.path.join(testdirbase, 'exe'),
+ override_envvars={"LDFLAGS": '{}{}'.format(libdir_flag, libdir.replace('\\', '/'))})
+ self.build()
+ self.assertBuildIsNoop()
+
+ # rebuild library
+ exebuilddir = self.builddir
+ self.installdir = installdir
+ self.builddir = libbuilddir
+ # Microsoft's compiler is quite smart about touching import libs on changes,
+ # so ensure that there is actually a change in symbols.
+ self.setconf('-Dmore_exports=true')
+ self.build()
+ self.install()
+ # no ensure_backend_detects_changes needed because self.setconf did that already
+
+ # assert user of library will be rebuild
+ self.builddir = exebuilddir
+ self.assertRebuiltTarget('app')
+
+ # restore dirs for the next test case
+ self.installdir = initial_builddir
+ self.builddir = initial_installdir
+
+ def test_conflicting_d_dash_option(self):
+ testdir = os.path.join(self.unit_test_dir, '37 mixed command line args')
+ with self.assertRaises((subprocess.CalledProcessError, RuntimeError)) as e:
+ self.init(testdir, extra_args=['-Dbindir=foo', '--bindir=bar'])
+ # Just to ensure that we caught the correct error
+ self.assertIn('as both', e.stderr)
+
+ def _test_same_option_twice(self, arg, args):
+ testdir = os.path.join(self.unit_test_dir, '37 mixed command line args')
+ self.init(testdir, extra_args=args)
+ opts = self.introspect('--buildoptions')
+ for item in opts:
+ if item['name'] == arg:
+ self.assertEqual(item['value'], 'bar')
+ return
+ raise Exception(f'Missing {arg} value?')
+
+ def test_same_dash_option_twice(self):
+ self._test_same_option_twice('bindir', ['--bindir=foo', '--bindir=bar'])
+
+ def test_same_d_option_twice(self):
+ self._test_same_option_twice('bindir', ['-Dbindir=foo', '-Dbindir=bar'])
+
+ def test_same_project_d_option_twice(self):
+ self._test_same_option_twice('one', ['-Done=foo', '-Done=bar'])
+
+ def _test_same_option_twice_configure(self, arg, args):
+ testdir = os.path.join(self.unit_test_dir, '37 mixed command line args')
+ self.init(testdir)
+ self.setconf(args)
+ opts = self.introspect('--buildoptions')
+ for item in opts:
+ if item['name'] == arg:
+ self.assertEqual(item['value'], 'bar')
+ return
+ raise Exception(f'Missing {arg} value?')
+
+ def test_same_dash_option_twice_configure(self):
+ self._test_same_option_twice_configure(
+ 'bindir', ['--bindir=foo', '--bindir=bar'])
+
+ def test_same_d_option_twice_configure(self):
+ self._test_same_option_twice_configure(
+ 'bindir', ['-Dbindir=foo', '-Dbindir=bar'])
+
+ def test_same_project_d_option_twice_configure(self):
+ self._test_same_option_twice_configure(
+ 'one', ['-Done=foo', '-Done=bar'])
+
+ def test_command_line(self):
+ testdir = os.path.join(self.unit_test_dir, '34 command line')
+
+ # Verify default values when passing no args that affect the
+ # configuration, and as a bonus, test that --profile-self works.
+ out = self.init(testdir, extra_args=['--profile-self', '--fatal-meson-warnings'])
+ self.assertNotIn('[default: true]', out)
+ obj = mesonbuild.coredata.load(self.builddir)
+ self.assertEqual(obj.options[OptionKey('default_library')].value, 'static')
+ self.assertEqual(obj.options[OptionKey('warning_level')].value, '1')
+ self.assertEqual(obj.options[OptionKey('set_sub_opt')].value, True)
+ self.assertEqual(obj.options[OptionKey('subp_opt', 'subp')].value, 'default3')
+ self.wipe()
+
+ # warning_level is special, it's --warnlevel instead of --warning-level
+ # for historical reasons
+ self.init(testdir, extra_args=['--warnlevel=2', '--fatal-meson-warnings'])
+ obj = mesonbuild.coredata.load(self.builddir)
+ self.assertEqual(obj.options[OptionKey('warning_level')].value, '2')
+ self.setconf('--warnlevel=3')
+ obj = mesonbuild.coredata.load(self.builddir)
+ self.assertEqual(obj.options[OptionKey('warning_level')].value, '3')
+ self.setconf('--warnlevel=everything')
+ obj = mesonbuild.coredata.load(self.builddir)
+ self.assertEqual(obj.options[OptionKey('warning_level')].value, 'everything')
+ self.wipe()
+
+ # But when using -D syntax, it should be 'warning_level'
+ self.init(testdir, extra_args=['-Dwarning_level=2', '--fatal-meson-warnings'])
+ obj = mesonbuild.coredata.load(self.builddir)
+ self.assertEqual(obj.options[OptionKey('warning_level')].value, '2')
+ self.setconf('-Dwarning_level=3')
+ obj = mesonbuild.coredata.load(self.builddir)
+ self.assertEqual(obj.options[OptionKey('warning_level')].value, '3')
+ self.setconf('-Dwarning_level=everything')
+ obj = mesonbuild.coredata.load(self.builddir)
+ self.assertEqual(obj.options[OptionKey('warning_level')].value, 'everything')
+ self.wipe()
+
+ # Mixing --option and -Doption is forbidden
+ with self.assertRaises((subprocess.CalledProcessError, RuntimeError)) as cm:
+ self.init(testdir, extra_args=['--warnlevel=1', '-Dwarning_level=3'])
+ if isinstance(cm.exception, subprocess.CalledProcessError):
+ self.assertNotEqual(0, cm.exception.returncode)
+ self.assertIn('as both', cm.exception.output)
+ else:
+ self.assertIn('as both', str(cm.exception))
+ self.init(testdir)
+ with self.assertRaises((subprocess.CalledProcessError, RuntimeError)) as cm:
+ self.setconf(['--warnlevel=1', '-Dwarning_level=3'])
+ if isinstance(cm.exception, subprocess.CalledProcessError):
+ self.assertNotEqual(0, cm.exception.returncode)
+ self.assertIn('as both', cm.exception.output)
+ else:
+ self.assertIn('as both', str(cm.exception))
+ self.wipe()
+
+ # --default-library should override default value from project()
+ self.init(testdir, extra_args=['--default-library=both', '--fatal-meson-warnings'])
+ obj = mesonbuild.coredata.load(self.builddir)
+ self.assertEqual(obj.options[OptionKey('default_library')].value, 'both')
+ self.setconf('--default-library=shared')
+ obj = mesonbuild.coredata.load(self.builddir)
+ self.assertEqual(obj.options[OptionKey('default_library')].value, 'shared')
+ if self.backend is Backend.ninja:
+ # reconfigure target works only with ninja backend
+ self.build('reconfigure')
+ obj = mesonbuild.coredata.load(self.builddir)
+ self.assertEqual(obj.options[OptionKey('default_library')].value, 'shared')
+ self.wipe()
+
+ # Should fail on unknown options
+ with self.assertRaises((subprocess.CalledProcessError, RuntimeError)) as cm:
+ self.init(testdir, extra_args=['-Dbad=1', '-Dfoo=2', '-Dwrong_link_args=foo'])
+ self.assertNotEqual(0, cm.exception.returncode)
+ self.assertIn(msg, cm.exception.output)
+ self.wipe()
+
+ # Should fail on malformed option
+ msg = "Option 'foo' must have a value separated by equals sign."
+ with self.assertRaises((subprocess.CalledProcessError, RuntimeError)) as cm:
+ self.init(testdir, extra_args=['-Dfoo'])
+ if isinstance(cm.exception, subprocess.CalledProcessError):
+ self.assertNotEqual(0, cm.exception.returncode)
+ self.assertIn(msg, cm.exception.output)
+ else:
+ self.assertIn(msg, str(cm.exception))
+ self.init(testdir)
+ with self.assertRaises((subprocess.CalledProcessError, RuntimeError)) as cm:
+ self.setconf('-Dfoo')
+ if isinstance(cm.exception, subprocess.CalledProcessError):
+ self.assertNotEqual(0, cm.exception.returncode)
+ self.assertIn(msg, cm.exception.output)
+ else:
+ self.assertIn(msg, str(cm.exception))
+ self.wipe()
+
+ # It is not an error to set wrong option for unknown subprojects or
+ # language because we don't have control on which one will be selected.
+ self.init(testdir, extra_args=['-Dc_wrong=1', '-Dwrong:bad=1', '-Db_wrong=1'])
+ self.wipe()
+
+ # Test we can set subproject option
+ self.init(testdir, extra_args=['-Dsubp:subp_opt=foo', '--fatal-meson-warnings'])
+ obj = mesonbuild.coredata.load(self.builddir)
+ self.assertEqual(obj.options[OptionKey('subp_opt', 'subp')].value, 'foo')
+ self.wipe()
+
+ # c_args value should be parsed with split_args
+ self.init(testdir, extra_args=['-Dc_args=-Dfoo -Dbar "-Dthird=one two"', '--fatal-meson-warnings'])
+ obj = mesonbuild.coredata.load(self.builddir)
+ self.assertEqual(obj.options[OptionKey('args', lang='c')].value, ['-Dfoo', '-Dbar', '-Dthird=one two'])
+
+ self.setconf('-Dc_args="foo bar" one two')
+ obj = mesonbuild.coredata.load(self.builddir)
+ self.assertEqual(obj.options[OptionKey('args', lang='c')].value, ['foo bar', 'one', 'two'])
+ self.wipe()
+
+ self.init(testdir, extra_args=['-Dset_percent_opt=myoption%', '--fatal-meson-warnings'])
+ obj = mesonbuild.coredata.load(self.builddir)
+ self.assertEqual(obj.options[OptionKey('set_percent_opt')].value, 'myoption%')
+ self.wipe()
+
+ # Setting a 2nd time the same option should override the first value
+ try:
+ self.init(testdir, extra_args=['--bindir=foo', '--bindir=bar',
+ '-Dbuildtype=plain', '-Dbuildtype=release',
+ '-Db_sanitize=address', '-Db_sanitize=thread',
+ '-Dc_args=-Dfoo', '-Dc_args=-Dbar',
+ '-Db_lundef=false', '--fatal-meson-warnings'])
+ obj = mesonbuild.coredata.load(self.builddir)
+ self.assertEqual(obj.options[OptionKey('bindir')].value, 'bar')
+ self.assertEqual(obj.options[OptionKey('buildtype')].value, 'release')
+ self.assertEqual(obj.options[OptionKey('b_sanitize')].value, 'thread')
+ self.assertEqual(obj.options[OptionKey('args', lang='c')].value, ['-Dbar'])
+ self.setconf(['--bindir=bar', '--bindir=foo',
+ '-Dbuildtype=release', '-Dbuildtype=plain',
+ '-Db_sanitize=thread', '-Db_sanitize=address',
+ '-Dc_args=-Dbar', '-Dc_args=-Dfoo'])
+ obj = mesonbuild.coredata.load(self.builddir)
+ self.assertEqual(obj.options[OptionKey('bindir')].value, 'foo')
+ self.assertEqual(obj.options[OptionKey('buildtype')].value, 'plain')
+ self.assertEqual(obj.options[OptionKey('b_sanitize')].value, 'address')
+ self.assertEqual(obj.options[OptionKey('args', lang='c')].value, ['-Dfoo'])
+ self.wipe()
+ except KeyError:
+ # Ignore KeyError, it happens on CI for compilers that does not
+ # support b_sanitize. We have to test with a base option because
+ # they used to fail this test with Meson 0.46 an earlier versions.
+ pass
+
+ def test_warning_level_0(self):
+ testdir = os.path.join(self.common_test_dir, '207 warning level 0')
+
+ # Verify default values when passing no args
+ self.init(testdir)
+ obj = mesonbuild.coredata.load(self.builddir)
+ self.assertEqual(obj.options[OptionKey('warning_level')].value, '0')
+ self.wipe()
+
+ # verify we can override w/ --warnlevel
+ self.init(testdir, extra_args=['--warnlevel=1'])
+ obj = mesonbuild.coredata.load(self.builddir)
+ self.assertEqual(obj.options[OptionKey('warning_level')].value, '1')
+ self.setconf('--warnlevel=0')
+ obj = mesonbuild.coredata.load(self.builddir)
+ self.assertEqual(obj.options[OptionKey('warning_level')].value, '0')
+ self.wipe()
+
+ # verify we can override w/ -Dwarning_level
+ self.init(testdir, extra_args=['-Dwarning_level=1'])
+ obj = mesonbuild.coredata.load(self.builddir)
+ self.assertEqual(obj.options[OptionKey('warning_level')].value, '1')
+ self.setconf('-Dwarning_level=0')
+ obj = mesonbuild.coredata.load(self.builddir)
+ self.assertEqual(obj.options[OptionKey('warning_level')].value, '0')
+ self.wipe()
+
+ def test_feature_check_usage_subprojects(self):
+ testdir = os.path.join(self.unit_test_dir, '40 featurenew subprojects')
+ out = self.init(testdir)
+ # Parent project warns correctly
+ self.assertRegex(out, "WARNING: Project targets '>=0.45'.*'0.47.0': dict")
+ # Subprojects warn correctly
+ self.assertRegex(out, r"foo\| .*WARNING: Project targets '>=0.40'.*'0.44.0': disabler")
+ self.assertRegex(out, r"baz\| .*WARNING: Project targets '!=0.40'.*'0.44.0': disabler")
+ # Subproject has a new-enough meson_version, no warning
+ self.assertNotRegex(out, "WARNING: Project targets.*Python")
+ # Ensure a summary is printed in the subproject and the outer project
+ self.assertRegex(out, r"\| WARNING: Project specifies a minimum meson_version '>=0.40'")
+ self.assertRegex(out, r"\| \* 0.44.0: {'disabler'}")
+ self.assertRegex(out, "WARNING: Project specifies a minimum meson_version '>=0.45'")
+ self.assertRegex(out, " * 0.47.0: {'dict'}")
+
+ def test_configure_file_warnings(self):
+ testdir = os.path.join(self.common_test_dir, "14 configure file")
+ out = self.init(testdir)
+ self.assertRegex(out, "WARNING:.*'empty'.*config.h.in.*not present.*")
+ self.assertRegex(out, "WARNING:.*'FOO_BAR'.*nosubst-nocopy2.txt.in.*not present.*")
+ self.assertRegex(out, "WARNING:.*'empty'.*config.h.in.*not present.*")
+ self.assertRegex(out, "WARNING:.*empty configuration_data.*test.py.in")
+ # Warnings for configuration files that are overwritten.
+ self.assertRegex(out, "WARNING:.*\"double_output.txt\".*overwrites")
+ self.assertRegex(out, "WARNING:.*\"subdir.double_output2.txt\".*overwrites")
+ self.assertNotRegex(out, "WARNING:.*no_write_conflict.txt.*overwrites")
+ self.assertNotRegex(out, "WARNING:.*@BASENAME@.*overwrites")
+ self.assertRegex(out, "WARNING:.*\"sameafterbasename\".*overwrites")
+ # No warnings about empty configuration data objects passed to files with substitutions
+ self.assertNotRegex(out, "WARNING:.*empty configuration_data.*nosubst-nocopy1.txt.in")
+ self.assertNotRegex(out, "WARNING:.*empty configuration_data.*nosubst-nocopy2.txt.in")
+ with open(os.path.join(self.builddir, 'nosubst-nocopy1.txt'), 'rb') as f:
+ self.assertEqual(f.read().strip(), b'/* #undef FOO_BAR */')
+ with open(os.path.join(self.builddir, 'nosubst-nocopy2.txt'), 'rb') as f:
+ self.assertEqual(f.read().strip(), b'')
+
+ def test_dirs(self):
+ with tempfile.TemporaryDirectory() as containing:
+ with tempfile.TemporaryDirectory(dir=containing) as srcdir:
+ mfile = os.path.join(srcdir, 'meson.build')
+ of = open(mfile, 'w', encoding='utf-8')
+ of.write("project('foobar', 'c')\n")
+ of.close()
+ pc = subprocess.run(self.setup_command,
+ cwd=srcdir,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.DEVNULL)
+ self.assertIn(b'Must specify at least one directory name', pc.stdout)
+ with tempfile.TemporaryDirectory(dir=srcdir) as builddir:
+ subprocess.run(self.setup_command,
+ check=True,
+ cwd=builddir,
+ stdout=subprocess.DEVNULL,
+ stderr=subprocess.DEVNULL)
+
+ def get_opts_as_dict(self):
+ result = {}
+ for i in self.introspect('--buildoptions'):
+ result[i['name']] = i['value']
+ return result
+
+ def test_buildtype_setting(self):
+ testdir = os.path.join(self.common_test_dir, '1 trivial')
+ self.init(testdir)
+ opts = self.get_opts_as_dict()
+ self.assertEqual(opts['buildtype'], 'debug')
+ self.assertEqual(opts['debug'], True)
+ self.setconf('-Ddebug=false')
+ opts = self.get_opts_as_dict()
+ self.assertEqual(opts['debug'], False)
+ self.assertEqual(opts['buildtype'], 'debug')
+ self.assertEqual(opts['optimization'], '0')
+ self.setconf('-Doptimization=g')
+ opts = self.get_opts_as_dict()
+ self.assertEqual(opts['debug'], False)
+ self.assertEqual(opts['buildtype'], 'debug')
+ self.assertEqual(opts['optimization'], 'g')
+
+ @skipIfNoPkgconfig
+ @skipIf(is_windows(), 'Help needed with fixing this test on windows')
+ def test_native_dep_pkgconfig(self):
+ testdir = os.path.join(self.unit_test_dir,
+ '45 native dep pkgconfig var')
+ with tempfile.NamedTemporaryFile(mode='w', delete=False) as crossfile:
+ crossfile.write(textwrap.dedent(
+ '''[binaries]
+ pkgconfig = '{}'
+
+ [properties]
+
+ [host_machine]
+ system = 'linux'
+ cpu_family = 'arm'
+ cpu = 'armv7'
+ endian = 'little'
+ '''.format(os.path.join(testdir, 'cross_pkgconfig.py'))))
+ crossfile.flush()
+ self.meson_cross_files = [crossfile.name]
+
+ env = {'PKG_CONFIG_LIBDIR': os.path.join(testdir,
+ 'native_pkgconfig')}
+ self.init(testdir, extra_args=['-Dstart_native=false'], override_envvars=env)
+ self.wipe()
+ self.init(testdir, extra_args=['-Dstart_native=true'], override_envvars=env)
+
+ @skipIfNoPkgconfig
+ @skipIf(is_windows(), 'Help needed with fixing this test on windows')
+ def test_pkg_config_libdir(self):
+ testdir = os.path.join(self.unit_test_dir,
+ '45 native dep pkgconfig var')
+ with tempfile.NamedTemporaryFile(mode='w', delete=False) as crossfile:
+ crossfile.write(textwrap.dedent(
+ '''[binaries]
+ pkgconfig = 'pkg-config'
+
+ [properties]
+ pkg_config_libdir = ['{}']
+
+ [host_machine]
+ system = 'linux'
+ cpu_family = 'arm'
+ cpu = 'armv7'
+ endian = 'little'
+ '''.format(os.path.join(testdir, 'cross_pkgconfig'))))
+ crossfile.flush()
+ self.meson_cross_files = [crossfile.name]
+
+ env = {'PKG_CONFIG_LIBDIR': os.path.join(testdir,
+ 'native_pkgconfig')}
+ self.init(testdir, extra_args=['-Dstart_native=false'], override_envvars=env)
+ self.wipe()
+ self.init(testdir, extra_args=['-Dstart_native=true'], override_envvars=env)
+
+ def __reconfigure(self):
+ # Set an older version to force a reconfigure from scratch
+ filename = os.path.join(self.privatedir, 'coredata.dat')
+ with open(filename, 'rb') as f:
+ obj = pickle.load(f)
+ obj.version = '0.47.0'
+ with open(filename, 'wb') as f:
+ pickle.dump(obj, f)
+
+ def test_reconfigure(self):
+ testdir = os.path.join(self.unit_test_dir, '47 reconfigure')
+ self.init(testdir, extra_args=['-Dopt1=val1', '-Dsub1:werror=true'])
+ self.setconf('-Dopt2=val2')
+
+ self.__reconfigure()
+
+ out = self.init(testdir, extra_args=['--reconfigure', '-Dopt3=val3'])
+ self.assertRegex(out, 'Regenerating configuration from scratch')
+ self.assertRegex(out, 'opt1 val1')
+ self.assertRegex(out, 'opt2 val2')
+ self.assertRegex(out, 'opt3 val3')
+ self.assertRegex(out, 'opt4 default4')
+ self.assertRegex(out, 'sub1:werror true')
+ self.build()
+ self.run_tests()
+
+ # Create a file in builddir and verify wipe command removes it
+ filename = os.path.join(self.builddir, 'something')
+ open(filename, 'w', encoding='utf-8').close()
+ self.assertTrue(os.path.exists(filename))
+ out = self.init(testdir, extra_args=['--wipe', '-Dopt4=val4'])
+ self.assertFalse(os.path.exists(filename))
+ self.assertRegex(out, 'opt1 val1')
+ self.assertRegex(out, 'opt2 val2')
+ self.assertRegex(out, 'opt3 val3')
+ self.assertRegex(out, 'opt4 val4')
+ self.assertRegex(out, 'sub1:werror true')
+ self.assertTrue(Path(self.builddir, '.gitignore').exists())
+ self.build()
+ self.run_tests()
+
+ def test_wipe_from_builddir(self):
+ testdir = os.path.join(self.common_test_dir, '157 custom target subdir depend files')
+ self.init(testdir)
+ self.__reconfigure()
+ self.init(testdir, extra_args=['--wipe'], workdir=self.builddir)
+
+ def test_target_construct_id_from_path(self):
+ # This id is stable but not guessable.
+ # The test is supposed to prevent unintentional
+ # changes of target ID generation.
+ target_id = Target.construct_id_from_path('some/obscure/subdir',
+ 'target-id', '@suffix')
+ self.assertEqual('5e002d3@@target-id@suffix', target_id)
+ target_id = Target.construct_id_from_path('subproject/foo/subdir/bar',
+ 'target2-id', '@other')
+ self.assertEqual('81d46d1@@target2-id@other', target_id)
+
+ def test_introspect_projectinfo_without_configured_build(self):
+ testfile = os.path.join(self.common_test_dir, '33 run program', 'meson.build')
+ res = self.introspect_directory(testfile, '--projectinfo')
+ self.assertEqual(set(res['buildsystem_files']), {'meson.build'})
+ self.assertEqual(res['version'], 'undefined')
+ self.assertEqual(res['descriptive_name'], 'run command')
+ self.assertEqual(res['subprojects'], [])
+
+ testfile = os.path.join(self.common_test_dir, '40 options', 'meson.build')
+ res = self.introspect_directory(testfile, '--projectinfo')
+ self.assertEqual(set(res['buildsystem_files']), {'meson_options.txt', 'meson.build'})
+ self.assertEqual(res['version'], 'undefined')
+ self.assertEqual(res['descriptive_name'], 'options')
+ self.assertEqual(res['subprojects'], [])
+
+ testfile = os.path.join(self.common_test_dir, '43 subproject options', 'meson.build')
+ res = self.introspect_directory(testfile, '--projectinfo')
+ self.assertEqual(set(res['buildsystem_files']), {'meson_options.txt', 'meson.build'})
+ self.assertEqual(res['version'], 'undefined')
+ self.assertEqual(res['descriptive_name'], 'suboptions')
+ self.assertEqual(len(res['subprojects']), 1)
+ subproject_files = {f.replace('\\', '/') for f in res['subprojects'][0]['buildsystem_files']}
+ self.assertEqual(subproject_files, {'subprojects/subproject/meson_options.txt', 'subprojects/subproject/meson.build'})
+ self.assertEqual(res['subprojects'][0]['name'], 'subproject')
+ self.assertEqual(res['subprojects'][0]['version'], 'undefined')
+ self.assertEqual(res['subprojects'][0]['descriptive_name'], 'subproject')
+
+ def test_introspect_projectinfo_subprojects(self):
+ testdir = os.path.join(self.common_test_dir, '98 subproject subdir')
+ self.init(testdir)
+ res = self.introspect('--projectinfo')
+ expected = {
+ 'descriptive_name': 'proj',
+ 'version': 'undefined',
+ 'subproject_dir': 'subprojects',
+ 'subprojects': [
+ {
+ 'descriptive_name': 'sub',
+ 'name': 'sub',
+ 'version': '1.0'
+ },
+ {
+ 'descriptive_name': 'sub_implicit',
+ 'name': 'sub_implicit',
+ 'version': '1.0',
+ },
+ {
+ 'descriptive_name': 'sub-novar',
+ 'name': 'sub_novar',
+ 'version': '1.0',
+ },
+ {
+ 'descriptive_name': 'sub_static',
+ 'name': 'sub_static',
+ 'version': 'undefined'
+ },
+ {
+ 'descriptive_name': 'subsub',
+ 'name': 'subsub',
+ 'version': 'undefined'
+ },
+ {
+ 'descriptive_name': 'subsubsub',
+ 'name': 'subsubsub',
+ 'version': 'undefined'
+ },
+ ]
+ }
+ res['subprojects'] = sorted(res['subprojects'], key=lambda i: i['name'])
+ self.assertDictEqual(expected, res)
+
+ def test_introspection_target_subproject(self):
+ testdir = os.path.join(self.common_test_dir, '42 subproject')
+ self.init(testdir)
+ res = self.introspect('--targets')
+
+ expected = {
+ 'sublib': 'sublib',
+ 'simpletest': 'sublib',
+ 'user': None
+ }
+
+ for entry in res:
+ name = entry['name']
+ self.assertEqual(entry['subproject'], expected[name])
+
+ def test_introspect_projectinfo_subproject_dir(self):
+ testdir = os.path.join(self.common_test_dir, '75 custom subproject dir')
+ self.init(testdir)
+ res = self.introspect('--projectinfo')
+
+ self.assertEqual(res['subproject_dir'], 'custom_subproject_dir')
+
+ def test_introspect_projectinfo_subproject_dir_from_source(self):
+ testfile = os.path.join(self.common_test_dir, '75 custom subproject dir', 'meson.build')
+ res = self.introspect_directory(testfile, '--projectinfo')
+
+ self.assertEqual(res['subproject_dir'], 'custom_subproject_dir')
+
+ @skipIfNoExecutable('clang-format')
+ def test_clang_format(self):
+ if self.backend is not Backend.ninja:
+ raise SkipTest(f'Clang-format is for now only supported on Ninja, not {self.backend.name}')
+ testdir = os.path.join(self.unit_test_dir, '53 clang-format')
+
+ # Ensure that test project is in git even when running meson from tarball.
+ srcdir = os.path.join(self.builddir, 'src')
+ shutil.copytree(testdir, srcdir)
+ git_init(srcdir)
+ testdir = srcdir
+ self.new_builddir()
+
+ testfile = os.path.join(testdir, 'prog.c')
+ badfile = os.path.join(testdir, 'prog_orig_c')
+ goodfile = os.path.join(testdir, 'prog_expected_c')
+ testheader = os.path.join(testdir, 'header.h')
+ badheader = os.path.join(testdir, 'header_orig_h')
+ goodheader = os.path.join(testdir, 'header_expected_h')
+ includefile = os.path.join(testdir, '.clang-format-include')
+ try:
+ shutil.copyfile(badfile, testfile)
+ shutil.copyfile(badheader, testheader)
+ self.init(testdir)
+ self.assertNotEqual(Path(testfile).read_text(encoding='utf-8'),
+ Path(goodfile).read_text(encoding='utf-8'))
+ self.assertNotEqual(Path(testheader).read_text(encoding='utf-8'),
+ Path(goodheader).read_text(encoding='utf-8'))
+
+ # test files are not in git so this should do nothing
+ self.run_target('clang-format')
+ self.assertNotEqual(Path(testfile).read_text(encoding='utf-8'),
+ Path(goodfile).read_text(encoding='utf-8'))
+ self.assertNotEqual(Path(testheader).read_text(encoding='utf-8'),
+ Path(goodheader).read_text(encoding='utf-8'))
+
+ # Add an include file to reformat everything
+ with open(includefile, 'w', encoding='utf-8') as f:
+ f.write('*')
+ self.run_target('clang-format')
+ self.assertEqual(Path(testheader).read_text(encoding='utf-8'),
+ Path(goodheader).read_text(encoding='utf-8'))
+ finally:
+ if os.path.exists(testfile):
+ os.unlink(testfile)
+ if os.path.exists(testheader):
+ os.unlink(testheader)
+ if os.path.exists(includefile):
+ os.unlink(includefile)
+
+ @skipIfNoExecutable('clang-tidy')
+ def test_clang_tidy(self):
+ if self.backend is not Backend.ninja:
+ raise SkipTest(f'Clang-tidy is for now only supported on Ninja, not {self.backend.name}')
+ if shutil.which('c++') is None:
+ raise SkipTest('Clang-tidy breaks when ccache is used and "c++" not in path.')
+ if is_osx():
+ raise SkipTest('Apple ships a broken clang-tidy that chokes on -pipe.')
+ testdir = os.path.join(self.unit_test_dir, '68 clang-tidy')
+ dummydir = os.path.join(testdir, 'dummydir.h')
+ self.init(testdir, override_envvars={'CXX': 'c++'})
+ out = self.run_target('clang-tidy')
+ self.assertIn('cttest.cpp:4:20', out)
+ self.assertNotIn(dummydir, out)
+
+ def test_identity_cross(self):
+ testdir = os.path.join(self.unit_test_dir, '69 cross')
+ # Do a build to generate a cross file where the host is this target
+ self.init(testdir, extra_args=['-Dgenerate=true'])
+ self.meson_cross_files = [os.path.join(self.builddir, "crossfile")]
+ self.assertTrue(os.path.exists(self.meson_cross_files[0]))
+ # Now verify that this is detected as cross
+ self.new_builddir()
+ self.init(testdir)
+
+ def test_introspect_buildoptions_without_configured_build(self):
+ testdir = os.path.join(self.unit_test_dir, '58 introspect buildoptions')
+ testfile = os.path.join(testdir, 'meson.build')
+ res_nb = self.introspect_directory(testfile, ['--buildoptions'] + self.meson_args)
+ self.init(testdir, default_args=False)
+ res_wb = self.introspect('--buildoptions')
+ self.maxDiff = None
+ # XXX: These now generate in a different order, is that okay?
+ self.assertListEqual(sorted(res_nb, key=lambda x: x['name']), sorted(res_wb, key=lambda x: x['name']))
+
+ def test_meson_configure_from_source_does_not_crash(self):
+ testdir = os.path.join(self.unit_test_dir, '58 introspect buildoptions')
+ self._run(self.mconf_command + [testdir])
+
+ def test_introspect_buildoptions_cross_only(self):
+ testdir = os.path.join(self.unit_test_dir, '82 cross only introspect')
+ testfile = os.path.join(testdir, 'meson.build')
+ res = self.introspect_directory(testfile, ['--buildoptions'] + self.meson_args)
+ optnames = [o['name'] for o in res]
+ self.assertIn('c_args', optnames)
+ self.assertNotIn('build.c_args', optnames)
+
+ def test_introspect_json_flat(self):
+ testdir = os.path.join(self.unit_test_dir, '56 introspection')
+ self.init(testdir, extra_args=['-Dlayout=flat'])
+ infodir = os.path.join(self.builddir, 'meson-info')
+ self.assertPathExists(infodir)
+
+ with open(os.path.join(infodir, 'intro-targets.json'), encoding='utf-8') as fp:
+ targets = json.load(fp)
+
+ for i in targets:
+ for out in i['filename']:
+ assert os.path.relpath(out, self.builddir).startswith('meson-out')
+
+ def test_introspect_json_dump(self):
+ testdir = os.path.join(self.unit_test_dir, '56 introspection')
+ self.init(testdir)
+ infodir = os.path.join(self.builddir, 'meson-info')
+ self.assertPathExists(infodir)
+
+ def assertKeyTypes(key_type_list, obj, strict: bool = True):
+ for i in key_type_list:
+ if isinstance(i[1], (list, tuple)) and None in i[1]:
+ i = (i[0], tuple(x for x in i[1] if x is not None))
+ if i[0] not in obj or obj[i[0]] is None:
+ continue
+ self.assertIn(i[0], obj)
+ self.assertIsInstance(obj[i[0]], i[1])
+ if strict:
+ for k in obj.keys():
+ found = False
+ for i in key_type_list:
+ if k == i[0]:
+ found = True
+ break
+ self.assertTrue(found, f'Key "{k}" not in expected list')
+
+ root_keylist = [
+ ('benchmarks', list),
+ ('buildoptions', list),
+ ('buildsystem_files', list),
+ ('dependencies', list),
+ ('installed', dict),
+ ('projectinfo', dict),
+ ('targets', list),
+ ('tests', list),
+ ]
+
+ test_keylist = [
+ ('cmd', list),
+ ('env', dict),
+ ('name', str),
+ ('timeout', int),
+ ('suite', list),
+ ('is_parallel', bool),
+ ('protocol', str),
+ ('depends', list),
+ ('workdir', (str, None)),
+ ('priority', int),
+ ]
+
+ buildoptions_keylist = [
+ ('name', str),
+ ('section', str),
+ ('type', str),
+ ('description', str),
+ ('machine', str),
+ ('choices', (list, None)),
+ ('value', (str, int, bool, list)),
+ ]
+
+ buildoptions_typelist = [
+ ('combo', str, [('choices', list)]),
+ ('string', str, []),
+ ('boolean', bool, []),
+ ('integer', int, []),
+ ('array', list, []),
+ ]
+
+ buildoptions_sections = ['core', 'backend', 'base', 'compiler', 'directory', 'user', 'test']
+ buildoptions_machines = ['any', 'build', 'host']
+
+ dependencies_typelist = [
+ ('name', str),
+ ('version', str),
+ ('compile_args', list),
+ ('link_args', list),
+ ]
+
+ targets_typelist = [
+ ('name', str),
+ ('id', str),
+ ('type', str),
+ ('defined_in', str),
+ ('filename', list),
+ ('build_by_default', bool),
+ ('target_sources', list),
+ ('extra_files', list),
+ ('subproject', (str, None)),
+ ('install_filename', (list, None)),
+ ('installed', bool),
+ ]
+
+ targets_sources_typelist = [
+ ('language', str),
+ ('compiler', list),
+ ('parameters', list),
+ ('sources', list),
+ ('generated_sources', list),
+ ]
+
+ # First load all files
+ res = {}
+ for i in root_keylist:
+ curr = os.path.join(infodir, 'intro-{}.json'.format(i[0]))
+ self.assertPathExists(curr)
+ with open(curr, encoding='utf-8') as fp:
+ res[i[0]] = json.load(fp)
+
+ assertKeyTypes(root_keylist, res)
+
+ # Match target ids to input and output files for ease of reference
+ src_to_id = {}
+ out_to_id = {}
+ name_to_out = {}
+ for i in res['targets']:
+ print(json.dump(i, sys.stdout))
+ out_to_id.update({os.path.relpath(out, self.builddir): i['id']
+ for out in i['filename']})
+ name_to_out.update({i['name']: i['filename']})
+ for group in i['target_sources']:
+ src_to_id.update({os.path.relpath(src, testdir): i['id']
+ for src in group['sources']})
+
+ # Check Tests and benchmarks
+ tests_to_find = ['test case 1', 'test case 2', 'benchmark 1']
+ deps_to_find = {'test case 1': [src_to_id['t1.cpp']],
+ 'test case 2': [src_to_id['t2.cpp'], src_to_id['t3.cpp']],
+ 'benchmark 1': [out_to_id['file2'], out_to_id['file3'], out_to_id['file4'], src_to_id['t3.cpp']]}
+ for i in res['benchmarks'] + res['tests']:
+ assertKeyTypes(test_keylist, i)
+ if i['name'] in tests_to_find:
+ tests_to_find.remove(i['name'])
+ self.assertEqual(sorted(i['depends']),
+ sorted(deps_to_find[i['name']]))
+ self.assertListEqual(tests_to_find, [])
+
+ # Check buildoptions
+ buildopts_to_find = {'cpp_std': 'c++11'}
+ for i in res['buildoptions']:
+ assertKeyTypes(buildoptions_keylist, i)
+ valid_type = False
+ for j in buildoptions_typelist:
+ if i['type'] == j[0]:
+ self.assertIsInstance(i['value'], j[1])
+ assertKeyTypes(j[2], i, strict=False)
+ valid_type = True
+ break
+
+ self.assertIn(i['section'], buildoptions_sections)
+ self.assertIn(i['machine'], buildoptions_machines)
+ self.assertTrue(valid_type)
+ if i['name'] in buildopts_to_find:
+ self.assertEqual(i['value'], buildopts_to_find[i['name']])
+ buildopts_to_find.pop(i['name'], None)
+ self.assertDictEqual(buildopts_to_find, {})
+
+ # Check buildsystem_files
+ bs_files = ['meson.build', 'meson_options.txt', 'sharedlib/meson.build', 'staticlib/meson.build']
+ bs_files = [os.path.join(testdir, x) for x in bs_files]
+ self.assertPathListEqual(list(sorted(res['buildsystem_files'])), list(sorted(bs_files)))
+
+ # Check dependencies
+ dependencies_to_find = ['threads']
+ for i in res['dependencies']:
+ assertKeyTypes(dependencies_typelist, i)
+ if i['name'] in dependencies_to_find:
+ dependencies_to_find.remove(i['name'])
+ self.assertListEqual(dependencies_to_find, [])
+
+ # Check projectinfo
+ self.assertDictEqual(res['projectinfo'], {'version': '1.2.3', 'descriptive_name': 'introspection', 'subproject_dir': 'subprojects', 'subprojects': []})
+
+ # Check targets
+ targets_to_find = {
+ 'sharedTestLib': ('shared library', True, False, 'sharedlib/meson.build',
+ [os.path.join(testdir, 'sharedlib', 'shared.cpp')]),
+ 'staticTestLib': ('static library', True, False, 'staticlib/meson.build',
+ [os.path.join(testdir, 'staticlib', 'static.c')]),
+ 'custom target test 1': ('custom', False, False, 'meson.build',
+ [os.path.join(testdir, 'cp.py')]),
+ 'custom target test 2': ('custom', False, False, 'meson.build',
+ name_to_out['custom target test 1']),
+ 'test1': ('executable', True, True, 'meson.build',
+ [os.path.join(testdir, 't1.cpp')]),
+ 'test2': ('executable', True, False, 'meson.build',
+ [os.path.join(testdir, 't2.cpp')]),
+ 'test3': ('executable', True, False, 'meson.build',
+ [os.path.join(testdir, 't3.cpp')]),
+ 'custom target test 3': ('custom', False, False, 'meson.build',
+ name_to_out['test3']),
+ }
+ for i in res['targets']:
+ assertKeyTypes(targets_typelist, i)
+ if i['name'] in targets_to_find:
+ tgt = targets_to_find[i['name']]
+ self.assertEqual(i['type'], tgt[0])
+ self.assertEqual(i['build_by_default'], tgt[1])
+ self.assertEqual(i['installed'], tgt[2])
+ self.assertPathEqual(i['defined_in'], os.path.join(testdir, tgt[3]))
+ targets_to_find.pop(i['name'], None)
+ for j in i['target_sources']:
+ assertKeyTypes(targets_sources_typelist, j)
+ self.assertEqual(j['sources'], [os.path.normpath(f) for f in tgt[4]])
+ self.assertDictEqual(targets_to_find, {})
+
+ def test_introspect_file_dump_equals_all(self):
+ testdir = os.path.join(self.unit_test_dir, '56 introspection')
+ self.init(testdir)
+ res_all = self.introspect('--all')
+ res_file = {}
+
+ root_keylist = [
+ 'benchmarks',
+ 'buildoptions',
+ 'buildsystem_files',
+ 'dependencies',
+ 'installed',
+ 'install_plan',
+ 'projectinfo',
+ 'targets',
+ 'tests',
+ ]
+
+ infodir = os.path.join(self.builddir, 'meson-info')
+ self.assertPathExists(infodir)
+ for i in root_keylist:
+ curr = os.path.join(infodir, f'intro-{i}.json')
+ self.assertPathExists(curr)
+ with open(curr, encoding='utf-8') as fp:
+ res_file[i] = json.load(fp)
+
+ self.assertEqual(res_all, res_file)
+
+ def test_introspect_meson_info(self):
+ testdir = os.path.join(self.unit_test_dir, '56 introspection')
+ introfile = os.path.join(self.builddir, 'meson-info', 'meson-info.json')
+ self.init(testdir)
+ self.assertPathExists(introfile)
+ with open(introfile, encoding='utf-8') as fp:
+ res1 = json.load(fp)
+
+ for i in ['meson_version', 'directories', 'introspection', 'build_files_updated', 'error']:
+ self.assertIn(i, res1)
+
+ self.assertEqual(res1['error'], False)
+ self.assertEqual(res1['build_files_updated'], True)
+
+ def test_introspect_config_update(self):
+ testdir = os.path.join(self.unit_test_dir, '56 introspection')
+ introfile = os.path.join(self.builddir, 'meson-info', 'intro-buildoptions.json')
+ self.init(testdir)
+ self.assertPathExists(introfile)
+ with open(introfile, encoding='utf-8') as fp:
+ res1 = json.load(fp)
+
+ for i in res1:
+ if i['name'] == 'cpp_std':
+ i['value'] = 'c++14'
+ if i['name'] == 'build.cpp_std':
+ i['value'] = 'c++14'
+ if i['name'] == 'buildtype':
+ i['value'] = 'release'
+ if i['name'] == 'optimization':
+ i['value'] = '3'
+ if i['name'] == 'debug':
+ i['value'] = False
+
+ self.setconf('-Dcpp_std=c++14')
+ self.setconf('-Dbuildtype=release')
+
+ with open(introfile, encoding='utf-8') as fp:
+ res2 = json.load(fp)
+
+ self.assertListEqual(res1, res2)
+
+ def test_introspect_targets_from_source(self):
+ testdir = os.path.join(self.unit_test_dir, '56 introspection')
+ testfile = os.path.join(testdir, 'meson.build')
+ introfile = os.path.join(self.builddir, 'meson-info', 'intro-targets.json')
+ self.init(testdir)
+ self.assertPathExists(introfile)
+ with open(introfile, encoding='utf-8') as fp:
+ res_wb = json.load(fp)
+
+ res_nb = self.introspect_directory(testfile, ['--targets'] + self.meson_args)
+
+ # Account for differences in output
+ res_wb = [i for i in res_wb if i['type'] != 'custom']
+ for i in res_wb:
+ i['filename'] = [os.path.relpath(x, self.builddir) for x in i['filename']]
+ if 'install_filename' in i:
+ del i['install_filename']
+
+ sources = []
+ for j in i['target_sources']:
+ sources += j['sources']
+ i['target_sources'] = [{
+ 'language': 'unknown',
+ 'compiler': [],
+ 'parameters': [],
+ 'sources': sources,
+ 'generated_sources': []
+ }]
+
+ self.maxDiff = None
+ self.assertListEqual(res_nb, res_wb)
+
+ def test_introspect_ast_source(self):
+ testdir = os.path.join(self.unit_test_dir, '56 introspection')
+ testfile = os.path.join(testdir, 'meson.build')
+ res_nb = self.introspect_directory(testfile, ['--ast'] + self.meson_args)
+
+ node_counter = {}
+
+ def accept_node(json_node):
+ self.assertIsInstance(json_node, dict)
+ for i in ['lineno', 'colno', 'end_lineno', 'end_colno']:
+ self.assertIn(i, json_node)
+ self.assertIsInstance(json_node[i], int)
+ self.assertIn('node', json_node)
+ n = json_node['node']
+ self.assertIsInstance(n, str)
+ self.assertIn(n, nodes)
+ if n not in node_counter:
+ node_counter[n] = 0
+ node_counter[n] = node_counter[n] + 1
+ for nodeDesc in nodes[n]:
+ key = nodeDesc[0]
+ func = nodeDesc[1]
+ self.assertIn(key, json_node)
+ if func is None:
+ tp = nodeDesc[2]
+ self.assertIsInstance(json_node[key], tp)
+ continue
+ func(json_node[key])
+
+ def accept_node_list(node_list):
+ self.assertIsInstance(node_list, list)
+ for i in node_list:
+ accept_node(i)
+
+ def accept_kwargs(kwargs):
+ self.assertIsInstance(kwargs, list)
+ for i in kwargs:
+ self.assertIn('key', i)
+ self.assertIn('val', i)
+ accept_node(i['key'])
+ accept_node(i['val'])
+
+ nodes = {
+ 'BooleanNode': [('value', None, bool)],
+ 'IdNode': [('value', None, str)],
+ 'NumberNode': [('value', None, int)],
+ 'StringNode': [('value', None, str)],
+ 'FormatStringNode': [('value', None, str)],
+ 'ContinueNode': [],
+ 'BreakNode': [],
+ 'ArgumentNode': [('positional', accept_node_list), ('kwargs', accept_kwargs)],
+ 'ArrayNode': [('args', accept_node)],
+ 'DictNode': [('args', accept_node)],
+ 'EmptyNode': [],
+ 'OrNode': [('left', accept_node), ('right', accept_node)],
+ 'AndNode': [('left', accept_node), ('right', accept_node)],
+ 'ComparisonNode': [('left', accept_node), ('right', accept_node), ('ctype', None, str)],
+ 'ArithmeticNode': [('left', accept_node), ('right', accept_node), ('op', None, str)],
+ 'NotNode': [('right', accept_node)],
+ 'CodeBlockNode': [('lines', accept_node_list)],
+ 'IndexNode': [('object', accept_node), ('index', accept_node)],
+ 'MethodNode': [('object', accept_node), ('args', accept_node), ('name', None, str)],
+ 'FunctionNode': [('args', accept_node), ('name', None, str)],
+ 'AssignmentNode': [('value', accept_node), ('var_name', None, str)],
+ 'PlusAssignmentNode': [('value', accept_node), ('var_name', None, str)],
+ 'ForeachClauseNode': [('items', accept_node), ('block', accept_node), ('varnames', None, list)],
+ 'IfClauseNode': [('ifs', accept_node_list), ('else', accept_node)],
+ 'IfNode': [('condition', accept_node), ('block', accept_node)],
+ 'UMinusNode': [('right', accept_node)],
+ 'TernaryNode': [('condition', accept_node), ('true', accept_node), ('false', accept_node)],
+ }
+
+ accept_node(res_nb)
+
+ for n, c in [('ContinueNode', 2), ('BreakNode', 1), ('NotNode', 3)]:
+ self.assertIn(n, node_counter)
+ self.assertEqual(node_counter[n], c)
+
+ def test_introspect_dependencies_from_source(self):
+ testdir = os.path.join(self.unit_test_dir, '56 introspection')
+ testfile = os.path.join(testdir, 'meson.build')
+ res_nb = self.introspect_directory(testfile, ['--scan-dependencies'] + self.meson_args)
+ expected = [
+ {
+ 'name': 'threads',
+ 'required': True,
+ 'version': [],
+ 'has_fallback': False,
+ 'conditional': False
+ },
+ {
+ 'name': 'zlib',
+ 'required': False,
+ 'version': [],
+ 'has_fallback': False,
+ 'conditional': False
+ },
+ {
+ 'name': 'bugDep1',
+ 'required': True,
+ 'version': [],
+ 'has_fallback': False,
+ 'conditional': False
+ },
+ {
+ 'name': 'somethingthatdoesnotexist',
+ 'required': True,
+ 'version': ['>=1.2.3'],
+ 'has_fallback': False,
+ 'conditional': True
+ },
+ {
+ 'name': 'look_i_have_a_fallback',
+ 'required': True,
+ 'version': ['>=1.0.0', '<=99.9.9'],
+ 'has_fallback': True,
+ 'conditional': True
+ }
+ ]
+ self.maxDiff = None
+ self.assertListEqual(res_nb, expected)
+
+ def test_unstable_coredata(self):
+ testdir = os.path.join(self.common_test_dir, '1 trivial')
+ self.init(testdir)
+ # just test that the command does not fail (e.g. because it throws an exception)
+ self._run([*self.meson_command, 'unstable-coredata', self.builddir])
+
+ @skip_if_no_cmake
+ def test_cmake_prefix_path(self):
+ testdir = os.path.join(self.unit_test_dir, '62 cmake_prefix_path')
+ self.init(testdir, extra_args=['-Dcmake_prefix_path=' + os.path.join(testdir, 'prefix')])
+
+ @skip_if_no_cmake
+ def test_cmake_parser(self):
+ testdir = os.path.join(self.unit_test_dir, '63 cmake parser')
+ self.init(testdir, extra_args=['-Dcmake_prefix_path=' + os.path.join(testdir, 'prefix')])
+
+ def test_alias_target(self):
+ testdir = os.path.join(self.unit_test_dir, '64 alias target')
+ self.init(testdir)
+ self.build()
+ self.assertPathDoesNotExist(os.path.join(self.builddir, 'prog' + exe_suffix))
+ self.assertPathDoesNotExist(os.path.join(self.builddir, 'hello.txt'))
+ self.run_target('build-all')
+ self.assertPathExists(os.path.join(self.builddir, 'prog' + exe_suffix))
+ self.assertPathExists(os.path.join(self.builddir, 'hello.txt'))
+ out = self.run_target('aliased-run')
+ self.assertIn('a run target was here', out)
+
+ def test_configure(self):
+ testdir = os.path.join(self.common_test_dir, '2 cpp')
+ self.init(testdir)
+ self._run(self.mconf_command + [self.builddir])
+
+ def test_summary(self):
+ testdir = os.path.join(self.unit_test_dir, '71 summary')
+ out = self.init(testdir, extra_args=['-Denabled_opt=enabled'])
+ expected = textwrap.dedent(r'''
+ Some Subproject 2.0
+
+ string : bar
+ integer: 1
+ boolean: True
+
+ subsub undefined
+
+ Something: Some value
+
+ My Project 1.0
+
+ Configuration
+ Some boolean : False
+ Another boolean: True
+ Some string : Hello World
+ A list : string
+ 1
+ True
+ empty list :
+ enabled_opt : enabled
+ A number : 1
+ yes : YES
+ no : NO
+ comma list : a, b, c
+
+ Stuff
+ missing prog : NO
+ existing prog : ''' + sys.executable + '''
+ missing dep : NO
+ external dep : YES 1.2.3
+ internal dep : YES
+ disabler : NO
+
+ Plugins
+ long comma list: alpha, alphacolor, apetag, audiofx, audioparsers, auparse,
+ autodetect, avi
+
+ Subprojects
+ sub : YES
+ sub2 : NO Problem encountered: This subproject failed
+ subsub : YES
+
+ User defined options
+ backend : ''' + self.backend.name + '''
+ libdir : lib
+ prefix : /usr
+ enabled_opt : enabled
+ ''')
+ expected_lines = expected.split('\n')[1:]
+ out_start = out.find(expected_lines[0])
+ out_lines = out[out_start:].split('\n')[:len(expected_lines)]
+ for e, o in zip(expected_lines, out_lines):
+ if e.startswith(' external dep'):
+ self.assertRegex(o, r'^ external dep : (YES [0-9.]*|NO)$')
+ else:
+ self.assertEqual(o, e)
+
+ def test_meson_compile(self):
+ """Test the meson compile command."""
+
+ def get_exe_name(basename: str) -> str:
+ if is_windows():
+ return f'{basename}.exe'
+ else:
+ return basename
+
+ def get_shared_lib_name(basename: str) -> str:
+ if mesonbuild.environment.detect_msys2_arch():
+ return f'lib{basename}.dll'
+ elif is_windows():
+ return f'{basename}.dll'
+ elif is_cygwin():
+ return f'cyg{basename}.dll'
+ elif is_osx():
+ return f'lib{basename}.dylib'
+ else:
+ return f'lib{basename}.so'
+
+ def get_static_lib_name(basename: str) -> str:
+ return f'lib{basename}.a'
+
+ # Base case (no targets or additional arguments)
+
+ testdir = os.path.join(self.common_test_dir, '1 trivial')
+ self.init(testdir)
+
+ self._run([*self.meson_command, 'compile', '-C', self.builddir])
+ self.assertPathExists(os.path.join(self.builddir, get_exe_name('trivialprog')))
+
+ # `--clean`
+
+ self._run([*self.meson_command, 'compile', '-C', self.builddir, '--clean'])
+ self.assertPathDoesNotExist(os.path.join(self.builddir, get_exe_name('trivialprog')))
+
+ # Target specified in a project with unique names
+
+ testdir = os.path.join(self.common_test_dir, '6 linkshared')
+ self.init(testdir, extra_args=['--wipe'])
+ # Multiple targets and target type specified
+ self._run([*self.meson_command, 'compile', '-C', self.builddir, 'mylib', 'mycpplib:shared_library'])
+ # Check that we have a shared lib, but not an executable, i.e. check that target actually worked
+ self.assertPathExists(os.path.join(self.builddir, get_shared_lib_name('mylib')))
+ self.assertPathDoesNotExist(os.path.join(self.builddir, get_exe_name('prog')))
+ self.assertPathExists(os.path.join(self.builddir, get_shared_lib_name('mycpplib')))
+ self.assertPathDoesNotExist(os.path.join(self.builddir, get_exe_name('cppprog')))
+
+ # Target specified in a project with non unique names
+
+ testdir = os.path.join(self.common_test_dir, '185 same target name')
+ self.init(testdir, extra_args=['--wipe'])
+ self._run([*self.meson_command, 'compile', '-C', self.builddir, './foo'])
+ self.assertPathExists(os.path.join(self.builddir, get_static_lib_name('foo')))
+ self._run([*self.meson_command, 'compile', '-C', self.builddir, 'sub/foo'])
+ self.assertPathExists(os.path.join(self.builddir, 'sub', get_static_lib_name('foo')))
+
+ # run_target
+
+ testdir = os.path.join(self.common_test_dir, '51 run target')
+ self.init(testdir, extra_args=['--wipe'])
+ out = self._run([*self.meson_command, 'compile', '-C', self.builddir, 'py3hi'])
+ self.assertIn('I am Python3.', out)
+
+ # `--$BACKEND-args`
+
+ testdir = os.path.join(self.common_test_dir, '1 trivial')
+ if self.backend is Backend.ninja:
+ self.init(testdir, extra_args=['--wipe'])
+ # Dry run - should not create a program
+ self._run([*self.meson_command, 'compile', '-C', self.builddir, '--ninja-args=-n'])
+ self.assertPathDoesNotExist(os.path.join(self.builddir, get_exe_name('trivialprog')))
+ elif self.backend is Backend.vs:
+ self.init(testdir, extra_args=['--wipe'])
+ self._run([*self.meson_command, 'compile', '-C', self.builddir])
+ # Explicitly clean the target through msbuild interface
+ self._run([*self.meson_command, 'compile', '-C', self.builddir, '--vs-args=-t:{}:Clean'.format(re.sub(r'[\%\$\@\;\.\(\)\']', '_', get_exe_name('trivialprog')))])
+ self.assertPathDoesNotExist(os.path.join(self.builddir, get_exe_name('trivialprog')))
+
+ def test_spurious_reconfigure_built_dep_file(self):
+ testdir = os.path.join(self.unit_test_dir, '73 dep files')
+
+ # Regression test: Spurious reconfigure was happening when build
+ # directory is inside source directory.
+ # See https://gitlab.freedesktop.org/gstreamer/gst-build/-/issues/85.
+ srcdir = os.path.join(self.builddir, 'srctree')
+ shutil.copytree(testdir, srcdir)
+ builddir = os.path.join(srcdir, '_build')
+ self.change_builddir(builddir)
+
+ self.init(srcdir)
+ self.build()
+
+ # During first configure the file did not exist so no dependency should
+ # have been set. A rebuild should not trigger a reconfigure.
+ self.clean()
+ out = self.build()
+ self.assertNotIn('Project configured', out)
+
+ self.init(srcdir, extra_args=['--reconfigure'])
+
+ # During the reconfigure the file did exist, but is inside build
+ # directory, so no dependency should have been set. A rebuild should not
+ # trigger a reconfigure.
+ self.clean()
+ out = self.build()
+ self.assertNotIn('Project configured', out)
+
+ def _test_junit(self, case: str) -> None:
+ try:
+ import lxml.etree as et
+ except ImportError:
+ raise SkipTest('lxml required, but not found.')
+
+ schema = et.XMLSchema(et.parse(str(Path(self.src_root) / 'data' / 'schema.xsd')))
+
+ self.init(case)
+ self.run_tests()
+
+ junit = et.parse(str(Path(self.builddir) / 'meson-logs' / 'testlog.junit.xml'))
+ try:
+ schema.assertValid(junit)
+ except et.DocumentInvalid as e:
+ self.fail(e.error_log)
+
+ def test_junit_valid_tap(self):
+ self._test_junit(os.path.join(self.common_test_dir, '206 tap tests'))
+
+ def test_junit_valid_exitcode(self):
+ self._test_junit(os.path.join(self.common_test_dir, '41 test args'))
+
+ def test_junit_valid_gtest(self):
+ self._test_junit(os.path.join(self.framework_test_dir, '2 gtest'))
+
+ def test_link_language_linker(self):
+ # TODO: there should be some way to query how we're linking things
+ # without resorting to reading the ninja.build file
+ if self.backend is not Backend.ninja:
+ raise SkipTest('This test reads the ninja file')
+
+ testdir = os.path.join(self.common_test_dir, '225 link language')
+ self.init(testdir)
+
+ build_ninja = os.path.join(self.builddir, 'build.ninja')
+ with open(build_ninja, encoding='utf-8') as f:
+ contents = f.read()
+
+ self.assertRegex(contents, r'build main(\.exe)?.*: c_LINKER')
+ self.assertRegex(contents, r'build (lib|cyg)?mylib.*: c_LINKER')
+
+ def test_commands_documented(self):
+ '''
+ Test that all listed meson commands are documented in Commands.md.
+ '''
+
+ # The docs directory is not in release tarballs.
+ if not os.path.isdir('docs'):
+ raise SkipTest('Doc directory does not exist.')
+ doc_path = 'docs/markdown/Commands.md'
+
+ md = None
+ with open(doc_path, encoding='utf-8') as f:
+ md = f.read()
+ self.assertIsNotNone(md)
+
+ ## Get command sections
+
+ section_pattern = re.compile(r'^### (.+)$', re.MULTILINE)
+ md_command_section_matches = [i for i in section_pattern.finditer(md)]
+ md_command_sections = dict()
+ for i, s in enumerate(md_command_section_matches):
+ section_end = len(md) if i == len(md_command_section_matches) - 1 else md_command_section_matches[i + 1].start()
+ md_command_sections[s.group(1)] = (s.start(), section_end)
+
+ ## Validate commands
+
+ md_commands = {k for k,v in md_command_sections.items()}
+ help_output = self._run(self.meson_command + ['--help'])
+ # Python's argument parser might put the command list to its own line. Or it might not.
+ self.assertTrue(help_output.startswith('usage: '))
+ lines = help_output.split('\n')
+ line1 = lines[0]
+ line2 = lines[1]
+ if '{' in line1:
+ cmndline = line1
+ else:
+ self.assertIn('{', line2)
+ cmndline = line2
+ cmndstr = cmndline.split('{')[1]
+ self.assertIn('}', cmndstr)
+ help_commands = set(cmndstr.split('}')[0].split(','))
+ self.assertTrue(len(help_commands) > 0, 'Must detect some command names.')
+
+ self.assertEqual(md_commands | {'help'}, help_commands, f'Doc file: `{doc_path}`')
+
+ ## Validate that each section has proper placeholders
+
+ def get_data_pattern(command):
+ return re.compile(
+ r'{{ ' + command + r'_usage.inc }}[\r\n]'
+ r'.*?'
+ r'{{ ' + command + r'_arguments.inc }}[\r\n]',
+ flags = re.MULTILINE|re.DOTALL)
+
+ for command in md_commands:
+ m = get_data_pattern(command).search(md, pos=md_command_sections[command][0], endpos=md_command_sections[command][1])
+ self.assertIsNotNone(m, f'Command `{command}` is missing placeholders for dynamic data. Doc file: `{doc_path}`')
+
+ def _check_coverage_files(self, types=('text', 'xml', 'html')):
+ covdir = Path(self.builddir) / 'meson-logs'
+ files = []
+ if 'text' in types:
+ files.append('coverage.txt')
+ if 'xml' in types:
+ files.append('coverage.xml')
+ if 'html' in types:
+ files.append('coveragereport/index.html')
+ for f in files:
+ self.assertTrue((covdir / f).is_file(), msg=f'{f} is not a file')
+
+ def test_coverage(self):
+ if mesonbuild.environment.detect_msys2_arch():
+ raise SkipTest('Skipped due to problems with coverage on MSYS2')
+ gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr()
+ if not gcovr_exe:
+ raise SkipTest('gcovr not found, or too old')
+ testdir = os.path.join(self.common_test_dir, '1 trivial')
+ env = get_fake_env(testdir, self.builddir, self.prefix)
+ cc = detect_c_compiler(env, MachineChoice.HOST)
+ if cc.get_id() == 'clang':
+ if not mesonbuild.environment.detect_llvm_cov():
+ raise SkipTest('llvm-cov not found')
+ if cc.get_id() == 'msvc':
+ raise SkipTest('Test only applies to non-MSVC compilers')
+ self.init(testdir, extra_args=['-Db_coverage=true'])
+ self.build()
+ self.run_tests()
+ self.run_target('coverage')
+ self._check_coverage_files()
+
+ def test_coverage_complex(self):
+ if mesonbuild.environment.detect_msys2_arch():
+ raise SkipTest('Skipped due to problems with coverage on MSYS2')
+ gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr()
+ if not gcovr_exe:
+ raise SkipTest('gcovr not found, or too old')
+ testdir = os.path.join(self.common_test_dir, '105 generatorcustom')
+ env = get_fake_env(testdir, self.builddir, self.prefix)
+ cc = detect_c_compiler(env, MachineChoice.HOST)
+ if cc.get_id() == 'clang':
+ if not mesonbuild.environment.detect_llvm_cov():
+ raise SkipTest('llvm-cov not found')
+ if cc.get_id() == 'msvc':
+ raise SkipTest('Test only applies to non-MSVC compilers')
+ self.init(testdir, extra_args=['-Db_coverage=true'])
+ self.build()
+ self.run_tests()
+ self.run_target('coverage')
+ self._check_coverage_files()
+
+ def test_coverage_html(self):
+ if mesonbuild.environment.detect_msys2_arch():
+ raise SkipTest('Skipped due to problems with coverage on MSYS2')
+ gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr()
+ if not gcovr_exe:
+ raise SkipTest('gcovr not found, or too old')
+ testdir = os.path.join(self.common_test_dir, '1 trivial')
+ env = get_fake_env(testdir, self.builddir, self.prefix)
+ cc = detect_c_compiler(env, MachineChoice.HOST)
+ if cc.get_id() == 'clang':
+ if not mesonbuild.environment.detect_llvm_cov():
+ raise SkipTest('llvm-cov not found')
+ if cc.get_id() == 'msvc':
+ raise SkipTest('Test only applies to non-MSVC compilers')
+ self.init(testdir, extra_args=['-Db_coverage=true'])
+ self.build()
+ self.run_tests()
+ self.run_target('coverage-html')
+ self._check_coverage_files(['html'])
+
+ def test_coverage_text(self):
+ if mesonbuild.environment.detect_msys2_arch():
+ raise SkipTest('Skipped due to problems with coverage on MSYS2')
+ gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr()
+ if not gcovr_exe:
+ raise SkipTest('gcovr not found, or too old')
+ testdir = os.path.join(self.common_test_dir, '1 trivial')
+ env = get_fake_env(testdir, self.builddir, self.prefix)
+ cc = detect_c_compiler(env, MachineChoice.HOST)
+ if cc.get_id() == 'clang':
+ if not mesonbuild.environment.detect_llvm_cov():
+ raise SkipTest('llvm-cov not found')
+ if cc.get_id() == 'msvc':
+ raise SkipTest('Test only applies to non-MSVC compilers')
+ self.init(testdir, extra_args=['-Db_coverage=true'])
+ self.build()
+ self.run_tests()
+ self.run_target('coverage-text')
+ self._check_coverage_files(['text'])
+
+ def test_coverage_xml(self):
+ if mesonbuild.environment.detect_msys2_arch():
+ raise SkipTest('Skipped due to problems with coverage on MSYS2')
+ gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr()
+ if not gcovr_exe:
+ raise SkipTest('gcovr not found, or too old')
+ testdir = os.path.join(self.common_test_dir, '1 trivial')
+ env = get_fake_env(testdir, self.builddir, self.prefix)
+ cc = detect_c_compiler(env, MachineChoice.HOST)
+ if cc.get_id() == 'clang':
+ if not mesonbuild.environment.detect_llvm_cov():
+ raise SkipTest('llvm-cov not found')
+ if cc.get_id() == 'msvc':
+ raise SkipTest('Test only applies to non-MSVC compilers')
+ self.init(testdir, extra_args=['-Db_coverage=true'])
+ self.build()
+ self.run_tests()
+ self.run_target('coverage-xml')
+ self._check_coverage_files(['xml'])
+
+ def test_coverage_escaping(self):
+ if mesonbuild.environment.detect_msys2_arch():
+ raise SkipTest('Skipped due to problems with coverage on MSYS2')
+ gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr()
+ if not gcovr_exe:
+ raise SkipTest('gcovr not found, or too old')
+ testdir = os.path.join(self.common_test_dir, '243 escape++')
+ env = get_fake_env(testdir, self.builddir, self.prefix)
+ cc = detect_c_compiler(env, MachineChoice.HOST)
+ if cc.get_id() == 'clang':
+ if not mesonbuild.environment.detect_llvm_cov():
+ raise SkipTest('llvm-cov not found')
+ if cc.get_id() == 'msvc':
+ raise SkipTest('Test only applies to non-MSVC compilers')
+ self.init(testdir, extra_args=['-Db_coverage=true'])
+ self.build()
+ self.run_tests()
+ self.run_target('coverage')
+ self._check_coverage_files()
+
+ def test_cross_file_constants(self):
+ with temp_filename() as crossfile1, temp_filename() as crossfile2:
+ with open(crossfile1, 'w', encoding='utf-8') as f:
+ f.write(textwrap.dedent(
+ '''
+ [constants]
+ compiler = 'gcc'
+ '''))
+ with open(crossfile2, 'w', encoding='utf-8') as f:
+ f.write(textwrap.dedent(
+ '''
+ [constants]
+ toolchain = '/toolchain/'
+ common_flags = ['--sysroot=' + toolchain / 'sysroot']
+
+ [properties]
+ c_args = common_flags + ['-DSOMETHING']
+ cpp_args = c_args + ['-DSOMETHING_ELSE']
+
+ [binaries]
+ c = toolchain / compiler
+ '''))
+
+ values = mesonbuild.coredata.parse_machine_files([crossfile1, crossfile2])
+ self.assertEqual(values['binaries']['c'], '/toolchain/gcc')
+ self.assertEqual(values['properties']['c_args'],
+ ['--sysroot=/toolchain/sysroot', '-DSOMETHING'])
+ self.assertEqual(values['properties']['cpp_args'],
+ ['--sysroot=/toolchain/sysroot', '-DSOMETHING', '-DSOMETHING_ELSE'])
+
+ @skipIf(is_windows(), 'Directory cleanup fails for some reason')
+ def test_wrap_git(self):
+ with tempfile.TemporaryDirectory() as tmpdir:
+ srcdir = os.path.join(tmpdir, 'src')
+ shutil.copytree(os.path.join(self.unit_test_dir, '80 wrap-git'), srcdir)
+ upstream = os.path.join(srcdir, 'subprojects', 'wrap_git_upstream')
+ upstream_uri = Path(upstream).as_uri()
+ git_init(upstream)
+ with open(os.path.join(srcdir, 'subprojects', 'wrap_git.wrap'), 'w', encoding='utf-8') as f:
+ f.write(textwrap.dedent('''
+ [wrap-git]
+ url = {}
+ patch_directory = wrap_git_builddef
+ revision = master
+ '''.format(upstream_uri)))
+ out = self.init(srcdir)
+ self.build()
+ self.run_tests()
+
+ # Make sure the warning does not occur on the first init.
+ out_of_date_warning = 'revision may be out of date'
+ self.assertNotIn(out_of_date_warning, out)
+
+ # Change the wrap's revisions, reconfigure, and make sure it does
+ # warn on the reconfigure.
+ with open(os.path.join(srcdir, 'subprojects', 'wrap_git.wrap'), 'w', encoding='utf-8') as f:
+ f.write(textwrap.dedent('''
+ [wrap-git]
+ url = {}
+ patch_directory = wrap_git_builddef
+ revision = not-master
+ '''.format(upstream_uri)))
+ out = self.init(srcdir, extra_args='--reconfigure')
+ self.assertIn(out_of_date_warning, out)
+
+ def test_extract_objects_custom_target_no_warning(self):
+ testdir = os.path.join(self.common_test_dir, '22 object extraction')
+
+ out = self.init(testdir)
+ self.assertNotRegex(out, "WARNING:.*can't be converted to File object")
+
+ def test_multi_output_custom_target_no_warning(self):
+ testdir = os.path.join(self.common_test_dir, '228 custom_target source')
+
+ out = self.init(testdir)
+ self.assertNotRegex(out, 'WARNING:.*Using the first one.')
+ self.build()
+ self.run_tests()
+
+ @skipUnless(is_linux() and (re.search('^i.86$|^x86$|^x64$|^x86_64$|^amd64$', platform.processor()) is not None),
+ 'Requires ASM compiler for x86 or x86_64 platform currently only available on Linux CI runners')
+ def test_nostdlib(self):
+ testdir = os.path.join(self.unit_test_dir, '77 nostdlib')
+ machinefile = os.path.join(self.builddir, 'machine.txt')
+ with open(machinefile, 'w', encoding='utf-8') as f:
+ f.write(textwrap.dedent('''
+ [properties]
+ c_stdlib = 'mylibc'
+ '''))
+
+ # Test native C stdlib
+ self.meson_native_files = [machinefile]
+ self.init(testdir)
+ self.build()
+
+ # Test cross C stdlib
+ self.new_builddir()
+ self.meson_native_files = []
+ self.meson_cross_files = [machinefile]
+ self.init(testdir)
+ self.build()
+
+ def test_meson_version_compare(self):
+ testdir = os.path.join(self.unit_test_dir, '81 meson version compare')
+ out = self.init(testdir)
+ self.assertNotRegex(out, r'WARNING')
+
+ def test_wrap_redirect(self):
+ redirect_wrap = os.path.join(self.builddir, 'redirect.wrap')
+ real_wrap = os.path.join(self.builddir, 'foo/subprojects/real.wrap')
+ os.makedirs(os.path.dirname(real_wrap))
+
+ # Invalid redirect, filename must have .wrap extension
+ with open(redirect_wrap, 'w', encoding='utf-8') as f:
+ f.write(textwrap.dedent('''
+ [wrap-redirect]
+ filename = foo/subprojects/real.wrapper
+ '''))
+ with self.assertRaisesRegex(WrapException, 'wrap-redirect filename must be a .wrap file'):
+ PackageDefinition(redirect_wrap)
+
+ # Invalid redirect, filename cannot be in parent directory
+ with open(redirect_wrap, 'w', encoding='utf-8') as f:
+ f.write(textwrap.dedent('''
+ [wrap-redirect]
+ filename = ../real.wrap
+ '''))
+ with self.assertRaisesRegex(WrapException, 'wrap-redirect filename cannot contain ".."'):
+ PackageDefinition(redirect_wrap)
+
+ # Invalid redirect, filename must be in foo/subprojects/real.wrap
+ with open(redirect_wrap, 'w', encoding='utf-8') as f:
+ f.write(textwrap.dedent('''
+ [wrap-redirect]
+ filename = foo/real.wrap
+ '''))
+ with self.assertRaisesRegex(WrapException, 'wrap-redirect filename must be in the form foo/subprojects/bar.wrap'):
+ PackageDefinition(redirect_wrap)
+
+ # Correct redirect
+ with open(redirect_wrap, 'w', encoding='utf-8') as f:
+ f.write(textwrap.dedent('''
+ [wrap-redirect]
+ filename = foo/subprojects/real.wrap
+ '''))
+ with open(real_wrap, 'w', encoding='utf-8') as f:
+ f.write(textwrap.dedent('''
+ [wrap-git]
+ url = http://invalid
+ '''))
+ wrap = PackageDefinition(redirect_wrap)
+ self.assertEqual(wrap.get('url'), 'http://invalid')
+
+ @skip_if_no_cmake
+ def test_nested_cmake_rebuild(self) -> None:
+ # This checks a bug where if a non-meson project is used as a third
+ # level (or deeper) subproject it doesn't cause a rebuild if the build
+ # files for that project are changed
+ testdir = os.path.join(self.unit_test_dir, '84 nested subproject regenerate depends')
+ cmakefile = Path(testdir) / 'subprojects' / 'sub2' / 'CMakeLists.txt'
+ self.init(testdir)
+ self.build()
+ with cmakefile.open('a', encoding='utf-8'):
+ os.utime(str(cmakefile))
+ self.assertReconfiguredBuildIsNoop()
+
+ def test_version_file(self):
+ srcdir = os.path.join(self.common_test_dir, '2 cpp')
+ self.init(srcdir)
+ projinfo = self.introspect('--projectinfo')
+ self.assertEqual(projinfo['version'], '1.0.0')
+
+ def test_cflags_cppflags(self):
+ envs = {'CPPFLAGS': '-DCPPFLAG',
+ 'CFLAGS': '-DCFLAG',
+ 'CXXFLAGS': '-DCXXFLAG'}
+ srcdir = os.path.join(self.unit_test_dir, '88 multiple envvars')
+ self.init(srcdir, override_envvars=envs)
+ self.build()
+
+ def test_build_b_options(self) -> None:
+ # Currently (0.57) these do nothing, but they've always been allowed
+ srcdir = os.path.join(self.common_test_dir, '2 cpp')
+ self.init(srcdir, extra_args=['-Dbuild.b_lto=true'])
+
+ def test_install_skip_subprojects(self):
+ testdir = os.path.join(self.unit_test_dir, '91 install skip subprojects')
+ self.init(testdir)
+ self.build()
+
+ main_expected = [
+ '',
+ 'share',
+ 'include',
+ 'foo',
+ 'bin',
+ 'share/foo',
+ 'share/foo/foo.dat',
+ 'include/foo.h',
+ 'foo/foofile',
+ 'bin/foo' + exe_suffix,
+ ]
+ bar_expected = [
+ 'bar',
+ 'share/foo/bar.dat',
+ 'include/bar.h',
+ 'bin/bar' + exe_suffix,
+ 'bar/barfile'
+ ]
+ env = get_fake_env(testdir, self.builddir, self.prefix)
+ cc = detect_c_compiler(env, MachineChoice.HOST)
+ if cc.get_argument_syntax() == 'msvc':
+ main_expected.append('bin/foo.pdb')
+ bar_expected.append('bin/bar.pdb')
+ prefix = destdir_join(self.installdir, self.prefix)
+ main_expected = [Path(prefix, p) for p in main_expected]
+ bar_expected = [Path(prefix, p) for p in bar_expected]
+ all_expected = main_expected + bar_expected
+
+ def check_installed_files(extra_args, expected):
+ args = ['install', '--destdir', self.installdir] + extra_args
+ self._run(self.meson_command + args, workdir=self.builddir)
+ all_files = [p for p in Path(self.installdir).rglob('*')]
+ self.assertEqual(sorted(expected), sorted(all_files))
+ windows_proof_rmtree(self.installdir)
+
+ check_installed_files([], all_expected)
+ check_installed_files(['--skip-subprojects'], main_expected)
+ check_installed_files(['--skip-subprojects', 'bar'], main_expected)
+ check_installed_files(['--skip-subprojects', 'another'], all_expected)
+
+ def test_adding_subproject_to_configure_project(self) -> None:
+ srcdir = os.path.join(self.unit_test_dir, '92 new subproject in configured project')
+ self.init(srcdir)
+ self.build()
+ self.setconf('-Duse-sub=true')
+ self.build()
+
+ def test_devenv(self):
+ testdir = os.path.join(self.unit_test_dir, '90 devenv')
+ self.init(testdir)
+ self.build()
+
+ cmd = self.meson_command + ['devenv', '-C', self.builddir]
+ script = os.path.join(testdir, 'test-devenv.py')
+ app = os.path.join(self.builddir, 'app')
+ self._run(cmd + python_command + [script])
+ self.assertEqual('This is text.', self._run(cmd + [app]).strip())
+
+ def test_clang_format_check(self):
+ if self.backend is not Backend.ninja:
+ raise SkipTest(f'Skipping clang-format tests with {self.backend.name} backend')
+ if not shutil.which('clang-format'):
+ raise SkipTest('clang-format not found')
+
+ testdir = os.path.join(self.unit_test_dir, '93 clangformat')
+ newdir = os.path.join(self.builddir, 'testdir')
+ shutil.copytree(testdir, newdir)
+ self.new_builddir()
+ self.init(newdir)
+
+ # Should reformat 1 file but not return error
+ output = self.build('clang-format')
+ self.assertEqual(1, output.count('File reformatted:'))
+
+ # Reset source tree then try again with clang-format-check, it should
+ # return an error code this time.
+ windows_proof_rmtree(newdir)
+ shutil.copytree(testdir, newdir)
+ with self.assertRaises(subprocess.CalledProcessError):
+ output = self.build('clang-format-check')
+ self.assertEqual(1, output.count('File reformatted:'))
+
+ # The check format should not touch any files. Thus
+ # running format again has some work to do.
+ output = self.build('clang-format')
+ self.assertEqual(1, output.count('File reformatted:'))
+ self.build('clang-format-check')
+
+ def test_custom_target_implicit_include(self):
+ testdir = os.path.join(self.unit_test_dir, '94 custominc')
+ self.init(testdir)
+ self.build()
+ compdb = self.get_compdb()
+ matches = 0
+ for c in compdb:
+ if 'prog.c' in c['file']:
+ self.assertNotIn('easytogrepfor', c['command'])
+ matches += 1
+ self.assertEqual(matches, 1)
+ matches = 0
+ for c in compdb:
+ if 'prog2.c' in c['file']:
+ self.assertIn('easytogrepfor', c['command'])
+ matches += 1
+ self.assertEqual(matches, 1)
+
+ def test_env_flags_to_linker(self) -> None:
+ # Compilers that act as drivers should add their compiler flags to the
+ # linker, those that do not shouldn't
+ with mock.patch.dict(os.environ, {'CFLAGS': '-DCFLAG', 'LDFLAGS': '-flto'}):
+ env = get_fake_env()
+
+ # Get the compiler so we know which compiler class to mock.
+ cc = detect_compiler_for(env, 'c', MachineChoice.HOST)
+ cc_type = type(cc)
+
+ # Test a compiler that acts as a linker
+ with mock.patch.object(cc_type, 'INVOKES_LINKER', True):
+ cc = detect_compiler_for(env, 'c', MachineChoice.HOST)
+ link_args = env.coredata.get_external_link_args(cc.for_machine, cc.language)
+ self.assertEqual(sorted(link_args), sorted(['-DCFLAG', '-flto']))
+
+ # And one that doesn't
+ with mock.patch.object(cc_type, 'INVOKES_LINKER', False):
+ cc = detect_compiler_for(env, 'c', MachineChoice.HOST)
+ link_args = env.coredata.get_external_link_args(cc.for_machine, cc.language)
+ self.assertEqual(sorted(link_args), sorted(['-flto']))
+
+ def test_install_tag(self) -> None:
+ testdir = os.path.join(self.unit_test_dir, '98 install all targets')
+ self.init(testdir)
+ self.build()
+
+ env = get_fake_env(testdir, self.builddir, self.prefix)
+ cc = detect_c_compiler(env, MachineChoice.HOST)
+
+ def shared_lib_name(name):
+ if cc.get_id() in {'msvc', 'clang-cl'}:
+ return f'bin/{name}.dll'
+ elif is_windows():
+ return f'bin/lib{name}.dll'
+ elif is_cygwin():
+ return f'bin/cyg{name}.dll'
+ elif is_osx():
+ return f'lib/lib{name}.dylib'
+ return f'lib/lib{name}.so'
+
+ def exe_name(name):
+ if is_windows() or is_cygwin():
+ return f'{name}.exe'
+ return name
+
+ installpath = Path(self.installdir)
+
+ expected_common = {
+ installpath,
+ Path(installpath, 'usr'),
+ }
+
+ expected_devel = expected_common | {
+ Path(installpath, 'usr/include'),
+ Path(installpath, 'usr/include/bar-devel.h'),
+ Path(installpath, 'usr/include/bar2-devel.h'),
+ Path(installpath, 'usr/include/foo1-devel.h'),
+ Path(installpath, 'usr/include/foo2-devel.h'),
+ Path(installpath, 'usr/include/foo3-devel.h'),
+ Path(installpath, 'usr/include/out-devel.h'),
+ Path(installpath, 'usr/lib'),
+ Path(installpath, 'usr/lib/libstatic.a'),
+ Path(installpath, 'usr/lib/libboth.a'),
+ Path(installpath, 'usr/lib/libboth2.a'),
+ Path(installpath, 'usr/include/ct-header1.h'),
+ Path(installpath, 'usr/include/ct-header3.h'),
+ Path(installpath, 'usr/include/subdir-devel'),
+ Path(installpath, 'usr/include/custom_files'),
+ Path(installpath, 'usr/include/custom_files/data.txt'),
+ }
+
+ if cc.get_id() in {'msvc', 'clang-cl'}:
+ expected_devel |= {
+ Path(installpath, 'usr/bin'),
+ Path(installpath, 'usr/bin/app.pdb'),
+ Path(installpath, 'usr/bin/app2.pdb'),
+ Path(installpath, 'usr/bin/both.pdb'),
+ Path(installpath, 'usr/bin/both2.pdb'),
+ Path(installpath, 'usr/bin/bothcustom.pdb'),
+ Path(installpath, 'usr/bin/shared.pdb'),
+ Path(installpath, 'usr/bin/versioned_shared-1.pdb'),
+ Path(installpath, 'usr/lib/both.lib'),
+ Path(installpath, 'usr/lib/both2.lib'),
+ Path(installpath, 'usr/lib/bothcustom.lib'),
+ Path(installpath, 'usr/lib/shared.lib'),
+ Path(installpath, 'usr/lib/versioned_shared.lib'),
+ Path(installpath, 'usr/otherbin'),
+ Path(installpath, 'usr/otherbin/app-otherdir.pdb'),
+ }
+ elif is_windows() or is_cygwin():
+ expected_devel |= {
+ Path(installpath, 'usr/lib/libboth.dll.a'),
+ Path(installpath, 'usr/lib/libboth2.dll.a'),
+ Path(installpath, 'usr/lib/libshared.dll.a'),
+ Path(installpath, 'usr/lib/libbothcustom.dll.a'),
+ Path(installpath, 'usr/lib/libversioned_shared.dll.a'),
+ }
+ else:
+ expected_devel |= {
+ Path(installpath, 'usr/' + shared_lib_name('versioned_shared')),
+ }
+
+ expected_runtime = expected_common | {
+ Path(installpath, 'usr/bin'),
+ Path(installpath, 'usr/bin/' + exe_name('app')),
+ Path(installpath, 'usr/otherbin'),
+ Path(installpath, 'usr/otherbin/' + exe_name('app-otherdir')),
+ Path(installpath, 'usr/bin/' + exe_name('app2')),
+ Path(installpath, 'usr/' + shared_lib_name('shared')),
+ Path(installpath, 'usr/' + shared_lib_name('both')),
+ Path(installpath, 'usr/' + shared_lib_name('both2')),
+ }
+
+ if is_windows() or is_cygwin():
+ expected_runtime |= {
+ Path(installpath, 'usr/' + shared_lib_name('versioned_shared-1')),
+ }
+ elif is_osx():
+ expected_runtime |= {
+ Path(installpath, 'usr/' + shared_lib_name('versioned_shared.1')),
+ }
+ else:
+ expected_runtime |= {
+ Path(installpath, 'usr/' + shared_lib_name('versioned_shared') + '.1'),
+ Path(installpath, 'usr/' + shared_lib_name('versioned_shared') + '.1.2.3'),
+ }
+
+ expected_custom = expected_common | {
+ Path(installpath, 'usr/share'),
+ Path(installpath, 'usr/share/bar-custom.txt'),
+ Path(installpath, 'usr/share/foo-custom.h'),
+ Path(installpath, 'usr/share/out1-custom.txt'),
+ Path(installpath, 'usr/share/out2-custom.txt'),
+ Path(installpath, 'usr/share/out3-custom.txt'),
+ Path(installpath, 'usr/share/custom_files'),
+ Path(installpath, 'usr/share/custom_files/data.txt'),
+ Path(installpath, 'usr/lib'),
+ Path(installpath, 'usr/lib/libbothcustom.a'),
+ Path(installpath, 'usr/' + shared_lib_name('bothcustom')),
+ }
+
+ if is_windows() or is_cygwin():
+ expected_custom |= {Path(installpath, 'usr/bin')}
+ else:
+ expected_runtime |= {Path(installpath, 'usr/lib')}
+
+ expected_runtime_custom = expected_runtime | expected_custom
+
+ expected_all = expected_devel | expected_runtime | expected_custom | {
+ Path(installpath, 'usr/share/foo-notag.h'),
+ Path(installpath, 'usr/share/bar-notag.txt'),
+ Path(installpath, 'usr/share/out1-notag.txt'),
+ Path(installpath, 'usr/share/out2-notag.txt'),
+ Path(installpath, 'usr/share/out3-notag.txt'),
+ Path(installpath, 'usr/share/foo2.h'),
+ Path(installpath, 'usr/share/out1.txt'),
+ Path(installpath, 'usr/share/out2.txt'),
+ }
+
+ def do_install(tags, expected_files, expected_scripts):
+ cmd = self.meson_command + ['install', '--dry-run', '--destdir', self.installdir]
+ cmd += ['--tags', tags] if tags else []
+ stdout = self._run(cmd, workdir=self.builddir)
+ installed = self.read_install_logs()
+ self.assertEqual(sorted(expected_files), sorted(installed))
+ self.assertEqual(expected_scripts, stdout.count('Running custom install script'))
+
+ do_install('devel', expected_devel, 0)
+ do_install('runtime', expected_runtime, 0)
+ do_install('custom', expected_custom, 1)
+ do_install('runtime,custom', expected_runtime_custom, 1)
+ do_install(None, expected_all, 2)
+
+ def test_introspect_install_plan(self):
+ testdir = os.path.join(self.unit_test_dir, '98 install all targets')
+ introfile = os.path.join(self.builddir, 'meson-info', 'intro-install_plan.json')
+ self.init(testdir)
+ self.assertPathExists(introfile)
+ with open(introfile, encoding='utf-8') as fp:
+ res = json.load(fp)
+
+ env = get_fake_env(testdir, self.builddir, self.prefix)
+
+ def output_name(name, type_):
+ target = type_(name=name, subdir=None, subproject=None,
+ for_machine=MachineChoice.HOST, sources=[],
+ structured_sources=None,
+ objects=[], environment=env, compilers=env.coredata.compilers[MachineChoice.HOST],
+ kwargs={})
+ target.process_compilers()
+ target.process_compilers_late([])
+ return target.filename
+
+ shared_lib_name = lambda name: output_name(name, SharedLibrary)
+ static_lib_name = lambda name: output_name(name, StaticLibrary)
+ exe_name = lambda name: output_name(name, Executable)
+
+ expected = {
+ 'targets': {
+ f'{self.builddir}/out1-notag.txt': {
+ 'destination': '{datadir}/out1-notag.txt',
+ 'tag': None,
+ },
+ f'{self.builddir}/out2-notag.txt': {
+ 'destination': '{datadir}/out2-notag.txt',
+ 'tag': None,
+ },
+ f'{self.builddir}/libstatic.a': {
+ 'destination': '{libdir_static}/libstatic.a',
+ 'tag': 'devel',
+ },
+ f'{self.builddir}/' + exe_name('app'): {
+ 'destination': '{bindir}/' + exe_name('app'),
+ 'tag': 'runtime',
+ },
+ f'{self.builddir}/' + exe_name('app-otherdir'): {
+ 'destination': '{prefix}/otherbin/' + exe_name('app-otherdir'),
+ 'tag': 'runtime',
+ },
+ f'{self.builddir}/subdir/' + exe_name('app2'): {
+ 'destination': '{bindir}/' + exe_name('app2'),
+ 'tag': 'runtime',
+ },
+ f'{self.builddir}/' + shared_lib_name('shared'): {
+ 'destination': '{libdir_shared}/' + shared_lib_name('shared'),
+ 'tag': 'runtime',
+ },
+ f'{self.builddir}/' + shared_lib_name('both'): {
+ 'destination': '{libdir_shared}/' + shared_lib_name('both'),
+ 'tag': 'runtime',
+ },
+ f'{self.builddir}/' + static_lib_name('both'): {
+ 'destination': '{libdir_static}/' + static_lib_name('both'),
+ 'tag': 'devel',
+ },
+ f'{self.builddir}/' + shared_lib_name('bothcustom'): {
+ 'destination': '{libdir_shared}/' + shared_lib_name('bothcustom'),
+ 'tag': 'custom',
+ },
+ f'{self.builddir}/' + static_lib_name('bothcustom'): {
+ 'destination': '{libdir_static}/' + static_lib_name('bothcustom'),
+ 'tag': 'custom',
+ },
+ f'{self.builddir}/subdir/' + shared_lib_name('both2'): {
+ 'destination': '{libdir_shared}/' + shared_lib_name('both2'),
+ 'tag': 'runtime',
+ },
+ f'{self.builddir}/subdir/' + static_lib_name('both2'): {
+ 'destination': '{libdir_static}/' + static_lib_name('both2'),
+ 'tag': 'devel',
+ },
+ f'{self.builddir}/out1-custom.txt': {
+ 'destination': '{datadir}/out1-custom.txt',
+ 'tag': 'custom',
+ },
+ f'{self.builddir}/out2-custom.txt': {
+ 'destination': '{datadir}/out2-custom.txt',
+ 'tag': 'custom',
+ },
+ f'{self.builddir}/out3-custom.txt': {
+ 'destination': '{datadir}/out3-custom.txt',
+ 'tag': 'custom',
+ },
+ f'{self.builddir}/subdir/out1.txt': {
+ 'destination': '{datadir}/out1.txt',
+ 'tag': None,
+ },
+ f'{self.builddir}/subdir/out2.txt': {
+ 'destination': '{datadir}/out2.txt',
+ 'tag': None,
+ },
+ f'{self.builddir}/out-devel.h': {
+ 'destination': '{includedir}/out-devel.h',
+ 'tag': 'devel',
+ },
+ f'{self.builddir}/out3-notag.txt': {
+ 'destination': '{datadir}/out3-notag.txt',
+ 'tag': None,
+ },
+ },
+ 'configure': {
+ f'{self.builddir}/foo-notag.h': {
+ 'destination': '{datadir}/foo-notag.h',
+ 'tag': None,
+ },
+ f'{self.builddir}/foo2-devel.h': {
+ 'destination': '{includedir}/foo2-devel.h',
+ 'tag': 'devel',
+ },
+ f'{self.builddir}/foo-custom.h': {
+ 'destination': '{datadir}/foo-custom.h',
+ 'tag': 'custom',
+ },
+ f'{self.builddir}/subdir/foo2.h': {
+ 'destination': '{datadir}/foo2.h',
+ 'tag': None,
+ },
+ },
+ 'data': {
+ f'{testdir}/bar-notag.txt': {
+ 'destination': '{datadir}/bar-notag.txt',
+ 'tag': None,
+ },
+ f'{testdir}/bar-devel.h': {
+ 'destination': '{includedir}/bar-devel.h',
+ 'tag': 'devel',
+ },
+ f'{testdir}/bar-custom.txt': {
+ 'destination': '{datadir}/bar-custom.txt',
+ 'tag': 'custom',
+ },
+ f'{testdir}/subdir/bar2-devel.h': {
+ 'destination': '{includedir}/bar2-devel.h',
+ 'tag': 'devel',
+ },
+ },
+ 'headers': {
+ f'{testdir}/foo1-devel.h': {
+ 'destination': '{includedir}/foo1-devel.h',
+ 'tag': 'devel',
+ },
+ f'{testdir}/subdir/foo3-devel.h': {
+ 'destination': '{includedir}/foo3-devel.h',
+ 'tag': 'devel',
+ },
+ },
+ 'install_subdirs': {
+ f'{testdir}/custom_files': {
+ 'destination': '{datadir}/custom_files',
+ 'tag': 'custom'
+ }
+ }
+ }
+
+ fix_path = lambda path: os.path.sep.join(path.split('/'))
+ expected_fixed = {
+ data_type: {
+ fix_path(source): {
+ key: fix_path(value) if key == 'destination' else value
+ for key, value in attributes.items()
+ }
+ for source, attributes in files.items()
+ }
+ for data_type, files in expected.items()
+ }
+
+ for data_type, files in expected_fixed.items():
+ for file, details in files.items():
+ with self.subTest(key='{}.{}'.format(data_type, file)):
+ self.assertEqual(res[data_type][file], details)
+
+ @skip_if_not_language('rust')
+ @unittest.skipIf(not shutil.which('clippy-driver'), 'Test requires clippy-driver')
+ def test_rust_clippy(self) -> None:
+ if self.backend is not Backend.ninja:
+ raise unittest.SkipTest('Rust is only supported with ninja currently')
+ # When clippy is used, we should get an exception since a variable named
+ # "foo" is used, but is on our denylist
+ testdir = os.path.join(self.rust_test_dir, '1 basic')
+ self.init(testdir, extra_args=['--werror'], override_envvars={'RUSTC': 'clippy-driver'})
+ with self.assertRaises(subprocess.CalledProcessError) as cm:
+ self.build()
+ self.assertTrue('error: use of a blacklisted/placeholder name `foo`' in cm.exception.stdout or
+ 'error: use of a disallowed/placeholder name `foo`' in cm.exception.stdout)
+
+ @skip_if_not_language('rust')
+ def test_rust_rlib_linkage(self) -> None:
+ if self.backend is not Backend.ninja:
+ raise unittest.SkipTest('Rust is only supported with ninja currently')
+ template = textwrap.dedent('''\
+ use std::process::exit;
+
+ pub fn fun() {{
+ exit({});
+ }}
+ ''')
+
+ testdir = os.path.join(self.unit_test_dir, '101 rlib linkage')
+ gen_file = os.path.join(testdir, 'lib.rs')
+ with open(gen_file, 'w') as f:
+ f.write(template.format(0))
+ self.addCleanup(windows_proof_rm, gen_file)
+
+ self.init(testdir)
+ self.build()
+ self.run_tests()
+
+ with open(gen_file, 'w') as f:
+ f.write(template.format(39))
+
+ self.build()
+ with self.assertRaises(subprocess.CalledProcessError) as cm:
+ self.run_tests()
+ self.assertEqual(cm.exception.returncode, 1)
+ self.assertIn('exit status 39', cm.exception.stdout)
+
+ def test_custom_target_name(self):
+ testdir = os.path.join(self.unit_test_dir, '99 custom target name')
+ self.init(testdir)
+ out = self.build()
+ if self.backend is Backend.ninja:
+ self.assertIn('Generating file.txt with a custom command', out)
+ self.assertIn('Generating subdir/file.txt with a custom command', out)
+
+ def test_symlinked_subproject(self):
+ testdir = os.path.join(self.unit_test_dir, '106 subproject symlink')
+ subproject_dir = os.path.join(testdir, 'subprojects')
+ subproject = os.path.join(testdir, 'symlinked_subproject')
+ symlinked_subproject = os.path.join(testdir, 'subprojects', 'symlinked_subproject')
+ if not os.path.exists(subproject_dir):
+ os.mkdir(subproject_dir)
+ os.symlink(subproject, symlinked_subproject)
+ self.addCleanup(os.remove, symlinked_subproject)
+
+ self.init(testdir)
+ self.build()
+
+ def test_configure_same_noop(self):
+ testdir = os.path.join(self.unit_test_dir, '108 configure same noop')
+ self.init(testdir, extra_args=['-Dopt=val'])
+
+ filename = os.path.join(self.privatedir, 'coredata.dat')
+ oldmtime = os.path.getmtime(filename)
+ self.setconf(["-Dopt=val"])
+ newmtime = os.path.getmtime(filename)
+ self.assertEqual(oldmtime, newmtime)
+
+ def test_scripts_loaded_modules(self):
+ '''
+ Simulate a wrapped command, as done for custom_target() that capture
+ output. The script will print all python modules loaded and we verify
+ that it contains only an acceptable subset. Loading too many modules
+ slows down the build when many custom targets get wrapped.
+ '''
+ es = ExecutableSerialisation(python_command + ['-c', 'exit(0)'], env=EnvironmentVariables())
+ p = Path(self.builddir, 'exe.dat')
+ with p.open('wb') as f:
+ pickle.dump(es, f)
+ cmd = self.meson_command + ['--internal', 'test_loaded_modules', '--unpickle', str(p)]
+ p = subprocess.run(cmd, stdout=subprocess.PIPE)
+ all_modules = json.loads(p.stdout.splitlines()[0])
+ meson_modules = [m for m in all_modules if 'meson' in m]
+ expected_meson_modules = [
+ 'mesonbuild',
+ 'mesonbuild._pathlib',
+ 'mesonbuild.utils',
+ 'mesonbuild.utils.core',
+ 'mesonbuild.mesonmain',
+ 'mesonbuild.mlog',
+ 'mesonbuild.scripts',
+ 'mesonbuild.scripts.meson_exe',
+ 'mesonbuild.scripts.test_loaded_modules'
+ ]
+ self.assertEqual(sorted(expected_meson_modules), sorted(meson_modules))