diff options
author | Dylan Baker <dylan@pnwbakers.com> | 2018-11-05 09:59:01 -0800 |
---|---|---|
committer | Dylan Baker <dylan@pnwbakers.com> | 2018-11-14 15:57:37 -0800 |
commit | e338e9ad2f08c52ec96541704e8f798d5a6e2742 (patch) | |
tree | c933b894e362c6d75153a4e3f48403931bfe0d73 | |
parent | aa04147a4a2a37aee197ebc0ed179c6fb582b3e9 (diff) | |
download | meson-e338e9ad2f08c52ec96541704e8f798d5a6e2742.zip meson-e338e9ad2f08c52ec96541704e8f798d5a6e2742.tar.gz meson-e338e9ad2f08c52ec96541704e8f798d5a6e2742.tar.bz2 |
environment: detect compilers from native files
-rw-r--r-- | mesonbuild/environment.py | 26 | ||||
-rwxr-xr-x | run_unittests.py | 185 |
2 files changed, 201 insertions, 10 deletions
diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index 72ca6a2..01a7c51 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -512,7 +512,10 @@ class Environment: The list of compilers is detected in the exact same way for C, C++, ObjC, ObjC++, Fortran, CS so consolidate it here. ''' + is_cross = False + exe_wrap = None evar = BinaryTable.evarMap[lang] + if self.is_cross_build() and want_cross: if lang not in self.cross_info.config['binaries']: raise EnvironmentException('{!r} compiler binary not defined in cross file'.format(lang)) @@ -528,13 +531,13 @@ class Environment: shlex.split(os.environ[evar])) # Return value has to be a list of compiler 'choices' compilers = [compilers] - is_cross = False - exe_wrap = None + elif lang in self.config_info.binaries: + compilers, ccache = BinaryTable.parse_entry( + mesonlib.stringlistify(self.config_info.binaries[lang])) + compilers = [compilers] else: compilers = getattr(self, 'default_' + lang) ccache = BinaryTable.detect_ccache() - is_cross = False - exe_wrap = None return compilers, ccache, is_cross, exe_wrap def _handle_exceptions(self, exceptions, binaries, bintype='compiler'): @@ -805,7 +808,11 @@ class Environment: self._handle_exceptions(popen_exceptions, compilers) def detect_java_compiler(self): - exelist = ['javac'] + if 'java' in self.config_info.binaries: + exelist = mesonlib.stringlistify(self.config_info.binaries['java']) + else: + exelist = ['javac'] + try: p, out, err = Popen_safe(exelist + ['-version']) except OSError: @@ -838,6 +845,8 @@ class Environment: def detect_vala_compiler(self): if 'VALAC' in os.environ: exelist = shlex.split(os.environ['VALAC']) + elif 'vala' in self.config_info.binaries: + exelist = mesonlib.stringlistify(self.config_info.binaries['vala']) else: exelist = ['valac'] try: @@ -882,6 +891,8 @@ class Environment: elif self.is_cross_build() and want_cross: exelist = mesonlib.stringlistify(self.cross_info.config['binaries']['d']) is_cross = True + elif 'd' in self.config_info.binaries: + exelist = mesonlib.stringlistify(self.config_info.binaries['d']) elif shutil.which("ldc2"): exelist = ['ldc2'] elif shutil.which("ldc"): @@ -919,7 +930,10 @@ class Environment: raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"') def detect_swift_compiler(self): - exelist = ['swiftc'] + if 'swift' in self.config_info.binaries: + exelist = mesonlib.stringlistify(self.config_info.binaries['swift']) + else: + exelist = ['swiftc'] try: p, _, err = Popen_safe(exelist + ['-v']) except OSError: diff --git a/run_unittests.py b/run_unittests.py index 05a8457..bc11732 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -26,6 +26,7 @@ import sys import unittest import platform import pickle +import functools from itertools import chain from unittest import mock from configparser import ConfigParser @@ -41,7 +42,7 @@ import mesonbuild.modules.gnome from mesonbuild.interpreter import Interpreter, ObjectHolder from mesonbuild.mesonlib import ( is_windows, is_osx, is_cygwin, is_dragonflybsd, is_openbsd, is_haiku, - windows_proof_rmtree, python_command, version_compare, + is_linux, windows_proof_rmtree, python_command, version_compare, BuildDirLock, Version ) from mesonbuild.environment import detect_ninja @@ -108,12 +109,41 @@ def skipIfNoPkgconfig(f): Note: Yes, we provide pkg-config even while running Windows CI ''' + @functools.wraps(f) def wrapped(*args, **kwargs): if not is_ci() and shutil.which('pkg-config') is None: raise unittest.SkipTest('pkg-config not found') return f(*args, **kwargs) return wrapped +def skip_if_not_language(lang): + def wrapper(func): + @functools.wraps(func) + def wrapped(*args, **kwargs): + try: + env = get_fake_env('', '', '') + f = getattr(env, 'detect_{}_compiler'.format(lang)) + if lang in ['cs', 'vala', 'java', 'swift']: + f() + else: + f(False) + except EnvironmentException: + raise unittest.SkipTest('No {} compiler found.'.format(lang)) + return func(*args, **kwargs) + return wrapped + return wrapper + +def skip_if_env_value(value): + def wrapper(func): + @functools.wraps(func) + def wrapped(*args, **kwargs): + if value in os.environ: + raise unittest.SkipTest( + 'Environment variable "{}" set, skipping.'.format(value)) + return func(*args, **kwargs) + return wrapped + return wrapper + class PatchModule: ''' Fancy monkey-patching! Whee! Can't use mock.patch because it only @@ -4481,7 +4511,6 @@ class NativeFileTests(BasePlatformTests): with open(filename, 'wt') as f: f.write(textwrap.dedent('''\ {} - #!/usr/bin/env python3 import argparse import subprocess import sys @@ -4490,11 +4519,11 @@ class NativeFileTests(BasePlatformTests): parser = argparse.ArgumentParser() '''.format(chbang))) for name in kwargs: - f.write(' parser.add_argument("--{}", action="store_true")\n'.format(name)) + 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 kwargs.items(): f.write(' if args.{}:\n'.format(name)) - f.write(' print({})\n'.format(value)) + 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( @@ -4522,6 +4551,20 @@ class NativeFileTests(BasePlatformTests): f.write('py -3 {} %*'.format(filename)) return batfile + def helper_for_compiler(self, lang, cb): + """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 = getattr(env, 'detect_{}_compiler'.format(lang)) + if lang not in ['cs']: + getter = functools.partial(getter, False) + cc = getter() + binary, newid = cb(cc) + env.config_info.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}}) @@ -4565,6 +4608,140 @@ class NativeFileTests(BasePlatformTests): raise unittest.SkipTest('bat indirection breaks internal sanity checks.') self._simple_test('python', 'python') + @unittest.skipIf(is_windows(), 'Setting up multiple compilers on windows is hard') + @skip_if_env_value('CC') + def test_c_compiler(self): + def cb(comp): + if comp.id == 'gcc': + if not shutil.which('clang'): + raise unittest.SkipTest('Only one compiler found, cannot test.') + return 'clang', 'clang' + if not shutil.which('gcc'): + raise unittest.SkipTest('Only one compiler found, cannot test.') + return 'gcc', 'gcc' + self.helper_for_compiler('c', cb) + + @unittest.skipIf(is_windows(), 'Setting up multiple compilers on windows is hard') + @skip_if_env_value('CXX') + def test_cpp_compiler(self): + def cb(comp): + if comp.id == 'gcc': + if not shutil.which('clang++'): + raise unittest.SkipTest('Only one compiler found, cannot test.') + return 'clang++', 'clang' + if not shutil.which('g++'): + raise unittest.SkipTest('Only one compiler found, cannot test.') + return 'g++', 'gcc' + self.helper_for_compiler('cpp', cb) + + @skip_if_not_language('objc') + @skip_if_env_value('OBJC') + def test_objc_compiler(self): + def cb(comp): + if comp.id == 'gcc': + if not shutil.which('clang'): + raise unittest.SkipTest('Only one compiler found, cannot test.') + return 'clang', 'clang' + if not shutil.which('gcc'): + raise unittest.SkipTest('Only one compiler found, cannot test.') + return 'gcc', 'gcc' + self.helper_for_compiler('objc', cb) + + @skip_if_not_language('objcpp') + @skip_if_env_value('OBJCXX') + def test_objcpp_compiler(self): + def cb(comp): + if comp.id == 'gcc': + if not shutil.which('clang++'): + raise unittest.SkipTest('Only one compiler found, cannot test.') + return 'clang++', 'clang' + if not shutil.which('g++'): + raise unittest.SkipTest('Only one compiler found, cannot test.') + return 'g++', 'gcc' + self.helper_for_compiler('objcpp', cb) + + @skip_if_not_language('d') + @skip_if_env_value('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 unittest.SkipTest('No alternative dlang compiler found.') + return 'dmd', 'dmd' + self.helper_for_compiler('d', cb) + + @skip_if_not_language('cs') + @skip_if_env_value('CSC') + def test_cs_compiler(self): + def cb(comp): + if comp.id == 'csc': + if not shutil.which('mcs'): + raise unittest.SkipTest('No alternate C# implementation.') + return 'mcs', 'mcs' + if not shutil.which('csc'): + raise unittest.SkipTest('No alternate C# implementation.') + return 'csc', 'csc' + self.helper_for_compiler('cs', cb) + + @skip_if_not_language('fortran') + @skip_if_env_value('FC') + def test_fortran_compiler(self): + def cb(comp): + if comp.id == 'gcc': + if shutil.which('ifort'): + return 'ifort', 'intel' + # XXX: there are several other fortran compilers meson + # supports, but I don't have any of them to test with + raise unittest.SkipTest('No alternate Fortran implementation.') + if not shutil.which('gfortran'): + raise unittest.SkipTest('No alternate C# implementation.') + return 'gfortran', 'gcc' + self.helper_for_compiler('fortran', cb) + + def _single_implementation_compiler(self, lang, binary, version_str, version): + """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('', '', '') + getter = getattr(env, 'detect_{}_compiler'.format(lang)) + if lang in ['rust']: + getter = functools.partial(getter, False) + env.config_info.binaries = {lang: wrapper} + compiler = getter() + self.assertEqual(compiler.version, version) + + @skip_if_not_language('vala') + @skip_if_env_value('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_value('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('swift') + def test_swift_compiler(self): + wrapper = self.helper_create_binary_wrapper( + 'swiftc', version='Swift 1.2345', outfile='stderr') + env = get_fake_env('', '', '') + env.config_info.binaries = {'swift': wrapper} + compiler = env.detect_swift_compiler() + self.assertEqual(compiler.version, '1.2345') + def unset_envs(): # For unit tests we must fully control all command lines |