diff options
author | Daniel Mensinger <daniel@mensinger-ka.de> | 2021-06-02 19:30:55 +0200 |
---|---|---|
committer | Daniel Mensinger <daniel@mensinger-ka.de> | 2021-06-25 19:34:48 +0200 |
commit | 3f889606c7debafacd3da9c9e74caa61b45a13ff (patch) | |
tree | 2bc4821baebd6e50bfd2999d034328d60c596ff2 | |
parent | 0f237b8d1d84e4906c758bbef44d81e93207f8e4 (diff) | |
download | meson-3f889606c7debafacd3da9c9e74caa61b45a13ff.zip meson-3f889606c7debafacd3da9c9e74caa61b45a13ff.tar.gz meson-3f889606c7debafacd3da9c9e74caa61b45a13ff.tar.bz2 |
Split compiler detection from Environment
This moves all the compiler detection logic into the new
compilers.detect module. This dramatically reduces the size
and complexity of Environment.
-rw-r--r-- | mesonbuild/ast/introspection.py | 3 | ||||
-rw-r--r-- | mesonbuild/backend/backends.py | 4 | ||||
-rw-r--r-- | mesonbuild/build.py | 4 | ||||
-rw-r--r-- | mesonbuild/compilers/__init__.py | 33 | ||||
-rw-r--r-- | mesonbuild/compilers/detect.py | 1209 | ||||
-rw-r--r-- | mesonbuild/compilers/mixins/intel.py | 5 | ||||
-rw-r--r-- | mesonbuild/dependencies/dev.py | 4 | ||||
-rw-r--r-- | mesonbuild/environment.py | 1368 | ||||
-rw-r--r-- | mesonbuild/interpreter/interpreter.py | 2 | ||||
-rw-r--r-- | mesonbuild/linkers/__init__.py | 11 | ||||
-rw-r--r-- | mesonbuild/linkers/detect.py | 216 | ||||
-rw-r--r-- | mesonbuild/mesonlib/universal.py | 44 | ||||
-rwxr-xr-x | run_project_tests.py | 9 | ||||
-rwxr-xr-x | run_unittests.py | 156 |
14 files changed, 1611 insertions, 1457 deletions
diff --git a/mesonbuild/ast/introspection.py b/mesonbuild/ast/introspection.py index 97ebb5a..42813db 100644 --- a/mesonbuild/ast/introspection.py +++ b/mesonbuild/ast/introspection.py @@ -23,6 +23,7 @@ from ..mesonlib import MachineChoice, OptionKey from ..interpreterbase import InvalidArguments, TYPE_nvar from ..build import BuildTarget, Executable, Jar, SharedLibrary, SharedModule, StaticLibrary from ..mparser import BaseNode, ArithmeticNode, ArrayNode, ElementaryNode, IdNode, FunctionNode, StringNode +from ..compilers import detect_compiler_for import typing as T import os import argparse @@ -162,7 +163,7 @@ class IntrospectionInterpreter(AstInterpreter): for lang in sorted(langs, key=compilers.sort_clink): lang = lang.lower() if lang not in self.coredata.compilers[for_machine]: - self.environment.detect_compiler_for(lang, for_machine) + detect_compiler_for(self.environment, lang, for_machine) def func_dependency(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs: T.Dict[str, TYPE_nvar]) -> None: args = self.flatten_args(args) diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 4e470cf..a342acc 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -29,7 +29,7 @@ from .. import dependencies from .. import programs from .. import mesonlib from .. import mlog -from ..compilers import LANGUAGES_USING_LDFLAGS +from ..compilers import LANGUAGES_USING_LDFLAGS, detect from ..mesonlib import ( File, MachineChoice, MesonException, OptionType, OrderedSet, OptionOverrideProxy, classify_unity_sources, OptionKey, join_args @@ -1315,7 +1315,7 @@ class Backend: mlog.warning('Cross file does not specify strip binary, result will not be stripped.') else: # TODO go through all candidates, like others - strip_bin = [self.environment.default_strip[0]] + strip_bin = [detect.defaults['strip'][0]] d = InstallData(self.environment.get_source_dir(), self.environment.get_build_dir(), self.environment.get_prefix(), diff --git a/mesonbuild/build.py b/mesonbuild/build.py index f88bf74..25c8ca1 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -37,7 +37,7 @@ from .mesonlib import ( ) from .compilers import ( Compiler, is_object, clink_langs, sort_clink, lang_suffixes, - is_known_suffix + is_known_suffix, detect_static_linker ) from .linkers import StaticLinker from .interpreterbase import FeatureNew @@ -275,7 +275,7 @@ class Build: def ensure_static_linker(self, compiler): if self.static_linker[compiler.for_machine] is None and compiler.needs_static_linker(): - self.static_linker[compiler.for_machine] = self.environment.detect_static_linker(compiler) + self.static_linker[compiler.for_machine] = detect_static_linker(self.environment, compiler) def get_project(self): return self.projects[''] diff --git a/mesonbuild/compilers/__init__.py b/mesonbuild/compilers/__init__.py index be4809f..3d39c9b 100644 --- a/mesonbuild/compilers/__init__.py +++ b/mesonbuild/compilers/__init__.py @@ -35,6 +35,22 @@ __all__ = [ 'lang_suffixes', 'sort_clink', + 'compiler_from_language', + 'detect_compiler_for', + 'detect_static_linker', + 'detect_c_compiler', + 'detect_cpp_compiler', + 'detect_cuda_compiler', + 'detect_fortran_compiler', + 'detect_objc_compiler', + 'detect_objcpp_compiler', + 'detect_java_compiler', + 'detect_cs_compiler', + 'detect_vala_compiler', + 'detect_rust_compiler', + 'detect_d_compiler', + 'detect_swift_compiler', + 'AppleClangCCompiler', 'AppleClangCPPCompiler', 'AppleClangObjCCompiler', @@ -133,6 +149,23 @@ from .compilers import ( LANGUAGES_USING_LDFLAGS, sort_clink, ) +from .detect import ( + compiler_from_language, + detect_compiler_for, + detect_static_linker, + detect_c_compiler, + detect_cpp_compiler, + detect_cuda_compiler, + detect_objc_compiler, + detect_objcpp_compiler, + detect_fortran_compiler, + detect_java_compiler, + detect_cs_compiler, + detect_vala_compiler, + detect_rust_compiler, + detect_d_compiler, + detect_swift_compiler, +) from .c import ( CCompiler, AppleClangCCompiler, diff --git a/mesonbuild/compilers/detect.py b/mesonbuild/compilers/detect.py new file mode 100644 index 0000000..f79a61b --- /dev/null +++ b/mesonbuild/compilers/detect.py @@ -0,0 +1,1209 @@ +# Copyright 2012-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. + +from ..mesonlib import ( + MachineChoice, MesonException, EnvironmentException, + search_version, is_windows, Popen_safe, windows_proof_rm, +) +from ..envconfig import BinaryTable +from .. import mlog + +from ..linkers import ( + guess_win_linker, + guess_nix_linker, + AIXArLinker, + ArLinker, + ArmarLinker, + ArmClangDynamicLinker, + ArmDynamicLinker, + CcrxLinker, + CcrxDynamicLinker, + CompCertLinker, + CompCertDynamicLinker, + C2000Linker, + C2000DynamicLinker, + DLinker, + NvidiaHPC_DynamicLinker, + PGIDynamicLinker, + PGIStaticLinker, + StaticLinker, + Xc16Linker, + Xc16DynamicLinker, + XilinkDynamicLinker, + CudaLinker, + IntelVisualStudioLinker, + VisualStudioLinker, + VisualStudioLikeLinkerMixin, + WASMDynamicLinker, +) +from .compilers import Compiler +from .c import ( + AppleClangCCompiler, + ArmCCompiler, + ArmclangCCompiler, + ClangCCompiler, + ClangClCCompiler, + GnuCCompiler, + ElbrusCCompiler, + EmscriptenCCompiler, + IntelCCompiler, + IntelClCCompiler, + NvidiaHPC_CCompiler, + PGICCompiler, + CcrxCCompiler, + Xc16CCompiler, + CompCertCCompiler, + C2000CCompiler, + VisualStudioCCompiler, +) +from .cpp import ( + AppleClangCPPCompiler, + ArmCPPCompiler, + ArmclangCPPCompiler, + ClangCPPCompiler, + ClangClCPPCompiler, + GnuCPPCompiler, + ElbrusCPPCompiler, + EmscriptenCPPCompiler, + IntelCPPCompiler, + IntelClCPPCompiler, + NvidiaHPC_CPPCompiler, + PGICPPCompiler, + CcrxCPPCompiler, + C2000CPPCompiler, + VisualStudioCPPCompiler, +) +from .cs import MonoCompiler, VisualStudioCsCompiler +from .d import ( + DCompiler, + DmdDCompiler, + GnuDCompiler, + LLVMDCompiler, +) +from .cuda import CudaCompiler +from .fortran import ( + G95FortranCompiler, + GnuFortranCompiler, + ElbrusFortranCompiler, + FlangFortranCompiler, + IntelFortranCompiler, + IntelClFortranCompiler, + NAGFortranCompiler, + Open64FortranCompiler, + PathScaleFortranCompiler, + NvidiaHPC_FortranCompiler, + PGIFortranCompiler, + SunFortranCompiler, +) +from .java import JavaCompiler +from .objc import ( + AppleClangObjCCompiler, + ClangObjCCompiler, + GnuObjCCompiler, +) +from .objcpp import ( + AppleClangObjCPPCompiler, + ClangObjCPPCompiler, + GnuObjCPPCompiler, +) +from .cython import CythonCompiler +from .rust import RustCompiler +from .swift import SwiftCompiler +from .vala import ValaCompiler +from .mixins.visualstudio import VisualStudioLikeCompiler +from .mixins.gnu import GnuCompiler +from .mixins.clang import ClangCompiler + +import subprocess +import platform +import re +import shutil +import tempfile +import os +import typing as T + +if T.TYPE_CHECKING: + from ..environment import Environment + from ..programs import ExternalProgram + + + +# Default compilers and linkers +# ============================= + +defaults: T.Dict[str, T.List[str]] = {} + +# List of potential compilers. +if is_windows(): + # Intel C and C++ compiler is icl on Windows, but icc and icpc elsewhere. + # Search for icl before cl, since Intel "helpfully" provides a + # cl.exe that returns *exactly the same thing* that microsofts + # cl.exe does, and if icl is present, it's almost certainly what + # you want. + defaults['c'] = ['icl', 'cl', 'cc', 'gcc', 'clang', 'clang-cl', 'pgcc'] + # There is currently no pgc++ for Windows, only for Mac and Linux. + defaults['cpp'] = ['icl', 'cl', 'c++', 'g++', 'clang++', 'clang-cl'] + defaults['fortran'] = ['ifort', 'gfortran', 'flang', 'pgfortran', 'g95'] + # Clang and clang++ are valid, but currently unsupported. + defaults['objc'] = ['cc', 'gcc'] + defaults['objcpp'] = ['c++', 'g++'] + defaults['cs'] = ['csc', 'mcs'] +else: + if platform.machine().lower() == 'e2k': + # There are no objc or objc++ compilers for Elbrus, + # and there's no clang which can build binaries for host. + defaults['c'] = ['cc', 'gcc', 'lcc'] + defaults['cpp'] = ['c++', 'g++', 'l++'] + defaults['objc'] = [] + defaults['objcpp'] = [] + else: + defaults['c'] = ['cc', 'gcc', 'clang', 'nvc', 'pgcc', 'icc'] + defaults['cpp'] = ['c++', 'g++', 'clang++', 'nvc++', 'pgc++', 'icpc'] + defaults['objc'] = ['cc', 'gcc', 'clang'] + defaults['objcpp'] = ['c++', 'g++', 'clang++'] + defaults['fortran'] = ['gfortran', 'flang', 'nvfortran', 'pgfortran', 'ifort', 'g95'] + defaults['cs'] = ['mcs', 'csc'] +defaults['d'] = ['ldc2', 'ldc', 'gdc', 'dmd'] +defaults['java'] = ['javac'] +defaults['cuda'] = ['nvcc'] +defaults['rust'] = ['rustc'] +defaults['swift'] = ['swiftc'] +defaults['vala'] = ['valac'] +defaults['cython'] = ['cython'] +defaults['static_linker'] = ['ar', 'gar'] +defaults['strip'] = ['strip'] +defaults['vs_static_linker'] = ['lib'] +defaults['clang_cl_static_linker'] = ['llvm-lib'] +defaults['cuda_static_linker'] = ['nvlink'] +defaults['gcc_static_linker'] = ['gcc-ar'] +defaults['clang_static_linker'] = ['llvm-ar'] + + +def compiler_from_language(env: 'Environment', lang: str, for_machine: MachineChoice): + lang_map = { + 'c': detect_c_compiler, + 'cpp': detect_cpp_compiler, + 'objc': detect_objc_compiler, + 'cuda': detect_cuda_compiler, + 'objcpp': detect_objcpp_compiler, + 'java': detect_java_compiler, + 'cs': detect_cs_compiler, + 'vala': detect_vala_compiler, + 'd': detect_d_compiler, + 'rust': detect_rust_compiler, + 'fortran': detect_fortran_compiler, + 'swift': detect_swift_compiler, + 'cython': detect_cython_compiler, + } + return lang_map[lang](env, for_machine) if lang in lang_map else None + +def detect_compiler_for(env: 'Environment', lang: str, for_machine: MachineChoice): + comp = compiler_from_language(env, lang, for_machine) + if comp is not None: + assert comp.for_machine == for_machine + env.coredata.process_new_compiler(lang, comp, env) + return comp + + +# Helpers +# ======= + +def _get_compilers(env: 'Environment', lang: str, for_machine: MachineChoice) -> T.Tuple[T.List[str], T.List[str], T.Optional['ExternalProgram']]: + ''' + The list of compilers is detected in the exact same way for + C, C++, ObjC, ObjC++, Fortran, CS so consolidate it here. + ''' + value = env.lookup_binary_entry(for_machine, lang) + if value is not None: + compilers, ccache = BinaryTable.parse_entry(value) + # Return value has to be a list of compiler 'choices' + compilers = [compilers] + else: + if not env.machines.matches_build_machine(for_machine): + raise EnvironmentException(f'{lang!r} compiler binary not defined in cross or native file') + compilers = defaults[lang] + ccache = BinaryTable.detect_ccache() + + if env.machines.matches_build_machine(for_machine): + exe_wrap = None + else: + exe_wrap = env.get_exe_wrapper() + + return compilers, ccache, exe_wrap + +def _handle_exceptions(exceptions, binaries, bintype='compiler'): + errmsg = f'Unknown {bintype}(s): {binaries}' + if exceptions: + errmsg += '\nThe following exception(s) were encountered:' + for c, e in exceptions.items(): + errmsg += f'\nRunning "{c}" gave "{e}"' + raise EnvironmentException(errmsg) + + +# Linker specific +# =============== + +def detect_static_linker(env: 'Environment', compiler): + linker = env.lookup_binary_entry(compiler.for_machine, 'ar') + if linker is not None: + linkers = [linker] + else: + default_linkers = [[l] for l in defaults['static_linker']] + if isinstance(compiler, CudaCompiler): + linkers = [defaults['cuda_static_linker']] + default_linkers + elif isinstance(compiler, VisualStudioLikeCompiler): + linkers = [defaults['vs_static_linker'], defaults['clang_cl_static_linker']] + elif isinstance(compiler, GnuCompiler): + # Use gcc-ar if available; needed for LTO + linkers = [defaults['gcc_static_linker']] + default_linkers + elif isinstance(compiler, ClangCompiler): + # Use llvm-ar if available; needed for LTO + linkers = [defaults['clang_static_linker']] + default_linkers + elif isinstance(compiler, DCompiler): + # Prefer static linkers over linkers used by D compilers + if is_windows(): + linkers = [defaults['vs_static_linker'], defaults['clang_cl_static_linker'], compiler.get_linker_exelist()] + else: + linkers = default_linkers + elif isinstance(compiler, IntelClCCompiler): + # Intel has it's own linker that acts like microsoft's lib + linkers = ['xilib'] + elif isinstance(compiler, (PGICCompiler, PGIFortranCompiler)) and is_windows(): + linkers = [['ar']] # For PGI on Windows, "ar" is just a wrapper calling link/lib. + else: + linkers = default_linkers + popen_exceptions = {} + for linker in linkers: + if not {'lib', 'lib.exe', 'llvm-lib', 'llvm-lib.exe', 'xilib', 'xilib.exe'}.isdisjoint(linker): + arg = '/?' + elif not {'ar2000', 'ar2000.exe'}.isdisjoint(linker): + arg = '?' + else: + arg = '--version' + try: + p, out, err = Popen_safe(linker + [arg]) + except OSError as e: + popen_exceptions[' '.join(linker + [arg])] = e + continue + if "xilib: executing 'lib'" in err: + return IntelVisualStudioLinker(linker, getattr(compiler, 'machine', None)) + if '/OUT:' in out.upper() or '/OUT:' in err.upper(): + return VisualStudioLinker(linker, getattr(compiler, 'machine', None)) + if 'ar-Error-Unknown switch: --version' in err: + return PGIStaticLinker(linker) + if p.returncode == 0 and ('armar' in linker or 'armar.exe' in linker): + return ArmarLinker(linker) + if 'DMD32 D Compiler' in out or 'DMD64 D Compiler' in out: + return DLinker(linker, compiler.arch) + if 'LDC - the LLVM D compiler' in out: + return DLinker(linker, compiler.arch, rsp_syntax=compiler.rsp_file_syntax()) + if 'GDC' in out and ' based on D ' in out: + return DLinker(linker, compiler.arch) + if err.startswith('Renesas') and ('rlink' in linker or 'rlink.exe' in linker): + return CcrxLinker(linker) + if out.startswith('GNU ar') and ('xc16-ar' in linker or 'xc16-ar.exe' in linker): + return Xc16Linker(linker) + if out.startswith('TMS320C2000') and ('ar2000' in linker or 'ar2000.exe' in linker): + return C2000Linker(linker) + if out.startswith('The CompCert'): + return CompCertLinker(linker) + if p.returncode == 0: + return ArLinker(linker) + if p.returncode == 1 and err.startswith('usage'): # OSX + return ArLinker(linker) + if p.returncode == 1 and err.startswith('Usage'): # AIX + return AIXArLinker(linker) + if p.returncode == 1 and err.startswith('ar: bad option: --'): # Solaris + return ArLinker(linker) + _handle_exceptions(popen_exceptions, linkers, 'linker') + raise EnvironmentException('Unknown static linker "{}"'.format(' '.join(linkers))) + + + +# Compilers +# ========= + + +def _detect_c_or_cpp_compiler(env: 'Environment', lang: str, for_machine: MachineChoice, *, override_compiler: T.Optional[T.List[str]] = None) -> Compiler: + """Shared implementation for finding the C or C++ compiler to use. + + the override_compiler option is provided to allow compilers which use + the compiler (GCC or Clang usually) as their shared linker, to find + the linker they need. + """ + popen_exceptions = {} + compilers, ccache, exe_wrap = _get_compilers(env, lang, for_machine) + if override_compiler is not None: + compilers = [override_compiler] + is_cross = env.is_cross_build(for_machine) + info = env.machines[for_machine] + + for compiler in compilers: + if isinstance(compiler, str): + compiler = [compiler] + compiler_name = os.path.basename(compiler[0]) + + if not {'cl', 'cl.exe', 'clang-cl', 'clang-cl.exe'}.isdisjoint(compiler): + # Watcom C provides it's own cl.exe clone that mimics an older + # version of Microsoft's compiler. Since Watcom's cl.exe is + # just a wrapper, we skip using it if we detect its presence + # so as not to confuse Meson when configuring for MSVC. + # + # Additionally the help text of Watcom's cl.exe is paged, and + # the binary will not exit without human intervention. In + # practice, Meson will block waiting for Watcom's cl.exe to + # exit, which requires user input and thus will never exit. + if 'WATCOM' in os.environ: + def sanitize(p): + return os.path.normcase(os.path.abspath(p)) + + watcom_cls = [sanitize(os.path.join(os.environ['WATCOM'], 'BINNT', 'cl')), + sanitize(os.path.join(os.environ['WATCOM'], 'BINNT', 'cl.exe'))] + found_cl = sanitize(shutil.which('cl')) + if found_cl in watcom_cls: + continue + arg = '/?' + elif 'armcc' in compiler_name: + arg = '--vsn' + elif 'ccrx' in compiler_name: + arg = '-v' + elif 'xc16' in compiler_name: + arg = '--version' + elif 'ccomp' in compiler_name: + arg = '-version' + elif 'cl2000' in compiler_name: + arg = '-version' + elif compiler_name in {'icl', 'icl.exe'}: + # if you pass anything to icl you get stuck in a pager + arg = '' + else: + arg = '--version' + + try: + p, out, err = Popen_safe(compiler + [arg]) + except OSError as e: + popen_exceptions[' '.join(compiler + [arg])] = e + continue + + if 'ccrx' in compiler_name: + out = err + + full_version = out.split('\n', 1)[0] + version = search_version(out) + + guess_gcc_or_lcc = False + if 'Free Software Foundation' in out or 'xt-' in out: + guess_gcc_or_lcc = 'gcc' + if 'e2k' in out and 'lcc' in out: + guess_gcc_or_lcc = 'lcc' + if 'Microchip Technology' in out: + # this output has "Free Software Foundation" in its version + guess_gcc_or_lcc = False + + if guess_gcc_or_lcc: + defines = _get_gnu_compiler_defines(compiler) + if not defines: + popen_exceptions[' '.join(compiler)] = 'no pre-processor defines' + continue + + if guess_gcc_or_lcc == 'lcc': + version = _get_lcc_version_from_defines(defines) + cls = ElbrusCCompiler if lang == 'c' else ElbrusCPPCompiler + else: + version = _get_gnu_version_from_defines(defines) + cls = GnuCCompiler if lang == 'c' else GnuCPPCompiler + + linker = guess_nix_linker(env, compiler, cls, for_machine) + + return cls( + ccache + compiler, version, for_machine, is_cross, + info, exe_wrap, defines=defines, full_version=full_version, + linker=linker) + + if 'Emscripten' in out: + cls = EmscriptenCCompiler if lang == 'c' else EmscriptenCPPCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + + # emcc requires a file input in order to pass arguments to the + # linker. It'll exit with an error code, but still print the + # linker version. Old emcc versions ignore -Wl,--version completely, + # however. We'll report "unknown version" in that case. + with tempfile.NamedTemporaryFile(suffix='.c') as f: + cmd = compiler + [cls.LINKER_PREFIX + "--version", f.name] + _, o, _ = Popen_safe(cmd) + + linker = WASMDynamicLinker( + compiler, for_machine, cls.LINKER_PREFIX, + [], version=search_version(o)) + return cls( + ccache + compiler, version, for_machine, is_cross, info, + exe_wrap, linker=linker, full_version=full_version) + + if 'armclang' in out: + # The compiler version is not present in the first line of output, + # instead it is present in second line, startswith 'Component:'. + # So, searching for the 'Component' in out although we know it is + # present in second line, as we are not sure about the + # output format in future versions + arm_ver_str = re.search('.*Component.*', out) + if arm_ver_str is None: + popen_exceptions[' '.join(compiler)] = 'version string not found' + continue + arm_ver_str = arm_ver_str.group(0) + # Override previous values + version = search_version(arm_ver_str) + full_version = arm_ver_str + cls = ArmclangCCompiler if lang == 'c' else ArmclangCPPCompiler + linker = ArmClangDynamicLinker(for_machine, version=version) + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + return cls( + ccache + compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=linker) + if 'CL.EXE COMPATIBILITY' in out: + # if this is clang-cl masquerading as cl, detect it as cl, not + # clang + arg = '--version' + try: + p, out, err = Popen_safe(compiler + [arg]) + except OSError as e: + popen_exceptions[' '.join(compiler + [arg])] = e + version = search_version(out) + match = re.search('^Target: (.*?)-', out, re.MULTILINE) + if match: + target = match.group(1) + else: + target = 'unknown target' + cls = ClangClCCompiler if lang == 'c' else ClangClCPPCompiler + linker = guess_win_linker(env, ['lld-link'], cls, for_machine) + return cls( + compiler, version, for_machine, is_cross, info, target, + exe_wrap, linker=linker) + if 'clang' in out or 'Clang' in out: + linker = None + + defines = _get_clang_compiler_defines(compiler) + + # Even if the for_machine is darwin, we could be using vanilla + # clang. + if 'Apple' in out: + cls = AppleClangCCompiler if lang == 'c' else AppleClangCPPCompiler + else: + cls = ClangCCompiler if lang == 'c' else ClangCPPCompiler + + if 'windows' in out or env.machines[for_machine].is_windows(): + # If we're in a MINGW context this actually will use a gnu + # style ld, but for clang on "real" windows we'll use + # either link.exe or lld-link.exe + try: + linker = guess_win_linker(env, compiler, cls, for_machine, invoked_directly=False) + except MesonException: + pass + if linker is None: + linker = guess_nix_linker(env, compiler, cls, for_machine) + + return cls( + ccache + compiler, version, for_machine, is_cross, info, + exe_wrap, defines=defines, full_version=full_version, linker=linker) + + if 'Intel(R) C++ Intel(R)' in err: + version = search_version(err) + target = 'x86' if 'IA-32' in err else 'x86_64' + cls = IntelClCCompiler if lang == 'c' else IntelClCPPCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = XilinkDynamicLinker(for_machine, [], version=version) + return cls( + compiler, version, for_machine, is_cross, info, target, + exe_wrap, linker=linker) + if 'Microsoft' in out or 'Microsoft' in err: + # Latest versions of Visual Studio print version + # number to stderr but earlier ones print version + # on stdout. Why? Lord only knows. + # Check both outputs to figure out version. + for lookat in [err, out]: + version = search_version(lookat) + if version != 'unknown version': + break + else: + m = 'Failed to detect MSVC compiler version: stderr was\n{!r}' + raise EnvironmentException(m.format(err)) + cl_signature = lookat.split('\n')[0] + match = re.search(r'.*(x86|x64|ARM|ARM64)([^_A-Za-z0-9]|$)', cl_signature) + if match: + target = match.group(1) + else: + m = 'Failed to detect MSVC compiler target architecture: \'cl /?\' output is\n{}' + raise EnvironmentException(m.format(cl_signature)) + cls = VisualStudioCCompiler if lang == 'c' else VisualStudioCPPCompiler + linker = guess_win_linker(env, ['link'], cls, for_machine) + return cls( + compiler, version, for_machine, is_cross, info, target, + exe_wrap, full_version=cl_signature, linker=linker) + if 'PGI Compilers' in out: + cls = PGICCompiler if lang == 'c' else PGICPPCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = PGIDynamicLinker(compiler, for_machine, cls.LINKER_PREFIX, [], version=version) + return cls( + ccache + compiler, version, for_machine, is_cross, + info, exe_wrap, linker=linker) + if 'NVIDIA Compilers and Tools' in out: + cls = NvidiaHPC_CCompiler if lang == 'c' else NvidiaHPC_CPPCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = NvidiaHPC_DynamicLinker(compiler, for_machine, cls.LINKER_PREFIX, [], version=version) + return cls( + ccache + compiler, version, for_machine, is_cross, + info, exe_wrap, linker=linker) + if '(ICC)' in out: + cls = IntelCCompiler if lang == 'c' else IntelCPPCompiler + l = guess_nix_linker(env, compiler, cls, for_machine) + return cls( + ccache + compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=l) + if 'ARM' in out: + cls = ArmCCompiler if lang == 'c' else ArmCPPCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = ArmDynamicLinker(for_machine, version=version) + return cls( + ccache + compiler, version, for_machine, is_cross, + info, exe_wrap, full_version=full_version, linker=linker) + if 'RX Family' in out: + cls = CcrxCCompiler if lang == 'c' else CcrxCPPCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = CcrxDynamicLinker(for_machine, version=version) + return cls( + ccache + compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=linker) + + if 'Microchip Technology' in out: + cls = Xc16CCompiler if lang == 'c' else Xc16CCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = Xc16DynamicLinker(for_machine, version=version) + return cls( + ccache + compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=linker) + + if 'CompCert' in out: + cls = CompCertCCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = CompCertDynamicLinker(for_machine, version=version) + return cls( + ccache + compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=linker) + + if 'TMS320C2000 C/C++' in out: + cls = C2000CCompiler if lang == 'c' else C2000CPPCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = C2000DynamicLinker(for_machine, version=version) + return cls( + ccache + compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=linker) + + + _handle_exceptions(popen_exceptions, compilers) + +def detect_c_compiler(env: 'Environment', for_machine: MachineChoice): + return _detect_c_or_cpp_compiler(env, 'c', for_machine) + +def detect_cpp_compiler(env: 'Environment', for_machine: MachineChoice): + return _detect_c_or_cpp_compiler(env, 'cpp', for_machine) + +def detect_cuda_compiler(env: 'Environment', for_machine: MachineChoice): + popen_exceptions = {} + is_cross = env.is_cross_build(for_machine) + compilers, ccache, exe_wrap = _get_compilers(env, 'cuda', for_machine) + info = env.machines[for_machine] + for compiler in compilers: + if isinstance(compiler, str): + compiler = [compiler] + arg = '--version' + try: + p, out, err = Popen_safe(compiler + [arg]) + except OSError as e: + popen_exceptions[' '.join(compiler + [arg])] = e + continue + # Example nvcc printout: + # + # nvcc: NVIDIA (R) Cuda compiler driver + # Copyright (c) 2005-2018 NVIDIA Corporation + # Built on Sat_Aug_25_21:08:01_CDT_2018 + # Cuda compilation tools, release 10.0, V10.0.130 + # + # search_version() first finds the "10.0" after "release", + # rather than the more precise "10.0.130" after "V". + # The patch version number is occasionally important; For + # instance, on Linux, + # - CUDA Toolkit 8.0.44 requires NVIDIA Driver 367.48 + # - CUDA Toolkit 8.0.61 requires NVIDIA Driver 375.26 + # Luckily, the "V" also makes it very simple to extract + # the full version: + version = out.strip().split('V')[-1] + cpp_compiler = detect_cpp_compiler(env, for_machine) + cls = CudaCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = CudaLinker(compiler, for_machine, CudaCompiler.LINKER_PREFIX, [], version=CudaLinker.parse_version()) + return cls(ccache + compiler, version, for_machine, is_cross, exe_wrap, host_compiler=cpp_compiler, info=info, linker=linker) + raise EnvironmentException('Could not find suitable CUDA compiler: "' + ' '.join(compilers) + '"') + +def detect_fortran_compiler(env: 'Environment', for_machine: MachineChoice): + popen_exceptions = {} + compilers, ccache, exe_wrap = _get_compilers(env, 'fortran', for_machine) + is_cross = env.is_cross_build(for_machine) + info = env.machines[for_machine] + for compiler in compilers: + if isinstance(compiler, str): + compiler = [compiler] + for arg in ['--version', '-V']: + try: + p, out, err = Popen_safe(compiler + [arg]) + except OSError as e: + popen_exceptions[' '.join(compiler + [arg])] = e + continue + + version = search_version(out) + full_version = out.split('\n', 1)[0] + + guess_gcc_or_lcc = False + if 'GNU Fortran' in out: + guess_gcc_or_lcc = 'gcc' + if 'e2k' in out and 'lcc' in out: + guess_gcc_or_lcc = 'lcc' + + if guess_gcc_or_lcc: + defines = _get_gnu_compiler_defines(compiler) + if not defines: + popen_exceptions[' '.join(compiler)] = 'no pre-processor defines' + continue + if guess_gcc_or_lcc == 'lcc': + version = _get_lcc_version_from_defines(defines) + cls = ElbrusFortranCompiler + else: + version = _get_gnu_version_from_defines(defines) + cls = GnuFortranCompiler + linker = guess_nix_linker(env, compiler, cls, for_machine) + return cls( + compiler, version, for_machine, is_cross, info, + exe_wrap, defines, full_version=full_version, + linker=linker) + + if 'G95' in out: + cls = G95FortranCompiler + linker = guess_nix_linker(env, compiler, cls, for_machine) + return G95FortranCompiler( + compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=linker) + + if 'Sun Fortran' in err: + version = search_version(err) + cls = SunFortranCompiler + linker = guess_nix_linker(env, compiler, cls, for_machine) + return SunFortranCompiler( + compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=linker) + + if 'Intel(R) Visual Fortran' in err: + version = search_version(err) + target = 'x86' if 'IA-32' in err else 'x86_64' + cls = IntelClFortranCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = XilinkDynamicLinker(for_machine, [], version=version) + return cls( + compiler, version, for_machine, is_cross, info, + target, exe_wrap, linker=linker) + + if 'ifort (IFORT)' in out: + linker = guess_nix_linker(env, compiler, IntelFortranCompiler, for_machine) + return IntelFortranCompiler( + compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=linker) + + if 'PathScale EKOPath(tm)' in err: + return PathScaleFortranCompiler( + compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version) + + if 'PGI Compilers' in out: + cls = PGIFortranCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = PGIDynamicLinker(compiler, for_machine, + cls.LINKER_PREFIX, [], version=version) + return cls( + compiler, version, for_machine, is_cross, info, exe_wrap, + full_version=full_version, linker=linker) + + if 'NVIDIA Compilers and Tools' in out: + cls = NvidiaHPC_FortranCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = PGIDynamicLinker(compiler, for_machine, + cls.LINKER_PREFIX, [], version=version) + return cls( + compiler, version, for_machine, is_cross, info, exe_wrap, + full_version=full_version, linker=linker) + + if 'flang' in out or 'clang' in out: + linker = guess_nix_linker(env, + compiler, FlangFortranCompiler, for_machine) + return FlangFortranCompiler( + compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=linker) + + if 'Open64 Compiler Suite' in err: + linker = guess_nix_linker(env, + compiler, Open64FortranCompiler, for_machine) + return Open64FortranCompiler( + compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=linker) + + if 'NAG Fortran' in err: + linker = guess_nix_linker(env, + compiler, NAGFortranCompiler, for_machine) + return NAGFortranCompiler( + compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=linker) + + _handle_exceptions(popen_exceptions, compilers) + +def detect_objc_compiler(env: 'Environment', for_machine: MachineChoice) -> 'Compiler': + return _detect_objc_or_objcpp_compiler(env, for_machine, True) + +def detect_objcpp_compiler(env: 'Environment', for_machine: MachineChoice) -> 'Compiler': + return _detect_objc_or_objcpp_compiler(env, for_machine, False) + +def _detect_objc_or_objcpp_compiler(env: 'Environment', for_machine: MachineChoice, objc: bool) -> 'Compiler': + popen_exceptions = {} + compilers, ccache, exe_wrap = _get_compilers(env, 'objc' if objc else 'objcpp', for_machine) + is_cross = env.is_cross_build(for_machine) + info = env.machines[for_machine] + + for compiler in compilers: + if isinstance(compiler, str): + compiler = [compiler] + arg = ['--version'] + try: + p, out, err = Popen_safe(compiler + arg) + except OSError as e: + popen_exceptions[' '.join(compiler + arg)] = e + continue + version = search_version(out) + if 'Free Software Foundation' in out: + defines = _get_gnu_compiler_defines(compiler) + if not defines: + popen_exceptions[' '.join(compiler)] = 'no pre-processor defines' + continue + version = _get_gnu_version_from_defines(defines) + comp = GnuObjCCompiler if objc else GnuObjCPPCompiler + linker = guess_nix_linker(env, compiler, comp, for_machine) + return comp( + ccache + compiler, version, for_machine, is_cross, info, + exe_wrap, defines, linker=linker) + if 'clang' in out: + linker = None + defines = _get_clang_compiler_defines(compiler) + if not defines: + popen_exceptions[' '.join(compiler)] = 'no pre-processor defines' + continue + if 'Apple' in out: + comp = AppleClangObjCCompiler if objc else AppleClangObjCPPCompiler + else: + comp = ClangObjCCompiler if objc else ClangObjCPPCompiler + if 'windows' in out or env.machines[for_machine].is_windows(): + # If we're in a MINGW context this actually will use a gnu style ld + try: + linker = guess_win_linker(env, compiler, comp, for_machine) + except MesonException: + pass + + if not linker: + linker = guess_nix_linker(env, compiler, comp, for_machine) + return comp( + ccache + compiler, version, for_machine, + is_cross, info, exe_wrap, linker=linker, defines=defines) + _handle_exceptions(popen_exceptions, compilers) + +def detect_java_compiler(env: 'Environment', for_machine): + exelist = env.lookup_binary_entry(for_machine, 'java') + info = env.machines[for_machine] + if exelist is None: + # TODO support fallback + exelist = [defaults['java'][0]] + + try: + p, out, err = Popen_safe(exelist + ['-version']) + except OSError: + raise EnvironmentException('Could not execute Java compiler "{}"'.format(' '.join(exelist))) + if 'javac' in out or 'javac' in err: + version = search_version(err if 'javac' in err else out) + if not version or version == 'unknown version': + parts = (err if 'javac' in err else out).split() + if len(parts) > 1: + version = parts[1] + comp_class = JavaCompiler + env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) + return comp_class(exelist, version, for_machine, info) + raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"') + +def detect_cs_compiler(env: 'Environment', for_machine): + compilers, ccache, exe_wrap = _get_compilers(env, 'cs', for_machine) + popen_exceptions = {} + info = env.machines[for_machine] + for comp in compilers: + if not isinstance(comp, list): + comp = [comp] + try: + p, out, err = Popen_safe(comp + ['--version']) + except OSError as e: + popen_exceptions[' '.join(comp + ['--version'])] = e + continue + + version = search_version(out) + if 'Mono' in out: + cls = MonoCompiler + elif "Visual C#" in out: + cls = VisualStudioCsCompiler + else: + continue + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + return cls(comp, version, for_machine, info) + + _handle_exceptions(popen_exceptions, compilers) + +def detect_cython_compiler(env: 'Environment', for_machine: MachineChoice) -> CythonCompiler: + """Search for a cython compiler.""" + compilers, _, _ = _get_compilers(env, 'cython', for_machine) + is_cross = env.is_cross_build(for_machine) + info = env.machines[for_machine] + + popen_exceptions: T.Dict[str, Exception] = {} + for comp in compilers: + if isinstance(comp, str): + comp = [comp] + try: + err = Popen_safe(comp + ['-V'])[2] + except OSError as e: + popen_exceptions[' '.join(comp + ['-V'])] = e + continue + + version = search_version(err) + if 'Cython' in err: + comp_class = CythonCompiler + env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) + return comp_class(comp, version, for_machine, info, is_cross=is_cross) + _handle_exceptions(popen_exceptions, compilers) + +def detect_vala_compiler(env: 'Environment', for_machine): + exelist = env.lookup_binary_entry(for_machine, 'vala') + is_cross = env.is_cross_build(for_machine) + info = env.machines[for_machine] + if exelist is None: + # TODO support fallback + exelist = [defaults['vala'][0]] + + try: + p, out = Popen_safe(exelist + ['--version'])[0:2] + except OSError: + raise EnvironmentException('Could not execute Vala compiler "{}"'.format(' '.join(exelist))) + version = search_version(out) + if 'Vala' in out: + comp_class = ValaCompiler + env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) + return comp_class(exelist, version, for_machine, info, is_cross) + raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"') + +def detect_rust_compiler(env: 'Environment', for_machine: MachineChoice) -> RustCompiler: + popen_exceptions = {} # type: T.Dict[str, Exception] + compilers, _, exe_wrap = _get_compilers(env, 'rust', for_machine) + is_cross = env.is_cross_build(for_machine) + info = env.machines[for_machine] + + cc = detect_c_compiler(env, for_machine) + is_link_exe = isinstance(cc.linker, VisualStudioLikeLinkerMixin) + override = env.lookup_binary_entry(for_machine, 'rust_ld') + + for compiler in compilers: + if isinstance(compiler, str): + compiler = [compiler] + arg = ['--version'] + try: + out = Popen_safe(compiler + arg)[1] + except OSError as e: + popen_exceptions[' '.join(compiler + arg)] = e + continue + + version = search_version(out) + + if 'rustc' in out: + # On Linux and mac rustc will invoke gcc (clang for mac + # presumably) and it can do this windows, for dynamic linking. + # this means the easiest way to C compiler for dynamic linking. + # figure out what linker to use is to just get the value of the + # C compiler and use that as the basis of the rust linker. + # However, there are two things we need to change, if CC is not + # the default use that, and second add the necessary arguments + # to rust to use -fuse-ld + + if any(a.startswith('linker=') for a in compiler): + mlog.warning( + 'Please do not put -C linker= in your compiler ' + 'command, set rust_ld=command in your cross file ' + 'or use the RUST_LD environment variable, otherwise meson ' + 'will override your selection.') + + if override is None: + extra_args = {} + always_args = [] + if is_link_exe: + compiler.extend(RustCompiler.use_linker_args(cc.linker.exelist[0])) + extra_args['direct'] = True + extra_args['machine'] = cc.linker.machine + else: + exelist = cc.linker.exelist.copy() + if 'ccache' in exelist[0]: + del exelist[0] + c = exelist.pop(0) + compiler.extend(RustCompiler.use_linker_args(c)) + + # Also ensure that we pass any extra arguments to the linker + for l in exelist: + compiler.extend(['-C', f'link-arg={l}']) + + # This trickery with type() gets us the class of the linker + # so we can initialize a new copy for the Rust Compiler + if is_link_exe: + linker = type(cc.linker)(for_machine, always_args, exelist=cc.linker.exelist, + version=cc.linker.version, **extra_args) + else: + linker = type(cc.linker)(compiler, for_machine, cc.LINKER_PREFIX, + always_args=always_args, version=cc.linker.version, + **extra_args) + elif 'link' in override[0]: + linker = guess_win_linker(env, + override, RustCompiler, for_machine, use_linker_prefix=False) + # rustc takes linker arguments without a prefix, and + # inserts the correct prefix itself. + linker.direct = True + compiler.extend(RustCompiler.use_linker_args(linker.exelist[0])) + else: + # On linux and macos rust will invoke the c compiler for + # linking, on windows it will use lld-link or link.exe. + # we will simply ask for the C compiler that corresponds to + # it, and use that. + cc = _detect_c_or_cpp_compiler(env, 'c', for_machine, override_compiler=override) + linker = cc.linker + + # Of course, we're not going to use any of that, we just + # need it to get the proper arguments to pass to rustc + c = linker.exelist[1] if linker.exelist[0].endswith('ccache') else linker.exelist[0] + compiler.extend(RustCompiler.use_linker_args(c)) + + env.coredata.add_lang_args(RustCompiler.language, RustCompiler, for_machine, env) + return RustCompiler( + compiler, version, for_machine, is_cross, info, exe_wrap, + linker=linker) + + _handle_exceptions(popen_exceptions, compilers) + +def detect_d_compiler(env: 'Environment', for_machine: MachineChoice): + info = env.machines[for_machine] + + # Detect the target architecture, required for proper architecture handling on Windows. + # MSVC compiler is required for correct platform detection. + c_compiler = {'c': detect_c_compiler(env, for_machine)} + is_msvc = isinstance(c_compiler['c'], VisualStudioCCompiler) + if not is_msvc: + c_compiler = {} + + # Import here to avoid circular imports + from ..environment import detect_cpu_family + arch = detect_cpu_family(c_compiler) + if is_msvc and arch == 'x86': + arch = 'x86_mscoff' + + popen_exceptions = {} + is_cross = env.is_cross_build(for_machine) + compilers, ccache, exe_wrap = _get_compilers(env, 'd', for_machine) + for exelist in compilers: + # Search for a D compiler. + # We prefer LDC over GDC unless overridden with the DC + # environment variable because LDC has a much more + # up to date language version at time (2016). + if not isinstance(exelist, list): + exelist = [exelist] + if os.path.basename(exelist[-1]).startswith(('ldmd', 'gdmd')): + raise EnvironmentException( + 'Meson does not support {} as it is only a DMD frontend for another compiler.' + 'Please provide a valid value for DC or unset it so that Meson can resolve the compiler by itself.'.format(exelist[-1])) + try: + p, out = Popen_safe(exelist + ['--version'])[0:2] + except OSError as e: + popen_exceptions[' '.join(exelist + ['--version'])] = e + continue + version = search_version(out) + full_version = out.split('\n', 1)[0] + + if 'LLVM D compiler' in out: + # LDC seems to require a file + # We cannot use NamedTemproraryFile on windows, its documented + # to not work for our uses. So, just use mkstemp and only have + # one path for simplicity. + o, f = tempfile.mkstemp('.d') + os.close(o) + + try: + if info.is_windows() or info.is_cygwin(): + objfile = os.path.basename(f)[:-1] + 'obj' + linker = guess_win_linker(env, + exelist, + LLVMDCompiler, for_machine, + use_linker_prefix=True, invoked_directly=False, + extra_args=[f]) + else: + # LDC writes an object file to the current working directory. + # Clean it up. + objfile = os.path.basename(f)[:-1] + 'o' + linker = guess_nix_linker(env, + exelist, LLVMDCompiler, for_machine, + extra_args=[f]) + finally: + windows_proof_rm(f) + windows_proof_rm(objfile) + + return LLVMDCompiler( + exelist, version, for_machine, info, arch, + full_version=full_version, linker=linker, version_output=out) + elif 'gdc' in out: + linker = guess_nix_linker(env, exelist, GnuDCompiler, for_machine) + return GnuDCompiler( + exelist, version, for_machine, info, arch, + exe_wrapper=exe_wrap, is_cross=is_cross, + full_version=full_version, linker=linker) + elif 'The D Language Foundation' in out or 'Digital Mars' in out: + # DMD seems to require a file + # We cannot use NamedTemproraryFile on windows, its documented + # to not work for our uses. So, just use mkstemp and only have + # one path for simplicity. + o, f = tempfile.mkstemp('.d') + os.close(o) + + # DMD as different detection logic for x86 and x86_64 + arch_arg = '-m64' if arch == 'x86_64' else '-m32' + + try: + if info.is_windows() or info.is_cygwin(): + objfile = os.path.basename(f)[:-1] + 'obj' + linker = guess_win_linker(env, + exelist, DmdDCompiler, for_machine, + invoked_directly=False, extra_args=[f, arch_arg]) + else: + objfile = os.path.basename(f)[:-1] + 'o' + linker = guess_nix_linker(env, + exelist, DmdDCompiler, for_machine, + extra_args=[f, arch_arg]) + finally: + windows_proof_rm(f) + windows_proof_rm(objfile) + + return DmdDCompiler( + exelist, version, for_machine, info, arch, + full_version=full_version, linker=linker) + raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"') + + _handle_exceptions(popen_exceptions, compilers) + +def detect_swift_compiler(env: 'Environment', for_machine): + exelist = env.lookup_binary_entry(for_machine, 'swift') + is_cross = env.is_cross_build(for_machine) + info = env.machines[for_machine] + if exelist is None: + # TODO support fallback + exelist = [defaults['swift'][0]] + + try: + p, _, err = Popen_safe(exelist + ['-v']) + except OSError: + raise EnvironmentException('Could not execute Swift compiler "{}"'.format(' '.join(exelist))) + version = search_version(err) + if 'Swift' in err: + # As for 5.0.1 swiftc *requires* a file to check the linker: + with tempfile.NamedTemporaryFile(suffix='.swift') as f: + linker = guess_nix_linker(env, + exelist, SwiftCompiler, for_machine, + extra_args=[f.name]) + return SwiftCompiler( + exelist, version, for_machine, info, is_cross, linker=linker) + + raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"') + + +# GNU/Clang defines and version +# ============================= + +def _get_gnu_compiler_defines(compiler): + """ + Detect GNU compiler platform type (Apple, MinGW, Unix) + """ + # Arguments to output compiler pre-processor defines to stdout + # gcc, g++, and gfortran all support these arguments + args = compiler + ['-E', '-dM', '-'] + p, output, error = Popen_safe(args, write='', stdin=subprocess.PIPE) + if p.returncode != 0: + raise EnvironmentException('Unable to detect GNU compiler type:\n' + output + error) + # Parse several lines of the type: + # `#define ___SOME_DEF some_value` + # and extract `___SOME_DEF` + defines = {} + for line in output.split('\n'): + if not line: + continue + d, *rest = line.split(' ', 2) + if d != '#define': + continue + if len(rest) == 1: + defines[rest] = True + if len(rest) == 2: + defines[rest[0]] = rest[1] + return defines + +def _get_clang_compiler_defines(compiler): + """ + Get the list of Clang pre-processor defines + """ + args = compiler + ['-E', '-dM', '-'] + p, output, error = Popen_safe(args, write='', stdin=subprocess.PIPE) + if p.returncode != 0: + raise EnvironmentException('Unable to get clang pre-processor defines:\n' + output + error) + defines = {} + for line in output.split('\n'): + if not line: + continue + d, *rest = line.split(' ', 2) + if d != '#define': + continue + if len(rest) == 1: + defines[rest] = True + if len(rest) == 2: + defines[rest[0]] = rest[1] + return defines + +def _get_gnu_version_from_defines(defines): + dot = '.' + major = defines.get('__GNUC__', '0') + minor = defines.get('__GNUC_MINOR__', '0') + patch = defines.get('__GNUC_PATCHLEVEL__', '0') + return dot.join((major, minor, patch)) + +def _get_lcc_version_from_defines(defines): + dot = '.' + generation_and_major = defines.get('__LCC__', '100') + generation = generation_and_major[:1] + major = generation_and_major[1:] + minor = defines.get('__LCC_MINOR__', '0') + return dot.join((generation, major, minor)) diff --git a/mesonbuild/compilers/mixins/intel.py b/mesonbuild/compilers/mixins/intel.py index 5bca254..89f3518 100644 --- a/mesonbuild/compilers/mixins/intel.py +++ b/mesonbuild/compilers/mixins/intel.py @@ -168,14 +168,11 @@ class IntelVisualStudioLikeCompiler(VisualStudioLikeCompiler): return args def get_toolset_version(self) -> T.Optional[str]: - # Avoid circular dependencies.... - from ...environment import search_version - # ICL provides a cl.exe that returns the version of MSVC it tries to # emulate, so we'll get the version from that and pass it to the same # function the real MSVC uses to calculate the toolset version. _, _, err = mesonlib.Popen_safe(['cl.exe']) - v1, v2, *_ = search_version(err).split('.') + v1, v2, *_ = mesonlib.search_version(err).split('.') version = int(v1 + v2) return self._calculate_toolset_version(version) diff --git a/mesonbuild/dependencies/dev.py b/mesonbuild/dependencies/dev.py index 60c4b74..0ab8332 100644 --- a/mesonbuild/dependencies/dev.py +++ b/mesonbuild/dependencies/dev.py @@ -23,7 +23,7 @@ import shutil import typing as T from .. import mesonlib, mlog -from ..compilers import AppleClangCCompiler, AppleClangCPPCompiler +from ..compilers import AppleClangCCompiler, AppleClangCPPCompiler, detect_compiler_for from ..environment import get_llvm_tool_names from ..mesonlib import version_compare, stringlistify, extract_as_list, MachineChoice from .base import DependencyException, DependencyMethods, strip_system_libdirs, SystemDependency @@ -520,7 +520,7 @@ class JDKSystemDependency(SystemDependency): m = self.env.machines[self.for_machine] if 'java' not in environment.coredata.compilers[self.for_machine]: - environment.detect_compiler_for('java', self.for_machine) + detect_compiler_for(environment, 'java', self.for_machine) self.javac = environment.coredata.compilers[self.for_machine]['java'] self.version = self.javac.version diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index 6248c58..df9c7eb 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -13,18 +13,16 @@ # limitations under the License. import itertools -import os, platform, re, sys, shutil, subprocess -import tempfile -import shlex +import os, platform, re, sys, shutil import typing as T import collections from . import coredata -from .linkers import ArLinker, ArmarLinker, VisualStudioLinker, DLinker, CcrxLinker, Xc16Linker, CompCertLinker, C2000Linker, IntelVisualStudioLinker, AIXArLinker from . import mesonlib from .mesonlib import ( MesonException, EnvironmentException, MachineChoice, Popen_safe, PerMachine, - PerMachineDefaultable, PerThreeMachineDefaultable, split_args, quote_arg, OptionKey + PerMachineDefaultable, PerThreeMachineDefaultable, split_args, quote_arg, OptionKey, + search_version ) from . import mlog from .programs import ( @@ -44,93 +42,8 @@ from .compilers import ( is_object, is_source, ) -from .linkers import ( - AppleDynamicLinker, - ArmClangDynamicLinker, - ArmDynamicLinker, - CcrxDynamicLinker, - Xc16DynamicLinker, - CompCertDynamicLinker, - C2000DynamicLinker, - ClangClDynamicLinker, - DynamicLinker, - GnuBFDDynamicLinker, - GnuGoldDynamicLinker, - LLVMDynamicLinker, - QualcommLLVMDynamicLinker, - MSVCDynamicLinker, - OptlinkDynamicLinker, - NvidiaHPC_DynamicLinker, - NvidiaHPC_StaticLinker, - PGIDynamicLinker, - PGIStaticLinker, - SolarisDynamicLinker, - AIXDynamicLinker, - XilinkDynamicLinker, - CudaLinker, - VisualStudioLikeLinkerMixin, - WASMDynamicLinker, -) + from functools import lru_cache -from .compilers import ( - ArmCCompiler, - ArmCPPCompiler, - ArmclangCCompiler, - ArmclangCPPCompiler, - AppleClangCCompiler, - AppleClangCPPCompiler, - AppleClangObjCCompiler, - AppleClangObjCPPCompiler, - ClangCCompiler, - ClangCPPCompiler, - ClangObjCCompiler, - ClangObjCPPCompiler, - ClangClCCompiler, - ClangClCPPCompiler, - CythonCompiler, - FlangFortranCompiler, - G95FortranCompiler, - GnuCCompiler, - GnuCPPCompiler, - GnuFortranCompiler, - GnuObjCCompiler, - GnuObjCPPCompiler, - ElbrusCCompiler, - ElbrusCPPCompiler, - ElbrusFortranCompiler, - EmscriptenCCompiler, - EmscriptenCPPCompiler, - IntelCCompiler, - IntelClCCompiler, - IntelCPPCompiler, - IntelClCPPCompiler, - IntelFortranCompiler, - IntelClFortranCompiler, - JavaCompiler, - MonoCompiler, - CudaCompiler, - VisualStudioCsCompiler, - NAGFortranCompiler, - Open64FortranCompiler, - PathScaleFortranCompiler, - NvidiaHPC_CCompiler, - NvidiaHPC_CPPCompiler, - NvidiaHPC_FortranCompiler, - PGICCompiler, - PGICPPCompiler, - PGIFortranCompiler, - RustCompiler, - CcrxCCompiler, - CcrxCPPCompiler, - Xc16CCompiler, - CompCertCCompiler, - C2000CCompiler, - C2000CPPCompiler, - SunFortranCompiler, - ValaCompiler, - VisualStudioCCompiler, - VisualStudioCPPCompiler, -) from mesonbuild import envconfig if T.TYPE_CHECKING: @@ -211,7 +124,7 @@ def detect_ninja(version: str = '1.8.2', log: bool = False) -> T.List[str]: r = detect_ninja_command_and_version(version, log) return r[0] if r else None -def detect_ninja_command_and_version(version: str = '1.8.2', log: bool = False) -> (T.List[str], str): +def detect_ninja_command_and_version(version: str = '1.8.2', log: bool = False) -> T.Tuple[T.List[str], str]: env_ninja = os.environ.get('NINJA', None) for n in [env_ninja] if env_ninja else ['ninja', 'ninja-build', 'samu']: prog = ExternalProgram(n, silent=True) @@ -520,49 +433,6 @@ def machine_info_can_run(machine_info: MachineInfo): ((true_build_cpu_family == 'x86_64') and (machine_info.cpu_family == 'x86')) or \ ((true_build_cpu_family == 'aarch64') and (machine_info.cpu_family == 'arm')) -def search_version(text: str) -> str: - # Usually of the type 4.1.4 but compiler output may contain - # stuff like this: - # (Sourcery CodeBench Lite 2014.05-29) 4.8.3 20140320 (prerelease) - # Limiting major version number to two digits seems to work - # thus far. When we get to GCC 100, this will break, but - # if we are still relevant when that happens, it can be - # considered an achievement in itself. - # - # This regex is reaching magic levels. If it ever needs - # to be updated, do not complexify but convert to something - # saner instead. - # We'll demystify it a bit with a verbose definition. - version_regex = re.compile(r""" - (?<! # Zero-width negative lookbehind assertion - ( - \d # One digit - | \. # Or one period - ) # One occurrence - ) - # Following pattern must not follow a digit or period - ( - \d{1,2} # One or two digits - ( - \.\d+ # Period and one or more digits - )+ # One or more occurrences - ( - -[a-zA-Z0-9]+ # Hyphen and one or more alphanumeric - )? # Zero or one occurrence - ) # One occurrence - """, re.VERBOSE) - match = version_regex.search(text) - if match: - return match.group(0) - - # try a simpler regex that has like "blah 2020.01.100 foo" or "blah 2020.01 foo" - version_regex = re.compile(r"(\d{1,4}\.\d{1,4}\.?\d{0,4})") - match = version_regex.search(text) - if match: - return match.group(0) - - return 'unknown version' - class Environment: private_dir = 'meson-private' log_dir = 'meson-logs' @@ -699,50 +569,6 @@ class Environment: else: self.exe_wrapper = None - # List of potential compilers. - if mesonlib.is_windows(): - # Intel C and C++ compiler is icl on Windows, but icc and icpc elsewhere. - # Search for icl before cl, since Intel "helpfully" provides a - # cl.exe that returns *exactly the same thing* that microsofts - # cl.exe does, and if icl is present, it's almost certainly what - # you want. - self.default_c = ['icl', 'cl', 'cc', 'gcc', 'clang', 'clang-cl', 'pgcc'] - # There is currently no pgc++ for Windows, only for Mac and Linux. - self.default_cpp = ['icl', 'cl', 'c++', 'g++', 'clang++', 'clang-cl'] - self.default_fortran = ['ifort', 'gfortran', 'flang', 'pgfortran', 'g95'] - # Clang and clang++ are valid, but currently unsupported. - self.default_objc = ['cc', 'gcc'] - self.default_objcpp = ['c++', 'g++'] - self.default_cs = ['csc', 'mcs'] - else: - if platform.machine().lower() == 'e2k': - # There are no objc or objc++ compilers for Elbrus, - # and there's no clang which can build binaries for host. - self.default_c = ['cc', 'gcc', 'lcc'] - self.default_cpp = ['c++', 'g++', 'l++'] - self.default_objc = [] - self.default_objcpp = [] - else: - self.default_c = ['cc', 'gcc', 'clang', 'nvc', 'pgcc', 'icc'] - self.default_cpp = ['c++', 'g++', 'clang++', 'nvc++', 'pgc++', 'icpc'] - self.default_objc = ['cc', 'gcc', 'clang'] - self.default_objcpp = ['c++', 'g++', 'clang++'] - self.default_fortran = ['gfortran', 'flang', 'nvfortran', 'pgfortran', 'ifort', 'g95'] - self.default_cs = ['mcs', 'csc'] - self.default_d = ['ldc2', 'ldc', 'gdc', 'dmd'] - self.default_java = ['javac'] - self.default_cuda = ['nvcc'] - self.default_rust = ['rustc'] - self.default_swift = ['swiftc'] - self.default_vala = ['valac'] - self.default_cython = [['cython']] - self.default_static_linker = ['ar', 'gar'] - self.default_strip = ['strip'] - self.vs_static_linker = ['lib'] - self.clang_cl_static_linker = ['llvm-lib'] - self.cuda_static_linker = ['nvlink'] - self.gcc_static_linker = ['gcc-ar'] - self.clang_static_linker = ['llvm-ar'] self.default_cmake = ['cmake'] self.default_pkgconfig = ['pkg-config'] self.wrap_resolver = None @@ -952,1193 +778,9 @@ class Environment: def lookup_binary_entry(self, for_machine: MachineChoice, name: str) -> T.Optional[T.List[str]]: return self.binaries[for_machine].lookup_entry(name) - @staticmethod - def get_gnu_compiler_defines(compiler): - """ - Detect GNU compiler platform type (Apple, MinGW, Unix) - """ - # Arguments to output compiler pre-processor defines to stdout - # gcc, g++, and gfortran all support these arguments - args = compiler + ['-E', '-dM', '-'] - p, output, error = Popen_safe(args, write='', stdin=subprocess.PIPE) - if p.returncode != 0: - raise EnvironmentException('Unable to detect GNU compiler type:\n' + output + error) - # Parse several lines of the type: - # `#define ___SOME_DEF some_value` - # and extract `___SOME_DEF` - defines = {} - for line in output.split('\n'): - if not line: - continue - d, *rest = line.split(' ', 2) - if d != '#define': - continue - if len(rest) == 1: - defines[rest] = True - if len(rest) == 2: - defines[rest[0]] = rest[1] - return defines - - @staticmethod - def get_gnu_version_from_defines(defines): - dot = '.' - major = defines.get('__GNUC__', '0') - minor = defines.get('__GNUC_MINOR__', '0') - patch = defines.get('__GNUC_PATCHLEVEL__', '0') - return dot.join((major, minor, patch)) - - @staticmethod - def get_lcc_version_from_defines(defines): - dot = '.' - generation_and_major = defines.get('__LCC__', '100') - generation = generation_and_major[:1] - major = generation_and_major[1:] - minor = defines.get('__LCC_MINOR__', '0') - return dot.join((generation, major, minor)) - - @staticmethod - def get_clang_compiler_defines(compiler): - """ - Get the list of Clang pre-processor defines - """ - args = compiler + ['-E', '-dM', '-'] - p, output, error = Popen_safe(args, write='', stdin=subprocess.PIPE) - if p.returncode != 0: - raise EnvironmentException('Unable to get clang pre-processor defines:\n' + output + error) - defines = {} - for line in output.split('\n'): - if not line: - continue - d, *rest = line.split(' ', 2) - if d != '#define': - continue - if len(rest) == 1: - defines[rest] = True - if len(rest) == 2: - defines[rest[0]] = rest[1] - return defines - - def _get_compilers(self, lang: str, for_machine: MachineChoice) -> T.Tuple[T.List[T.List[str]], T.List[str], T.Optional['ExternalProgram']]: - ''' - The list of compilers is detected in the exact same way for - C, C++, ObjC, ObjC++, Fortran, CS so consolidate it here. - ''' - value = self.lookup_binary_entry(for_machine, lang) - if value is not None: - compilers, ccache = BinaryTable.parse_entry(value) - # Return value has to be a list of compiler 'choices' - compilers = [compilers] - else: - if not self.machines.matches_build_machine(for_machine): - raise EnvironmentException(f'{lang!r} compiler binary not defined in cross or native file') - compilers = getattr(self, 'default_' + lang) - ccache = BinaryTable.detect_ccache() - - if self.machines.matches_build_machine(for_machine): - exe_wrap = None - else: - exe_wrap = self.get_exe_wrapper() - - return compilers, ccache, exe_wrap - - def _handle_exceptions(self, exceptions, binaries, bintype='compiler'): - errmsg = f'Unknown {bintype}(s): {binaries}' - if exceptions: - errmsg += '\nThe following exception(s) were encountered:' - for (c, e) in exceptions.items(): - errmsg += f'\nRunning "{c}" gave "{e}"' - raise EnvironmentException(errmsg) - - @staticmethod - def __failed_to_detect_linker(compiler: T.List[str], args: T.List[str], stdout: str, stderr: str) -> 'T.NoReturn': - msg = 'Unable to detect linker for compiler "{} {}"\nstdout: {}\nstderr: {}'.format( - ' '.join(compiler), ' '.join(args), stdout, stderr) - raise EnvironmentException(msg) - - def _guess_win_linker(self, compiler: T.List[str], comp_class: Compiler, - for_machine: MachineChoice, *, - use_linker_prefix: bool = True, invoked_directly: bool = True, - extra_args: T.Optional[T.List[str]] = None) -> 'DynamicLinker': - self.coredata.add_lang_args(comp_class.language, comp_class, for_machine, self) - - # Explicitly pass logo here so that we can get the version of link.exe - if not use_linker_prefix or comp_class.LINKER_PREFIX is None: - check_args = ['/logo', '--version'] - elif isinstance(comp_class.LINKER_PREFIX, str): - check_args = [comp_class.LINKER_PREFIX + '/logo', comp_class.LINKER_PREFIX + '--version'] - elif isinstance(comp_class.LINKER_PREFIX, list): - check_args = comp_class.LINKER_PREFIX + ['/logo'] + comp_class.LINKER_PREFIX + ['--version'] - - check_args += self.coredata.options[OptionKey('args', lang=comp_class.language, machine=for_machine)].value - - override = [] # type: T.List[str] - value = self.lookup_binary_entry(for_machine, comp_class.language + '_ld') - if value is not None: - override = comp_class.use_linker_args(value[0]) - check_args += override - - if extra_args is not None: - check_args.extend(extra_args) - - p, o, _ = Popen_safe(compiler + check_args) - if o.startswith('LLD'): - if '(compatible with GNU linkers)' in o: - return LLVMDynamicLinker( - compiler, for_machine, comp_class.LINKER_PREFIX, - override, version=search_version(o)) - elif not invoked_directly: - return ClangClDynamicLinker( - for_machine, override, exelist=compiler, prefix=comp_class.LINKER_PREFIX, - version=search_version(o), direct=False, machine=None) - - if value is not None and invoked_directly: - compiler = value - # We've already hanedled the non-direct case above - - p, o, e = Popen_safe(compiler + check_args) - if o.startswith('LLD'): - return ClangClDynamicLinker( - for_machine, [], - prefix=comp_class.LINKER_PREFIX if use_linker_prefix else [], - exelist=compiler, version=search_version(o), direct=invoked_directly) - elif 'OPTLINK' in o: - # Opltink's stdout *may* beging with a \r character. - return OptlinkDynamicLinker(compiler, for_machine, version=search_version(o)) - elif o.startswith('Microsoft') or e.startswith('Microsoft'): - out = o or e - match = re.search(r'.*(X86|X64|ARM|ARM64).*', out) - if match: - target = str(match.group(1)) - else: - target = 'x86' - - return MSVCDynamicLinker( - for_machine, [], machine=target, exelist=compiler, - prefix=comp_class.LINKER_PREFIX if use_linker_prefix else [], - version=search_version(out), direct=invoked_directly) - elif 'GNU coreutils' in o: - raise EnvironmentException( - "Found GNU link.exe instead of MSVC link.exe. This link.exe " - "is not a linker. You may need to reorder entries to your " - "%PATH% variable to resolve this.") - self.__failed_to_detect_linker(compiler, check_args, o, e) - - def _guess_nix_linker(self, compiler: T.List[str], comp_class: T.Type[Compiler], - for_machine: MachineChoice, *, - extra_args: T.Optional[T.List[str]] = None) -> 'DynamicLinker': - """Helper for guessing what linker to use on Unix-Like OSes. - - :compiler: Invocation to use to get linker - :comp_class: The Compiler Type (uninstantiated) - :for_machine: which machine this linker targets - :extra_args: Any additional arguments required (such as a source file) - """ - self.coredata.add_lang_args(comp_class.language, comp_class, for_machine, self) - extra_args = extra_args or [] - extra_args += self.coredata.options[OptionKey('args', lang=comp_class.language, machine=for_machine)].value - - if isinstance(comp_class.LINKER_PREFIX, str): - check_args = [comp_class.LINKER_PREFIX + '--version'] + extra_args - else: - check_args = comp_class.LINKER_PREFIX + ['--version'] + extra_args - - override = [] # type: T.List[str] - value = self.lookup_binary_entry(for_machine, comp_class.language + '_ld') - if value is not None: - override = comp_class.use_linker_args(value[0]) - check_args += override - - _, o, e = Popen_safe(compiler + check_args) - v = search_version(o + e) - if o.startswith('LLD'): - linker = LLVMDynamicLinker( - compiler, for_machine, comp_class.LINKER_PREFIX, override, version=v) # type: DynamicLinker - elif 'Snapdragon' in e and 'LLVM' in e: - linker = QualcommLLVMDynamicLinker( - compiler, for_machine, comp_class.LINKER_PREFIX, override, version=v) # type: DynamicLinker - elif e.startswith('lld-link: '): - # The LLD MinGW frontend didn't respond to --version before version 9.0.0, - # and produced an error message about failing to link (when no object - # files were specified), instead of printing the version number. - # Let's try to extract the linker invocation command to grab the version. - - _, o, e = Popen_safe(compiler + check_args + ['-v']) - - try: - linker_cmd = re.match(r'.*\n(.*?)\nlld-link: ', e, re.DOTALL).group(1) - linker_cmd = shlex.split(linker_cmd)[0] - except (AttributeError, IndexError, ValueError): - pass - else: - _, o, e = Popen_safe([linker_cmd, '--version']) - v = search_version(o) - - linker = LLVMDynamicLinker(compiler, for_machine, comp_class.LINKER_PREFIX, override, version=v) - # first is for apple clang, second is for real gcc, the third is icc - elif e.endswith('(use -v to see invocation)\n') or 'macosx_version' in e or 'ld: unknown option:' in e: - if isinstance(comp_class.LINKER_PREFIX, str): - _, _, e = Popen_safe(compiler + [comp_class.LINKER_PREFIX + '-v'] + extra_args) - else: - _, _, e = Popen_safe(compiler + comp_class.LINKER_PREFIX + ['-v'] + extra_args) - for line in e.split('\n'): - if 'PROJECT:ld' in line: - v = line.split('-')[1] - break - else: - v = 'unknown version' - linker = AppleDynamicLinker(compiler, for_machine, comp_class.LINKER_PREFIX, override, version=v) - elif 'GNU' in o or 'GNU' in e: - if 'gold' in o or 'gold' in e: - cls = GnuGoldDynamicLinker - else: - cls = GnuBFDDynamicLinker - linker = cls(compiler, for_machine, comp_class.LINKER_PREFIX, override, version=v) - elif 'Solaris' in e or 'Solaris' in o: - for line in (o+e).split('\n'): - if 'ld: Software Generation Utilities' in line: - v = line.split(':')[2].lstrip() - break - else: - v = 'unknown version' - linker = SolarisDynamicLinker( - compiler, for_machine, comp_class.LINKER_PREFIX, override, - version=v) - elif 'ld: 0706-012 The -- flag is not recognized' in e: - if isinstance(comp_class.LINKER_PREFIX, str): - _, _, e = Popen_safe(compiler + [comp_class.LINKER_PREFIX + '-V'] + extra_args) - else: - _, _, e = Popen_safe(compiler + comp_class.LINKER_PREFIX + ['-V'] + extra_args) - linker = AIXDynamicLinker( - compiler, for_machine, comp_class.LINKER_PREFIX, override, - version=search_version(e)) - else: - self.__failed_to_detect_linker(compiler, check_args, o, e) - return linker - - def _detect_c_or_cpp_compiler(self, lang: str, for_machine: MachineChoice, *, override_compiler: T.Optional[T.List[str]] = None) -> Compiler: - """Shared implementation for finding the C or C++ compiler to use. - - the override_compiler option is provided to allow compilers which use - the compiler (GCC or Clang usually) as their shared linker, to find - the linker they need. - """ - popen_exceptions = {} - compilers, ccache, exe_wrap = self._get_compilers(lang, for_machine) - if override_compiler is not None: - compilers = [override_compiler] - is_cross = self.is_cross_build(for_machine) - info = self.machines[for_machine] - - for compiler in compilers: - if isinstance(compiler, str): - compiler = [compiler] - compiler_name = os.path.basename(compiler[0]) - - if not {'cl', 'cl.exe', 'clang-cl', 'clang-cl.exe'}.isdisjoint(compiler): - # Watcom C provides it's own cl.exe clone that mimics an older - # version of Microsoft's compiler. Since Watcom's cl.exe is - # just a wrapper, we skip using it if we detect its presence - # so as not to confuse Meson when configuring for MSVC. - # - # Additionally the help text of Watcom's cl.exe is paged, and - # the binary will not exit without human intervention. In - # practice, Meson will block waiting for Watcom's cl.exe to - # exit, which requires user input and thus will never exit. - if 'WATCOM' in os.environ: - def sanitize(p): - return os.path.normcase(os.path.abspath(p)) - - watcom_cls = [sanitize(os.path.join(os.environ['WATCOM'], 'BINNT', 'cl')), - sanitize(os.path.join(os.environ['WATCOM'], 'BINNT', 'cl.exe'))] - found_cl = sanitize(shutil.which('cl')) - if found_cl in watcom_cls: - continue - arg = '/?' - elif 'armcc' in compiler_name: - arg = '--vsn' - elif 'ccrx' in compiler_name: - arg = '-v' - elif 'xc16' in compiler_name: - arg = '--version' - elif 'ccomp' in compiler_name: - arg = '-version' - elif 'cl2000' in compiler_name: - arg = '-version' - elif compiler_name in {'icl', 'icl.exe'}: - # if you pass anything to icl you get stuck in a pager - arg = '' - else: - arg = '--version' - - try: - p, out, err = Popen_safe(compiler + [arg]) - except OSError as e: - popen_exceptions[' '.join(compiler + [arg])] = e - continue - - if 'ccrx' in compiler_name: - out = err - - full_version = out.split('\n', 1)[0] - version = search_version(out) - - guess_gcc_or_lcc = False - if 'Free Software Foundation' in out or 'xt-' in out: - guess_gcc_or_lcc = 'gcc' - if 'e2k' in out and 'lcc' in out: - guess_gcc_or_lcc = 'lcc' - if 'Microchip Technology' in out: - # this output has "Free Software Foundation" in its version - guess_gcc_or_lcc = False - - if guess_gcc_or_lcc: - defines = self.get_gnu_compiler_defines(compiler) - if not defines: - popen_exceptions[' '.join(compiler)] = 'no pre-processor defines' - continue - - if guess_gcc_or_lcc == 'lcc': - version = self.get_lcc_version_from_defines(defines) - cls = ElbrusCCompiler if lang == 'c' else ElbrusCPPCompiler - else: - version = self.get_gnu_version_from_defines(defines) - cls = GnuCCompiler if lang == 'c' else GnuCPPCompiler - - linker = self._guess_nix_linker(compiler, cls, for_machine) - - return cls( - ccache + compiler, version, for_machine, is_cross, - info, exe_wrap, defines=defines, full_version=full_version, - linker=linker) - - if 'Emscripten' in out: - cls = EmscriptenCCompiler if lang == 'c' else EmscriptenCPPCompiler - self.coredata.add_lang_args(cls.language, cls, for_machine, self) - - # emcc requires a file input in order to pass arguments to the - # linker. It'll exit with an error code, but still print the - # linker version. Old emcc versions ignore -Wl,--version completely, - # however. We'll report "unknown version" in that case. - with tempfile.NamedTemporaryFile(suffix='.c') as f: - cmd = compiler + [cls.LINKER_PREFIX + "--version", f.name] - _, o, _ = Popen_safe(cmd) - - linker = WASMDynamicLinker( - compiler, for_machine, cls.LINKER_PREFIX, - [], version=search_version(o)) - return cls( - ccache + compiler, version, for_machine, is_cross, info, - exe_wrap, linker=linker, full_version=full_version) - - if 'armclang' in out: - # The compiler version is not present in the first line of output, - # instead it is present in second line, startswith 'Component:'. - # So, searching for the 'Component' in out although we know it is - # present in second line, as we are not sure about the - # output format in future versions - arm_ver_str = re.search('.*Component.*', out) - if arm_ver_str is None: - popen_exceptions[' '.join(compiler)] = 'version string not found' - continue - arm_ver_str = arm_ver_str.group(0) - # Override previous values - version = search_version(arm_ver_str) - full_version = arm_ver_str - cls = ArmclangCCompiler if lang == 'c' else ArmclangCPPCompiler - linker = ArmClangDynamicLinker(for_machine, version=version) - self.coredata.add_lang_args(cls.language, cls, for_machine, self) - return cls( - ccache + compiler, version, for_machine, is_cross, info, - exe_wrap, full_version=full_version, linker=linker) - if 'CL.EXE COMPATIBILITY' in out: - # if this is clang-cl masquerading as cl, detect it as cl, not - # clang - arg = '--version' - try: - p, out, err = Popen_safe(compiler + [arg]) - except OSError as e: - popen_exceptions[' '.join(compiler + [arg])] = e - version = search_version(out) - match = re.search('^Target: (.*?)-', out, re.MULTILINE) - if match: - target = match.group(1) - else: - target = 'unknown target' - cls = ClangClCCompiler if lang == 'c' else ClangClCPPCompiler - linker = self._guess_win_linker(['lld-link'], cls, for_machine) - return cls( - compiler, version, for_machine, is_cross, info, target, - exe_wrap, linker=linker) - if 'clang' in out or 'Clang' in out: - linker = None - - defines = self.get_clang_compiler_defines(compiler) - - # Even if the for_machine is darwin, we could be using vanilla - # clang. - if 'Apple' in out: - cls = AppleClangCCompiler if lang == 'c' else AppleClangCPPCompiler - else: - cls = ClangCCompiler if lang == 'c' else ClangCPPCompiler - - if 'windows' in out or self.machines[for_machine].is_windows(): - # If we're in a MINGW context this actually will use a gnu - # style ld, but for clang on "real" windows we'll use - # either link.exe or lld-link.exe - try: - linker = self._guess_win_linker(compiler, cls, for_machine, invoked_directly=False) - except MesonException: - pass - if linker is None: - linker = self._guess_nix_linker(compiler, cls, for_machine) - - return cls( - ccache + compiler, version, for_machine, is_cross, info, - exe_wrap, defines=defines, full_version=full_version, linker=linker) - - if 'Intel(R) C++ Intel(R)' in err: - version = search_version(err) - target = 'x86' if 'IA-32' in err else 'x86_64' - cls = IntelClCCompiler if lang == 'c' else IntelClCPPCompiler - self.coredata.add_lang_args(cls.language, cls, for_machine, self) - linker = XilinkDynamicLinker(for_machine, [], version=version) - return cls( - compiler, version, for_machine, is_cross, info, target, - exe_wrap, linker=linker) - if 'Microsoft' in out or 'Microsoft' in err: - # Latest versions of Visual Studio print version - # number to stderr but earlier ones print version - # on stdout. Why? Lord only knows. - # Check both outputs to figure out version. - for lookat in [err, out]: - version = search_version(lookat) - if version != 'unknown version': - break - else: - m = 'Failed to detect MSVC compiler version: stderr was\n{!r}' - raise EnvironmentException(m.format(err)) - cl_signature = lookat.split('\n')[0] - match = re.search(r'.*(x86|x64|ARM|ARM64)([^_A-Za-z0-9]|$)', cl_signature) - if match: - target = match.group(1) - else: - m = 'Failed to detect MSVC compiler target architecture: \'cl /?\' output is\n{}' - raise EnvironmentException(m.format(cl_signature)) - cls = VisualStudioCCompiler if lang == 'c' else VisualStudioCPPCompiler - linker = self._guess_win_linker(['link'], cls, for_machine) - return cls( - compiler, version, for_machine, is_cross, info, target, - exe_wrap, full_version=cl_signature, linker=linker) - if 'PGI Compilers' in out: - cls = PGICCompiler if lang == 'c' else PGICPPCompiler - self.coredata.add_lang_args(cls.language, cls, for_machine, self) - linker = PGIDynamicLinker(compiler, for_machine, cls.LINKER_PREFIX, [], version=version) - return cls( - ccache + compiler, version, for_machine, is_cross, - info, exe_wrap, linker=linker) - if 'NVIDIA Compilers and Tools' in out: - cls = NvidiaHPC_CCompiler if lang == 'c' else NvidiaHPC_CPPCompiler - self.coredata.add_lang_args(cls.language, cls, for_machine, self) - linker = NvidiaHPC_DynamicLinker(compiler, for_machine, cls.LINKER_PREFIX, [], version=version) - return cls( - ccache + compiler, version, for_machine, is_cross, - info, exe_wrap, linker=linker) - if '(ICC)' in out: - cls = IntelCCompiler if lang == 'c' else IntelCPPCompiler - l = self._guess_nix_linker(compiler, cls, for_machine) - return cls( - ccache + compiler, version, for_machine, is_cross, info, - exe_wrap, full_version=full_version, linker=l) - if 'ARM' in out: - cls = ArmCCompiler if lang == 'c' else ArmCPPCompiler - self.coredata.add_lang_args(cls.language, cls, for_machine, self) - linker = ArmDynamicLinker(for_machine, version=version) - return cls( - ccache + compiler, version, for_machine, is_cross, - info, exe_wrap, full_version=full_version, linker=linker) - if 'RX Family' in out: - cls = CcrxCCompiler if lang == 'c' else CcrxCPPCompiler - self.coredata.add_lang_args(cls.language, cls, for_machine, self) - linker = CcrxDynamicLinker(for_machine, version=version) - return cls( - ccache + compiler, version, for_machine, is_cross, info, - exe_wrap, full_version=full_version, linker=linker) - - if 'Microchip Technology' in out: - cls = Xc16CCompiler if lang == 'c' else Xc16CCompiler - self.coredata.add_lang_args(cls.language, cls, for_machine, self) - linker = Xc16DynamicLinker(for_machine, version=version) - return cls( - ccache + compiler, version, for_machine, is_cross, info, - exe_wrap, full_version=full_version, linker=linker) - - if 'CompCert' in out: - cls = CompCertCCompiler - self.coredata.add_lang_args(cls.language, cls, for_machine, self) - linker = CompCertDynamicLinker(for_machine, version=version) - return cls( - ccache + compiler, version, for_machine, is_cross, info, - exe_wrap, full_version=full_version, linker=linker) - - if 'TMS320C2000 C/C++' in out: - cls = C2000CCompiler if lang == 'c' else C2000CPPCompiler - self.coredata.add_lang_args(cls.language, cls, for_machine, self) - linker = C2000DynamicLinker(for_machine, version=version) - return cls( - ccache + compiler, version, for_machine, is_cross, info, - exe_wrap, full_version=full_version, linker=linker) - - - self._handle_exceptions(popen_exceptions, compilers) - - def detect_c_compiler(self, for_machine): - return self._detect_c_or_cpp_compiler('c', for_machine) - - def detect_cpp_compiler(self, for_machine): - return self._detect_c_or_cpp_compiler('cpp', for_machine) - - def detect_cuda_compiler(self, for_machine): - popen_exceptions = {} - is_cross = self.is_cross_build(for_machine) - compilers, ccache, exe_wrap = self._get_compilers('cuda', for_machine) - info = self.machines[for_machine] - for compiler in compilers: - if isinstance(compiler, str): - compiler = [compiler] - arg = '--version' - try: - p, out, err = Popen_safe(compiler + [arg]) - except OSError as e: - popen_exceptions[' '.join(compiler + [arg])] = e - continue - # Example nvcc printout: - # - # nvcc: NVIDIA (R) Cuda compiler driver - # Copyright (c) 2005-2018 NVIDIA Corporation - # Built on Sat_Aug_25_21:08:01_CDT_2018 - # Cuda compilation tools, release 10.0, V10.0.130 - # - # search_version() first finds the "10.0" after "release", - # rather than the more precise "10.0.130" after "V". - # The patch version number is occasionally important; For - # instance, on Linux, - # - CUDA Toolkit 8.0.44 requires NVIDIA Driver 367.48 - # - CUDA Toolkit 8.0.61 requires NVIDIA Driver 375.26 - # Luckily, the "V" also makes it very simple to extract - # the full version: - version = out.strip().split('V')[-1] - cpp_compiler = self.detect_cpp_compiler(for_machine) - cls = CudaCompiler - self.coredata.add_lang_args(cls.language, cls, for_machine, self) - linker = CudaLinker(compiler, for_machine, CudaCompiler.LINKER_PREFIX, [], version=CudaLinker.parse_version()) - return cls(ccache + compiler, version, for_machine, is_cross, exe_wrap, host_compiler=cpp_compiler, info=info, linker=linker) - raise EnvironmentException('Could not find suitable CUDA compiler: "' + ' '.join(compilers) + '"') - - def detect_fortran_compiler(self, for_machine: MachineChoice): - popen_exceptions = {} - compilers, ccache, exe_wrap = self._get_compilers('fortran', for_machine) - is_cross = self.is_cross_build(for_machine) - info = self.machines[for_machine] - for compiler in compilers: - if isinstance(compiler, str): - compiler = [compiler] - for arg in ['--version', '-V']: - try: - p, out, err = Popen_safe(compiler + [arg]) - except OSError as e: - popen_exceptions[' '.join(compiler + [arg])] = e - continue - - version = search_version(out) - full_version = out.split('\n', 1)[0] - - guess_gcc_or_lcc = False - if 'GNU Fortran' in out: - guess_gcc_or_lcc = 'gcc' - if 'e2k' in out and 'lcc' in out: - guess_gcc_or_lcc = 'lcc' - - if guess_gcc_or_lcc: - defines = self.get_gnu_compiler_defines(compiler) - if not defines: - popen_exceptions[' '.join(compiler)] = 'no pre-processor defines' - continue - if guess_gcc_or_lcc == 'lcc': - version = self.get_lcc_version_from_defines(defines) - cls = ElbrusFortranCompiler - else: - version = self.get_gnu_version_from_defines(defines) - cls = GnuFortranCompiler - linker = self._guess_nix_linker( - compiler, cls, for_machine) - return cls( - compiler, version, for_machine, is_cross, info, - exe_wrap, defines, full_version=full_version, - linker=linker) - - if 'G95' in out: - linker = self._guess_nix_linker( - compiler, cls, for_machine) - return G95FortranCompiler( - compiler, version, for_machine, is_cross, info, - exe_wrap, full_version=full_version, linker=linker) - - if 'Sun Fortran' in err: - version = search_version(err) - linker = self._guess_nix_linker( - compiler, cls, for_machine) - return SunFortranCompiler( - compiler, version, for_machine, is_cross, info, - exe_wrap, full_version=full_version, linker=linker) - - if 'Intel(R) Visual Fortran' in err: - version = search_version(err) - target = 'x86' if 'IA-32' in err else 'x86_64' - cls = IntelClFortranCompiler - self.coredata.add_lang_args(cls.language, cls, for_machine, self) - linker = XilinkDynamicLinker(for_machine, [], version=version) - return cls( - compiler, version, for_machine, is_cross, info, - target, exe_wrap, linker=linker) - - if 'ifort (IFORT)' in out: - linker = self._guess_nix_linker(compiler, IntelFortranCompiler, for_machine) - return IntelFortranCompiler( - compiler, version, for_machine, is_cross, info, - exe_wrap, full_version=full_version, linker=linker) - - if 'PathScale EKOPath(tm)' in err: - return PathScaleFortranCompiler( - compiler, version, for_machine, is_cross, info, - exe_wrap, full_version=full_version) - - if 'PGI Compilers' in out: - cls = PGIFortranCompiler - self.coredata.add_lang_args(cls.language, cls, for_machine, self) - linker = PGIDynamicLinker(compiler, for_machine, - cls.LINKER_PREFIX, [], version=version) - return cls( - compiler, version, for_machine, is_cross, info, exe_wrap, - full_version=full_version, linker=linker) - - if 'NVIDIA Compilers and Tools' in out: - cls = NvidiaHPC_FortranCompiler - self.coredata.add_lang_args(cls.language, cls, for_machine, self) - linker = PGIDynamicLinker(compiler, for_machine, - cls.LINKER_PREFIX, [], version=version) - return cls( - compiler, version, for_machine, is_cross, info, exe_wrap, - full_version=full_version, linker=linker) - - if 'flang' in out or 'clang' in out: - linker = self._guess_nix_linker( - compiler, FlangFortranCompiler, for_machine) - return FlangFortranCompiler( - compiler, version, for_machine, is_cross, info, - exe_wrap, full_version=full_version, linker=linker) - - if 'Open64 Compiler Suite' in err: - linker = self._guess_nix_linker( - compiler, Open64FortranCompiler, for_machine) - return Open64FortranCompiler( - compiler, version, for_machine, is_cross, info, - exe_wrap, full_version=full_version, linker=linker) - - if 'NAG Fortran' in err: - linker = self._guess_nix_linker( - compiler, NAGFortranCompiler, for_machine) - return NAGFortranCompiler( - compiler, version, for_machine, is_cross, info, - exe_wrap, full_version=full_version, linker=linker) - - self._handle_exceptions(popen_exceptions, compilers) - def get_scratch_dir(self) -> str: return self.scratch_dir - def detect_objc_compiler(self, for_machine: MachineChoice) -> 'Compiler': - return self._detect_objc_or_objcpp_compiler(for_machine, True) - - def detect_objcpp_compiler(self, for_machine: MachineChoice) -> 'Compiler': - return self._detect_objc_or_objcpp_compiler(for_machine, False) - - def _detect_objc_or_objcpp_compiler(self, for_machine: MachineChoice, objc: bool) -> 'Compiler': - popen_exceptions = {} - compilers, ccache, exe_wrap = self._get_compilers('objc' if objc else 'objcpp', for_machine) - is_cross = self.is_cross_build(for_machine) - info = self.machines[for_machine] - - for compiler in compilers: - if isinstance(compiler, str): - compiler = [compiler] - arg = ['--version'] - try: - p, out, err = Popen_safe(compiler + arg) - except OSError as e: - popen_exceptions[' '.join(compiler + arg)] = e - continue - version = search_version(out) - if 'Free Software Foundation' in out: - defines = self.get_gnu_compiler_defines(compiler) - if not defines: - popen_exceptions[' '.join(compiler)] = 'no pre-processor defines' - continue - version = self.get_gnu_version_from_defines(defines) - comp = GnuObjCCompiler if objc else GnuObjCPPCompiler - linker = self._guess_nix_linker(compiler, comp, for_machine) - return comp( - ccache + compiler, version, for_machine, is_cross, info, - exe_wrap, defines, linker=linker) - if 'clang' in out: - linker = None - defines = self.get_clang_compiler_defines(compiler) - if not defines: - popen_exceptions[' '.join(compiler)] = 'no pre-processor defines' - continue - if 'Apple' in out: - comp = AppleClangObjCCompiler if objc else AppleClangObjCPPCompiler - else: - comp = ClangObjCCompiler if objc else ClangObjCPPCompiler - if 'windows' in out or self.machines[for_machine].is_windows(): - # If we're in a MINGW context this actually will use a gnu style ld - try: - linker = self._guess_win_linker(compiler, comp, for_machine) - except MesonException: - pass - - if not linker: - linker = self._guess_nix_linker( - compiler, comp, for_machine) - return comp( - ccache + compiler, version, for_machine, - is_cross, info, exe_wrap, linker=linker, defines=defines) - self._handle_exceptions(popen_exceptions, compilers) - - def detect_java_compiler(self, for_machine): - exelist = self.lookup_binary_entry(for_machine, 'java') - info = self.machines[for_machine] - if exelist is None: - # TODO support fallback - exelist = [self.default_java[0]] - - try: - p, out, err = Popen_safe(exelist + ['-version']) - except OSError: - raise EnvironmentException('Could not execute Java compiler "{}"'.format(' '.join(exelist))) - if 'javac' in out or 'javac' in err: - version = search_version(err if 'javac' in err else out) - if not version or version == 'unknown version': - parts = (err if 'javac' in err else out).split() - if len(parts) > 1: - version = parts[1] - comp_class = JavaCompiler - self.coredata.add_lang_args(comp_class.language, comp_class, for_machine, self) - return comp_class(exelist, version, for_machine, info) - raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"') - - def detect_cs_compiler(self, for_machine): - compilers, ccache, exe_wrap = self._get_compilers('cs', for_machine) - popen_exceptions = {} - info = self.machines[for_machine] - for comp in compilers: - if not isinstance(comp, list): - comp = [comp] - try: - p, out, err = Popen_safe(comp + ['--version']) - except OSError as e: - popen_exceptions[' '.join(comp + ['--version'])] = e - continue - - version = search_version(out) - if 'Mono' in out: - cls = MonoCompiler - elif "Visual C#" in out: - cls = VisualStudioCsCompiler - else: - continue - self.coredata.add_lang_args(cls.language, cls, for_machine, self) - return cls(comp, version, for_machine, info) - - self._handle_exceptions(popen_exceptions, compilers) - - def detect_cython_compiler(self, for_machine: MachineChoice) -> CythonCompiler: - """Search for a cython compiler.""" - compilers = self.lookup_binary_entry(for_machine, 'cython') - is_cross = self.is_cross_build(for_machine) - info = self.machines[for_machine] - if compilers is None: - # TODO support fallback - compilers = [self.default_cython[0]] - - popen_exceptions: T.Dict[str, Exception] = {} - for comp in compilers: - try: - err = Popen_safe(comp + ['-V'])[2] - except OSError as e: - popen_exceptions[' '.join(comp + ['-V'])] = e - continue - - version = search_version(err) - if 'Cython' in err: - comp_class = CythonCompiler - self.coredata.add_lang_args(comp_class.language, comp_class, for_machine, self) - return comp_class(comp, version, for_machine, info, is_cross=is_cross) - self._handle_exceptions(popen_exceptions, compilers) - - def detect_vala_compiler(self, for_machine): - exelist = self.lookup_binary_entry(for_machine, 'vala') - is_cross = self.is_cross_build(for_machine) - info = self.machines[for_machine] - if exelist is None: - # TODO support fallback - exelist = [self.default_vala[0]] - - try: - p, out = Popen_safe(exelist + ['--version'])[0:2] - except OSError: - raise EnvironmentException('Could not execute Vala compiler "{}"'.format(' '.join(exelist))) - version = search_version(out) - if 'Vala' in out: - comp_class = ValaCompiler - self.coredata.add_lang_args(comp_class.language, comp_class, for_machine, self) - return comp_class(exelist, version, for_machine, info, is_cross) - raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"') - - def detect_rust_compiler(self, for_machine: MachineChoice) -> RustCompiler: - popen_exceptions = {} # type: T.Dict[str, Exception] - compilers, _, exe_wrap = self._get_compilers('rust', for_machine) - is_cross = self.is_cross_build(for_machine) - info = self.machines[for_machine] - - cc = self.detect_c_compiler(for_machine) - is_link_exe = isinstance(cc.linker, VisualStudioLikeLinkerMixin) - override = self.lookup_binary_entry(for_machine, 'rust_ld') - - for compiler in compilers: - if isinstance(compiler, str): - compiler = [compiler] - arg = ['--version'] - try: - out = Popen_safe(compiler + arg)[1] - except OSError as e: - popen_exceptions[' '.join(compiler + arg)] = e - continue - - version = search_version(out) - - if 'rustc' in out: - # On Linux and mac rustc will invoke gcc (clang for mac - # presumably) and it can do this windows, for dynamic linking. - # this means the easiest way to C compiler for dynamic linking. - # figure out what linker to use is to just get the value of the - # C compiler and use that as the basis of the rust linker. - # However, there are two things we need to change, if CC is not - # the default use that, and second add the necessary arguments - # to rust to use -fuse-ld - - if any(a.startswith('linker=') for a in compiler): - mlog.warning( - 'Please do not put -C linker= in your compiler ' - 'command, set rust_ld=command in your cross file ' - 'or use the RUST_LD environment variable, otherwise meson ' - 'will override your selection.') - - if override is None: - extra_args = {} - always_args = [] - if is_link_exe: - compiler.extend(RustCompiler.use_linker_args(cc.linker.exelist[0])) - extra_args['direct'] = True - extra_args['machine'] = cc.linker.machine - else: - exelist = cc.linker.exelist.copy() - if 'ccache' in exelist[0]: - del exelist[0] - c = exelist.pop(0) - compiler.extend(RustCompiler.use_linker_args(c)) - - # Also ensure that we pass any extra arguments to the linker - for l in exelist: - compiler.extend(['-C', f'link-arg={l}']) - - # This trickery with type() gets us the class of the linker - # so we can initialize a new copy for the Rust Compiler - if is_link_exe: - linker = type(cc.linker)(for_machine, always_args, exelist=cc.linker.exelist, - version=cc.linker.version, **extra_args) - else: - linker = type(cc.linker)(compiler, for_machine, cc.LINKER_PREFIX, - always_args=always_args, version=cc.linker.version, - **extra_args) - elif 'link' in override[0]: - linker = self._guess_win_linker( - override, RustCompiler, for_machine, use_linker_prefix=False) - # rustc takes linker arguments without a prefix, and - # inserts the correct prefix itself. - linker.direct = True - compiler.extend(RustCompiler.use_linker_args(linker.exelist[0])) - else: - # On linux and macos rust will invoke the c compiler for - # linking, on windows it will use lld-link or link.exe. - # we will simply ask for the C compiler that corresponds to - # it, and use that. - cc = self._detect_c_or_cpp_compiler('c', for_machine, override_compiler=override) - linker = cc.linker - - # Of course, we're not going to use any of that, we just - # need it to get the proper arguments to pass to rustc - c = linker.exelist[1] if linker.exelist[0].endswith('ccache') else linker.exelist[0] - compiler.extend(RustCompiler.use_linker_args(c)) - - self.coredata.add_lang_args(RustCompiler.language, RustCompiler, for_machine, self) - return RustCompiler( - compiler, version, for_machine, is_cross, info, exe_wrap, - linker=linker) - - self._handle_exceptions(popen_exceptions, compilers) - - def detect_d_compiler(self, for_machine: MachineChoice): - info = self.machines[for_machine] - - # Detect the target architecture, required for proper architecture handling on Windows. - # MSVC compiler is required for correct platform detection. - c_compiler = {'c': self.detect_c_compiler(for_machine)} - is_msvc = isinstance(c_compiler['c'], VisualStudioCCompiler) - if not is_msvc: - c_compiler = {} - - arch = detect_cpu_family(c_compiler) - if is_msvc and arch == 'x86': - arch = 'x86_mscoff' - - popen_exceptions = {} - is_cross = self.is_cross_build(for_machine) - results, ccache, exe_wrap = self._get_compilers('d', for_machine) - for exelist in results: - # Search for a D compiler. - # We prefer LDC over GDC unless overridden with the DC - # environment variable because LDC has a much more - # up to date language version at time (2016). - if not isinstance(exelist, list): - exelist = [exelist] - if os.path.basename(exelist[-1]).startswith(('ldmd', 'gdmd')): - raise EnvironmentException( - 'Meson does not support {} as it is only a DMD frontend for another compiler.' - 'Please provide a valid value for DC or unset it so that Meson can resolve the compiler by itself.'.format(exelist[-1])) - try: - p, out = Popen_safe(exelist + ['--version'])[0:2] - except OSError as e: - popen_exceptions[' '.join(exelist + ['--version'])] = e - continue - version = search_version(out) - full_version = out.split('\n', 1)[0] - - if 'LLVM D compiler' in out: - # LDC seems to require a file - # We cannot use NamedTemproraryFile on windows, its documented - # to not work for our uses. So, just use mkstemp and only have - # one path for simplicity. - o, f = tempfile.mkstemp('.d') - os.close(o) - - try: - if info.is_windows() or info.is_cygwin(): - objfile = os.path.basename(f)[:-1] + 'obj' - linker = self._guess_win_linker( - exelist, - compilers.LLVMDCompiler, for_machine, - use_linker_prefix=True, invoked_directly=False, - extra_args=[f]) - else: - # LDC writes an object file to the current working directory. - # Clean it up. - objfile = os.path.basename(f)[:-1] + 'o' - linker = self._guess_nix_linker( - exelist, compilers.LLVMDCompiler, for_machine, - extra_args=[f]) - finally: - mesonlib.windows_proof_rm(f) - mesonlib.windows_proof_rm(objfile) - - return compilers.LLVMDCompiler( - exelist, version, for_machine, info, arch, - full_version=full_version, linker=linker, version_output=out) - elif 'gdc' in out: - linker = self._guess_nix_linker(exelist, compilers.GnuDCompiler, for_machine) - return compilers.GnuDCompiler( - exelist, version, for_machine, info, arch, - exe_wrapper=exe_wrap, is_cross=is_cross, - full_version=full_version, linker=linker) - elif 'The D Language Foundation' in out or 'Digital Mars' in out: - # DMD seems to require a file - # We cannot use NamedTemproraryFile on windows, its documented - # to not work for our uses. So, just use mkstemp and only have - # one path for simplicity. - o, f = tempfile.mkstemp('.d') - os.close(o) - - # DMD as different detection logic for x86 and x86_64 - arch_arg = '-m64' if arch == 'x86_64' else '-m32' - - try: - if info.is_windows() or info.is_cygwin(): - objfile = os.path.basename(f)[:-1] + 'obj' - linker = self._guess_win_linker( - exelist, compilers.DmdDCompiler, for_machine, - invoked_directly=False, extra_args=[f, arch_arg]) - else: - objfile = os.path.basename(f)[:-1] + 'o' - linker = self._guess_nix_linker( - exelist, compilers.DmdDCompiler, for_machine, - extra_args=[f, arch_arg]) - finally: - mesonlib.windows_proof_rm(f) - mesonlib.windows_proof_rm(objfile) - - return compilers.DmdDCompiler( - exelist, version, for_machine, info, arch, - full_version=full_version, linker=linker) - raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"') - - self._handle_exceptions(popen_exceptions, compilers) - - def detect_swift_compiler(self, for_machine): - exelist = self.lookup_binary_entry(for_machine, 'swift') - is_cross = self.is_cross_build(for_machine) - info = self.machines[for_machine] - if exelist is None: - # TODO support fallback - exelist = [self.default_swift[0]] - - try: - p, _, err = Popen_safe(exelist + ['-v']) - except OSError: - raise EnvironmentException('Could not execute Swift compiler "{}"'.format(' '.join(exelist))) - version = search_version(err) - if 'Swift' in err: - # As for 5.0.1 swiftc *requires* a file to check the linker: - with tempfile.NamedTemporaryFile(suffix='.swift') as f: - linker = self._guess_nix_linker( - exelist, compilers.SwiftCompiler, for_machine, - extra_args=[f.name]) - return compilers.SwiftCompiler( - exelist, version, for_machine, info, is_cross, linker=linker) - - raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"') - - def compiler_from_language(self, lang: str, for_machine: MachineChoice): - if lang == 'c': - comp = self.detect_c_compiler(for_machine) - elif lang == 'cpp': - comp = self.detect_cpp_compiler(for_machine) - elif lang == 'objc': - comp = self.detect_objc_compiler(for_machine) - elif lang == 'cuda': - comp = self.detect_cuda_compiler(for_machine) - elif lang == 'objcpp': - comp = self.detect_objcpp_compiler(for_machine) - elif lang == 'java': - comp = self.detect_java_compiler(for_machine) - elif lang == 'cs': - comp = self.detect_cs_compiler(for_machine) - elif lang == 'vala': - comp = self.detect_vala_compiler(for_machine) - elif lang == 'd': - comp = self.detect_d_compiler(for_machine) - elif lang == 'rust': - comp = self.detect_rust_compiler(for_machine) - elif lang == 'fortran': - comp = self.detect_fortran_compiler(for_machine) - elif lang == 'swift': - comp = self.detect_swift_compiler(for_machine) - elif lang == 'cython': - comp = self.detect_cython_compiler(for_machine) - else: - comp = None - return comp - - def detect_compiler_for(self, lang: str, for_machine: MachineChoice): - comp = self.compiler_from_language(lang, for_machine) - if comp is not None: - assert comp.for_machine == for_machine - self.coredata.process_new_compiler(lang, comp, self) - return comp - - def detect_static_linker(self, compiler): - linker = self.lookup_binary_entry(compiler.for_machine, 'ar') - if linker is not None: - linkers = [linker] - else: - defaults = [[l] for l in self.default_static_linker] - if isinstance(compiler, compilers.CudaCompiler): - linkers = [self.cuda_static_linker] + defaults - elif isinstance(compiler, compilers.VisualStudioLikeCompiler): - linkers = [self.vs_static_linker, self.clang_cl_static_linker] - elif isinstance(compiler, compilers.GnuCompiler): - # Use gcc-ar if available; needed for LTO - linkers = [self.gcc_static_linker] + defaults - elif isinstance(compiler, compilers.ClangCompiler): - # Use llvm-ar if available; needed for LTO - linkers = [self.clang_static_linker] + defaults - elif isinstance(compiler, compilers.DCompiler): - # Prefer static linkers over linkers used by D compilers - if mesonlib.is_windows(): - linkers = [self.vs_static_linker, self.clang_cl_static_linker, compiler.get_linker_exelist()] - else: - linkers = defaults - elif isinstance(compiler, IntelClCCompiler): - # Intel has it's own linker that acts like microsoft's lib - linkers = ['xilib'] - elif isinstance(compiler, (PGICCompiler, PGIFortranCompiler)) and mesonlib.is_windows(): - linkers = [['ar']] # For PGI on Windows, "ar" is just a wrapper calling link/lib. - else: - linkers = defaults - popen_exceptions = {} - for linker in linkers: - if not {'lib', 'lib.exe', 'llvm-lib', 'llvm-lib.exe', 'xilib', 'xilib.exe'}.isdisjoint(linker): - arg = '/?' - elif not {'ar2000', 'ar2000.exe'}.isdisjoint(linker): - arg = '?' - else: - arg = '--version' - try: - p, out, err = Popen_safe(linker + [arg]) - except OSError as e: - popen_exceptions[' '.join(linker + [arg])] = e - continue - if "xilib: executing 'lib'" in err: - return IntelVisualStudioLinker(linker, getattr(compiler, 'machine', None)) - if '/OUT:' in out.upper() or '/OUT:' in err.upper(): - return VisualStudioLinker(linker, getattr(compiler, 'machine', None)) - if 'ar-Error-Unknown switch: --version' in err: - return PGIStaticLinker(linker) - if p.returncode == 0 and ('armar' in linker or 'armar.exe' in linker): - return ArmarLinker(linker) - if 'DMD32 D Compiler' in out or 'DMD64 D Compiler' in out: - return DLinker(linker, compiler.arch) - if 'LDC - the LLVM D compiler' in out: - return DLinker(linker, compiler.arch, rsp_syntax=compiler.rsp_file_syntax()) - if 'GDC' in out and ' based on D ' in out: - return DLinker(linker, compiler.arch) - if err.startswith('Renesas') and ('rlink' in linker or 'rlink.exe' in linker): - return CcrxLinker(linker) - if out.startswith('GNU ar') and ('xc16-ar' in linker or 'xc16-ar.exe' in linker): - return Xc16Linker(linker) - if out.startswith('TMS320C2000') and ('ar2000' in linker or 'ar2000.exe' in linker): - return C2000Linker(linker) - if out.startswith('The CompCert'): - return CompCertLinker(linker) - if p.returncode == 0: - return ArLinker(linker) - if p.returncode == 1 and err.startswith('usage'): # OSX - return ArLinker(linker) - if p.returncode == 1 and err.startswith('Usage'): # AIX - return AIXArLinker(linker) - if p.returncode == 1 and err.startswith('ar: bad option: --'): # Solaris - return ArLinker(linker) - self._handle_exceptions(popen_exceptions, linkers, 'linker') - raise EnvironmentException('Unknown static linker "{}"'.format(' '.join(linkers))) - def get_source_dir(self) -> str: return self.source_dir diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index f1c7f33..d9ecd25 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -1328,7 +1328,7 @@ external dependencies (including libraries) must go to "dependencies".''') comp = clist[lang] else: try: - comp = self.environment.detect_compiler_for(lang, for_machine) + comp = compilers.detect_compiler_for(self.environment, lang, for_machine) if comp is None: raise InvalidArguments('Tried to use unknown language "%s".' % lang) if self.should_skip_sanity_check(for_machine): diff --git a/mesonbuild/linkers/__init__.py b/mesonbuild/linkers/__init__.py index 0d6fbc1..9182fa1 100644 --- a/mesonbuild/linkers/__init__.py +++ b/mesonbuild/linkers/__init__.py @@ -12,6 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. +from .detect import ( + defaults, + guess_win_linker, + guess_nix_linker, +) from .linkers import ( RSPFileSyntax, @@ -64,6 +69,12 @@ from .linkers import ( ) __all__ = [ + # detect.py + 'defaults', + 'guess_win_linker', + 'guess_nix_linker', + + # linkers.py 'RSPFileSyntax', 'StaticLinker', diff --git a/mesonbuild/linkers/detect.py b/mesonbuild/linkers/detect.py new file mode 100644 index 0000000..702bf3d --- /dev/null +++ b/mesonbuild/linkers/detect.py @@ -0,0 +1,216 @@ +# Copyright 2012-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. + +from ..mesonlib import ( + EnvironmentException, MachineChoice, OptionKey, + Popen_safe, search_version +) +from .linkers import ( + DynamicLinker, + AppleDynamicLinker, + GnuDynamicLinker, + GnuGoldDynamicLinker, + GnuBFDDynamicLinker, + LLVMDynamicLinker, + QualcommLLVMDynamicLinker, + MSVCDynamicLinker, + ClangClDynamicLinker, + SolarisDynamicLinker, + AIXDynamicLinker, + OptlinkDynamicLinker, +) + +import re +import shlex +import typing as T + +if T.TYPE_CHECKING: + from ..environment import Environment + from ..compilers import Compiler + +defaults: T.Dict[str, T.List[str]] = {} +defaults['static_linker'] = ['ar', 'gar'] +defaults['vs_static_linker'] = ['lib'] +defaults['clang_cl_static_linker'] = ['llvm-lib'] +defaults['cuda_static_linker'] = ['nvlink'] +defaults['gcc_static_linker'] = ['gcc-ar'] +defaults['clang_static_linker'] = ['llvm-ar'] + +def __failed_to_detect_linker(compiler: T.List[str], args: T.List[str], stdout: str, stderr: str) -> 'T.NoReturn': + msg = 'Unable to detect linker for compiler "{} {}"\nstdout: {}\nstderr: {}'.format( + ' '.join(compiler), ' '.join(args), stdout, stderr) + raise EnvironmentException(msg) + + +def guess_win_linker(env: 'Environment', compiler: T.List[str], comp_class: T.Type['Compiler'], + for_machine: MachineChoice, *, + use_linker_prefix: bool = True, invoked_directly: bool = True, + extra_args: T.Optional[T.List[str]] = None) -> 'DynamicLinker': + env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) + + # Explicitly pass logo here so that we can get the version of link.exe + if not use_linker_prefix or comp_class.LINKER_PREFIX is None: + check_args = ['/logo', '--version'] + elif isinstance(comp_class.LINKER_PREFIX, str): + check_args = [comp_class.LINKER_PREFIX + '/logo', comp_class.LINKER_PREFIX + '--version'] + elif isinstance(comp_class.LINKER_PREFIX, list): + check_args = comp_class.LINKER_PREFIX + ['/logo'] + comp_class.LINKER_PREFIX + ['--version'] + + check_args += env.coredata.options[OptionKey('args', lang=comp_class.language, machine=for_machine)].value + + override = [] # type: T.List[str] + value = env.lookup_binary_entry(for_machine, comp_class.language + '_ld') + if value is not None: + override = comp_class.use_linker_args(value[0]) + check_args += override + + if extra_args is not None: + check_args.extend(extra_args) + + p, o, _ = Popen_safe(compiler + check_args) + if o.startswith('LLD'): + if '(compatible with GNU linkers)' in o: + return LLVMDynamicLinker( + compiler, for_machine, comp_class.LINKER_PREFIX, + override, version=search_version(o)) + elif not invoked_directly: + return ClangClDynamicLinker( + for_machine, override, exelist=compiler, prefix=comp_class.LINKER_PREFIX, + version=search_version(o), direct=False, machine=None) + + if value is not None and invoked_directly: + compiler = value + # We've already hanedled the non-direct case above + + p, o, e = Popen_safe(compiler + check_args) + if o.startswith('LLD'): + return ClangClDynamicLinker( + for_machine, [], + prefix=comp_class.LINKER_PREFIX if use_linker_prefix else [], + exelist=compiler, version=search_version(o), direct=invoked_directly) + elif 'OPTLINK' in o: + # Opltink's stdout *may* beging with a \r character. + return OptlinkDynamicLinker(compiler, for_machine, version=search_version(o)) + elif o.startswith('Microsoft') or e.startswith('Microsoft'): + out = o or e + match = re.search(r'.*(X86|X64|ARM|ARM64).*', out) + if match: + target = str(match.group(1)) + else: + target = 'x86' + + return MSVCDynamicLinker( + for_machine, [], machine=target, exelist=compiler, + prefix=comp_class.LINKER_PREFIX if use_linker_prefix else [], + version=search_version(out), direct=invoked_directly) + elif 'GNU coreutils' in o: + raise EnvironmentException( + "Found GNU link.exe instead of MSVC link.exe. This link.exe " + "is not a linker. You may need to reorder entries to your " + "%PATH% variable to resolve this.") + __failed_to_detect_linker(compiler, check_args, o, e) + +def guess_nix_linker(env: 'Environment', compiler: T.List[str], comp_class: T.Type['Compiler'], + for_machine: MachineChoice, *, + extra_args: T.Optional[T.List[str]] = None) -> 'DynamicLinker': + """Helper for guessing what linker to use on Unix-Like OSes. + + :compiler: Invocation to use to get linker + :comp_class: The Compiler Type (uninstantiated) + :for_machine: which machine this linker targets + :extra_args: Any additional arguments required (such as a source file) + """ + env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) + extra_args = extra_args or [] + extra_args += env.coredata.options[OptionKey('args', lang=comp_class.language, machine=for_machine)].value + + if isinstance(comp_class.LINKER_PREFIX, str): + check_args = [comp_class.LINKER_PREFIX + '--version'] + extra_args + else: + check_args = comp_class.LINKER_PREFIX + ['--version'] + extra_args + + override = [] # type: T.List[str] + value = env.lookup_binary_entry(for_machine, comp_class.language + '_ld') + if value is not None: + override = comp_class.use_linker_args(value[0]) + check_args += override + + _, o, e = Popen_safe(compiler + check_args) + v = search_version(o + e) + linker: DynamicLinker + if o.startswith('LLD'): + linker = LLVMDynamicLinker( + compiler, for_machine, comp_class.LINKER_PREFIX, override, version=v) + elif 'Snapdragon' in e and 'LLVM' in e: + linker = QualcommLLVMDynamicLinker( + compiler, for_machine, comp_class.LINKER_PREFIX, override, version=v) + elif e.startswith('lld-link: '): + # The LLD MinGW frontend didn't respond to --version before version 9.0.0, + # and produced an error message about failing to link (when no object + # files were specified), instead of printing the version number. + # Let's try to extract the linker invocation command to grab the version. + + _, o, e = Popen_safe(compiler + check_args + ['-v']) + + try: + linker_cmd = re.match(r'.*\n(.*?)\nlld-link: ', e, re.DOTALL).group(1) + linker_cmd = shlex.split(linker_cmd)[0] + except (AttributeError, IndexError, ValueError): + pass + else: + _, o, e = Popen_safe([linker_cmd, '--version']) + v = search_version(o) + + linker = LLVMDynamicLinker(compiler, for_machine, comp_class.LINKER_PREFIX, override, version=v) + # first is for apple clang, second is for real gcc, the third is icc + elif e.endswith('(use -v to see invocation)\n') or 'macosx_version' in e or 'ld: unknown option:' in e: + if isinstance(comp_class.LINKER_PREFIX, str): + _, _, e = Popen_safe(compiler + [comp_class.LINKER_PREFIX + '-v'] + extra_args) + else: + _, _, e = Popen_safe(compiler + comp_class.LINKER_PREFIX + ['-v'] + extra_args) + for line in e.split('\n'): + if 'PROJECT:ld' in line: + v = line.split('-')[1] + break + else: + v = 'unknown version' + linker = AppleDynamicLinker(compiler, for_machine, comp_class.LINKER_PREFIX, override, version=v) + elif 'GNU' in o or 'GNU' in e: + cls: T.Type[GnuDynamicLinker] + if 'gold' in o or 'gold' in e: + cls = GnuGoldDynamicLinker + else: + cls = GnuBFDDynamicLinker + linker = cls(compiler, for_machine, comp_class.LINKER_PREFIX, override, version=v) + elif 'Solaris' in e or 'Solaris' in o: + for line in (o+e).split('\n'): + if 'ld: Software Generation Utilities' in line: + v = line.split(':')[2].lstrip() + break + else: + v = 'unknown version' + linker = SolarisDynamicLinker( + compiler, for_machine, comp_class.LINKER_PREFIX, override, + version=v) + elif 'ld: 0706-012 The -- flag is not recognized' in e: + if isinstance(comp_class.LINKER_PREFIX, str): + _, _, e = Popen_safe(compiler + [comp_class.LINKER_PREFIX + '-V'] + extra_args) + else: + _, _, e = Popen_safe(compiler + comp_class.LINKER_PREFIX + ['-V'] + extra_args) + linker = AIXDynamicLinker( + compiler, for_machine, comp_class.LINKER_PREFIX, override, + version=search_version(e)) + else: + __failed_to_detect_linker(compiler, check_args, o, e) + return linker diff --git a/mesonbuild/mesonlib/universal.py b/mesonbuild/mesonlib/universal.py index 160cc37..4b4eb6e 100644 --- a/mesonbuild/mesonlib/universal.py +++ b/mesonbuild/mesonlib/universal.py @@ -135,6 +135,7 @@ __all__ = [ 'version_compare', 'version_compare_condition_with_min', 'version_compare_many', + 'search_version', 'windows_proof_rm', 'windows_proof_rmtree', ] @@ -883,6 +884,49 @@ def version_compare_condition_with_min(condition: str, minimum: str) -> bool: return T.cast(bool, cmpop(Version(minimum), Version(condition))) +def search_version(text: str) -> str: + # Usually of the type 4.1.4 but compiler output may contain + # stuff like this: + # (Sourcery CodeBench Lite 2014.05-29) 4.8.3 20140320 (prerelease) + # Limiting major version number to two digits seems to work + # thus far. When we get to GCC 100, this will break, but + # if we are still relevant when that happens, it can be + # considered an achievement in itself. + # + # This regex is reaching magic levels. If it ever needs + # to be updated, do not complexify but convert to something + # saner instead. + # We'll demystify it a bit with a verbose definition. + version_regex = re.compile(r""" + (?<! # Zero-width negative lookbehind assertion + ( + \d # One digit + | \. # Or one period + ) # One occurrence + ) + # Following pattern must not follow a digit or period + ( + \d{1,2} # One or two digits + ( + \.\d+ # Period and one or more digits + )+ # One or more occurrences + ( + -[a-zA-Z0-9]+ # Hyphen and one or more alphanumeric + )? # Zero or one occurrence + ) # One occurrence + """, re.VERBOSE) + match = version_regex.search(text) + if match: + return match.group(0) + + # try a simpler regex that has like "blah 2020.01.100 foo" or "blah 2020.01 foo" + version_regex = re.compile(r"(\d{1,4}\.\d{1,4}\.?\d{0,4})") + match = version_regex.search(text) + if match: + return match.group(0) + + return 'unknown version' + def default_libdir() -> str: if is_debianlike(): diff --git a/run_project_tests.py b/run_project_tests.py index 3522009..c9b7a8f 100755 --- a/run_project_tests.py +++ b/run_project_tests.py @@ -42,6 +42,7 @@ from mesonbuild import compilers from mesonbuild import mesonlib from mesonbuild import mlog from mesonbuild import mtest +from mesonbuild.compilers import compiler_from_language, detect_objc_compiler, detect_objcpp_compiler from mesonbuild.build import ConfigurationData from mesonbuild.mesonlib import MachineChoice, Popen_safe, TemporaryDirectoryWinProof from mesonbuild.mlog import blue, bold, cyan, green, red, yellow, normal_green @@ -884,7 +885,7 @@ def have_objc_compiler(use_tmp: bool) -> bool: with TemporaryDirectoryWinProof(prefix='b ', dir=None if use_tmp else '.') as build_dir: env = environment.Environment(None, build_dir, get_fake_options('/')) try: - objc_comp = env.detect_objc_compiler(MachineChoice.HOST) + objc_comp = detect_objc_compiler(env, MachineChoice.HOST) except mesonlib.MesonException: return False if not objc_comp: @@ -900,7 +901,7 @@ def have_objcpp_compiler(use_tmp: bool) -> bool: with TemporaryDirectoryWinProof(prefix='b ', dir=None if use_tmp else '.') as build_dir: env = environment.Environment(None, build_dir, get_fake_options('/')) try: - objcpp_comp = env.detect_objcpp_compiler(MachineChoice.HOST) + objcpp_comp = detect_objcpp_compiler(env, MachineChoice.HOST) except mesonlib.MesonException: return False if not objcpp_comp: @@ -1392,7 +1393,7 @@ def detect_system_compiler(options: 'CompilerArgumentType') -> None: for lang in sorted(compilers.all_languages): try: - comp = env.compiler_from_language(lang, MachineChoice.HOST) + comp = compiler_from_language(env, lang, MachineChoice.HOST) # note compiler id for later use with test.json matrix compiler_id_map[lang] = comp.get_id() except mesonlib.MesonException: @@ -1412,7 +1413,7 @@ def print_compilers(env: 'Environment', machine: MachineChoice) -> None: print() for lang in sorted(compilers.all_languages): try: - comp = env.compiler_from_language(lang, machine) + comp = compiler_from_language(env, lang, machine) details = '{:<10} {} {}'.format('[' + comp.get_id() + ']', ' '.join(comp.get_exelist()), comp.get_version_string()) except mesonlib.MesonException: details = '[not found]' diff --git a/run_unittests.py b/run_unittests.py index 7f74e91..f1ff2b4 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -59,7 +59,13 @@ from mesonbuild.mesonlib import ( BuildDirLock, LibType, MachineChoice, PerMachine, Version, is_windows, is_osx, is_cygwin, is_dragonflybsd, is_openbsd, is_haiku, is_sunos, windows_proof_rmtree, windows_proof_rm, python_command, - version_compare, split_args, quote_arg, relpath, is_linux, git + version_compare, split_args, quote_arg, relpath, is_linux, git, + search_version +) +from mesonbuild.compilers import ( + detect_static_linker, detect_c_compiler, detect_cpp_compiler, + detect_objc_compiler, detect_objcpp_compiler, detect_d_compiler, + detect_swift_compiler, compiler_from_language ) from mesonbuild.environment import detect_ninja from mesonbuild.mesonlib import MesonException, EnvironmentException, OptionKey @@ -140,7 +146,7 @@ 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(mesonbuild.environment.search_version(out), '>= 2.28'): + if version_compare(search_version(out), '>= 2.28'): extra_cmd = ['--initial-branch', 'master'] else: extra_cmd = [] @@ -226,14 +232,12 @@ def skip_if_no_cmake(f): return f(*args, **kwargs) return wrapped -def skip_if_not_language(lang): +def skip_if_not_language(lang: str): def wrapper(func): @functools.wraps(func) def wrapped(*args, **kwargs): try: - env = get_fake_env() - f = getattr(env, f'detect_{lang}_compiler') - f(MachineChoice.HOST) + compiler_from_language(get_fake_env(), lang, MachineChoice.HOST) except EnvironmentException: raise unittest.SkipTest(f'No {lang} compiler found.') return func(*args, **kwargs) @@ -269,7 +273,7 @@ def skip_if_not_base_option(feature): @functools.wraps(f) def wrapped(*args, **kwargs): env = get_fake_env() - cc = env.detect_c_compiler(MachineChoice.HOST) + cc = detect_c_compiler(env, MachineChoice.HOST) key = OptionKey(feature) if key not in cc.base_options: raise unittest.SkipTest( @@ -327,18 +331,17 @@ def no_pkgconfig(): class InternalTests(unittest.TestCase): def test_version_number(self): - searchfunc = mesonbuild.environment.search_version - self.assertEqual(searchfunc('foobar 1.2.3'), '1.2.3') - self.assertEqual(searchfunc('1.2.3'), '1.2.3') - self.assertEqual(searchfunc('foobar 2016.10.28 1.2.3'), '1.2.3') - self.assertEqual(searchfunc('2016.10.28 1.2.3'), '1.2.3') - self.assertEqual(searchfunc('foobar 2016.10.128'), '2016.10.128') - self.assertEqual(searchfunc('2016.10.128'), '2016.10.128') - self.assertEqual(searchfunc('2016.10'), '2016.10') - self.assertEqual(searchfunc('2016.10 1.2.3'), '1.2.3') - self.assertEqual(searchfunc('oops v1.2.3'), '1.2.3') - self.assertEqual(searchfunc('2016.oops 1.2.3'), '1.2.3') - self.assertEqual(searchfunc('2016.x'), 'unknown version') + self.assertEqual(search_version('foobar 1.2.3'), '1.2.3') + self.assertEqual(search_version('1.2.3'), '1.2.3') + self.assertEqual(search_version('foobar 2016.10.28 1.2.3'), '1.2.3') + self.assertEqual(search_version('2016.10.28 1.2.3'), '1.2.3') + self.assertEqual(search_version('foobar 2016.10.128'), '2016.10.128') + self.assertEqual(search_version('2016.10.128'), '2016.10.128') + self.assertEqual(search_version('2016.10'), '2016.10') + self.assertEqual(search_version('2016.10 1.2.3'), '1.2.3') + self.assertEqual(search_version('oops v1.2.3'), '1.2.3') + self.assertEqual(search_version('2016.oops 1.2.3'), '1.2.3') + self.assertEqual(search_version('2016.x'), 'unknown version') def test_mode_symbolic_to_bits(self): modefunc = mesonbuild.mesonlib.FileMode.perms_s_to_bits @@ -837,7 +840,7 @@ class InternalTests(unittest.TestCase): '{}.dll.a', '{}.lib', '{}.dll'), 'static': msvc_static}} env = get_fake_env() - cc = env.detect_c_compiler(MachineChoice.HOST) + cc = detect_c_compiler(env, MachineChoice.HOST) if is_osx(): self._test_all_naming(cc, env, patterns, 'darwin') elif is_cygwin(): @@ -881,7 +884,7 @@ class InternalTests(unittest.TestCase): with tempfile.TemporaryDirectory() as tmpdir: pkgbin = ExternalProgram('pkg-config', command=['pkg-config'], silent=True) env = get_fake_env() - compiler = env.detect_c_compiler(MachineChoice.HOST) + compiler = detect_c_compiler(env, MachineChoice.HOST) env.coredata.compilers.host = {'c': compiler} env.coredata.options[OptionKey('link_args', lang='c')] = FakeCompilerOptions() p1 = Path(tmpdir) / '1' @@ -1063,7 +1066,7 @@ class InternalTests(unittest.TestCase): Ensure that the toolset version returns the correct value for this MSVC ''' env = get_fake_env() - cc = env.detect_c_compiler(MachineChoice.HOST) + cc = detect_c_compiler(env, MachineChoice.HOST) if cc.get_argument_syntax() != 'msvc': raise unittest.SkipTest('Test only applies to MSVC-like compilers') toolset_ver = cc.get_toolset_version() @@ -1752,8 +1755,8 @@ class DataTests(unittest.TestCase): self.assertIsNotNone(md) env = get_fake_env() # FIXME: Support other compilers - cc = env.detect_c_compiler(MachineChoice.HOST) - cpp = env.detect_cpp_compiler(MachineChoice.HOST) + cc = detect_c_compiler(env, MachineChoice.HOST) + cpp = detect_cpp_compiler(env, MachineChoice.HOST) for comp in (cc, cpp): for opt in comp.get_options(): self.assertIn(str(opt), md) @@ -2556,7 +2559,7 @@ class AllPlatformTests(BasePlatformTests): def test_clike_get_library_dirs(self): env = get_fake_env() - cc = env.detect_c_compiler(MachineChoice.HOST) + 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)) @@ -2572,8 +2575,8 @@ class AllPlatformTests(BasePlatformTests): ''' testdir = os.path.join(self.common_test_dir, '3 static') env = get_fake_env(testdir, self.builddir, self.prefix) - cc = env.detect_c_compiler(MachineChoice.HOST) - static_linker = env.detect_static_linker(cc) + cc = detect_c_compiler(env, MachineChoice.HOST) + static_linker = detect_static_linker(env, cc) if is_windows(): raise unittest.SkipTest('https://github.com/mesonbuild/meson/issues/1526') if not isinstance(static_linker, mesonbuild.linkers.ArLinker): @@ -3041,9 +3044,9 @@ class AllPlatformTests(BasePlatformTests): for lang, evar in langs: # Detect with evar and do sanity checks on that if evar in os.environ: - ecc = getattr(env, f'detect_{lang}_compiler')(MachineChoice.HOST) + ecc = compiler_from_language(env, lang, MachineChoice.HOST) self.assertTrue(ecc.version) - elinker = env.detect_static_linker(ecc) + 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 @@ -3069,9 +3072,9 @@ class AllPlatformTests(BasePlatformTests): # 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 = getattr(env, f'detect_{lang}_compiler')(MachineChoice.HOST) + cc = compiler_from_language(env, lang, MachineChoice.HOST) self.assertTrue(cc.version) - linker = env.detect_static_linker(cc) + linker = detect_static_linker(env, cc) # Check compiler type if isinstance(cc, gnu): self.assertIsInstance(linker, ar) @@ -3129,8 +3132,8 @@ class AllPlatformTests(BasePlatformTests): # Need a new env to re-run environment loading env = get_fake_env(testdir, self.builddir, self.prefix) - wcc = getattr(env, f'detect_{lang}_compiler')(MachineChoice.HOST) - wlinker = env.detect_static_linker(wcc) + 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 evalue = os.environ.pop('AR') # Must be the same type since it's a wrapper around the same exelist @@ -3148,7 +3151,7 @@ class AllPlatformTests(BasePlatformTests): 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 env.detect_c_compiler(MachineChoice.HOST).get_id() == 'msvc': + if detect_c_compiler(env, MachineChoice.HOST).get_id() == 'msvc': raise unittest.SkipTest('MSVC can\'t compile assembly') self.init(testdir) commands = {'c-asm': {}, 'cpp-asm': {}, 'cpp-c-asm': {}, 'c-cpp-asm': {}} @@ -3309,7 +3312,7 @@ class AllPlatformTests(BasePlatformTests): testdir = os.path.join(self.common_test_dir, '5 linkstatic') env = get_fake_env(testdir, self.builddir, self.prefix) - if env.detect_c_compiler(MachineChoice.HOST).get_id() == 'clang' and is_windows(): + if detect_c_compiler(env, MachineChoice.HOST).get_id() == 'clang' and is_windows(): raise unittest.SkipTest('LTO not (yet) supported by windows clang') self.init(testdir, extra_args='-Db_lto=true') @@ -3321,7 +3324,7 @@ class AllPlatformTests(BasePlatformTests): testdir = os.path.join(self.common_test_dir, '6 linkshared') env = get_fake_env(testdir, self.builddir, self.prefix) - cc = env.detect_c_compiler(MachineChoice.HOST) + cc = detect_c_compiler(env, MachineChoice.HOST) extra_args: T.List[str] = [] if cc.get_id() == 'clang': if is_windows(): @@ -3347,7 +3350,7 @@ class AllPlatformTests(BasePlatformTests): testdir = os.path.join(self.common_test_dir, '6 linkshared') env = get_fake_env(testdir, self.builddir, self.prefix) - cc = env.detect_c_compiler(MachineChoice.HOST) + cc = detect_c_compiler(env, MachineChoice.HOST) if cc.get_id() != 'clang': raise unittest.SkipTest('Only clang currently supports thinLTO') if cc.linker.id not in {'ld.lld', 'ld.gold', 'ld64', 'lld-link'}: @@ -3615,8 +3618,8 @@ class AllPlatformTests(BasePlatformTests): def detect_prebuild_env(self): env = get_fake_env() - cc = env.detect_c_compiler(MachineChoice.HOST) - stlinker = env.detect_static_linker(cc) + cc = detect_c_compiler(env, MachineChoice.HOST) + stlinker = detect_static_linker(env, cc) if mesonbuild.mesonlib.is_windows(): object_suffix = 'obj' shared_suffix = 'dll' @@ -4060,7 +4063,7 @@ class AllPlatformTests(BasePlatformTests): env = get_fake_env() for l in ['cpp', 'cs', 'd', 'java', 'cuda', 'fortran', 'objc', 'objcpp', 'rust']: try: - comp = env.detect_compiler_for(l, MachineChoice.HOST) + comp = mesonbuild.compilers.detect_compiler_for(env, l, MachineChoice.HOST) with tempfile.TemporaryDirectory() as d: comp.sanity_check(d, env) langs.append(l) @@ -4170,7 +4173,7 @@ class AllPlatformTests(BasePlatformTests): extra_args = None libdir_flags = ['-L'] env = get_fake_env(testdirlib, self.builddir, self.prefix) - if env.detect_c_compiler(MachineChoice.HOST).get_id() in {'msvc', 'clang-cl', 'intel-cl'}: + 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: @@ -5562,7 +5565,7 @@ class AllPlatformTests(BasePlatformTests): raise unittest.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 = env.detect_c_compiler(MachineChoice.HOST) + cc = detect_c_compiler(env, MachineChoice.HOST) if cc.get_id() == 'clang': if not mesonbuild.environment.detect_llvm_cov(): raise unittest.SkipTest('llvm-cov not found') @@ -5582,7 +5585,7 @@ class AllPlatformTests(BasePlatformTests): raise unittest.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 = env.detect_c_compiler(MachineChoice.HOST) + cc = detect_c_compiler(env, MachineChoice.HOST) if cc.get_id() == 'clang': if not mesonbuild.environment.detect_llvm_cov(): raise unittest.SkipTest('llvm-cov not found') @@ -5602,7 +5605,7 @@ class AllPlatformTests(BasePlatformTests): raise unittest.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 = env.detect_c_compiler(MachineChoice.HOST) + cc = detect_c_compiler(env, MachineChoice.HOST) if cc.get_id() == 'clang': if not mesonbuild.environment.detect_llvm_cov(): raise unittest.SkipTest('llvm-cov not found') @@ -5622,7 +5625,7 @@ class AllPlatformTests(BasePlatformTests): raise unittest.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 = env.detect_c_compiler(MachineChoice.HOST) + cc = detect_c_compiler(env, MachineChoice.HOST) if cc.get_id() == 'clang': if not mesonbuild.environment.detect_llvm_cov(): raise unittest.SkipTest('llvm-cov not found') @@ -5642,7 +5645,7 @@ class AllPlatformTests(BasePlatformTests): raise unittest.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 = env.detect_c_compiler(MachineChoice.HOST) + cc = detect_c_compiler(env, MachineChoice.HOST) if cc.get_id() == 'clang': if not mesonbuild.environment.detect_llvm_cov(): raise unittest.SkipTest('llvm-cov not found') @@ -5842,7 +5845,7 @@ class AllPlatformTests(BasePlatformTests): 'bar/barfile' ] env = get_fake_env(testdir, self.builddir, self.prefix) - cc = env.detect_c_compiler(MachineChoice.HOST) + 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') @@ -5936,18 +5939,18 @@ class AllPlatformTests(BasePlatformTests): env = get_fake_env() # Get the compiler so we know which compiler class to mock. - cc = env.detect_compiler_for('c', MachineChoice.HOST) + cc = mesonbuild.compilers.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 = env.detect_compiler_for('c', MachineChoice.HOST) + cc = mesonbuild.compilers.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 = env.detect_compiler_for('c', MachineChoice.HOST) + cc = mesonbuild.compilers.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'])) @@ -6141,8 +6144,8 @@ class FailureTests(BasePlatformTests): ''' env = get_fake_env() try: - env.detect_objc_compiler(MachineChoice.HOST) - env.detect_objcpp_compiler(MachineChoice.HOST) + detect_objc_compiler(env, MachineChoice.HOST) + detect_objcpp_compiler(env, MachineChoice.HOST) except EnvironmentException: code = "add_languages('objc')\nadd_languages('objcpp')" self.assertMesonRaises(code, "Unknown compiler") @@ -6362,7 +6365,7 @@ class WindowsTests(BasePlatformTests): ''' testdir = os.path.join(self.platform_test_dir, '1 basic') env = get_fake_env(testdir, self.builddir, self.prefix) - cc = env.detect_c_compiler(MachineChoice.HOST) + cc = detect_c_compiler(env, MachineChoice.HOST) if cc.get_argument_syntax() != 'msvc': raise unittest.SkipTest('Not using MSVC') # To force people to update this test, and also test @@ -6375,7 +6378,7 @@ class WindowsTests(BasePlatformTests): # resource compiler depfile generation is not yet implemented for msvc env = get_fake_env(testdir, self.builddir, self.prefix) - depfile_works = env.detect_c_compiler(MachineChoice.HOST).get_id() not in {'msvc', 'clang-cl', 'intel-cl'} + depfile_works = detect_c_compiler(env, MachineChoice.HOST).get_id() not in {'msvc', 'clang-cl', 'intel-cl'} self.init(testdir) self.build() @@ -6406,7 +6409,7 @@ class WindowsTests(BasePlatformTests): testdir = os.path.join(self.unit_test_dir, '45 vscpp17') env = get_fake_env(testdir, self.builddir, self.prefix) - cc = env.detect_c_compiler(MachineChoice.HOST) + cc = detect_c_compiler(env, MachineChoice.HOST) if cc.get_argument_syntax() != 'msvc': raise unittest.SkipTest('Test only applies to MSVC-like compilers') @@ -6424,7 +6427,7 @@ class WindowsTests(BasePlatformTests): testdir = os.path.join(self.platform_test_dir, '1 basic') env = get_fake_env(testdir, self.builddir, self.prefix) - cc = env.detect_c_compiler(MachineChoice.HOST) + cc = detect_c_compiler(env, MachineChoice.HOST) if cc.get_argument_syntax() != 'msvc': raise unittest.SkipTest('Test only applies to MSVC-like compilers') @@ -6448,28 +6451,28 @@ class WindowsTests(BasePlatformTests): with mock.patch.dict(os.environ, {envvar: name}): env = get_fake_env() try: - comp = getattr(env, f'detect_{lang}_compiler')(MachineChoice.HOST) + comp = compiler_from_language(env, lang, MachineChoice.HOST) except EnvironmentException: raise unittest.SkipTest(f'Could not find a compiler for {lang}') self.assertEqual(comp.linker.id, expected) def test_link_environment_variable_lld_link(self): env = get_fake_env() - comp = getattr(env, 'detect_c_compiler')(MachineChoice.HOST) + comp = detect_c_compiler(env, MachineChoice.HOST) if isinstance(comp, mesonbuild.compilers.GnuLikeCompiler): raise unittest.SkipTest('GCC cannot be used with link compatible linkers.') self._check_ld('lld-link', 'c', 'lld-link') def test_link_environment_variable_link(self): env = get_fake_env() - comp = getattr(env, 'detect_c_compiler')(MachineChoice.HOST) + comp = detect_c_compiler(env, MachineChoice.HOST) if isinstance(comp, mesonbuild.compilers.GnuLikeCompiler): raise unittest.SkipTest('GCC cannot be used with link compatible linkers.') self._check_ld('link', 'c', 'link') def test_link_environment_variable_optlink(self): env = get_fake_env() - comp = getattr(env, 'detect_c_compiler')(MachineChoice.HOST) + comp = detect_c_compiler(env, MachineChoice.HOST) if isinstance(comp, mesonbuild.compilers.GnuLikeCompiler): raise unittest.SkipTest('GCC cannot be used with link compatible linkers.') self._check_ld('optlink', 'c', 'optlink') @@ -6481,7 +6484,7 @@ class WindowsTests(BasePlatformTests): @skip_if_not_language('d') def test_link_environment_variable_d(self): env = get_fake_env() - comp = getattr(env, 'detect_d_compiler')(MachineChoice.HOST) + comp = detect_d_compiler(env, MachineChoice.HOST) if comp.id == 'dmd': raise unittest.SkipTest('meson cannot reliably make DMD use a different linker.') self._check_ld('lld-link', 'd', 'lld-link') @@ -6498,7 +6501,7 @@ class WindowsTests(BasePlatformTests): self.build() # Test that binaries have a non-zero checksum env = get_fake_env() - cc = env.detect_c_compiler(MachineChoice.HOST) + cc = detect_c_compiler(env, MachineChoice.HOST) cc_id = cc.get_id() ld_id = cc.get_linker_id() dll = glob(os.path.join(self.builddir, '*mycpplib.dll'))[0] @@ -6520,7 +6523,7 @@ class WindowsTests(BasePlatformTests): ''' # Verify that the `b_vscrt` option is available env = get_fake_env() - cc = env.detect_c_compiler(MachineChoice.HOST) + cc = detect_c_compiler(env, MachineChoice.HOST) if OptionKey('b_vscrt') not in cc.base_options: raise unittest.SkipTest('Compiler does not support setting the VS CRT') # Verify that qmake is for Qt5 @@ -6546,7 +6549,7 @@ class WindowsTests(BasePlatformTests): ''' # Verify that the `b_vscrt` option is available env = get_fake_env() - cc = env.detect_c_compiler(MachineChoice.HOST) + cc = detect_c_compiler(env, MachineChoice.HOST) if OptionKey('b_vscrt') not in cc.base_options: raise unittest.SkipTest('Compiler does not support setting the VS CRT') @@ -6614,7 +6617,7 @@ class DarwinTests(BasePlatformTests): ''' testdir = os.path.join(self.platform_test_dir, '7 bitcode') env = get_fake_env(testdir, self.builddir, self.prefix) - cc = env.detect_c_compiler(MachineChoice.HOST) + cc = detect_c_compiler(env, MachineChoice.HOST) if cc.id != 'clang': raise unittest.SkipTest('Not using Clang on OSX') # Try with bitcode enabled @@ -6794,7 +6797,7 @@ class LinuxlikeTests(BasePlatformTests): self.assertEqual(libhello_nolib.get_pkgconfig_variable('escaped_var', {}), r'hello\ world') self.assertEqual(libhello_nolib.get_pkgconfig_variable('unescaped_var', {}), 'hello world') - cc = env.detect_c_compiler(MachineChoice.HOST) + cc = detect_c_compiler(env, MachineChoice.HOST) if cc.get_id() in {'gcc', 'clang'}: for name in {'ct', 'ct0'}: ct_dep = PkgConfigDependency(name, env, kwargs) @@ -7069,7 +7072,7 @@ class LinuxlikeTests(BasePlatformTests): ''' testdir = os.path.join(self.common_test_dir, '36 has function') env = get_fake_env(testdir, self.builddir, self.prefix) - cpp = env.detect_cpp_compiler(MachineChoice.HOST) + cpp = detect_cpp_compiler(env, MachineChoice.HOST) Oflag = '-O3' OflagCPP = Oflag if cpp.get_id() in ('clang', 'gcc'): @@ -7161,7 +7164,7 @@ class LinuxlikeTests(BasePlatformTests): ''' testdir = os.path.join(self.common_test_dir, '1 trivial') env = get_fake_env(testdir, self.builddir, self.prefix) - cc = env.detect_c_compiler(MachineChoice.HOST) + cc = detect_c_compiler(env, MachineChoice.HOST) self._test_stds_impl(testdir, cc) def test_compiler_cpp_stds(self): @@ -7171,7 +7174,7 @@ class LinuxlikeTests(BasePlatformTests): ''' testdir = os.path.join(self.common_test_dir, '2 cpp') env = get_fake_env(testdir, self.builddir, self.prefix) - cpp = env.detect_cpp_compiler(MachineChoice.HOST) + cpp = detect_cpp_compiler(env, MachineChoice.HOST) self._test_stds_impl(testdir, cpp) def test_unity_subproj(self): @@ -8011,7 +8014,7 @@ class LinuxlikeTests(BasePlatformTests): if is_cygwin() or is_osx(): raise unittest.SkipTest('Not applicable on Cygwin or OSX.') env = get_fake_env() - cc = env.detect_c_compiler(MachineChoice.HOST) + cc = detect_c_compiler(env, MachineChoice.HOST) linker = cc.linker if not linker.export_dynamic_args(env): raise unittest.SkipTest('Not applicable for linkers without --export-dynamic') @@ -8124,7 +8127,7 @@ class LinuxlikeTests(BasePlatformTests): for envvar in envvars: with mock.patch.dict(os.environ, {envvar: name}): env = get_fake_env() - comp = getattr(env, f'detect_{lang}_compiler')(MachineChoice.HOST) + comp = compiler_from_language(env, lang, MachineChoice.HOST) if isinstance(comp, (mesonbuild.compilers.AppleClangCCompiler, mesonbuild.compilers.AppleClangCPPCompiler, mesonbuild.compilers.AppleClangObjCCompiler, @@ -8910,8 +8913,7 @@ class NativeFileTests(BasePlatformTests): with more than one implementation, such as C, C++, ObjC, ObjC++, and D. """ env = get_fake_env() - getter = getattr(env, f'detect_{lang}_compiler') - getter = functools.partial(getter, for_machine) + getter = lambda: compiler_from_language(env, lang, for_machine) cc = getter() binary, newid = cb(cc) env.binaries[for_machine].binaries[lang] = binary @@ -9115,10 +9117,8 @@ class NativeFileTests(BasePlatformTests): """ wrapper = self.helper_create_binary_wrapper(binary, version=version_str) env = get_fake_env() - getter = getattr(env, f'detect_{lang}_compiler') - getter = functools.partial(getter, MachineChoice.HOST) env.binaries.host.binaries[lang] = [wrapper] - compiler = getter() + compiler = compiler_from_language(env, lang, MachineChoice.HOST) self.assertEqual(compiler.version, version) @skip_if_not_language('vala') @@ -9145,7 +9145,7 @@ class NativeFileTests(BasePlatformTests): extra_args={'Xlinker': 'macosx_version. PROJECT:ld - 1.2.3'}) env = get_fake_env() env.binaries.host.binaries['swift'] = [wrapper] - compiler = env.detect_swift_compiler(MachineChoice.HOST) + compiler = detect_swift_compiler(env, MachineChoice.HOST) self.assertEqual(compiler.version, '1.2345') def test_native_file_dirs(self): @@ -10023,7 +10023,7 @@ class SubprojectsCommandTests(BasePlatformTests): # 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(mesonbuild.environment.search_version(out), '>= 2.28'): + if version_compare(search_version(out), '>= 2.28'): extra_cmd = ['--initial-branch', 'master'] else: extra_cmd = [] |