summaryrefslogtreecommitdiffstats
path: root/unittests/machinefiletests.py
diff options
context:
space:
mode:
Diffstat (limited to 'unittests/machinefiletests.py')
-rw-r--r--unittests/machinefiletests.py953
1 files changed, 953 insertions, 0 deletions
diff --git a/unittests/machinefiletests.py b/unittests/machinefiletests.py
new file mode 100644
index 0000000..bf109b2
--- /dev/null
+++ b/unittests/machinefiletests.py
@@ -0,0 +1,953 @@
+# 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 tempfile
+import textwrap
+import os
+import shutil
+import functools
+import threading
+import sys
+from itertools import chain
+from unittest import mock, skipIf, SkipTest
+from pathlib import Path
+import typing as T
+
+import mesonbuild.mlog
+import mesonbuild.depfile
+import mesonbuild.dependencies.factory
+import mesonbuild.envconfig
+import mesonbuild.environment
+import mesonbuild.coredata
+import mesonbuild.modules.gnome
+from mesonbuild.mesonlib import (
+ MachineChoice, is_windows, is_osx, is_cygwin, is_haiku, is_sunos
+)
+from mesonbuild.compilers import (
+ detect_swift_compiler, compiler_from_language
+)
+import mesonbuild.modules.pkgconfig
+
+
+from run_tests import (
+ Backend,
+ get_fake_env
+)
+
+from .baseplatformtests import BasePlatformTests
+from .helpers import *
+
+@functools.lru_cache()
+def is_real_gnu_compiler(path):
+ '''
+ Check if the gcc we have is a real gcc and not a macOS wrapper around clang
+ '''
+ if not path:
+ return False
+ out = subprocess.check_output([path, '--version'], universal_newlines=True, stderr=subprocess.STDOUT)
+ return 'Free Software Foundation' in out
+
+class NativeFileTests(BasePlatformTests):
+
+ def setUp(self):
+ super().setUp()
+ self.testcase = os.path.join(self.unit_test_dir, '46 native file binary')
+ self.current_config = 0
+ self.current_wrapper = 0
+
+ def helper_create_native_file(self, values):
+ """Create a config file as a temporary file.
+
+ values should be a nested dictionary structure of {section: {key:
+ value}}
+ """
+ filename = os.path.join(self.builddir, f'generated{self.current_config}.config')
+ self.current_config += 1
+ with open(filename, 'wt', encoding='utf-8') as f:
+ for section, entries in values.items():
+ f.write(f'[{section}]\n')
+ for k, v in entries.items():
+ if isinstance(v, (bool, int, float)):
+ f.write(f"{k}={v}\n")
+ elif isinstance(v, list):
+ f.write("{}=[{}]\n".format(k, ', '.join([f"'{w}'" for w in v])))
+ else:
+ f.write(f"{k}='{v}'\n")
+ return filename
+
+ def helper_create_binary_wrapper(self, binary, dir_=None, extra_args=None, **kwargs):
+ """Creates a wrapper around a binary that overrides specific values."""
+ filename = os.path.join(dir_ or self.builddir, f'binary_wrapper{self.current_wrapper}.py')
+ extra_args = extra_args or {}
+ self.current_wrapper += 1
+ if is_haiku():
+ chbang = '#!/bin/env python3'
+ else:
+ chbang = '#!/usr/bin/env python3'
+
+ with open(filename, 'wt', encoding='utf-8') as f:
+ f.write(textwrap.dedent('''\
+ {}
+ import argparse
+ import subprocess
+ import sys
+
+ def main():
+ parser = argparse.ArgumentParser()
+ '''.format(chbang)))
+ for name in chain(extra_args, kwargs):
+ f.write(' parser.add_argument("-{0}", "--{0}", action="store_true")\n'.format(name))
+ f.write(' args, extra_args = parser.parse_known_args()\n')
+ for name, value in chain(extra_args.items(), kwargs.items()):
+ f.write(f' if args.{name}:\n')
+ f.write(' print("{}", file=sys.{})\n'.format(value, kwargs.get('outfile', 'stdout')))
+ f.write(' sys.exit(0)\n')
+ f.write(textwrap.dedent('''
+ ret = subprocess.run(
+ ["{}"] + extra_args,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ print(ret.stdout.decode('utf-8'))
+ print(ret.stderr.decode('utf-8'), file=sys.stderr)
+ sys.exit(ret.returncode)
+
+ if __name__ == '__main__':
+ main()
+ '''.format(binary)))
+
+ if not is_windows():
+ os.chmod(filename, 0o755)
+ return filename
+
+ # On windows we need yet another level of indirection, as cmd cannot
+ # invoke python files itself, so instead we generate a .bat file, which
+ # invokes our python wrapper
+ batfile = os.path.join(self.builddir, f'binary_wrapper{self.current_wrapper}.bat')
+ with open(batfile, 'wt', encoding='utf-8') as f:
+ f.write(fr'@{sys.executable} {filename} %*')
+ return batfile
+
+ def helper_for_compiler(self, lang, cb, for_machine = MachineChoice.HOST):
+ """Helper for generating tests for overriding compilers for langaugages
+ with more than one implementation, such as C, C++, ObjC, ObjC++, and D.
+ """
+ env = get_fake_env()
+ getter = lambda: compiler_from_language(env, lang, for_machine)
+ cc = getter()
+ binary, newid = cb(cc)
+ env.binaries[for_machine].binaries[lang] = binary
+ compiler = getter()
+ self.assertEqual(compiler.id, newid)
+
+ def test_multiple_native_files_override(self):
+ wrapper = self.helper_create_binary_wrapper('bash', version='foo')
+ config = self.helper_create_native_file({'binaries': {'bash': wrapper}})
+ wrapper = self.helper_create_binary_wrapper('bash', version='12345')
+ config2 = self.helper_create_native_file({'binaries': {'bash': wrapper}})
+ self.init(self.testcase, extra_args=[
+ '--native-file', config, '--native-file', config2,
+ '-Dcase=find_program'])
+
+ # This test hangs on cygwin.
+ @skipIf(os.name != 'posix' or is_cygwin(), 'Uses fifos, which are not available on non Unix OSes.')
+ def test_native_file_is_pipe(self):
+ fifo = os.path.join(self.builddir, 'native.file')
+ os.mkfifo(fifo)
+ with tempfile.TemporaryDirectory() as d:
+ wrapper = self.helper_create_binary_wrapper('bash', d, version='12345')
+
+ def filler():
+ with open(fifo, 'w', encoding='utf-8') as f:
+ f.write('[binaries]\n')
+ f.write(f"bash = '{wrapper}'\n")
+
+ thread = threading.Thread(target=filler)
+ thread.start()
+
+ self.init(self.testcase, extra_args=['--native-file', fifo, '-Dcase=find_program'])
+
+ thread.join()
+ os.unlink(fifo)
+
+ self.init(self.testcase, extra_args=['--wipe'])
+
+ def test_multiple_native_files(self):
+ wrapper = self.helper_create_binary_wrapper('bash', version='12345')
+ config = self.helper_create_native_file({'binaries': {'bash': wrapper}})
+ wrapper = self.helper_create_binary_wrapper('python')
+ config2 = self.helper_create_native_file({'binaries': {'python': wrapper}})
+ self.init(self.testcase, extra_args=[
+ '--native-file', config, '--native-file', config2,
+ '-Dcase=find_program'])
+
+ def _simple_test(self, case, binary, entry=None):
+ wrapper = self.helper_create_binary_wrapper(binary, version='12345')
+ config = self.helper_create_native_file({'binaries': {entry or binary: wrapper}})
+ self.init(self.testcase, extra_args=['--native-file', config, f'-Dcase={case}'])
+
+ def test_find_program(self):
+ self._simple_test('find_program', 'bash')
+
+ def test_config_tool_dep(self):
+ # Do the skip at this level to avoid screwing up the cache
+ if mesonbuild.environment.detect_msys2_arch():
+ raise SkipTest('Skipped due to problems with LLVM on MSYS2')
+ if not shutil.which('llvm-config'):
+ raise SkipTest('No llvm-installed, cannot test')
+ self._simple_test('config_dep', 'llvm-config')
+
+ def test_python3_module(self):
+ self._simple_test('python3', 'python3')
+
+ def test_python_module(self):
+ if is_windows():
+ # Bat adds extra crap to stdout, so the version check logic in the
+ # python module breaks. This is fine on other OSes because they
+ # don't need the extra indirection.
+ raise SkipTest('bat indirection breaks internal sanity checks.')
+ elif is_osx():
+ binary = 'python'
+ else:
+ binary = 'python2'
+
+ # We not have python2, check for it
+ for v in ['2', '2.7', '-2.7']:
+ rc = subprocess.call(['pkg-config', '--cflags', f'python{v}'],
+ stdout=subprocess.DEVNULL,
+ stderr=subprocess.DEVNULL)
+ if rc == 0:
+ break
+ else:
+ raise SkipTest('Not running Python 2 tests because dev packages not installed.')
+ self._simple_test('python', binary, entry='python')
+
+ @skipIf(is_windows(), 'Setting up multiple compilers on windows is hard')
+ @skip_if_env_set('CC')
+ def test_c_compiler(self):
+ def cb(comp):
+ if comp.id == 'gcc':
+ if not shutil.which('clang'):
+ raise SkipTest('Only one compiler found, cannot test.')
+ return 'clang', 'clang'
+ if not is_real_gnu_compiler(shutil.which('gcc')):
+ raise SkipTest('Only one compiler found, cannot test.')
+ return 'gcc', 'gcc'
+ self.helper_for_compiler('c', cb)
+
+ @skipIf(is_windows(), 'Setting up multiple compilers on windows is hard')
+ @skip_if_env_set('CXX')
+ def test_cpp_compiler(self):
+ def cb(comp):
+ if comp.id == 'gcc':
+ if not shutil.which('clang++'):
+ raise SkipTest('Only one compiler found, cannot test.')
+ return 'clang++', 'clang'
+ if not is_real_gnu_compiler(shutil.which('g++')):
+ raise SkipTest('Only one compiler found, cannot test.')
+ return 'g++', 'gcc'
+ self.helper_for_compiler('cpp', cb)
+
+ @skip_if_not_language('objc')
+ @skip_if_env_set('OBJC')
+ def test_objc_compiler(self):
+ def cb(comp):
+ if comp.id == 'gcc':
+ if not shutil.which('clang'):
+ raise SkipTest('Only one compiler found, cannot test.')
+ return 'clang', 'clang'
+ if not is_real_gnu_compiler(shutil.which('gcc')):
+ raise SkipTest('Only one compiler found, cannot test.')
+ return 'gcc', 'gcc'
+ self.helper_for_compiler('objc', cb)
+
+ @skip_if_not_language('objcpp')
+ @skip_if_env_set('OBJCXX')
+ def test_objcpp_compiler(self):
+ def cb(comp):
+ if comp.id == 'gcc':
+ if not shutil.which('clang++'):
+ raise SkipTest('Only one compiler found, cannot test.')
+ return 'clang++', 'clang'
+ if not is_real_gnu_compiler(shutil.which('g++')):
+ raise SkipTest('Only one compiler found, cannot test.')
+ return 'g++', 'gcc'
+ self.helper_for_compiler('objcpp', cb)
+
+ @skip_if_not_language('d')
+ @skip_if_env_set('DC')
+ def test_d_compiler(self):
+ def cb(comp):
+ if comp.id == 'dmd':
+ if shutil.which('ldc'):
+ return 'ldc', 'ldc'
+ elif shutil.which('gdc'):
+ return 'gdc', 'gdc'
+ else:
+ raise SkipTest('No alternative dlang compiler found.')
+ if shutil.which('dmd'):
+ return 'dmd', 'dmd'
+ raise SkipTest('No alternative dlang compiler found.')
+ self.helper_for_compiler('d', cb)
+
+ @skip_if_not_language('cs')
+ @skip_if_env_set('CSC')
+ def test_cs_compiler(self):
+ def cb(comp):
+ if comp.id == 'csc':
+ if not shutil.which('mcs'):
+ raise SkipTest('No alternate C# implementation.')
+ return 'mcs', 'mcs'
+ if not shutil.which('csc'):
+ raise SkipTest('No alternate C# implementation.')
+ return 'csc', 'csc'
+ self.helper_for_compiler('cs', cb)
+
+ @skip_if_not_language('fortran')
+ @skip_if_env_set('FC')
+ def test_fortran_compiler(self):
+ def cb(comp):
+ if comp.id == 'lcc':
+ if shutil.which('lfortran'):
+ return 'lfortran', 'lcc'
+ raise SkipTest('No alternate Fortran implementation.')
+ elif comp.id == 'gcc':
+ if shutil.which('ifort'):
+ # There is an ICC for windows (windows build, linux host),
+ # but we don't support that ATM so lets not worry about it.
+ if is_windows():
+ return 'ifort', 'intel-cl'
+ return 'ifort', 'intel'
+ elif shutil.which('flang'):
+ return 'flang', 'flang'
+ elif shutil.which('pgfortran'):
+ return 'pgfortran', 'pgi'
+ # XXX: there are several other fortran compilers meson
+ # supports, but I don't have any of them to test with
+ raise SkipTest('No alternate Fortran implementation.')
+ if not shutil.which('gfortran'):
+ raise SkipTest('No alternate Fortran implementation.')
+ return 'gfortran', 'gcc'
+ self.helper_for_compiler('fortran', cb)
+
+ def _single_implementation_compiler(self, lang: str, binary: str, version_str: str, version: str) -> None:
+ """Helper for languages with a single (supported) implementation.
+
+ Builds a wrapper around the compiler to override the version.
+ """
+ wrapper = self.helper_create_binary_wrapper(binary, version=version_str)
+ env = get_fake_env()
+ env.binaries.host.binaries[lang] = [wrapper]
+ compiler = compiler_from_language(env, lang, MachineChoice.HOST)
+ self.assertEqual(compiler.version, version)
+
+ @skip_if_not_language('vala')
+ @skip_if_env_set('VALAC')
+ def test_vala_compiler(self):
+ self._single_implementation_compiler(
+ 'vala', 'valac', 'Vala 1.2345', '1.2345')
+
+ @skip_if_not_language('rust')
+ @skip_if_env_set('RUSTC')
+ def test_rust_compiler(self):
+ self._single_implementation_compiler(
+ 'rust', 'rustc', 'rustc 1.2345', '1.2345')
+
+ @skip_if_not_language('java')
+ def test_java_compiler(self):
+ self._single_implementation_compiler(
+ 'java', 'javac', 'javac 9.99.77', '9.99.77')
+
+ @skip_if_not_language('java')
+ def test_java_classpath(self):
+ if self.backend is not Backend.ninja:
+ raise SkipTest('Jar is only supported with Ninja')
+ testdir = os.path.join(self.unit_test_dir, '110 classpath')
+ self.init(testdir)
+ self.build()
+ one_build_path = get_classpath(os.path.join(self.builddir, 'one.jar'))
+ self.assertIsNone(one_build_path)
+ two_build_path = get_classpath(os.path.join(self.builddir, 'two.jar'))
+ self.assertEqual(two_build_path, 'one.jar')
+ self.install()
+ one_install_path = get_classpath(os.path.join(self.installdir, 'usr/bin/one.jar'))
+ self.assertIsNone(one_install_path)
+ two_install_path = get_classpath(os.path.join(self.installdir, 'usr/bin/two.jar'))
+ self.assertIsNone(two_install_path)
+
+ @skip_if_not_language('swift')
+ def test_swift_compiler(self):
+ wrapper = self.helper_create_binary_wrapper(
+ 'swiftc', version='Swift 1.2345', outfile='stderr',
+ extra_args={'Xlinker': 'macosx_version. PROJECT:ld - 1.2.3'})
+ env = get_fake_env()
+ env.binaries.host.binaries['swift'] = [wrapper]
+ compiler = detect_swift_compiler(env, MachineChoice.HOST)
+ self.assertEqual(compiler.version, '1.2345')
+
+ def test_native_file_dirs(self):
+ testcase = os.path.join(self.unit_test_dir, '59 native file override')
+ self.init(testcase, default_args=False,
+ extra_args=['--native-file', os.path.join(testcase, 'nativefile')])
+
+ def test_native_file_dirs_overridden(self):
+ testcase = os.path.join(self.unit_test_dir, '59 native file override')
+ self.init(testcase, default_args=False,
+ extra_args=['--native-file', os.path.join(testcase, 'nativefile'),
+ '-Ddef_libdir=liblib', '-Dlibdir=liblib'])
+
+ def test_compile_sys_path(self):
+ """Compiling with a native file stored in a system path works.
+
+ There was a bug which caused the paths to be stored incorrectly and
+ would result in ninja invoking meson in an infinite loop. This tests
+ for that by actually invoking ninja.
+ """
+ testcase = os.path.join(self.common_test_dir, '1 trivial')
+
+ # It really doesn't matter what's in the native file, just that it exists
+ config = self.helper_create_native_file({'binaries': {'bash': 'false'}})
+
+ self.init(testcase, extra_args=['--native-file', config])
+ self.build()
+
+ def test_user_options(self):
+ testcase = os.path.join(self.common_test_dir, '40 options')
+ for opt, value in [('testoption', 'some other val'), ('other_one', True),
+ ('combo_opt', 'one'), ('array_opt', ['two']),
+ ('integer_opt', 0),
+ ('CaseSenSiTivE', 'SOME other Value'),
+ ('CASESENSITIVE', 'some other Value')]:
+ config = self.helper_create_native_file({'project options': {opt: value}})
+ with self.assertRaises(subprocess.CalledProcessError) as cm:
+ self.init(testcase, extra_args=['--native-file', config])
+ self.assertRegex(cm.exception.stdout, r'Incorrect value to [a-z]+ option')
+
+ def test_user_options_command_line_overrides(self):
+ testcase = os.path.join(self.common_test_dir, '40 options')
+ config = self.helper_create_native_file({'project options': {'other_one': True}})
+ self.init(testcase, extra_args=['--native-file', config, '-Dother_one=false'])
+
+ def test_user_options_subproject(self):
+ testcase = os.path.join(self.unit_test_dir, '78 user options for subproject')
+
+ s = os.path.join(testcase, 'subprojects')
+ if not os.path.exists(s):
+ os.mkdir(s)
+ s = os.path.join(s, 'sub')
+ if not os.path.exists(s):
+ sub = os.path.join(self.common_test_dir, '40 options')
+ shutil.copytree(sub, s)
+
+ for opt, value in [('testoption', 'some other val'), ('other_one', True),
+ ('combo_opt', 'one'), ('array_opt', ['two']),
+ ('integer_opt', 0)]:
+ config = self.helper_create_native_file({'sub:project options': {opt: value}})
+ with self.assertRaises(subprocess.CalledProcessError) as cm:
+ self.init(testcase, extra_args=['--native-file', config])
+ self.assertRegex(cm.exception.stdout, r'Incorrect value to [a-z]+ option')
+
+ def test_option_bool(self):
+ # Bools are allowed to be unquoted
+ testcase = os.path.join(self.common_test_dir, '1 trivial')
+ config = self.helper_create_native_file({'built-in options': {'werror': True}})
+ self.init(testcase, extra_args=['--native-file', config])
+ configuration = self.introspect('--buildoptions')
+ for each in configuration:
+ # Test that no-per subproject options are inherited from the parent
+ if 'werror' in each['name']:
+ self.assertEqual(each['value'], True)
+ break
+ else:
+ self.fail('Did not find werror in build options?')
+
+ def test_option_integer(self):
+ # Bools are allowed to be unquoted
+ testcase = os.path.join(self.common_test_dir, '1 trivial')
+ config = self.helper_create_native_file({'built-in options': {'unity_size': 100}})
+ self.init(testcase, extra_args=['--native-file', config])
+ configuration = self.introspect('--buildoptions')
+ for each in configuration:
+ # Test that no-per subproject options are inherited from the parent
+ if 'unity_size' in each['name']:
+ self.assertEqual(each['value'], 100)
+ break
+ else:
+ self.fail('Did not find unity_size in build options?')
+
+ def test_builtin_options(self):
+ testcase = os.path.join(self.common_test_dir, '2 cpp')
+ config = self.helper_create_native_file({'built-in options': {'cpp_std': 'c++14'}})
+
+ self.init(testcase, extra_args=['--native-file', config])
+ configuration = self.introspect('--buildoptions')
+ for each in configuration:
+ if each['name'] == 'cpp_std':
+ self.assertEqual(each['value'], 'c++14')
+ break
+ else:
+ self.fail('Did not find werror in build options?')
+
+ def test_builtin_options_conf_overrides_env(self):
+ testcase = os.path.join(self.common_test_dir, '2 cpp')
+ config = self.helper_create_native_file({'built-in options': {'pkg_config_path': '/foo'}})
+
+ self.init(testcase, extra_args=['--native-file', config], override_envvars={'PKG_CONFIG_PATH': '/bar'})
+ configuration = self.introspect('--buildoptions')
+ for each in configuration:
+ if each['name'] == 'pkg_config_path':
+ self.assertEqual(each['value'], ['/foo'])
+ break
+ else:
+ self.fail('Did not find pkg_config_path in build options?')
+
+ def test_builtin_options_subprojects(self):
+ testcase = os.path.join(self.common_test_dir, '98 subproject subdir')
+ config = self.helper_create_native_file({'built-in options': {'default_library': 'both', 'c_args': ['-Dfoo']}, 'sub:built-in options': {'default_library': 'static'}})
+
+ self.init(testcase, extra_args=['--native-file', config])
+ configuration = self.introspect('--buildoptions')
+ found = 0
+ for each in configuration:
+ # Test that no-per subproject options are inherited from the parent
+ if 'c_args' in each['name']:
+ # This path will be hit twice, once for build and once for host,
+ self.assertEqual(each['value'], ['-Dfoo'])
+ found += 1
+ elif each['name'] == 'default_library':
+ self.assertEqual(each['value'], 'both')
+ found += 1
+ elif each['name'] == 'sub:default_library':
+ self.assertEqual(each['value'], 'static')
+ found += 1
+ self.assertEqual(found, 4, 'Did not find all three sections')
+
+ def test_builtin_options_subprojects_overrides_buildfiles(self):
+ # If the buildfile says subproject(... default_library: shared), ensure that's overwritten
+ testcase = os.path.join(self.common_test_dir, '223 persubproject options')
+ config = self.helper_create_native_file({'sub2:built-in options': {'default_library': 'shared'}})
+
+ with self.assertRaises((RuntimeError, subprocess.CalledProcessError)) as cm:
+ self.init(testcase, extra_args=['--native-file', config])
+ if isinstance(cm, RuntimeError):
+ check = str(cm.exception)
+ else:
+ check = cm.exception.stdout
+ self.assertIn(check, 'Parent should override default_library')
+
+ def test_builtin_options_subprojects_dont_inherits_parent_override(self):
+ # If the buildfile says subproject(... default_library: shared), ensure that's overwritten
+ testcase = os.path.join(self.common_test_dir, '223 persubproject options')
+ config = self.helper_create_native_file({'built-in options': {'default_library': 'both'}})
+ self.init(testcase, extra_args=['--native-file', config])
+
+ def test_builtin_options_compiler_properties(self):
+ # the properties section can have lang_args, and those need to be
+ # overwritten by the built-in options
+ testcase = os.path.join(self.common_test_dir, '1 trivial')
+ config = self.helper_create_native_file({
+ 'built-in options': {'c_args': ['-DFOO']},
+ 'properties': {'c_args': ['-DBAR']},
+ })
+
+ self.init(testcase, extra_args=['--native-file', config])
+ configuration = self.introspect('--buildoptions')
+ for each in configuration:
+ if each['name'] == 'c_args':
+ self.assertEqual(each['value'], ['-DFOO'])
+ break
+ else:
+ self.fail('Did not find c_args in build options?')
+
+ def test_builtin_options_compiler_properties_legacy(self):
+ # The legacy placement in properties is still valid if a 'built-in
+ # options' setting is present, but doesn't have the lang_args
+ testcase = os.path.join(self.common_test_dir, '1 trivial')
+ config = self.helper_create_native_file({
+ 'built-in options': {'default_library': 'static'},
+ 'properties': {'c_args': ['-DBAR']},
+ })
+
+ self.init(testcase, extra_args=['--native-file', config])
+ configuration = self.introspect('--buildoptions')
+ for each in configuration:
+ if each['name'] == 'c_args':
+ self.assertEqual(each['value'], ['-DBAR'])
+ break
+ else:
+ self.fail('Did not find c_args in build options?')
+
+ def test_builtin_options_paths(self):
+ # the properties section can have lang_args, and those need to be
+ # overwritten by the built-in options
+ testcase = os.path.join(self.common_test_dir, '1 trivial')
+ config = self.helper_create_native_file({
+ 'built-in options': {'bindir': 'foo'},
+ 'paths': {'bindir': 'bar'},
+ })
+
+ self.init(testcase, extra_args=['--native-file', config])
+ configuration = self.introspect('--buildoptions')
+ for each in configuration:
+ if each['name'] == 'bindir':
+ self.assertEqual(each['value'], 'foo')
+ break
+ else:
+ self.fail('Did not find bindir in build options?')
+
+ def test_builtin_options_paths_legacy(self):
+ testcase = os.path.join(self.common_test_dir, '1 trivial')
+ config = self.helper_create_native_file({
+ 'built-in options': {'default_library': 'static'},
+ 'paths': {'bindir': 'bar'},
+ })
+
+ self.init(testcase, extra_args=['--native-file', config])
+ configuration = self.introspect('--buildoptions')
+ for each in configuration:
+ if each['name'] == 'bindir':
+ self.assertEqual(each['value'], 'bar')
+ break
+ else:
+ self.fail('Did not find bindir in build options?')
+
+
+class CrossFileTests(BasePlatformTests):
+
+ """Tests for cross file functionality not directly related to
+ cross compiling.
+
+ This is mainly aimed to testing overrides from cross files.
+ """
+
+ def setUp(self):
+ super().setUp()
+ self.current_config = 0
+ self.current_wrapper = 0
+
+ def _cross_file_generator(self, *, needs_exe_wrapper: bool = False,
+ exe_wrapper: T.Optional[T.List[str]] = None) -> str:
+ if is_windows():
+ raise SkipTest('Cannot run this test on non-mingw/non-cygwin windows')
+
+ return textwrap.dedent(f"""\
+ [binaries]
+ c = '{shutil.which('gcc' if is_sunos() else 'cc')}'
+ ar = '{shutil.which('ar')}'
+ strip = '{shutil.which('strip')}'
+ exe_wrapper = {str(exe_wrapper) if exe_wrapper is not None else '[]'}
+
+ [properties]
+ needs_exe_wrapper = {needs_exe_wrapper}
+
+ [host_machine]
+ system = 'linux'
+ cpu_family = 'x86'
+ cpu = 'i686'
+ endian = 'little'
+ """)
+
+ def _stub_exe_wrapper(self) -> str:
+ return textwrap.dedent('''\
+ #!/usr/bin/env python3
+ import subprocess
+ import sys
+
+ sys.exit(subprocess.run(sys.argv[1:]).returncode)
+ ''')
+
+ def test_needs_exe_wrapper_true(self):
+ testdir = os.path.join(self.unit_test_dir, '70 cross test passed')
+ with tempfile.TemporaryDirectory() as d:
+ p = Path(d) / 'crossfile'
+ with p.open('wt', encoding='utf-8') as f:
+ f.write(self._cross_file_generator(needs_exe_wrapper=True))
+ self.init(testdir, extra_args=['--cross-file=' + str(p)])
+ out = self.run_target('test')
+ self.assertRegex(out, r'Skipped:\s*1\s*\n')
+
+ def test_needs_exe_wrapper_false(self):
+ testdir = os.path.join(self.unit_test_dir, '70 cross test passed')
+ with tempfile.TemporaryDirectory() as d:
+ p = Path(d) / 'crossfile'
+ with p.open('wt', encoding='utf-8') as f:
+ f.write(self._cross_file_generator(needs_exe_wrapper=False))
+ self.init(testdir, extra_args=['--cross-file=' + str(p)])
+ out = self.run_target('test')
+ self.assertNotRegex(out, r'Skipped:\s*1\n')
+
+ def test_needs_exe_wrapper_true_wrapper(self):
+ testdir = os.path.join(self.unit_test_dir, '70 cross test passed')
+ with tempfile.TemporaryDirectory() as d:
+ s = Path(d) / 'wrapper.py'
+ with s.open('wt', encoding='utf-8') as f:
+ f.write(self._stub_exe_wrapper())
+ s.chmod(0o774)
+ p = Path(d) / 'crossfile'
+ with p.open('wt', encoding='utf-8') as f:
+ f.write(self._cross_file_generator(
+ needs_exe_wrapper=True,
+ exe_wrapper=[str(s)]))
+
+ self.init(testdir, extra_args=['--cross-file=' + str(p), '-Dexpect=true'])
+ out = self.run_target('test')
+ self.assertRegex(out, r'Ok:\s*3\s*\n')
+
+ def test_cross_exe_passed_no_wrapper(self):
+ testdir = os.path.join(self.unit_test_dir, '70 cross test passed')
+ with tempfile.TemporaryDirectory() as d:
+ p = Path(d) / 'crossfile'
+ with p.open('wt', encoding='utf-8') as f:
+ f.write(self._cross_file_generator(needs_exe_wrapper=True))
+
+ self.init(testdir, extra_args=['--cross-file=' + str(p)])
+ self.build()
+ out = self.run_target('test')
+ self.assertRegex(out, r'Skipped:\s*1\s*\n')
+
+ # The test uses mocking and thus requires that the current process is the
+ # one to run the Meson steps. If we are using an external test executable
+ # (most commonly in Debian autopkgtests) then the mocking won't work.
+ @skipIf('MESON_EXE' in os.environ, 'MESON_EXE is defined, can not use mocking.')
+ def test_cross_file_system_paths(self):
+ if is_windows():
+ raise SkipTest('system crossfile paths not defined for Windows (yet)')
+
+ testdir = os.path.join(self.common_test_dir, '1 trivial')
+ cross_content = self._cross_file_generator()
+ with tempfile.TemporaryDirectory() as d:
+ dir_ = os.path.join(d, 'meson', 'cross')
+ os.makedirs(dir_)
+ with tempfile.NamedTemporaryFile('w', dir=dir_, delete=False) as f:
+ f.write(cross_content)
+ name = os.path.basename(f.name)
+
+ with mock.patch.dict(os.environ, {'XDG_DATA_HOME': d}):
+ self.init(testdir, extra_args=['--cross-file=' + name], inprocess=True)
+ self.wipe()
+
+ with mock.patch.dict(os.environ, {'XDG_DATA_DIRS': d}):
+ os.environ.pop('XDG_DATA_HOME', None)
+ self.init(testdir, extra_args=['--cross-file=' + name], inprocess=True)
+ self.wipe()
+
+ with tempfile.TemporaryDirectory() as d:
+ dir_ = os.path.join(d, '.local', 'share', 'meson', 'cross')
+ os.makedirs(dir_)
+ with tempfile.NamedTemporaryFile('w', dir=dir_, delete=False) as f:
+ f.write(cross_content)
+ name = os.path.basename(f.name)
+
+ # If XDG_DATA_HOME is set in the environment running the
+ # tests this test will fail, os mock the environment, pop
+ # it, then test
+ with mock.patch.dict(os.environ):
+ os.environ.pop('XDG_DATA_HOME', None)
+ with mock.patch('mesonbuild.coredata.os.path.expanduser', lambda x: x.replace('~', d)):
+ self.init(testdir, extra_args=['--cross-file=' + name], inprocess=True)
+ self.wipe()
+
+ def helper_create_cross_file(self, values):
+ """Create a config file as a temporary file.
+
+ values should be a nested dictionary structure of {section: {key:
+ value}}
+ """
+ filename = os.path.join(self.builddir, f'generated{self.current_config}.config')
+ self.current_config += 1
+ with open(filename, 'wt', encoding='utf-8') as f:
+ for section, entries in values.items():
+ f.write(f'[{section}]\n')
+ for k, v in entries.items():
+ f.write(f"{k}={v!r}\n")
+ return filename
+
+ def test_cross_file_dirs(self):
+ testcase = os.path.join(self.unit_test_dir, '59 native file override')
+ self.init(testcase, default_args=False,
+ extra_args=['--native-file', os.path.join(testcase, 'nativefile'),
+ '--cross-file', os.path.join(testcase, 'crossfile'),
+ '-Ddef_bindir=binbar',
+ '-Ddef_datadir=databar',
+ '-Ddef_includedir=includebar',
+ '-Ddef_infodir=infobar',
+ '-Ddef_libdir=libbar',
+ '-Ddef_libexecdir=libexecbar',
+ '-Ddef_localedir=localebar',
+ '-Ddef_localstatedir=localstatebar',
+ '-Ddef_mandir=manbar',
+ '-Ddef_sbindir=sbinbar',
+ '-Ddef_sharedstatedir=sharedstatebar',
+ '-Ddef_sysconfdir=sysconfbar'])
+
+ def test_cross_file_dirs_overridden(self):
+ testcase = os.path.join(self.unit_test_dir, '59 native file override')
+ self.init(testcase, default_args=False,
+ extra_args=['--native-file', os.path.join(testcase, 'nativefile'),
+ '--cross-file', os.path.join(testcase, 'crossfile'),
+ '-Ddef_libdir=liblib', '-Dlibdir=liblib',
+ '-Ddef_bindir=binbar',
+ '-Ddef_datadir=databar',
+ '-Ddef_includedir=includebar',
+ '-Ddef_infodir=infobar',
+ '-Ddef_libexecdir=libexecbar',
+ '-Ddef_localedir=localebar',
+ '-Ddef_localstatedir=localstatebar',
+ '-Ddef_mandir=manbar',
+ '-Ddef_sbindir=sbinbar',
+ '-Ddef_sharedstatedir=sharedstatebar',
+ '-Ddef_sysconfdir=sysconfbar'])
+
+ def test_cross_file_dirs_chain(self):
+ # crossfile2 overrides crossfile overrides nativefile
+ testcase = os.path.join(self.unit_test_dir, '59 native file override')
+ self.init(testcase, default_args=False,
+ extra_args=['--native-file', os.path.join(testcase, 'nativefile'),
+ '--cross-file', os.path.join(testcase, 'crossfile'),
+ '--cross-file', os.path.join(testcase, 'crossfile2'),
+ '-Ddef_bindir=binbar2',
+ '-Ddef_datadir=databar',
+ '-Ddef_includedir=includebar',
+ '-Ddef_infodir=infobar',
+ '-Ddef_libdir=libbar',
+ '-Ddef_libexecdir=libexecbar',
+ '-Ddef_localedir=localebar',
+ '-Ddef_localstatedir=localstatebar',
+ '-Ddef_mandir=manbar',
+ '-Ddef_sbindir=sbinbar',
+ '-Ddef_sharedstatedir=sharedstatebar',
+ '-Ddef_sysconfdir=sysconfbar'])
+
+ def test_user_options(self):
+ # This is just a touch test for cross file, since the implementation
+ # shares code after loading from the files
+ testcase = os.path.join(self.common_test_dir, '40 options')
+ config = self.helper_create_cross_file({'project options': {'testoption': 'some other value'}})
+ with self.assertRaises(subprocess.CalledProcessError) as cm:
+ self.init(testcase, extra_args=['--cross-file', config])
+ self.assertRegex(cm.exception.stdout, r'Incorrect value to [a-z]+ option')
+
+ def test_builtin_options(self):
+ testcase = os.path.join(self.common_test_dir, '2 cpp')
+ config = self.helper_create_cross_file({'built-in options': {'cpp_std': 'c++14'}})
+
+ self.init(testcase, extra_args=['--cross-file', config])
+ configuration = self.introspect('--buildoptions')
+ for each in configuration:
+ if each['name'] == 'cpp_std':
+ self.assertEqual(each['value'], 'c++14')
+ break
+ else:
+ self.fail('No c++ standard set?')
+
+ def test_builtin_options_per_machine(self):
+ """Test options that are allowed to be set on a per-machine basis.
+
+ Such options could be passed twice, once for the build machine, and
+ once for the host machine. I've picked pkg-config path, but any would
+ do that can be set for both.
+ """
+ testcase = os.path.join(self.common_test_dir, '2 cpp')
+ cross = self.helper_create_cross_file({'built-in options': {'pkg_config_path': '/cross/path', 'cpp_std': 'c++17'}})
+ native = self.helper_create_cross_file({'built-in options': {'pkg_config_path': '/native/path', 'cpp_std': 'c++14'}})
+
+ # Ensure that PKG_CONFIG_PATH is not set in the environment
+ with mock.patch.dict('os.environ'):
+ for k in ['PKG_CONFIG_PATH', 'PKG_CONFIG_PATH_FOR_BUILD']:
+ try:
+ del os.environ[k]
+ except KeyError:
+ pass
+ self.init(testcase, extra_args=['--cross-file', cross, '--native-file', native])
+
+ configuration = self.introspect('--buildoptions')
+ found = 0
+ for each in configuration:
+ if each['name'] == 'pkg_config_path':
+ self.assertEqual(each['value'], ['/cross/path'])
+ found += 1
+ elif each['name'] == 'cpp_std':
+ self.assertEqual(each['value'], 'c++17')
+ found += 1
+ elif each['name'] == 'build.pkg_config_path':
+ self.assertEqual(each['value'], ['/native/path'])
+ found += 1
+ elif each['name'] == 'build.cpp_std':
+ self.assertEqual(each['value'], 'c++14')
+ found += 1
+
+ if found == 4:
+ break
+ self.assertEqual(found, 4, 'Did not find all sections.')
+
+ def test_builtin_options_conf_overrides_env(self):
+ testcase = os.path.join(self.common_test_dir, '2 cpp')
+ config = self.helper_create_cross_file({'built-in options': {'pkg_config_path': '/native', 'cpp_args': '-DFILE'}})
+ cross = self.helper_create_cross_file({'built-in options': {'pkg_config_path': '/cross', 'cpp_args': '-DFILE'}})
+
+ self.init(testcase, extra_args=['--native-file', config, '--cross-file', cross],
+ override_envvars={'PKG_CONFIG_PATH': '/bar', 'PKG_CONFIG_PATH_FOR_BUILD': '/dir',
+ 'CXXFLAGS': '-DENV', 'CXXFLAGS_FOR_BUILD': '-DENV'})
+ configuration = self.introspect('--buildoptions')
+ found = 0
+ expected = 4
+ for each in configuration:
+ if each['name'] == 'pkg_config_path':
+ self.assertEqual(each['value'], ['/cross'])
+ found += 1
+ elif each['name'] == 'build.pkg_config_path':
+ self.assertEqual(each['value'], ['/native'])
+ found += 1
+ elif each['name'].endswith('cpp_args'):
+ self.assertEqual(each['value'], ['-DFILE'])
+ found += 1
+ if found == expected:
+ break
+ self.assertEqual(found, expected, 'Did not find all sections.')
+
+ def test_for_build_env_vars(self) -> None:
+ testcase = os.path.join(self.common_test_dir, '2 cpp')
+ config = self.helper_create_cross_file({'built-in options': {}})
+ cross = self.helper_create_cross_file({'built-in options': {}})
+
+ self.init(testcase, extra_args=['--native-file', config, '--cross-file', cross],
+ override_envvars={'PKG_CONFIG_PATH': '/bar', 'PKG_CONFIG_PATH_FOR_BUILD': '/dir'})
+ configuration = self.introspect('--buildoptions')
+ found = 0
+ for each in configuration:
+ if each['name'] == 'pkg_config_path':
+ self.assertEqual(each['value'], ['/bar'])
+ found += 1
+ elif each['name'] == 'build.pkg_config_path':
+ self.assertEqual(each['value'], ['/dir'])
+ found += 1
+ if found == 2:
+ break
+ self.assertEqual(found, 2, 'Did not find all sections.')
+
+ def test_project_options_native_only(self) -> None:
+ # Do not load project options from a native file when doing a cross
+ # build
+ testcase = os.path.join(self.unit_test_dir, '19 array option')
+ config = self.helper_create_cross_file({'project options': {'list': ['bar', 'foo']}})
+ cross = self.helper_create_cross_file({'binaries': {}})
+
+ self.init(testcase, extra_args=['--native-file', config, '--cross-file', cross])
+ configuration = self.introspect('--buildoptions')
+ for each in configuration:
+ if each['name'] == 'list':
+ self.assertEqual(each['value'], ['foo', 'bar'])
+ break
+ else:
+ self.fail('Did not find expected option.')