The Meson Build System http://mesonbuild.com/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1473 lines
69 KiB

# 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 Microsoft's
# 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']
# the binary flang-new will be renamed to flang in the foreseeable future
defaults['fortran'] = ['ifort', 'gfortran', 'flang-new', 'flang', 'pgfortran', 'g95']
defaults['objc'] = ['clang', 'clang-cl', 'gcc']
defaults['objcpp'] = ['clang++', 'clang-cl', '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'] = ['clang', 'gcc']
defaults['objcpp'] = ['clang++', 'g++']
# the binary flang-new will be renamed to flang in the foreseeable future
defaults['fortran'] = ['gfortran', 'flang-new', '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,
allow_build_machine: bool = False) -> 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):
if allow_build_machine:
return _get_compilers(env, lang, MachineChoice.BUILD)
raise EnvironmentException(f'{lang!r} compiler binary not defined in cross file [binaries] section')
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 its 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 'Texas Instruments Incorporated' in out:
if 'ar2000' in linker_name:
return linkers.C2000Linker(linker)
elif 'ar6000' in linker_name:
return linkers.C6000Linker(linker)
else:
return linkers.TILinker(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 its 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: T.Optional[str]) -> T.Optional[str]:
return os.path.normcase(os.path.abspath(p)) if p else None
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, lang)
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 identifier, compiler_classes in ti_compilers.items():
if identifier 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, lang)
# 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 complaining 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 complaining 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, Phase
from ..options import OptionKey
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)
key = OptionKey('cuda_link_args', machine=for_machine)
if key in env.options:
# To fix LDFLAGS issue
val = env.options[key]
assert isinstance(val, list)
env.coredata.set_options({key: cls.to_host_flags_base(val, Phase.LINKER)})
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:
# capture help text for possible fallback
try:
_, help_out, _ = Popen_safe_logged(compiler + ['--help'], msg='Detecting compiler via')
except OSError as e:
popen_exceptions[join_args(compiler + ['--help'])] = e
help_out = ''
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, 'fortran')
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)
def _get_linker_try_windows(cls: T.Type['Compiler']) -> T.Optional['DynamicLinker']:
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 linker
if 'flang-new' in out or 'flang LLVM compiler' in help_out:
cls = fortran.LlvmFlangFortranCompiler
linker = _get_linker_try_windows(cls)
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.ClassicFlangFortranCompiler
linker = _get_linker_try_windows(cls)
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, lang)
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, lang)
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 its
# 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'
extra_args = [f]
if is_cross:
extra_args.append(f'-mtriple={info.cpu}-windows')
linker = guess_win_linker(env,
exelist,
cls, full_version, for_machine,
use_linker_prefix=True, invoked_directly=False,
extra_args=extra_args)
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,
is_cross=is_cross, 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, '-o', '/dev/null'])
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
is_cross = env.is_cross_build(for_machine)
# When cross compiling and nasm is not defined in the cross file we can
# fallback to the build machine nasm.
compilers, _ = _get_compilers(env, 'nasm', for_machine, allow_build_machine=True)
# 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], lang: str) -> T.Dict[str, str]:
"""
Get the list of GCC pre-processor defines
"""
from .mixins.gnu import gnu_lang_map
def _try_obtain_compiler_defines(args: T.List[str]) -> str:
mlog.debug(f'Running command: {join_args(args)}')
p, output, error = Popen_safe(compiler + args, write='', stdin=subprocess.PIPE)
if p.returncode != 0:
raise EnvironmentException('Unable to get gcc pre-processor defines:\n'
f'Compiler stdout:\n{output}\n-----\n'
f'Compiler stderr:\n{error}\n-----\n')
return output
# Arguments to output compiler pre-processor defines to stdout
# gcc, g++, and gfortran all support these arguments
baseline_test_args = ['-E', '-dM', '-']
try:
# We assume that when _get_gnu_compiler_defines is called, it's
# close enough to a GCCish compiler so we reuse the _LANG_MAP
# from the GCC mixin. This isn't a dangerous assumption because
# we fallback if the detection fails anyway.
# We might not have a match for Fortran, so fallback to detection
# based on the driver.
lang = gnu_lang_map[lang]
# The compiler may not infer the target language based on the driver name
# so first, try with '-cpp -x lang', then fallback without given it's less
# portable. We try with '-cpp' as GCC needs it for Fortran at least, and
# it seems to do no harm.
output = _try_obtain_compiler_defines(['-cpp', '-x', lang] + baseline_test_args)
except (EnvironmentException, KeyError):
mlog.debug(f'pre-processor extraction using -cpp -x {lang} failed, falling back w/o lang')
output = _try_obtain_compiler_defines(baseline_test_args)
# 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], lang: str) -> T.Dict[str, str]:
"""
Get the list of Clang pre-processor defines
"""
from .mixins.clang import clang_lang_map
def _try_obtain_compiler_defines(args: T.List[str]) -> str:
mlog.debug(f'Running command: {join_args(args)}')
p, output, error = Popen_safe(compiler + 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')
return output
# Arguments to output compiler pre-processor defines to stdout
baseline_test_args = ['-E', '-dM', '-']
try:
# We assume that when _get_clang_compiler_defines is called, it's
# close enough to a Clangish compiler so we reuse the _LANG_MAP
# from the Clang mixin. This isn't a dangerous assumption because
# we fallback if the detection fails anyway.
# We might not have a match for Fortran, so fallback to detection
# based on the driver.
lang = clang_lang_map[lang]
# The compiler may not infer the target language based on the driver name.
# Try first with '-x lang' to supported systemwide language level overrides,
# then fallback to without since it's a more recent option.
output = _try_obtain_compiler_defines(['-x', lang] + baseline_test_args)
except (EnvironmentException, KeyError):
mlog.debug(f'pre-processor extraction using -x {lang} failed, falling back w/o lang')
output = _try_obtain_compiler_defines(baseline_test_args)
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))