# SPDX-License-Identifier: Apache-2.0 # Copyright 2012-2022 The Meson development team from __future__ import annotations from ..mesonlib import ( MesonException, EnvironmentException, MachineChoice, join_args, search_version, is_windows, Popen_safe, Popen_safe_logged, windows_proof_rm, ) from ..envconfig import BinaryTable from .. import mlog from ..linkers import guess_win_linker, guess_nix_linker import subprocess import platform import re import shutil import tempfile import os import typing as T if T.TYPE_CHECKING: from .compilers import Compiler from .c import CCompiler from .cpp import CPPCompiler from .fortran import FortranCompiler from .rust import RustCompiler from ..linkers.linkers import StaticLinker, DynamicLinker from ..environment import Environment # 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': defaults['c'] = ['cc', 'gcc', 'lcc', 'clang'] defaults['cpp'] = ['c++', 'g++', 'l++', 'clang++'] defaults['objc'] = ['clang'] defaults['objcpp'] = ['clang++'] else: defaults['c'] = ['cc', 'gcc', 'clang', 'nvc', 'pgcc', 'icc', 'icx'] defaults['cpp'] = ['c++', 'g++', 'clang++', 'nvc++', 'pgc++', 'icpc', 'icpx'] defaults['objc'] = ['cc', 'gcc', 'clang'] defaults['objcpp'] = ['c++', 'g++', 'clang++'] defaults['fortran'] = ['gfortran', 'flang', 'nvfortran', 'pgfortran', 'ifort', 'ifx', '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', 'cython3'] # Official name is cython, but Debian renamed it to cython3. 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'] defaults['nasm'] = ['nasm', 'yasm'] def compiler_from_language(env: 'Environment', lang: str, for_machine: MachineChoice) -> T.Optional[Compiler]: lang_map: T.Dict[str, T.Callable[['Environment', MachineChoice], Compiler]] = { '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, 'nasm': detect_nasm_compiler, 'masm': detect_masm_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, skip_sanity_check: bool, subproject: str) -> T.Optional[Compiler]: comp = compiler_from_language(env, lang, for_machine) if comp is None: return comp assert comp.for_machine == for_machine env.coredata.process_compiler_options(lang, comp, env, subproject) if not skip_sanity_check: comp.sanity_check(env.get_scratch_dir(), env) env.coredata.compilers[comp.for_machine][lang] = comp return comp # Helpers # ======= def _get_compilers(env: 'Environment', lang: str, for_machine: MachineChoice) -> T.Tuple[T.List[T.List[str]], T.List[str]]: ''' 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: comp, ccache = BinaryTable.parse_entry(value) # Return value has to be a list of compiler 'choices' compilers = [comp] 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 = [[x] for x in defaults[lang]] ccache = BinaryTable.detect_compiler_cache() return compilers, ccache def _handle_exceptions( exceptions: T.Mapping[str, T.Union[Exception, str]], binaries: T.List[T.List[str]], bintype: str = 'compiler') -> T.NoReturn: 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: Compiler) -> StaticLinker: from . import d from ..linkers import linkers linker = env.lookup_binary_entry(compiler.for_machine, 'ar') if linker is not None: trials = [linker] else: default_linkers = [[l] for l in defaults['static_linker']] if compiler.language == 'cuda': trials = [defaults['cuda_static_linker']] + default_linkers elif compiler.get_argument_syntax() == 'msvc': trials = [defaults['vs_static_linker'], defaults['clang_cl_static_linker']] elif compiler.id == 'gcc': # Use gcc-ar if available; needed for LTO trials = [defaults['gcc_static_linker']] + default_linkers elif compiler.id == 'clang': # Use llvm-ar if available; needed for LTO llvm_ar = defaults['clang_static_linker'] # Extract the version major of the compiler to use as a suffix suffix = compiler.version.split('.')[0] # Prefer suffixed llvm-ar first, then unsuffixed then the defaults trials = [[f'{llvm_ar[0]}-{suffix}'], llvm_ar] + default_linkers elif compiler.language == 'd': # Prefer static linkers over linkers used by D compilers if is_windows(): trials = [defaults['vs_static_linker'], defaults['clang_cl_static_linker'], compiler.get_linker_exelist()] else: trials = default_linkers elif compiler.id == 'intel-cl' and compiler.language == 'c': # why not cpp? Is this a bug? # Intel has it's own linker that acts like microsoft's lib trials = [['xilib']] elif is_windows() and compiler.id == 'pgi': # this handles cpp / nvidia HPC, in addition to just c/fortran trials = [['ar']] # For PGI on Windows, "ar" is just a wrapper calling link/lib. elif is_windows() and compiler.id == 'nasm': # This may well be LINK.EXE if it's under a MSVC environment trials = [defaults['vs_static_linker'], defaults['clang_cl_static_linker']] + default_linkers else: trials = default_linkers popen_exceptions = {} for linker in trials: linker_name = os.path.basename(linker[0]) if any(os.path.basename(x) in {'lib', 'lib.exe', 'llvm-lib', 'llvm-lib.exe', 'xilib', 'xilib.exe'} for x in linker): arg = '/?' elif linker_name in {'ar2000', 'ar2000.exe', 'ar430', 'ar430.exe', 'armar', 'armar.exe', 'ar6x', 'ar6x.exe'}: arg = '?' else: arg = '--version' try: p, out, err = Popen_safe_logged(linker + [arg], msg='Detecting archiver via') except OSError as e: popen_exceptions[join_args(linker + [arg])] = e continue if "xilib: executing 'lib'" in err: return linkers.IntelVisualStudioLinker(linker, getattr(compiler, 'machine', None)) if '/OUT:' in out.upper() or '/OUT:' in err.upper(): return linkers.VisualStudioLinker(linker, getattr(compiler, 'machine', None)) if 'ar-Error-Unknown switch: --version' in err: return linkers.PGIStaticLinker(linker) if p.returncode == 0 and 'armar' in linker_name: return linkers.ArmarLinker(linker) if 'DMD32 D Compiler' in out or 'DMD64 D Compiler' in out: assert isinstance(compiler, d.DCompiler) return linkers.DLinker(linker, compiler.arch) if 'LDC - the LLVM D compiler' in out: assert isinstance(compiler, d.DCompiler) return linkers.DLinker(linker, compiler.arch, rsp_syntax=compiler.rsp_file_syntax()) if 'GDC' in out and ' based on D ' in out: assert isinstance(compiler, d.DCompiler) return linkers.DLinker(linker, compiler.arch) if err.startswith('Renesas') and 'rlink' in linker_name: return linkers.CcrxLinker(linker) if out.startswith('GNU ar') and 'xc16-ar' in linker_name: return linkers.Xc16Linker(linker) if "--> error: bad option 'e'" in err: # TI if 'ar2000' in linker_name: return linkers.C2000Linker(linker) else: return linkers.TILinker(linker) if 'Texas Instruments Incorporated' in out: if 'ar6000' in linker_name: return linkers.C6000Linker(linker) if out.startswith('The CompCert'): return linkers.CompCertLinker(linker) if out.strip().startswith('Metrowerks') or out.strip().startswith('Freescale'): if 'ARM' in out: return linkers.MetrowerksStaticLinkerARM(linker) else: return linkers.MetrowerksStaticLinkerEmbeddedPowerPC(linker) if p.returncode == 0: return linkers.ArLinker(compiler.for_machine, linker) if p.returncode == 1 and err.startswith('usage'): # OSX return linkers.AppleArLinker(compiler.for_machine, linker) if p.returncode == 1 and err.startswith('Usage'): # AIX return linkers.AIXArLinker(linker) if p.returncode == 1 and err.startswith('ar: bad option: --'): # Solaris return linkers.ArLinker(compiler.for_machine, linker) _handle_exceptions(popen_exceptions, trials, 'linker') raise EnvironmentException('Unreachable code (exception to make mypy happy)') # 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. """ from . import c, cpp from ..linkers import linkers popen_exceptions: T.Dict[str, T.Union[Exception, str]] = {} compilers, ccache = _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] cls: T.Union[T.Type[CCompiler], T.Type[CPPCompiler]] lnk: T.Union[T.Type[StaticLinker], T.Type[DynamicLinker]] for compiler in compilers: if isinstance(compiler, str): compiler = [compiler] compiler_name = os.path.basename(compiler[0]) if any(os.path.basename(x) in {'cl', 'cl.exe', 'clang-cl', 'clang-cl.exe'} for x in 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: str) -> str: 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')), sanitize(os.path.join(os.environ['WATCOM'], 'BINNT64', 'cl')), sanitize(os.path.join(os.environ['WATCOM'], 'BINNT64', 'cl.exe'))] found_cl = sanitize(shutil.which('cl')) if found_cl in watcom_cls: mlog.debug('Skipping unsupported cl.exe clone at:', found_cl) 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 compiler_name in {'cl2000', 'cl2000.exe', 'cl430', 'cl430.exe', 'armcl', 'armcl.exe', 'cl6x', 'cl6x.exe'}: # TI compiler 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' cmd = compiler + [arg] try: p, out, err = Popen_safe_logged(cmd, msg='Detecting compiler via') except OSError as e: popen_exceptions[join_args(cmd)] = e continue if 'ccrx' in compiler_name: out = err full_version = out.split('\n', 1)[0] version = search_version(out) guess_gcc_or_lcc: T.Optional[str] = None if 'Free Software Foundation' in out or out.startswith('xt-'): 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 = None if guess_gcc_or_lcc: defines = _get_gnu_compiler_defines(compiler) if not defines: popen_exceptions[join_args(compiler)] = 'no pre-processor defines' continue if guess_gcc_or_lcc == 'lcc': version = _get_lcc_version_from_defines(defines) cls = c.ElbrusCCompiler if lang == 'c' else cpp.ElbrusCPPCompiler else: version = _get_gnu_version_from_defines(defines) cls = c.GnuCCompiler if lang == 'c' else cpp.GnuCPPCompiler linker = guess_nix_linker(env, compiler, cls, version, for_machine) return cls( ccache, compiler, version, for_machine, is_cross, info, defines=defines, full_version=full_version, linker=linker) if 'Emscripten' in out: cls = c.EmscriptenCCompiler if lang == 'c' else cpp.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. with tempfile.NamedTemporaryFile(suffix='.c') as f: cmd = compiler + [cls.LINKER_PREFIX + "--version", f.name] _, o, _ = Popen_safe(cmd) linker = linkers.WASMDynamicLinker( compiler, for_machine, cls.LINKER_PREFIX, [], version=search_version(o)) return cls( ccache, compiler, version, for_machine, is_cross, info, linker=linker, full_version=full_version) if 'Arm C/C++/Fortran Compiler' in out: arm_ver_match = re.search(r'version (\d+)\.(\d+)\.?(\d+)? \(build number (\d+)\)', out) assert arm_ver_match is not None, 'for mypy' # because mypy *should* be complaining that this could be None version = '.'.join([x for x in arm_ver_match.groups() if x is not None]) if lang == 'c': cls = c.ArmLtdClangCCompiler elif lang == 'cpp': cls = cpp.ArmLtdClangCPPCompiler linker = guess_nix_linker(env, compiler, cls, version, for_machine) return cls( ccache, compiler, version, for_machine, is_cross, info, linker=linker) 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_match = re.search('.*Component.*', out) if arm_ver_match is None: popen_exceptions[join_args(compiler)] = 'version string not found' continue arm_ver_str = arm_ver_match.group(0) # Override previous values version = search_version(arm_ver_str) full_version = arm_ver_str cls = c.ArmclangCCompiler if lang == 'c' else cpp.ArmclangCPPCompiler linker = linkers.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, 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_args(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 = c.ClangClCCompiler if lang == 'c' else cpp.ClangClCPPCompiler linker = guess_win_linker(env, ['lld-link'], cls, version, for_machine) return cls( compiler, version, for_machine, is_cross, info, target, linker=linker) # must be detected here before clang because TI compilers contain 'clang' in their output and so that they can be detected as 'clang' ti_compilers = { 'TMS320C2000 C/C++': (c.C2000CCompiler, cpp.C2000CPPCompiler, linkers.C2000DynamicLinker), 'TMS320C6x C/C++': (c.C6000CCompiler, cpp.C6000CPPCompiler, linkers.C6000DynamicLinker), 'TI ARM C/C++ Compiler': (c.TICCompiler, cpp.TICPPCompiler, linkers.TIDynamicLinker), 'MSP430 C/C++': (c.TICCompiler, cpp.TICPPCompiler, linkers.TIDynamicLinker) } for indentifier, compiler_classes in ti_compilers.items(): if indentifier in out: cls = compiler_classes[0] if lang == 'c' else compiler_classes[1] lnk = compiler_classes[2] env.coredata.add_lang_args(cls.language, cls, for_machine, env) linker = lnk(compiler, for_machine, version=version) return cls( ccache, compiler, version, for_machine, is_cross, info, full_version=full_version, 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 = c.AppleClangCCompiler if lang == 'c' else cpp.AppleClangCPPCompiler else: cls = c.ClangCCompiler if lang == 'c' else cpp.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, version, for_machine, invoked_directly=False) except MesonException: pass if linker is None: linker = guess_nix_linker(env, compiler, cls, version, for_machine) return cls( ccache, compiler, version, for_machine, is_cross, info, 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 = c.IntelClCCompiler if lang == 'c' else cpp.IntelClCPPCompiler env.coredata.add_lang_args(cls.language, cls, for_machine, env) linker = linkers.XilinkDynamicLinker(for_machine, [], version=version) return cls( compiler, version, for_machine, is_cross, info, target, linker=linker) if 'Intel(R) oneAPI DPC++/C++ Compiler for applications' in err: version = search_version(err) target = 'x86' if 'IA-32' in err else 'x86_64' cls = c.IntelLLVMClCCompiler if lang == 'c' else cpp.IntelLLVMClCPPCompiler env.coredata.add_lang_args(cls.language, cls, for_machine, env) linker = linkers.XilinkDynamicLinker(for_machine, [], version=version) return cls( compiler, version, for_machine, is_cross, info, target, 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: raise EnvironmentException(f'Failed to detect MSVC compiler version: stderr was\n{err!r}') cl_signature = lookat.split('\n', maxsplit=1)[0] match = re.search(r'.*(x86|x64|ARM|ARM64)([^_A-Za-z0-9]|$)', cl_signature) if match: target = match.group(1) else: m = f'Failed to detect MSVC compiler target architecture: \'cl /?\' output is\n{cl_signature}' raise EnvironmentException(m) cls = c.VisualStudioCCompiler if lang == 'c' else cpp.VisualStudioCPPCompiler linker = guess_win_linker(env, ['link'], cls, version, for_machine) # As of this writing, CCache does not support MSVC but sccache does. if 'sccache' not in ccache: ccache = [] return cls( ccache, compiler, version, for_machine, is_cross, info, target, full_version=cl_signature, linker=linker) if 'PGI Compilers' in out: cls = c.PGICCompiler if lang == 'c' else cpp.PGICPPCompiler env.coredata.add_lang_args(cls.language, cls, for_machine, env) linker = linkers.PGIDynamicLinker(compiler, for_machine, cls.LINKER_PREFIX, [], version=version) return cls( ccache, compiler, version, for_machine, is_cross, info, linker=linker) if 'NVIDIA Compilers and Tools' in out: cls = c.NvidiaHPC_CCompiler if lang == 'c' else cpp.NvidiaHPC_CPPCompiler env.coredata.add_lang_args(cls.language, cls, for_machine, env) linker = linkers.NvidiaHPC_DynamicLinker(compiler, for_machine, cls.LINKER_PREFIX, [], version=version) return cls( ccache, compiler, version, for_machine, is_cross, info, linker=linker) if '(ICC)' in out: cls = c.IntelCCompiler if lang == 'c' else cpp.IntelCPPCompiler l = guess_nix_linker(env, compiler, cls, version, for_machine) return cls( ccache, compiler, version, for_machine, is_cross, info, full_version=full_version, linker=l) if 'Intel(R) oneAPI' in out: cls = c.IntelLLVMCCompiler if lang == 'c' else cpp.IntelLLVMCPPCompiler l = guess_nix_linker(env, compiler, cls, version, for_machine) return cls( ccache, compiler, version, for_machine, is_cross, info, full_version=full_version, linker=l) if 'ARM' in out and not ('Metrowerks' in out or 'Freescale' in out): cls = c.ArmCCompiler if lang == 'c' else cpp.ArmCPPCompiler env.coredata.add_lang_args(cls.language, cls, for_machine, env) linker = linkers.ArmDynamicLinker(for_machine, version=version) return cls( ccache, compiler, version, for_machine, is_cross, info, full_version=full_version, linker=linker) if 'RX Family' in out: cls = c.CcrxCCompiler if lang == 'c' else cpp.CcrxCPPCompiler env.coredata.add_lang_args(cls.language, cls, for_machine, env) linker = linkers.CcrxDynamicLinker(for_machine, version=version) return cls( ccache, compiler, version, for_machine, is_cross, info, full_version=full_version, linker=linker) if 'Microchip Technology' in out: cls = c.Xc16CCompiler env.coredata.add_lang_args(cls.language, cls, for_machine, env) linker = linkers.Xc16DynamicLinker(for_machine, version=version) return cls( ccache, compiler, version, for_machine, is_cross, info, full_version=full_version, linker=linker) if 'CompCert' in out: cls = c.CompCertCCompiler env.coredata.add_lang_args(cls.language, cls, for_machine, env) linker = linkers.CompCertDynamicLinker(for_machine, version=version) return cls( ccache, compiler, version, for_machine, is_cross, info, full_version=full_version, linker=linker) if 'Metrowerks C/C++' in out or 'Freescale C/C++' in out: if 'ARM' in out: cls = c.MetrowerksCCompilerARM if lang == 'c' else cpp.MetrowerksCPPCompilerARM lnk = linkers.MetrowerksLinkerARM else: cls = c.MetrowerksCCompilerEmbeddedPowerPC if lang == 'c' else cpp.MetrowerksCPPCompilerEmbeddedPowerPC lnk = linkers.MetrowerksLinkerEmbeddedPowerPC mwcc_ver_match = re.search(r'Version (\d+)\.(\d+)\.?(\d+)? build (\d+)', out) assert mwcc_ver_match is not None, 'for mypy' # because mypy *should* be complaning that this could be None compiler_version = '.'.join(x for x in mwcc_ver_match.groups() if x is not None) env.coredata.add_lang_args(cls.language, cls, for_machine, env) ld = env.lookup_binary_entry(for_machine, cls.language + '_ld') if ld is not None: _, o_ld, _ = Popen_safe(ld + ['--version']) mwld_ver_match = re.search(r'Version (\d+)\.(\d+)\.?(\d+)? build (\d+)', o_ld) assert mwld_ver_match is not None, 'for mypy' # because mypy *should* be complaning that this could be None linker_version = '.'.join(x for x in mwld_ver_match.groups() if x is not None) linker = lnk(ld, for_machine, version=linker_version) else: raise EnvironmentException(f'Failed to detect linker for {cls.id!r} compiler. Please update your cross file(s).') return cls( ccache, compiler, compiler_version, for_machine, is_cross, info, full_version=full_version, linker=linker) _handle_exceptions(popen_exceptions, compilers) raise EnvironmentException(f'Unknown compiler {compilers}') def detect_c_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: return _detect_c_or_cpp_compiler(env, 'c', for_machine) def detect_cpp_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: return _detect_c_or_cpp_compiler(env, 'cpp', for_machine) def detect_cuda_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: from .cuda import CudaCompiler from ..linkers.linkers import CudaLinker popen_exceptions = {} is_cross = env.is_cross_build(for_machine) compilers, ccache = _get_compilers(env, 'cuda', for_machine) info = env.machines[for_machine] for compiler in compilers: arg = '--version' try: p, out, err = Popen_safe_logged(compiler + [arg], msg='Detecting compiler via') except OSError as e: popen_exceptions[join_args(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().rsplit('V', maxsplit=1)[-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, host_compiler=cpp_compiler, info=info, linker=linker) raise EnvironmentException(f'Could not find suitable CUDA compiler: "{"; ".join([" ".join(c) for c in compilers])}"') def detect_fortran_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: from . import fortran from ..linkers import linkers popen_exceptions: T.Dict[str, T.Union[Exception, str]] = {} compilers, ccache = _get_compilers(env, 'fortran', for_machine) is_cross = env.is_cross_build(for_machine) info = env.machines[for_machine] cls: T.Type[FortranCompiler] for compiler in compilers: for arg in ['--version', '-V']: try: p, out, err = Popen_safe_logged(compiler + [arg], msg='Detecting compiler via') except OSError as e: popen_exceptions[join_args(compiler + [arg])] = e continue version = search_version(out) full_version = out.split('\n', 1)[0] guess_gcc_or_lcc: T.Optional[str] = None 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_args(compiler)] = 'no pre-processor defines' continue if guess_gcc_or_lcc == 'lcc': version = _get_lcc_version_from_defines(defines) cls = fortran.ElbrusFortranCompiler linker = guess_nix_linker(env, compiler, cls, version, for_machine) return cls( compiler, version, for_machine, is_cross, info, defines, full_version=full_version, linker=linker) else: version = _get_gnu_version_from_defines(defines) cls = fortran.GnuFortranCompiler linker = guess_nix_linker(env, compiler, cls, version, for_machine) return cls( compiler, version, for_machine, is_cross, info, defines, full_version=full_version, linker=linker) if 'Arm C/C++/Fortran Compiler' in out: cls = fortran.ArmLtdFlangFortranCompiler arm_ver_match = re.search(r'version (\d+)\.(\d+)\.?(\d+)? \(build number (\d+)\)', out) assert arm_ver_match is not None, 'for mypy' # because mypy *should* be complaining that this could be None version = '.'.join([x for x in arm_ver_match.groups() if x is not None]) linker = guess_nix_linker(env, compiler, cls, version, for_machine) return cls( compiler, version, for_machine, is_cross, info, linker=linker) if 'G95' in out: cls = fortran.G95FortranCompiler linker = guess_nix_linker(env, compiler, cls, version, for_machine) return cls( compiler, version, for_machine, is_cross, info, full_version=full_version, linker=linker) if 'Sun Fortran' in err: version = search_version(err) cls = fortran.SunFortranCompiler linker = guess_nix_linker(env, compiler, cls, version, for_machine) return cls( compiler, version, for_machine, is_cross, info, full_version=full_version, linker=linker) if 'Intel(R) Fortran Compiler for applications' in err: version = search_version(err) target = 'x86' if 'IA-32' in err else 'x86_64' cls = fortran.IntelLLVMClFortranCompiler env.coredata.add_lang_args(cls.language, cls, for_machine, env) linker = linkers.XilinkDynamicLinker(for_machine, [], version=version) return cls( compiler, version, for_machine, is_cross, info, target, linker=linker) if 'Intel(R) Visual Fortran' in err or 'Intel(R) Fortran' in err: version = search_version(err) target = 'x86' if 'IA-32' in err else 'x86_64' cls = fortran.IntelClFortranCompiler env.coredata.add_lang_args(cls.language, cls, for_machine, env) linker = linkers.XilinkDynamicLinker(for_machine, [], version=version) return cls( compiler, version, for_machine, is_cross, info, target, linker=linker) if 'ifort (IFORT)' in out: cls = fortran.IntelFortranCompiler linker = guess_nix_linker(env, compiler, cls, version, for_machine) return cls( compiler, version, for_machine, is_cross, info, full_version=full_version, linker=linker) if 'ifx (IFORT)' in out or 'ifx (IFX)' in out: cls = fortran.IntelLLVMFortranCompiler linker = guess_nix_linker(env, compiler, cls, version, for_machine) return cls( compiler, version, for_machine, is_cross, info, full_version=full_version, linker=linker) if 'PathScale EKOPath(tm)' in err: return fortran.PathScaleFortranCompiler( compiler, version, for_machine, is_cross, info, full_version=full_version) if 'PGI Compilers' in out: cls = fortran.PGIFortranCompiler env.coredata.add_lang_args(cls.language, cls, for_machine, env) linker = linkers.PGIDynamicLinker(compiler, for_machine, cls.LINKER_PREFIX, [], version=version) return cls( compiler, version, for_machine, is_cross, info, full_version=full_version, linker=linker) if 'NVIDIA Compilers and Tools' in out: cls = fortran.NvidiaHPC_FortranCompiler env.coredata.add_lang_args(cls.language, cls, for_machine, env) linker = linkers.PGIDynamicLinker(compiler, for_machine, cls.LINKER_PREFIX, [], version=version) return cls( compiler, version, for_machine, is_cross, info, full_version=full_version, linker=linker) if 'flang' in out or 'clang' in out: cls = fortran.FlangFortranCompiler linker = None 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 flang on "real" windows we'll use # either link.exe or lld-link.exe try: linker = guess_win_linker( env, compiler, cls, version, for_machine, invoked_directly=False ) except MesonException: pass if linker is None: linker = guess_nix_linker(env, compiler, cls, version, for_machine) return cls( compiler, version, for_machine, is_cross, info, full_version=full_version, linker=linker) if 'Open64 Compiler Suite' in err: cls = fortran.Open64FortranCompiler linker = guess_nix_linker(env, compiler, cls, version, for_machine) return cls( compiler, version, for_machine, is_cross, info, full_version=full_version, linker=linker) if 'NAG Fortran' in err: full_version = err.split('\n', 1)[0] version = full_version.split()[-1] cls = fortran.NAGFortranCompiler env.coredata.add_lang_args(cls.language, cls, for_machine, env) linker = linkers.NAGDynamicLinker( compiler, for_machine, cls.LINKER_PREFIX, [], version=version) return cls( compiler, version, for_machine, is_cross, info, full_version=full_version, linker=linker) _handle_exceptions(popen_exceptions, compilers) raise EnvironmentException('Unreachable code (exception to make mypy happy)') def detect_objc_compiler(env: 'Environment', for_machine: MachineChoice) -> 'Compiler': return _detect_objc_or_objcpp_compiler(env, 'objc', for_machine) def detect_objcpp_compiler(env: 'Environment', for_machine: MachineChoice) -> 'Compiler': return _detect_objc_or_objcpp_compiler(env, 'objcpp', for_machine) def _detect_objc_or_objcpp_compiler(env: 'Environment', lang: str, for_machine: MachineChoice) -> 'Compiler': from . import objc, objcpp popen_exceptions: T.Dict[str, T.Union[Exception, str]] = {} compilers, ccache = _get_compilers(env, lang, for_machine) is_cross = env.is_cross_build(for_machine) info = env.machines[for_machine] comp: T.Union[T.Type[objc.ObjCCompiler], T.Type[objcpp.ObjCPPCompiler]] for compiler in compilers: arg = ['--version'] try: p, out, err = Popen_safe_logged(compiler + arg, msg='Detecting compiler via') except OSError as e: popen_exceptions[join_args(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_args(compiler)] = 'no pre-processor defines' continue version = _get_gnu_version_from_defines(defines) comp = objc.GnuObjCCompiler if lang == 'objc' else objcpp.GnuObjCPPCompiler linker = guess_nix_linker(env, compiler, comp, version, for_machine) return comp( ccache, compiler, version, for_machine, is_cross, info, defines, linker=linker) if 'clang' in out: linker = None defines = _get_clang_compiler_defines(compiler) if not defines: popen_exceptions[join_args(compiler)] = 'no pre-processor defines' continue if 'Apple' in out: comp = objc.AppleClangObjCCompiler if lang == 'objc' else objcpp.AppleClangObjCPPCompiler else: comp = objc.ClangObjCCompiler if lang == 'objc' else objcpp.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, version, for_machine) except MesonException: pass if not linker: linker = guess_nix_linker(env, compiler, comp, version, for_machine) return comp( ccache, compiler, version, for_machine, is_cross, info, linker=linker, defines=defines) _handle_exceptions(popen_exceptions, compilers) raise EnvironmentException('Unreachable code (exception to make mypy happy)') def detect_java_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: from .java import JavaCompiler 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_logged(exelist + ['-version'], msg='Detecting compiler via') except OSError: raise EnvironmentException('Could not execute Java compiler: {}'.format(join_args(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_args(exelist)) def detect_cs_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: from . import cs compilers, ccache = _get_compilers(env, 'cs', for_machine) popen_exceptions = {} info = env.machines[for_machine] for comp in compilers: try: p, out, err = Popen_safe_logged(comp + ['--version'], msg='Detecting compiler via') except OSError as e: popen_exceptions[join_args(comp + ['--version'])] = e continue version = search_version(out) cls: T.Type[cs.CsCompiler] if 'Mono' in out: cls = cs.MonoCompiler elif "Visual C#" in out: cls = cs.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) raise EnvironmentException('Unreachable code (exception to make mypy happy)') def detect_cython_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: """Search for a cython compiler.""" from .cython import CythonCompiler compilers, _ = _get_compilers(env, 'cython', MachineChoice.BUILD) is_cross = env.is_cross_build(for_machine) info = env.machines[for_machine] popen_exceptions: T.Dict[str, Exception] = {} for comp in compilers: try: _, out, err = Popen_safe_logged(comp + ['-V'], msg='Detecting compiler via') except OSError as e: popen_exceptions[join_args(comp + ['-V'])] = e continue version: T.Optional[str] = None # 3.0 if 'Cython' in out: version = search_version(out) # older elif 'Cython' in err: version = search_version(err) if version is not None: 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) raise EnvironmentException('Unreachable code (exception to make mypy happy)') def detect_vala_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: from .vala import ValaCompiler exelist = env.lookup_binary_entry(MachineChoice.BUILD, '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_logged(exelist + ['--version'], msg='Detecting compiler via')[0:2] except OSError: raise EnvironmentException('Could not execute Vala compiler: {}'.format(join_args(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, is_cross, info) raise EnvironmentException('Unknown compiler: ' + join_args(exelist)) def detect_rust_compiler(env: 'Environment', for_machine: MachineChoice) -> RustCompiler: from . import rust from ..linkers import linkers popen_exceptions: T.Dict[str, Exception] = {} compilers, _ = _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, linkers.VisualStudioLikeLinkerMixin) override = env.lookup_binary_entry(for_machine, 'rust_ld') for compiler in compilers: arg = ['--version'] try: out = Popen_safe_logged(compiler + arg, msg='Detecting compiler via')[1] except OSError as e: popen_exceptions[join_args(compiler + arg)] = e continue version = search_version(out) cls: T.Type[RustCompiler] = rust.RustCompiler # Clippy is a wrapper around rustc, but it doesn't have rustc in it's # output. We can otherwise treat it as rustc. if 'clippy' in out: # clippy returns its own version and not the rustc version by # default so try harder here to get the correct version. # Also replace the whole output with the rustc output in # case this is later used for other purposes. arg = ['--rustc', '--version'] try: out = Popen_safe(compiler + arg)[1] except OSError as e: popen_exceptions[join_args(compiler + arg)] = e continue version = search_version(out) cls = rust.ClippyRustCompiler 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 RUSTC_LD environment variable, otherwise meson ' 'will override your selection.') compiler = compiler.copy() # avoid mutating the original list if override is None: extra_args: T.Dict[str, T.Union[str, bool]] = {} always_args: T.List[str] = [] if is_link_exe: compiler.extend(cls.use_linker_args(cc.linker.exelist[0], '')) extra_args['direct'] = True extra_args['machine'] = cc.linker.machine else: exelist = cc.linker.exelist + cc.linker.get_always_args() if os.path.basename(exelist[0]) in {'ccache', 'sccache'}: del exelist[0] c = exelist.pop(0) compiler.extend(cls.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 # TODO rewrite this without type: ignore assert cc.linker is not None, 'for mypy' if is_link_exe: linker = type(cc.linker)(for_machine, always_args, exelist=cc.linker.exelist, # type: ignore version=cc.linker.version, **extra_args) # type: ignore 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, cls, version, for_machine, use_linker_prefix=False) # rustc takes linker arguments without a prefix, and # inserts the correct prefix itself. assert isinstance(linker, linkers.VisualStudioLikeLinkerMixin) linker.direct = True compiler.extend(cls.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(cls.use_linker_args(c, '')) env.coredata.add_lang_args(cls.language, cls, for_machine, env) return cls( compiler, version, for_machine, is_cross, info, linker=linker) _handle_exceptions(popen_exceptions, compilers) raise EnvironmentException('Unreachable code (exception to make mypy happy)') def detect_d_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: from . import c, d 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'], 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 = _get_compilers(env, 'd', for_machine) cls: T.Type[d.DCompiler] 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 os.path.basename(exelist[-1]).startswith(('ldmd', 'gdmd')): raise EnvironmentException( f'Meson does not support {exelist[-1]} 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.') try: p, out = Popen_safe(exelist + ['--version'])[0:2] except OSError as e: popen_exceptions[join_args(exelist + ['--version'])] = e continue version = search_version(out) full_version = out.split('\n', 1)[0] if 'LLVM D compiler' in out: cls = d.LLVMDCompiler # LDC seems to require a file # We cannot use NamedTemporaryFile 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, cls, full_version, 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, cls, full_version, for_machine, extra_args=[f]) finally: windows_proof_rm(f) windows_proof_rm(objfile) return cls( exelist, version, for_machine, info, arch, full_version=full_version, linker=linker, version_output=out) elif 'gdc' in out: cls = d.GnuDCompiler linker = guess_nix_linker(env, exelist, cls, version, for_machine) return cls( exelist, version, for_machine, info, arch, is_cross=is_cross, full_version=full_version, linker=linker) elif 'The D Language Foundation' in out or 'Digital Mars' in out: cls = d.DmdDCompiler # DMD seems to require a file # We cannot use NamedTemporaryFile 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, cls, full_version, for_machine, invoked_directly=False, extra_args=[f, arch_arg]) else: objfile = os.path.basename(f)[:-1] + 'o' linker = guess_nix_linker(env, exelist, cls, full_version, for_machine, extra_args=[f, arch_arg]) finally: windows_proof_rm(f) windows_proof_rm(objfile) return cls( exelist, version, for_machine, info, arch, full_version=full_version, linker=linker) raise EnvironmentException('Unknown compiler: ' + join_args(exelist)) _handle_exceptions(popen_exceptions, compilers) raise EnvironmentException('Unreachable code (exception to make mypy happy)') def detect_swift_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: from .swift import SwiftCompiler 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_logged(exelist + ['-v'], msg='Detecting compiler via') except OSError: raise EnvironmentException('Could not execute Swift compiler: {}'.format(join_args(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: cls = SwiftCompiler linker = guess_nix_linker(env, exelist, cls, version, for_machine, extra_args=[f.name]) return cls( exelist, version, for_machine, is_cross, info, linker=linker) raise EnvironmentException('Unknown compiler: ' + join_args(exelist)) def detect_nasm_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: from .asm import NasmCompiler, YasmCompiler, MetrowerksAsmCompilerARM, MetrowerksAsmCompilerEmbeddedPowerPC compilers, _ = _get_compilers(env, 'nasm', for_machine) is_cross = env.is_cross_build(for_machine) # We need a C compiler to properly detect the machine info and linker cc = detect_c_compiler(env, for_machine) if not is_cross: from ..environment import detect_machine_info info = detect_machine_info({'c': cc}) else: info = env.machines[for_machine] popen_exceptions: T.Dict[str, Exception] = {} for comp in compilers: if comp == ['nasm'] and is_windows() and not shutil.which(comp[0]): # nasm is not in PATH on Windows by default default_path = os.path.join(os.environ['ProgramFiles'], 'NASM') comp[0] = shutil.which(comp[0], path=default_path) or comp[0] try: output = Popen_safe_logged(comp + ['--version'], msg='Detecting compiler via')[1] except OSError as e: popen_exceptions[' '.join(comp + ['--version'])] = e continue version = search_version(output) if 'NASM' in output: comp_class = NasmCompiler env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) return comp_class([], comp, version, for_machine, info, cc.linker, is_cross=is_cross) elif 'yasm' in output: comp_class = YasmCompiler env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) return comp_class([], comp, version, for_machine, info, cc.linker, is_cross=is_cross) elif 'Metrowerks' in output or 'Freescale' in output: if 'ARM' in output: comp_class_mwasmarm = MetrowerksAsmCompilerARM env.coredata.add_lang_args(comp_class_mwasmarm.language, comp_class_mwasmarm, for_machine, env) return comp_class_mwasmarm([], comp, version, for_machine, info, cc.linker, is_cross=is_cross) else: comp_class_mwasmeppc = MetrowerksAsmCompilerEmbeddedPowerPC env.coredata.add_lang_args(comp_class_mwasmeppc.language, comp_class_mwasmeppc, for_machine, env) return comp_class_mwasmeppc([], comp, version, for_machine, info, cc.linker, is_cross=is_cross) _handle_exceptions(popen_exceptions, compilers) raise EnvironmentException('Unreachable code (exception to make mypy happy)') def detect_masm_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: # We need a C compiler to properly detect the machine info and linker is_cross = env.is_cross_build(for_machine) cc = detect_c_compiler(env, for_machine) if not is_cross: from ..environment import detect_machine_info info = detect_machine_info({'c': cc}) else: info = env.machines[for_machine] from .asm import MasmCompiler, MasmARMCompiler comp_class: T.Type[Compiler] if info.cpu_family == 'x86': comp = ['ml'] comp_class = MasmCompiler arg = '/?' elif info.cpu_family == 'x86_64': comp = ['ml64'] comp_class = MasmCompiler arg = '/?' elif info.cpu_family == 'arm': comp = ['armasm'] comp_class = MasmARMCompiler arg = '-h' elif info.cpu_family == 'aarch64': comp = ['armasm64'] comp_class = MasmARMCompiler arg = '-h' else: raise EnvironmentException(f'Platform {info.cpu_family} not supported by MASM') popen_exceptions: T.Dict[str, Exception] = {} try: output = Popen_safe(comp + [arg])[2] version = search_version(output) env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) return comp_class([], comp, version, for_machine, info, cc.linker, is_cross=is_cross) except OSError as e: popen_exceptions[' '.join(comp + [arg])] = e _handle_exceptions(popen_exceptions, [comp]) raise EnvironmentException('Unreachable code (exception to make mypy happy)') # GNU/Clang defines and version # ============================= def _get_gnu_compiler_defines(compiler: T.List[str]) -> T.Dict[str, str]: """ 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', '-'] mlog.debug(f'Running command: {join_args(args)}') p, output, error = Popen_safe(args, write='', stdin=subprocess.PIPE) if p.returncode != 0: raise EnvironmentException('Unable to detect GNU compiler type:\n' f'Compiler stdout:\n{output}\n-----\n' f'Compiler stderr:\n{error}\n-----\n') # Parse several lines of the type: # `#define ___SOME_DEF some_value` # and extract `___SOME_DEF` defines: T.Dict[str, str] = {} 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[0]] = '' if len(rest) == 2: defines[rest[0]] = rest[1] return defines def _get_clang_compiler_defines(compiler: T.List[str]) -> T.Dict[str, str]: """ Get the list of Clang pre-processor defines """ args = compiler + ['-E', '-dM', '-'] mlog.debug(f'Running command: {join_args(args)}') p, output, error = Popen_safe(args, write='', stdin=subprocess.PIPE) if p.returncode != 0: raise EnvironmentException('Unable to get clang pre-processor defines:\n' f'Compiler stdout:\n{output}\n-----\n' f'Compiler stderr:\n{error}\n-----\n') defines: T.Dict[str, str] = {} 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[0]] = '' if len(rest) == 2: defines[rest[0]] = rest[1] return defines def _get_gnu_version_from_defines(defines: T.Dict[str, str]) -> str: 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: T.Dict[str, str]) -> str: 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))