diff options
-rw-r--r-- | mesonbuild/backend/backends.py | 4 | ||||
-rw-r--r-- | mesonbuild/backend/ninjabackend.py | 14 | ||||
-rw-r--r-- | mesonbuild/compilers/__init__.py | 6 | ||||
-rw-r--r-- | mesonbuild/compilers/c.py | 1498 | ||||
-rw-r--r-- | mesonbuild/compilers/clike.py | 1187 | ||||
-rw-r--r-- | mesonbuild/compilers/compilers.py | 329 | ||||
-rw-r--r-- | mesonbuild/compilers/cpp.py | 84 | ||||
-rw-r--r-- | mesonbuild/compilers/fortran.py | 177 | ||||
-rw-r--r-- | mesonbuild/compilers/objc.py | 12 | ||||
-rw-r--r-- | mesonbuild/compilers/objcpp.py | 12 | ||||
-rw-r--r-- | mesonbuild/environment.py | 39 | ||||
-rwxr-xr-x | run_unittests.py | 4 |
12 files changed, 1627 insertions, 1739 deletions
diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index d0b4bb5..0565de3 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -23,7 +23,7 @@ import subprocess from ..mesonlib import MachineChoice, MesonException, OrderedSet from ..mesonlib import classify_unity_sources from ..mesonlib import File -from ..compilers import CompilerArgs, VisualStudioCCompiler +from ..compilers import CompilerArgs, VisualStudioLikeCompiler from collections import OrderedDict import shlex from functools import lru_cache @@ -551,7 +551,7 @@ class Backend: return args extra_args = [] # Compiler-specific escaping is needed for -D args but not for any others - if isinstance(compiler, VisualStudioCCompiler): + if isinstance(compiler, VisualStudioLikeCompiler): # MSVC needs escaping when a -D argument ends in \ or \" for arg in args: if arg.startswith('-D') or arg.startswith('/D'): diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 7eafcad..e0a6440 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -29,7 +29,7 @@ from .. import build from .. import mlog from .. import dependencies from .. import compilers -from ..compilers import CompilerArgs, CCompiler, VisualStudioCCompiler, FortranCompiler +from ..compilers import CompilerArgs, CCompiler, VisualStudioLikeCompiler, FortranCompiler from ..linkers import ArLinker from ..mesonlib import File, MachineChoice, MesonException, OrderedSet, LibType from ..mesonlib import get_compiler_for_source, has_path_sep @@ -221,7 +221,7 @@ class NinjaBackend(backends.Backend): Detect the search prefix to use.''' for compiler in self.build.compilers.values(): # Have to detect the dependency format - if isinstance(compiler, VisualStudioCCompiler): + if isinstance(compiler, VisualStudioLikeCompiler): break else: # None of our compilers are MSVC, we're done. @@ -1673,7 +1673,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) command = [ninja_quote(i) for i in compiler.get_exelist()] args = ['$ARGS'] + quoted_depargs + compiler.get_output_args('$out') + compiler.get_compile_only_args() + ['$in'] description = 'Compiling %s object $out.' % compiler.get_display_language() - if isinstance(compiler, VisualStudioCCompiler): + if isinstance(compiler, VisualStudioLikeCompiler): deps = 'msvc' depfile = None else: @@ -1698,13 +1698,13 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) if d != '$out' and d != '$in': d = quote_func(d) quoted_depargs.append(d) - if isinstance(compiler, VisualStudioCCompiler): + if isinstance(compiler, VisualStudioLikeCompiler): output = [] else: output = compiler.get_output_args('$out') command = compiler.get_exelist() + ['$ARGS'] + quoted_depargs + output + compiler.get_compile_only_args() + ['$in'] description = 'Precompiling header $in.' - if isinstance(compiler, VisualStudioCCompiler): + if isinstance(compiler, VisualStudioLikeCompiler): deps = 'msvc' depfile = None else: @@ -1886,7 +1886,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) return compiler.get_no_stdinc_args() def get_compile_debugfile_args(self, compiler, target, objfile): - if not isinstance(compiler, VisualStudioCCompiler): + if not isinstance(compiler, VisualStudioLikeCompiler): return [] # The way MSVC uses PDB files is documented exactly nowhere so # the following is what we have been able to decipher via @@ -2275,7 +2275,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) ''.format(target.get_basename()) raise InvalidArguments(msg) compiler = target.compilers[lang] - if isinstance(compiler, VisualStudioCCompiler): + if isinstance(compiler, VisualStudioLikeCompiler): (commands, dep, dst, objs, src) = self.generate_msvc_pch_command(target, compiler, pch) extradep = os.path.join(self.build_to_src, target.get_source_subdir(), pch[0]) elif compiler.id == 'intel': diff --git a/mesonbuild/compilers/__init__.py b/mesonbuild/compilers/__init__.py index 4cb7ebf..3c06f38 100644 --- a/mesonbuild/compilers/__init__.py +++ b/mesonbuild/compilers/__init__.py @@ -69,8 +69,6 @@ __all__ = [ 'IntelCCompiler', 'IntelCPPCompiler', 'IntelFortranCompiler', - 'IntelClCCompiler', - 'IntelClCPPCompiler', 'JavaCompiler', 'LLVMDCompiler', 'MonoCompiler', @@ -91,6 +89,7 @@ __all__ = [ 'SunFortranCompiler', 'SwiftCompiler', 'ValaCompiler', + 'VisualStudioLikeCompiler', 'VisualStudioCCompiler', 'VisualStudioCPPCompiler', ] @@ -122,6 +121,7 @@ from .compilers import ( GnuCompiler, IntelCompiler, CcrxCompiler, + VisualStudioLikeCompiler, ) from .c import ( CCompiler, @@ -132,7 +132,6 @@ from .c import ( GnuCCompiler, ElbrusCCompiler, IntelCCompiler, - IntelClCCompiler, PGICCompiler, CcrxCCompiler, VisualStudioCCompiler, @@ -146,7 +145,6 @@ from .cpp import ( GnuCPPCompiler, ElbrusCPPCompiler, IntelCPPCompiler, - IntelClCPPCompiler, PGICPPCompiler, CcrxCPPCompiler, VisualStudioCPPCompiler, diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py index 1aeb637..73a4083 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py @@ -12,55 +12,32 @@ # See the License for the specific language governing permissions and # limitations under the License. -import re -import glob import os.path -import subprocess -import functools -import itertools -from pathlib import Path -from typing import List +import typing -from .. import mlog from .. import coredata -from . import compilers -from ..mesonlib import ( - EnvironmentException, MachineChoice, MesonException, Popen_safe, listify, - version_compare, for_windows, for_darwin, for_cygwin, for_haiku, - for_openbsd, darwin_get_object_archs, LibType -) +from ..mesonlib import MesonException, version_compare from .c_function_attributes import C_FUNC_ATTRIBUTES +from .clike import CLikeCompiler from .compilers import ( - get_largefile_args, gnu_winlibs, msvc_winlibs, - unixy_compiler_internal_libs, - vs32_instruction_set_args, - vs64_instruction_set_args, ArmCompiler, ArmclangCompiler, ClangCompiler, Compiler, - CompilerArgs, CompilerType, - CrossNoRunException, GnuCompiler, ElbrusCompiler, IntelCompiler, PGICompiler, - RunResult, CcrxCompiler, + VisualStudioLikeCompiler, ) -class CCompiler(Compiler): - # TODO: Replace this manual cache with functools.lru_cache - library_dirs_cache = {} - program_dirs_cache = {} - find_library_cache = {} - find_framework_cache = {} - internal_libs = unixy_compiler_internal_libs +class CCompiler(CLikeCompiler, Compiler): @staticmethod def attribute_check_func(name): @@ -69,318 +46,20 @@ class CCompiler(Compiler): except KeyError: raise MesonException('Unknown function attribute "{}"'.format(name)) - def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwargs): + def __init__(self, exelist, version, is_cross: bool, + exe_wrapper: typing.Optional[str] = None, **kwargs): # If a child ObjC or CPP class has already set it, don't set it ourselves - if not hasattr(self, 'language'): - self.language = 'c' - super().__init__(exelist, version, **kwargs) - self.id = 'unknown' - self.is_cross = is_cross - self.can_compile_suffixes.add('h') - # If the exe wrapper was not found, pretend it wasn't set so that the - # sanity check is skipped and compiler checks use fallbacks. - if not exe_wrapper or not exe_wrapper.found(): - self.exe_wrapper = None - else: - self.exe_wrapper = exe_wrapper.get_command() - - # Set to None until we actually need to check this - self.has_fatal_warnings_link_arg = None - - def needs_static_linker(self): - return True # When compiling static libraries, so yes. - - def get_always_args(self): - ''' - Args that are always-on for all C compilers other than MSVC - ''' - return ['-pipe'] + get_largefile_args(self) - - def get_linker_debug_crt_args(self): - """ - Arguments needed to select a debug crt for the linker - This is only needed for MSVC - """ - return [] + self.language = 'c' + Compiler.__init__(self, exelist, version, **kwargs) + CLikeCompiler.__init__(self, is_cross, exe_wrapper) def get_no_stdinc_args(self): return ['-nostdinc'] - def get_no_stdlib_link_args(self): - return ['-nostdlib'] - - def get_warn_args(self, level): - return self.warn_args[level] - - def get_no_warn_args(self): - # Almost every compiler uses this for disabling warnings - return ['-w'] - - def get_soname_args(self, *args): - return [] - - def split_shlib_to_parts(self, fname): - return None, fname - - # The default behavior is this, override in MSVC - @functools.lru_cache(maxsize=None) - def build_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath): - if self.compiler_type.is_windows_compiler: - return [] - return self.build_unix_rpath_args(build_dir, from_dir, rpath_paths, build_rpath, install_rpath) - - def get_dependency_gen_args(self, outtarget, outfile): - return ['-MD', '-MQ', outtarget, '-MF', outfile] - - def depfile_for_object(self, objfile): - return objfile + '.' + self.get_depfile_suffix() - - def get_depfile_suffix(self): - return 'd' - - def get_exelist(self): - return self.exelist[:] - - def get_linker_exelist(self): - return self.exelist[:] - - def get_preprocess_only_args(self): - return ['-E', '-P'] - - def get_compile_only_args(self): - return ['-c'] - - def get_no_optimization_args(self): - return ['-O0'] - - def get_compiler_check_args(self): - ''' - Get arguments useful for compiler checks such as being permissive in - the code quality and not doing any optimization. - ''' - return self.get_no_optimization_args() - - def get_output_args(self, target): - return ['-o', target] - - def get_linker_output_args(self, outputname): - return ['-o', outputname] - - def get_coverage_args(self): - return ['--coverage'] - - def get_coverage_link_args(self): - return ['--coverage'] - - def get_werror_args(self): - return ['-Werror'] - - def get_std_exe_link_args(self): - return [] - - def get_include_args(self, path, is_system): - if path == '': - path = '.' - if is_system: - return ['-isystem', path] - return ['-I' + path] - - def get_std_shared_lib_link_args(self): - return ['-shared'] - - @functools.lru_cache() - def _get_search_dirs(self, env): - extra_args = ['--print-search-dirs'] - stdo = None - with self._build_wrapper('', env, extra_args=extra_args, - dependencies=None, mode='compile', - want_output=True) as p: - stdo = p.stdo - return stdo - - @staticmethod - def _split_fetch_real_dirs(pathstr, sep=':'): - paths = [] - for p in pathstr.split(sep): - # GCC returns paths like this: - # /usr/lib/gcc/x86_64-linux-gnu/8/../../../../x86_64-linux-gnu/lib - # It would make sense to normalize them to get rid of the .. parts - # Sadly when you are on a merged /usr fs it also kills these: - # /lib/x86_64-linux-gnu - # since /lib is a symlink to /usr/lib. This would mean - # paths under /lib would be considered not a "system path", - # which is wrong and breaks things. Store everything, just to be sure. - pobj = Path(p) - unresolved = pobj.as_posix() - if pobj.exists(): - if unresolved not in paths: - paths.append(unresolved) - try: - resolved = Path(p).resolve().as_posix() - if resolved not in paths: - paths.append(resolved) - except FileNotFoundError: - pass - return tuple(paths) - - def get_compiler_dirs(self, env, name): - ''' - Get dirs from the compiler, either `libraries:` or `programs:` - ''' - stdo = self._get_search_dirs(env) - for line in stdo.split('\n'): - if line.startswith(name + ':'): - return CCompiler._split_fetch_real_dirs(line.split('=', 1)[1]) - return () - - @functools.lru_cache() - def get_library_dirs(self, env, elf_class = None): - dirs = self.get_compiler_dirs(env, 'libraries') - if elf_class is None or elf_class == 0: - return dirs - - # if we do have an elf class for 32-bit or 64-bit, we want to check that - # the directory in question contains libraries of the appropriate class. Since - # system directories aren't mixed, we only need to check one file for each - # directory and go by that. If we can't check the file for some reason, assume - # the compiler knows what it's doing, and accept the directory anyway. - retval = [] - for d in dirs: - files = [f for f in os.listdir(d) if f.endswith('.so') and os.path.isfile(os.path.join(d, f))] - # if no files, accept directory and move on - if not files: - retval.append(d) - continue - file_to_check = os.path.join(d, files[0]) - with open(file_to_check, 'rb') as fd: - header = fd.read(5) - # if file is not an ELF file, it's weird, but accept dir - # if it is elf, and the class matches, accept dir - if header[1:4] != b'ELF' or int(header[4]) == elf_class: - retval.append(d) - # at this point, it's an ELF file which doesn't match the - # appropriate elf_class, so skip this one - pass - return tuple(retval) - - @functools.lru_cache() - def get_program_dirs(self, env): - ''' - Programs used by the compiler. Also where toolchain DLLs such as - libstdc++-6.dll are found with MinGW. - ''' - return self.get_compiler_dirs(env, 'programs') - - def get_pic_args(self): - return ['-fPIC'] - - def name_string(self): - return ' '.join(self.exelist) - - def get_pch_use_args(self, pch_dir, header): - return ['-include', os.path.basename(header)] - - def get_pch_name(self, header_name): - return os.path.basename(header_name) + '.' + self.get_pch_suffix() - - def get_linker_search_args(self, dirname): - return ['-L' + dirname] - - def get_default_include_dirs(self): - return [] - - def gen_export_dynamic_link_args(self, env): - if for_windows(env.is_cross_build(), env) or for_cygwin(env.is_cross_build(), env): - return ['-Wl,--export-all-symbols'] - elif for_darwin(env.is_cross_build(), env): - return [] - else: - return ['-Wl,-export-dynamic'] - - def gen_import_library_args(self, implibname): - """ - The name of the outputted import library - - This implementation is used only on Windows by compilers that use GNU ld - """ - return ['-Wl,--out-implib=' + implibname] - - def sanity_check_impl(self, work_dir, environment, sname, code): - mlog.debug('Sanity testing ' + self.get_display_language() + ' compiler:', ' '.join(self.exelist)) - mlog.debug('Is cross compiler: %s.' % str(self.is_cross)) - - source_name = os.path.join(work_dir, sname) - binname = sname.rsplit('.', 1)[0] - mode = 'link' - if self.is_cross: - binname += '_cross' - if self.exe_wrapper is None: - # Linking cross built apps is painful. You can't really - # tell if you should use -nostdlib or not and for example - # on OSX the compiler binary is the same but you need - # a ton of compiler flags to differentiate between - # arm and x86_64. So just compile. - mode = 'compile' - extra_flags = self._get_basic_compiler_args(environment, mode) - - # Is a valid executable output for all toolchains and platforms - binname += '.exe' - # Write binary check source - binary_name = os.path.join(work_dir, binname) - with open(source_name, 'w') as ofile: - ofile.write(code) - # Compile sanity check - cmdlist = self.exelist + extra_flags + [source_name] + self.get_output_args(binary_name) - pc, stdo, stde = Popen_safe(cmdlist, cwd=work_dir) - mlog.debug('Sanity check compiler command line:', ' '.join(cmdlist)) - mlog.debug('Sanity check compile stdout:') - mlog.debug(stdo) - mlog.debug('-----\nSanity check compile stderr:') - mlog.debug(stde) - mlog.debug('-----') - if pc.returncode != 0: - raise EnvironmentException('Compiler {0} can not compile programs.'.format(self.name_string())) - # Run sanity check - if self.is_cross: - if self.exe_wrapper is None: - # Can't check if the binaries run so we have to assume they do - return - cmdlist = self.exe_wrapper + [binary_name] - else: - cmdlist = [binary_name] - mlog.debug('Running test binary command: ' + ' '.join(cmdlist)) - try: - pe = subprocess.Popen(cmdlist) - except Exception as e: - raise EnvironmentException('Could not invoke sanity test executable: %s.' % str(e)) - pe.wait() - if pe.returncode != 0: - raise EnvironmentException('Executables created by {0} compiler {1} are not runnable.'.format(self.language, self.name_string())) - def sanity_check(self, work_dir, environment): code = 'int main(int argc, char **argv) { int class=0; return class; }\n' return self.sanity_check_impl(work_dir, environment, 'sanitycheckc.c', code) - def check_header(self, hname, prefix, env, *, extra_args=None, dependencies=None): - fargs = {'prefix': prefix, 'header': hname} - code = '''{prefix} - #include <{header}>''' - return self.compiles(code.format(**fargs), env, extra_args=extra_args, - dependencies=dependencies) - - def has_header(self, hname, prefix, env, *, extra_args=None, dependencies=None, disable_cache=False): - fargs = {'prefix': prefix, 'header': hname} - code = '''{prefix} - #ifdef __has_include - #if !__has_include("{header}") - #error "Header '{header}' could not be found" - #endif - #else - #include <{header}> - #endif''' - return self.compiles(code.format(**fargs), env, extra_args=extra_args, - dependencies=dependencies, mode='preprocess', disable_cache=disable_cache) - def has_header_symbol(self, hname, symbol, prefix, env, *, extra_args=None, dependencies=None): fargs = {'prefix': prefix, 'header': hname, 'symbol': symbol} t = '''{prefix} @@ -394,826 +73,6 @@ class CCompiler(Compiler): return self.compiles(t.format(**fargs), env, extra_args=extra_args, dependencies=dependencies) - def _get_basic_compiler_args(self, env, mode): - args = [] - # Select a CRT if needed since we're linking - if mode == 'link': - args += self.get_linker_debug_crt_args() - if env.is_cross_build() and not self.is_cross: - for_machine = MachineChoice.BUILD - else: - for_machine = MachineChoice.HOST - if mode in {'compile', 'preprocess'}: - # Add CFLAGS/CXXFLAGS/OBJCFLAGS/OBJCXXFLAGS and CPPFLAGS from the env - sys_args = env.coredata.get_external_args(for_machine, self.language) - # Apparently it is a thing to inject linker flags both - # via CFLAGS _and_ LDFLAGS, even though the former are - # also used during linking. These flags can break - # argument checks. Thanks, Autotools. - cleaned_sys_args = self.remove_linkerlike_args(sys_args) - args += cleaned_sys_args - elif mode == 'link': - # Add LDFLAGS from the env - args += env.coredata.get_external_link_args(for_machine, self.language) - return args - - def _get_compiler_check_args(self, env, extra_args, dependencies, mode='compile'): - if extra_args is None: - extra_args = [] - else: - extra_args = listify(extra_args) - extra_args = listify([e(mode) if callable(e) else e for e in extra_args]) - - if dependencies is None: - dependencies = [] - elif not isinstance(dependencies, list): - dependencies = [dependencies] - # Collect compiler arguments - args = CompilerArgs(self) - for d in dependencies: - # Add compile flags needed by dependencies - args += d.get_compile_args() - if mode == 'link': - # Add link flags needed to find dependencies - args += d.get_link_args() - - args += self._get_basic_compiler_args(env, mode) - - args += self.get_compiler_check_args() - # extra_args must override all other arguments, so we add them last - args += extra_args - return args - - def compiles(self, code, env, *, extra_args=None, dependencies=None, mode='compile', disable_cache=False): - with self._build_wrapper(code, env, extra_args, dependencies, mode, disable_cache=disable_cache) as p: - return p.returncode == 0, p.cached - - def _build_wrapper(self, code, env, extra_args, dependencies=None, mode='compile', want_output=False, disable_cache=False): - args = self._get_compiler_check_args(env, extra_args, dependencies, mode) - if disable_cache or want_output: - return self.compile(code, extra_args=args, mode=mode, want_output=want_output) - return self.cached_compile(code, env.coredata, extra_args=args, mode=mode) - - def links(self, code, env, *, extra_args=None, dependencies=None, disable_cache=False): - return self.compiles(code, env, extra_args=extra_args, - dependencies=dependencies, mode='link', disable_cache=disable_cache) - - def run(self, code: str, env, *, extra_args=None, dependencies=None): - if self.is_cross and self.exe_wrapper is None: - raise CrossNoRunException('Can not run test applications in this cross environment.') - with self._build_wrapper(code, env, extra_args, dependencies, mode='link', want_output=True) as p: - if p.returncode != 0: - mlog.debug('Could not compile test file %s: %d\n' % ( - p.input_name, - p.returncode)) - return RunResult(False) - if self.is_cross: - cmdlist = self.exe_wrapper + [p.output_name] - else: - cmdlist = p.output_name - try: - pe, so, se = Popen_safe(cmdlist) - except Exception as e: - mlog.debug('Could not run: %s (error: %s)\n' % (cmdlist, e)) - return RunResult(False) - - mlog.debug('Program stdout:\n') - mlog.debug(so) - mlog.debug('Program stderr:\n') - mlog.debug(se) - return RunResult(True, pe.returncode, so, se) - - def _compile_int(self, expression, prefix, env, extra_args, dependencies): - fargs = {'prefix': prefix, 'expression': expression} - t = '''#include <stdio.h> - {prefix} - int main() {{ static int a[1-2*!({expression})]; a[0]=0; return 0; }}''' - return self.compiles(t.format(**fargs), env, extra_args=extra_args, - dependencies=dependencies)[0] - - def cross_compute_int(self, expression, low, high, guess, prefix, env, extra_args, dependencies): - # Try user's guess first - if isinstance(guess, int): - if self._compile_int('%s == %d' % (expression, guess), prefix, env, extra_args, dependencies): - return guess - - # If no bounds are given, compute them in the limit of int32 - maxint = 0x7fffffff - minint = -0x80000000 - if not isinstance(low, int) or not isinstance(high, int): - if self._compile_int('%s >= 0' % (expression), prefix, env, extra_args, dependencies): - low = cur = 0 - while self._compile_int('%s > %d' % (expression, cur), prefix, env, extra_args, dependencies): - low = cur + 1 - if low > maxint: - raise EnvironmentException('Cross-compile check overflowed') - cur = cur * 2 + 1 - if cur > maxint: - cur = maxint - high = cur - else: - low = cur = -1 - while self._compile_int('%s < %d' % (expression, cur), prefix, env, extra_args, dependencies): - high = cur - 1 - if high < minint: - raise EnvironmentException('Cross-compile check overflowed') - cur = cur * 2 - if cur < minint: - cur = minint - low = cur - else: - # Sanity check limits given by user - if high < low: - raise EnvironmentException('high limit smaller than low limit') - condition = '%s <= %d && %s >= %d' % (expression, high, expression, low) - if not self._compile_int(condition, prefix, env, extra_args, dependencies): - raise EnvironmentException('Value out of given range') - - # Binary search - while low != high: - cur = low + int((high - low) / 2) - if self._compile_int('%s <= %d' % (expression, cur), prefix, env, extra_args, dependencies): - high = cur - else: - low = cur + 1 - - return low - - def compute_int(self, expression, low, high, guess, prefix, env, *, extra_args=None, dependencies=None): - if extra_args is None: - extra_args = [] - if self.is_cross: - return self.cross_compute_int(expression, low, high, guess, prefix, env, extra_args, dependencies) - fargs = {'prefix': prefix, 'expression': expression} - t = '''#include<stdio.h> - {prefix} - int main(int argc, char **argv) {{ - printf("%ld\\n", (long)({expression})); - return 0; - }};''' - res = self.run(t.format(**fargs), env, extra_args=extra_args, - dependencies=dependencies) - if not res.compiled: - return -1 - if res.returncode != 0: - raise EnvironmentException('Could not run compute_int test binary.') - return int(res.stdout) - - def cross_sizeof(self, typename, prefix, env, *, extra_args=None, dependencies=None): - if extra_args is None: - extra_args = [] - fargs = {'prefix': prefix, 'type': typename} - t = '''#include <stdio.h> - {prefix} - int main(int argc, char **argv) {{ - {type} something; - }}''' - if not self.compiles(t.format(**fargs), env, extra_args=extra_args, - dependencies=dependencies)[0]: - return -1 - return self.cross_compute_int('sizeof(%s)' % typename, None, None, None, prefix, env, extra_args, dependencies) - - def sizeof(self, typename, prefix, env, *, extra_args=None, dependencies=None): - if extra_args is None: - extra_args = [] - fargs = {'prefix': prefix, 'type': typename} - if self.is_cross: - return self.cross_sizeof(typename, prefix, env, extra_args=extra_args, - dependencies=dependencies) - t = '''#include<stdio.h> - {prefix} - int main(int argc, char **argv) {{ - printf("%ld\\n", (long)(sizeof({type}))); - return 0; - }};''' - res = self.run(t.format(**fargs), env, extra_args=extra_args, - dependencies=dependencies) - if not res.compiled: - return -1 - if res.returncode != 0: - raise EnvironmentException('Could not run sizeof test binary.') - return int(res.stdout) - - def cross_alignment(self, typename, prefix, env, *, extra_args=None, dependencies=None): - if extra_args is None: - extra_args = [] - fargs = {'prefix': prefix, 'type': typename} - t = '''#include <stdio.h> - {prefix} - int main(int argc, char **argv) {{ - {type} something; - }}''' - if not self.compiles(t.format(**fargs), env, extra_args=extra_args, - dependencies=dependencies)[0]: - return -1 - t = '''#include <stddef.h> - {prefix} - struct tmp {{ - char c; - {type} target; - }};''' - return self.cross_compute_int('offsetof(struct tmp, target)', None, None, None, t.format(**fargs), env, extra_args, dependencies) - - def alignment(self, typename, prefix, env, *, extra_args=None, dependencies=None): - if extra_args is None: - extra_args = [] - if self.is_cross: - return self.cross_alignment(typename, prefix, env, extra_args=extra_args, - dependencies=dependencies) - fargs = {'prefix': prefix, 'type': typename} - t = '''#include <stdio.h> - #include <stddef.h> - {prefix} - struct tmp {{ - char c; - {type} target; - }}; - int main(int argc, char **argv) {{ - printf("%d", (int)offsetof(struct tmp, target)); - return 0; - }}''' - res = self.run(t.format(**fargs), env, extra_args=extra_args, - dependencies=dependencies) - if not res.compiled: - raise EnvironmentException('Could not compile alignment test.') - if res.returncode != 0: - raise EnvironmentException('Could not run alignment test binary.') - align = int(res.stdout) - if align == 0: - raise EnvironmentException('Could not determine alignment of %s. Sorry. You might want to file a bug.' % typename) - return align - - def get_define(self, dname, prefix, env, extra_args, dependencies, disable_cache=False): - delim = '"MESON_GET_DEFINE_DELIMITER"' - fargs = {'prefix': prefix, 'define': dname, 'delim': delim} - code = ''' - {prefix} - #ifndef {define} - # define {define} - #endif - {delim}\n{define}''' - args = self._get_compiler_check_args(env, extra_args, dependencies, - mode='preprocess').to_native() - func = lambda: self.cached_compile(code.format(**fargs), env.coredata, extra_args=args, mode='preprocess') - if disable_cache: - func = lambda: self.compile(code.format(**fargs), extra_args=args, mode='preprocess') - with func() as p: - cached = p.cached - if p.returncode != 0: - raise EnvironmentException('Could not get define {!r}'.format(dname)) - # Get the preprocessed value after the delimiter, - # minus the extra newline at the end and - # merge string literals. - return CCompiler.concatenate_string_literals(p.stdo.split(delim + '\n')[-1][:-1]), cached - - def get_return_value(self, fname, rtype, prefix, env, extra_args, dependencies): - if rtype == 'string': - fmt = '%s' - cast = '(char*)' - elif rtype == 'int': - fmt = '%lli' - cast = '(long long int)' - else: - raise AssertionError('BUG: Unknown return type {!r}'.format(rtype)) - fargs = {'prefix': prefix, 'f': fname, 'cast': cast, 'fmt': fmt} - code = '''{prefix} - #include <stdio.h> - int main(int argc, char *argv[]) {{ - printf ("{fmt}", {cast} {f}()); - }}'''.format(**fargs) - res = self.run(code, env, extra_args=extra_args, dependencies=dependencies) - if not res.compiled: - m = 'Could not get return value of {}()' - raise EnvironmentException(m.format(fname)) - if rtype == 'string': - return res.stdout - elif rtype == 'int': - try: - return int(res.stdout.strip()) - except ValueError: - m = 'Return value of {}() is not an int' - raise EnvironmentException(m.format(fname)) - - @staticmethod - def _no_prototype_templ(): - """ - Try to find the function without a prototype from a header by defining - our own dummy prototype and trying to link with the C library (and - whatever else the compiler links in by default). This is very similar - to the check performed by Autoconf for AC_CHECK_FUNCS. - """ - # Define the symbol to something else since it is defined by the - # includes or defines listed by the user or by the compiler. This may - # include, for instance _GNU_SOURCE which must be defined before - # limits.h, which includes features.h - # Then, undef the symbol to get rid of it completely. - head = ''' - #define {func} meson_disable_define_of_{func} - {prefix} - #include <limits.h> - #undef {func} - ''' - # Override any GCC internal prototype and declare our own definition for - # the symbol. Use char because that's unlikely to be an actual return - # value for a function which ensures that we override the definition. - head += ''' - #ifdef __cplusplus - extern "C" - #endif - char {func} (); - ''' - # The actual function call - main = ''' - int main () {{ - return {func} (); - }}''' - return head, main - - @staticmethod - def _have_prototype_templ(): - """ - Returns a head-er and main() call that uses the headers listed by the - user for the function prototype while checking if a function exists. - """ - # Add the 'prefix', aka defines, includes, etc that the user provides - # This may include, for instance _GNU_SOURCE which must be defined - # before limits.h, which includes features.h - head = '{prefix}\n#include <limits.h>\n' - # We don't know what the function takes or returns, so return it as an int. - # Just taking the address or comparing it to void is not enough because - # compilers are smart enough to optimize it away. The resulting binary - # is not run so we don't care what the return value is. - main = '''\nint main() {{ - void *a = (void*) &{func}; - long b = (long) a; - return (int) b; - }}''' - return head, main - - def has_function(self, funcname, prefix, env, *, extra_args=None, dependencies=None): - """ - First, this function looks for the symbol in the default libraries - provided by the compiler (stdlib + a few others usually). If that - fails, it checks if any of the headers specified in the prefix provide - an implementation of the function, and if that fails, it checks if it's - implemented as a compiler-builtin. - """ - if extra_args is None: - extra_args = [] - - # Short-circuit if the check is already provided by the cross-info file - varname = 'has function ' + funcname - varname = varname.replace(' ', '_') - if self.is_cross: - val = env.properties.host.get(varname, None) - if val is not None: - if isinstance(val, bool): - return val, False - raise EnvironmentException('Cross variable {0} is not a boolean.'.format(varname)) - - fargs = {'prefix': prefix, 'func': funcname} - - # glibc defines functions that are not available on Linux as stubs that - # fail with ENOSYS (such as e.g. lchmod). In this case we want to fail - # instead of detecting the stub as a valid symbol. - # We already included limits.h earlier to ensure that these are defined - # for stub functions. - stubs_fail = ''' - #if defined __stub_{func} || defined __stub___{func} - fail fail fail this function is not going to work - #endif - ''' - - # If we have any includes in the prefix supplied by the user, assume - # that the user wants us to use the symbol prototype defined in those - # includes. If not, then try to do the Autoconf-style check with - # a dummy prototype definition of our own. - # This is needed when the linker determines symbol availability from an - # SDK based on the prototype in the header provided by the SDK. - # Ignoring this prototype would result in the symbol always being - # marked as available. - if '#include' in prefix: - head, main = self._have_prototype_templ() - else: - head, main = self._no_prototype_templ() - templ = head + stubs_fail + main - - res, cached = self.links(templ.format(**fargs), env, extra_args=extra_args, - dependencies=dependencies) - if res: - return True, cached - - # MSVC does not have compiler __builtin_-s. - if self.get_id() == 'msvc': - return False, False - - # Detect function as a built-in - # - # Some functions like alloca() are defined as compiler built-ins which - # are inlined by the compiler and you can't take their address, so we - # need to look for them differently. On nice compilers like clang, we - # can just directly use the __has_builtin() macro. - fargs['no_includes'] = '#include' not in prefix - t = '''{prefix} - int main() {{ - #ifdef __has_builtin - #if !__has_builtin(__builtin_{func}) - #error "__builtin_{func} not found" - #endif - #elif ! defined({func}) - /* Check for __builtin_{func} only if no includes were added to the - * prefix above, which means no definition of {func} can be found. - * We would always check for this, but we get false positives on - * MSYS2 if we do. Their toolchain is broken, but we can at least - * give them a workaround. */ - #if {no_includes:d} - __builtin_{func}; - #else - #error "No definition for __builtin_{func} found in the prefix" - #endif - #endif - }}''' - return self.links(t.format(**fargs), env, extra_args=extra_args, - dependencies=dependencies) - - def has_members(self, typename, membernames, prefix, env, *, extra_args=None, dependencies=None): - if extra_args is None: - extra_args = [] - fargs = {'prefix': prefix, 'type': typename, 'name': 'foo'} - # Create code that accesses all members - members = '' - for member in membernames: - members += '{}.{};\n'.format(fargs['name'], member) - fargs['members'] = members - t = '''{prefix} - void bar() {{ - {type} {name}; - {members} - }};''' - return self.compiles(t.format(**fargs), env, extra_args=extra_args, - dependencies=dependencies) - - def has_type(self, typename, prefix, env, extra_args, dependencies=None): - fargs = {'prefix': prefix, 'type': typename} - t = '''{prefix} - void bar() {{ - sizeof({type}); - }};''' - return self.compiles(t.format(**fargs), env, extra_args=extra_args, - dependencies=dependencies) - - def symbols_have_underscore_prefix(self, env): - ''' - Check if the compiler prefixes an underscore to global C symbols - ''' - symbol_name = b'meson_uscore_prefix' - code = '''#ifdef __cplusplus - extern "C" { - #endif - void ''' + symbol_name.decode() + ''' () {} - #ifdef __cplusplus - } - #endif - ''' - args = self.get_compiler_check_args() - n = 'symbols_have_underscore_prefix' - with self.compile(code, args, 'compile', want_output=True) as p: - if p.returncode != 0: - m = 'BUG: Unable to compile {!r} check: {}' - raise RuntimeError(m.format(n, p.stdo)) - if not os.path.isfile(p.output_name): - m = 'BUG: Can\'t find compiled test code for {!r} check' - raise RuntimeError(m.format(n)) - with open(p.output_name, 'rb') as o: - for line in o: - # Check if the underscore form of the symbol is somewhere - # in the output file. - if b'_' + symbol_name in line: - return True - # Else, check if the non-underscored form is present - elif symbol_name in line: - return False - raise RuntimeError('BUG: {!r} check failed unexpectedly'.format(n)) - - def _get_patterns(self, env, prefixes, suffixes, shared=False): - patterns = [] - for p in prefixes: - for s in suffixes: - patterns.append(p + '{}.' + s) - if shared and for_openbsd(self.is_cross, env): - # Shared libraries on OpenBSD can be named libfoo.so.X.Y: - # https://www.openbsd.org/faq/ports/specialtopics.html#SharedLibs - # - # This globbing is probably the best matching we can do since regex - # is expensive. It's wrong in many edge cases, but it will match - # correctly-named libraries and hopefully no one on OpenBSD names - # their files libfoo.so.9a.7b.1.0 - for p in prefixes: - patterns.append(p + '{}.so.[0-9]*.[0-9]*') - return patterns - - def get_library_naming(self, env, libtype: LibType, strict=False): - ''' - Get library prefixes and suffixes for the target platform ordered by - priority - ''' - stlibext = ['a'] - # We've always allowed libname to be both `foo` and `libfoo`, and now - # people depend on it. Also, some people use prebuilt `foo.so` instead - # of `libfoo.so` for unknown reasons, and may also want to create - # `foo.so` by setting name_prefix to '' - if strict and not isinstance(self, VisualStudioCCompiler): # lib prefix is not usually used with msvc - prefixes = ['lib'] - else: - prefixes = ['lib', ''] - # Library suffixes and prefixes - if for_darwin(env.is_cross_build(), env): - shlibext = ['dylib', 'so'] - elif for_windows(env.is_cross_build(), env): - # FIXME: .lib files can be import or static so we should read the - # file, figure out which one it is, and reject the wrong kind. - if isinstance(self, VisualStudioCCompiler): - shlibext = ['lib'] - else: - shlibext = ['dll.a', 'lib', 'dll'] - # Yep, static libraries can also be foo.lib - stlibext += ['lib'] - elif for_cygwin(env.is_cross_build(), env): - shlibext = ['dll', 'dll.a'] - prefixes = ['cyg'] + prefixes - else: - # Linux/BSDs - shlibext = ['so'] - # Search priority - if libtype is LibType.PREFER_SHARED: - patterns = self._get_patterns(env, prefixes, shlibext, True) - patterns.extend([x for x in self._get_patterns(env, prefixes, stlibext, False) if x not in patterns]) - elif libtype is LibType.PREFER_STATIC: - patterns = self._get_patterns(env, prefixes, stlibext, False) - patterns.extend([x for x in self._get_patterns(env, prefixes, shlibext, True) if x not in patterns]) - elif libtype is LibType.SHARED: - patterns = self._get_patterns(env, prefixes, shlibext, True) - else: - assert libtype is LibType.STATIC - patterns = self._get_patterns(env, prefixes, stlibext, False) - return tuple(patterns) - - @staticmethod - def _sort_shlibs_openbsd(libs): - filtered = [] - for lib in libs: - # Validate file as a shared library of type libfoo.so.X.Y - ret = lib.rsplit('.so.', maxsplit=1) - if len(ret) != 2: - continue - try: - float(ret[1]) - except ValueError: - continue - filtered.append(lib) - float_cmp = lambda x: float(x.rsplit('.so.', maxsplit=1)[1]) - return sorted(filtered, key=float_cmp, reverse=True) - - @classmethod - def _get_trials_from_pattern(cls, pattern, directory, libname): - f = Path(directory) / pattern.format(libname) - # Globbing for OpenBSD - if '*' in pattern: - # NOTE: globbing matches directories and broken symlinks - # so we have to do an isfile test on it later - return [Path(x) for x in cls._sort_shlibs_openbsd(glob.glob(str(f)))] - return [f] - - @staticmethod - def _get_file_from_list(env, files: List[str]) -> Path: - ''' - We just check whether the library exists. We can't do a link check - because the library might have unresolved symbols that require other - libraries. On macOS we check if the library matches our target - architecture. - ''' - # If not building on macOS for Darwin, do a simple file check - files = [Path(f) for f in files] - if not env.machines.host.is_darwin() or not env.machines.build.is_darwin(): - for f in files: - if f.is_file(): - return f - # Run `lipo` and check if the library supports the arch we want - for f in files: - if not f.is_file(): - continue - archs = darwin_get_object_archs(f) - if archs and env.machines.host.cpu_family in archs: - return f - else: - mlog.debug('Rejected {}, supports {} but need {}' - .format(f, archs, env.machines.host.cpu_family)) - return None - - @functools.lru_cache() - def output_is_64bit(self, env): - ''' - returns true if the output produced is 64-bit, false if 32-bit - ''' - return self.sizeof('void *', '', env) == 8 - - def find_library_real(self, libname, env, extra_dirs, code, libtype: LibType): - # First try if we can just add the library as -l. - # Gcc + co seem to prefer builtin lib dirs to -L dirs. - # Only try to find std libs if no extra dirs specified. - # The built-in search procedure will always favour .so and then always - # search for .a. This is only allowed if libtype is LibType.PREFER_SHARED - if ((not extra_dirs and libtype is LibType.PREFER_SHARED) or - libname in self.internal_libs): - args = ['-l' + libname] - largs = self.linker_to_compiler_args(self.get_allow_undefined_link_args()) - if self.links(code, env, extra_args=(args + largs), disable_cache=True)[0]: - return args - # Don't do a manual search for internal libs - if libname in self.internal_libs: - return None - # Not found or we want to use a specific libtype? Try to find the - # library file itself. - patterns = self.get_library_naming(env, libtype) - # try to detect if we are 64-bit or 32-bit. If we can't - # detect, we will just skip path validity checks done in - # get_library_dirs() call - try: - if self.output_is_64bit(env): - elf_class = 2 - else: - elf_class = 1 - except (MesonException, KeyError): # TODO evaluate if catching KeyError is wanted here - elf_class = 0 - # Search in the specified dirs, and then in the system libraries - for d in itertools.chain(extra_dirs, self.get_library_dirs(env, elf_class)): - for p in patterns: - trial = self._get_trials_from_pattern(p, d, libname) - if not trial: - continue - trial = self._get_file_from_list(env, trial) - if not trial: - continue - return [trial.as_posix()] - return None - - def find_library_impl(self, libname, env, extra_dirs, code, libtype: LibType): - # These libraries are either built-in or invalid - if libname in self.ignore_libs: - return [] - if isinstance(extra_dirs, str): - extra_dirs = [extra_dirs] - key = (tuple(self.exelist), libname, tuple(extra_dirs), code, libtype) - if key not in self.find_library_cache: - value = self.find_library_real(libname, env, extra_dirs, code, libtype) - self.find_library_cache[key] = value - else: - value = self.find_library_cache[key] - if value is None: - return None - return value[:] - - def find_library(self, libname, env, extra_dirs, libtype: LibType = LibType.PREFER_SHARED): - code = 'int main(int argc, char **argv) { return 0; }' - return self.find_library_impl(libname, env, extra_dirs, code, libtype) - - def find_framework_paths(self, env): - ''' - These are usually /Library/Frameworks and /System/Library/Frameworks, - unless you select a particular macOS SDK with the -isysroot flag. - You can also add to this by setting -F in CFLAGS. - ''' - if self.id != 'clang': - raise MesonException('Cannot find framework path with non-clang compiler') - # Construct the compiler command-line - commands = self.get_exelist() + ['-v', '-E', '-'] - commands += self.get_always_args() - # Add CFLAGS/CXXFLAGS/OBJCFLAGS/OBJCXXFLAGS from the env - if env.is_cross_build() and not self.is_cross: - for_machine = MachineChoice.BUILD - else: - for_machine = MachineChoice.HOST - commands += env.coredata.get_external_args(for_machine, self.language) - mlog.debug('Finding framework path by running: ', ' '.join(commands), '\n') - os_env = os.environ.copy() - os_env['LC_ALL'] = 'C' - _, _, stde = Popen_safe(commands, env=os_env, stdin=subprocess.PIPE) - paths = [] - for line in stde.split('\n'): - if '(framework directory)' not in line: - continue - # line is of the form: - # ` /path/to/framework (framework directory)` - paths.append(line[:-21].strip()) - return paths - - def find_framework_real(self, name, env, extra_dirs, allow_system): - code = 'int main(int argc, char **argv) { return 0; }' - link_args = [] - for d in extra_dirs: - link_args += ['-F' + d] - # We can pass -Z to disable searching in the system frameworks, but - # then we must also pass -L/usr/lib to pick up libSystem.dylib - extra_args = [] if allow_system else ['-Z', '-L/usr/lib'] - link_args += ['-framework', name] - if self.links(code, env, extra_args=(extra_args + link_args), disable_cache=True)[0]: - return link_args - - def find_framework_impl(self, name, env, extra_dirs, allow_system): - if isinstance(extra_dirs, str): - extra_dirs = [extra_dirs] - key = (tuple(self.exelist), name, tuple(extra_dirs), allow_system) - if key in self.find_framework_cache: - value = self.find_framework_cache[key] - else: - value = self.find_framework_real(name, env, extra_dirs, allow_system) - self.find_framework_cache[key] = value - if value is None: - return None - return value[:] - - def find_framework(self, name, env, extra_dirs, allow_system=True): - ''' - Finds the framework with the specified name, and returns link args for - the same or returns None when the framework is not found. - ''' - if self.id != 'clang': - raise MesonException('Cannot find frameworks with non-clang compiler') - return self.find_framework_impl(name, env, extra_dirs, allow_system) - - def thread_flags(self, env): - if for_haiku(self.is_cross, env) or for_darwin(self.is_cross, env): - return [] - return ['-pthread'] - - def thread_link_flags(self, env): - if for_haiku(self.is_cross, env) or for_darwin(self.is_cross, env): - return [] - return ['-pthread'] - - def linker_to_compiler_args(self, args): - return args - - def has_arguments(self, args, env, code, mode): - return self.compiles(code, env, extra_args=args, mode=mode) - - def has_multi_arguments(self, args, env): - for arg in args[:]: - # some compilers, e.g. GCC, don't warn for unsupported warning-disable - # flags, so when we are testing a flag like "-Wno-forgotten-towel", also - # check the equivalent enable flag too "-Wforgotten-towel" - if arg.startswith('-Wno-'): - args.append('-W' + arg[5:]) - if arg.startswith('-Wl,'): - mlog.warning('{} looks like a linker argument, ' - 'but has_argument and other similar methods only ' - 'support checking compiler arguments. Using them ' - 'to check linker arguments are never supported, ' - 'and results are likely to be wrong regardless of ' - 'the compiler you are using. has_link_argument or ' - 'other similar method can be used instead.' - .format(arg)) - code = 'int i;\n' - return self.has_arguments(args, env, code, mode='compile') - - def has_multi_link_arguments(self, args, env): - # First time we check for link flags we need to first check if we have - # --fatal-warnings, otherwise some linker checks could give some - # false positive. - fatal_warnings_args = ['-Wl,--fatal-warnings'] - if self.has_fatal_warnings_link_arg is None: - self.has_fatal_warnings_link_arg = False - self.has_fatal_warnings_link_arg = self.has_multi_link_arguments(fatal_warnings_args, env)[0] - - if self.has_fatal_warnings_link_arg: - args = fatal_warnings_args + args - - args = self.linker_to_compiler_args(args) - code = 'int main(int argc, char **argv) { return 0; }' - return self.has_arguments(args, env, code, mode='link') - - @staticmethod - def concatenate_string_literals(s): - pattern = re.compile(r'(?P<pre>.*([^\\]")|^")(?P<str1>([^\\"]|\\.)*)"\s+"(?P<str2>([^\\"]|\\.)*)(?P<post>".*)') - ret = s - m = pattern.match(ret) - while m: - ret = ''.join(m.group('pre', 'str1', 'str2', 'post')) - m = pattern.match(ret) - return ret - - def has_func_attribute(self, name, env): - # Just assume that if we're not on windows that dllimport and dllexport - # don't work - if not (for_windows(env.is_cross_build(), env) or - for_cygwin(env.is_cross_build(), env)): - if name in ['dllimport', 'dllexport']: - return False, False - - # Clang and GCC both return warnings if the __attribute__ is undefined, - # so set -Werror - return self.compiles(self.attribute_check_func(name), env, extra_args='-Werror') - class ClangCCompiler(ClangCompiler, CCompiler): def __init__(self, exelist, version, compiler_type, is_cross, exe_wrapper=None, **kwargs): @@ -1392,170 +251,12 @@ class IntelCCompiler(IntelCompiler, CCompiler): return args -class VisualStudioCCompiler(CCompiler): - std_warn_args = ['/W3'] - std_opt_args = ['/O2'] - ignore_libs = unixy_compiler_internal_libs - internal_libs = () +class VisualStudioLikeCCompilerMixin: - crt_args = {'none': [], - 'md': ['/MD'], - 'mdd': ['/MDd'], - 'mt': ['/MT'], - 'mtd': ['/MTd'], - } - - def __init__(self, exelist, version, is_cross, exe_wrap, target): - CCompiler.__init__(self, exelist, version, is_cross, exe_wrap) - self.id = 'msvc' - # /showIncludes is needed for build dependency tracking in Ninja - # See: https://ninja-build.org/manual.html#_deps - self.always_args = ['/nologo', '/showIncludes'] - self.warn_args = {'0': ['/W1'], - '1': ['/W2'], - '2': ['/W3'], - '3': ['/W4']} - self.base_options = ['b_pch', 'b_ndebug', 'b_vscrt'] # FIXME add lto, pgo and the like - self.target = target - self.is_64 = ('x64' in target) or ('x86_64' in target) - # do some canonicalization of target machine - if 'x86_64' in target: - self.machine = 'x64' - elif '86' in target: - self.machine = 'x86' - else: - self.machine = target - - # Override CCompiler.get_always_args - def get_always_args(self): - return self.always_args - - def get_linker_debug_crt_args(self): - """ - Arguments needed to select a debug crt for the linker - - Sometimes we need to manually select the CRT (C runtime) to use with - MSVC. One example is when trying to link with static libraries since - MSVC won't auto-select a CRT for us in that case and will error out - asking us to select one. - """ - return ['/MDd'] - - def get_buildtype_args(self, buildtype): - args = compilers.msvc_buildtype_args[buildtype] - if self.id == 'msvc' and version_compare(self.version, '<18.0'): - args = [arg for arg in args if arg != '/Gw'] - return args - - def get_buildtype_linker_args(self, buildtype): - return compilers.msvc_buildtype_linker_args[buildtype] - - def get_pch_suffix(self): - return 'pch' - - def get_pch_name(self, header): - chopped = os.path.basename(header).split('.')[:-1] - chopped.append(self.get_pch_suffix()) - pchname = '.'.join(chopped) - return pchname - - def get_pch_use_args(self, pch_dir, header): - base = os.path.basename(header) - if self.id == 'clang-cl': - base = header - pchname = self.get_pch_name(header) - return ['/FI' + base, '/Yu' + base, '/Fp' + os.path.join(pch_dir, pchname)] - - def get_preprocess_only_args(self): - return ['/EP'] - - def get_compile_only_args(self): - return ['/c'] - - def get_no_optimization_args(self): - return ['/Od'] - - def get_output_args(self, target): - if target.endswith('.exe'): - return ['/Fe' + target] - return ['/Fo' + target] - - def get_optimization_args(self, optimization_level): - return compilers.msvc_optimization_args[optimization_level] - - def get_debug_args(self, is_debug): - return compilers.msvc_debug_args[is_debug] - - def get_dependency_gen_args(self, outtarget, outfile): - return [] - - def get_linker_exelist(self): - # FIXME, should have same path as compiler. - # FIXME, should be controllable via cross-file. - if self.id == 'clang-cl': - return ['lld-link'] - else: - return ['link'] - - def get_linker_always_args(self): - return ['/nologo'] - - def get_linker_output_args(self, outputname): - return ['/MACHINE:' + self.machine, '/OUT:' + outputname] - - def get_linker_search_args(self, dirname): - return ['/LIBPATH:' + dirname] - - def linker_to_compiler_args(self, args): - return ['/link'] + args - - def get_gui_app_args(self, value): - # the default is for the linker to guess the subsystem based on presence - # of main or WinMain symbols, so always be explicit - if value: - return ['/SUBSYSTEM:WINDOWS'] - else: - return ['/SUBSYSTEM:CONSOLE'] - - def get_pic_args(self): - return [] # PIC is handled by the loader on Windows - - def gen_export_dynamic_link_args(self, env): - return [] # Not applicable with MSVC - - def get_std_shared_lib_link_args(self): - return ['/DLL'] - - def gen_vs_module_defs_args(self, defsfile): - if not isinstance(defsfile, str): - raise RuntimeError('Module definitions file should be str') - # With MSVC, DLLs only export symbols that are explicitly exported, - # so if a module defs file is specified, we use that to export symbols - return ['/DEF:' + defsfile] - - def gen_pch_args(self, header, source, pchname): - objname = os.path.splitext(pchname)[0] + '.obj' - return objname, ['/Yc' + header, '/Fp' + pchname, '/Fo' + objname] - - def gen_import_library_args(self, implibname): - "The name of the outputted import library" - return ['/IMPLIB:' + implibname] - - def build_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath): - return [] - - def openmp_flags(self): - return ['/openmp'] - - # FIXME, no idea what these should be. - def thread_flags(self, env): - return [] - - def thread_link_flags(self, env): - return [] + """Shared methods that apply to MSVC-like C compilers.""" def get_options(self): - opts = CCompiler.get_options(self) + opts = super().get_options() opts.update({'c_winlibs': coredata.UserArrayOption('c_winlibs', 'Windows libs to link against.', msvc_winlibs)}) @@ -1564,177 +265,20 @@ class VisualStudioCCompiler(CCompiler): def get_option_link_args(self, options): return options['c_winlibs'].value[:] - @classmethod - def unix_args_to_native(cls, args): - result = [] - for i in args: - # -mms-bitfields is specific to MinGW-GCC - # -pthread is only valid for GCC - if i in ('-mms-bitfields', '-pthread'): - continue - if i.startswith('-L'): - i = '/LIBPATH:' + i[2:] - # Translate GNU-style -lfoo library name to the import library - elif i.startswith('-l'): - name = i[2:] - if name in cls.ignore_libs: - # With MSVC, these are provided by the C runtime which is - # linked in by default - continue - else: - i = name + '.lib' - # -pthread in link flags is only used on Linux - elif i == '-pthread': - continue - result.append(i) - return result - - def get_werror_args(self): - return ['/WX'] - - def get_include_args(self, path, is_system): - if path == '': - path = '.' - # msvc does not have a concept of system header dirs. - return ['-I' + path] - - def compute_parameters_with_absolute_paths(self, parameter_list, build_dir): - for idx, i in enumerate(parameter_list): - if i[:2] == '-I' or i[:2] == '/I': - parameter_list[idx] = i[:2] + os.path.normpath(os.path.join(build_dir, i[2:])) - elif i[:9] == '/LIBPATH:': - parameter_list[idx] = i[:9] + os.path.normpath(os.path.join(build_dir, i[9:])) - - return parameter_list - - # Visual Studio is special. It ignores some arguments it does not - # understand and you can't tell it to error out on those. - # http://stackoverflow.com/questions/15259720/how-can-i-make-the-microsoft-c-compiler-treat-unknown-flags-as-errors-rather-t - def has_arguments(self, args, env, code, mode): - warning_text = '4044' if mode == 'link' else '9002' - if self.id == 'clang-cl' and mode != 'link': - args = args + ['-Werror=unknown-argument'] - with self._build_wrapper(code, env, extra_args=args, mode=mode) as p: - if p.returncode != 0: - return False, p.cached - return not(warning_text in p.stde or warning_text in p.stdo), p.cached - - def get_compile_debugfile_args(self, rel_obj, pch=False): - pdbarr = rel_obj.split('.')[:-1] - pdbarr += ['pdb'] - args = ['/Fd' + '.'.join(pdbarr)] - # When generating a PDB file with PCH, all compile commands write - # to the same PDB file. Hence, we need to serialize the PDB - # writes using /FS since we do parallel builds. This slows down the - # build obviously, which is why we only do this when PCH is on. - # This was added in Visual Studio 2013 (MSVC 18.0). Before that it was - # always on: https://msdn.microsoft.com/en-us/library/dn502518.aspx - if pch and self.id == 'msvc' and version_compare(self.version, '>=18.0'): - args = ['/FS'] + args - return args - - def get_link_debugfile_args(self, targetfile): - pdbarr = targetfile.split('.')[:-1] - pdbarr += ['pdb'] - return ['/DEBUG', '/PDB:' + '.'.join(pdbarr)] - - def get_link_whole_for(self, args): - # Only since VS2015 - args = listify(args) - return ['/WHOLEARCHIVE:' + x for x in args] - - def get_instruction_set_args(self, instruction_set): - if self.is_64: - return vs64_instruction_set_args.get(instruction_set, None) - if self.id == 'msvc' and self.version.split('.')[0] == '16' and instruction_set == 'avx': - # VS documentation says that this exists and should work, but - # it does not. The headers do not contain AVX intrinsics - # and the can not be called. - return None - return vs32_instruction_set_args.get(instruction_set, None) - - def get_toolset_version(self): - if self.id == 'clang-cl': - # I have no idea - return '14.1' - - # See boost/config/compiler/visualc.cpp for up to date mapping - try: - version = int(''.join(self.version.split('.')[0:2])) - except ValueError: - return None - if version < 1310: - return '7.0' - elif version < 1400: - return '7.1' # (Visual Studio 2003) - elif version < 1500: - return '8.0' # (Visual Studio 2005) - elif version < 1600: - return '9.0' # (Visual Studio 2008) - elif version < 1700: - return '10.0' # (Visual Studio 2010) - elif version < 1800: - return '11.0' # (Visual Studio 2012) - elif version < 1900: - return '12.0' # (Visual Studio 2013) - elif version < 1910: - return '14.0' # (Visual Studio 2015) - elif version < 1920: - return '14.1' # (Visual Studio 2017) - elif version < 1930: - return '14.2' # (Visual Studio 2019) - mlog.warning('Could not find toolset for version {!r}'.format(self.version)) - return None - - def get_default_include_dirs(self): - if 'INCLUDE' not in os.environ: - return [] - return os.environ['INCLUDE'].split(os.pathsep) - - def get_crt_compile_args(self, crt_val, buildtype): - if crt_val in self.crt_args: - return self.crt_args[crt_val] - assert(crt_val == 'from_buildtype') - # Match what build type flags used to do. - if buildtype == 'plain': - return [] - elif buildtype == 'debug': - return self.crt_args['mdd'] - elif buildtype == 'debugoptimized': - return self.crt_args['md'] - elif buildtype == 'release': - return self.crt_args['md'] - elif buildtype == 'minsize': - return self.crt_args['md'] - else: - assert(buildtype == 'custom') - raise EnvironmentException('Requested C runtime based on buildtype, but buildtype is "custom".') - - def has_func_attribute(self, name, env): - # MSVC doesn't have __attribute__ like Clang and GCC do, so just return - # false without compiling anything - return name in ['dllimport', 'dllexport'], False - - def get_argument_syntax(self): - return 'msvc' - - def get_allow_undefined_link_args(self): - # link.exe - return ['/FORCE:UNRESOLVED'] +class VisualStudioCCompiler(VisualStudioLikeCompiler, VisualStudioLikeCCompilerMixin, CCompiler): + def __init__(self, exelist, version, is_cross, exe_wrap, target: str): + CCompiler.__init__(self, exelist, version, is_cross, exe_wrap) + VisualStudioLikeCompiler.__init__(self, target) + self.id = 'msvc' -class ClangClCCompiler(VisualStudioCCompiler): +class ClangClCCompiler(VisualStudioLikeCompiler, VisualStudioLikeCCompilerMixin, CCompiler): def __init__(self, exelist, version, is_cross, exe_wrap, target): - super().__init__(exelist, version, is_cross, exe_wrap, target) + CCompiler.__init__(self, exelist, version, is_cross, exe_wrap) + VisualStudioLikeCompiler.__init__(self, target) self.id = 'clang-cl' -class IntelClCCompiler(VisualStudioCCompiler): - def __init__(self, exelist, version, is_cross, exe_wrap, target): - super().__init__(exelist, version, is_cross, exe_wrap, target) - self.id = 'intel' - - class ArmCCompiler(ArmCompiler, CCompiler): def __init__(self, exelist, version, compiler_type, is_cross, exe_wrapper=None, **kwargs): CCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwargs) diff --git a/mesonbuild/compilers/clike.py b/mesonbuild/compilers/clike.py new file mode 100644 index 0000000..e9d5d1d --- /dev/null +++ b/mesonbuild/compilers/clike.py @@ -0,0 +1,1187 @@ +# Copyright 2012-2017 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Mixin classes to be shared between C and C++ compilers. + +Without this we'll end up with awful diamond inherintance problems. The goal +of this is to have mixin's, which are classes that are designed *not* to be +standalone, they only work through inheritance. +""" + +import functools +import glob +import itertools +import os +import re +import subprocess +import typing +from pathlib import Path + +from .. import mesonlib +from ..mesonlib import MachineChoice, LibType +from .. import mlog +from . import compilers + +class CLikeCompiler: + + """Shared bits for the C and CPP Compilers.""" + + # TODO: Replace this manual cache with functools.lru_cache + library_dirs_cache = {} + program_dirs_cache = {} + find_library_cache = {} + find_framework_cache = {} + internal_libs = compilers.unixy_compiler_internal_libs + + def __init__(self, is_cross: bool, exe_wrapper: typing.Optional[str] = None): + # If a child ObjC or CPP class has already set it, don't set it ourselves + self.is_cross = is_cross + self.can_compile_suffixes.add('h') + # If the exe wrapper was not found, pretend it wasn't set so that the + # sanity check is skipped and compiler checks use fallbacks. + if not exe_wrapper or not exe_wrapper.found(): + self.exe_wrapper = None + else: + self.exe_wrapper = exe_wrapper.get_command() + + # Set to None until we actually need to check this + self.has_fatal_warnings_link_arg = None + + def needs_static_linker(self): + return True # When compiling static libraries, so yes. + + def get_always_args(self): + ''' + Args that are always-on for all C compilers other than MSVC + ''' + return ['-pipe'] + compilers.get_largefile_args(self) + + def get_linker_debug_crt_args(self): + """ + Arguments needed to select a debug crt for the linker + This is only needed for MSVC + """ + return [] + + def get_no_stdinc_args(self): + return ['-nostdinc'] + + def get_no_stdlib_link_args(self): + return ['-nostdlib'] + + def get_warn_args(self, level): + return self.warn_args[level] + + def get_no_warn_args(self): + # Almost every compiler uses this for disabling warnings + return ['-w'] + + def get_soname_args(self, *args): + return [] + + def split_shlib_to_parts(self, fname): + return None, fname + + # The default behavior is this, override in MSVC + @functools.lru_cache(maxsize=None) + def build_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath): + if self.compiler_type.is_windows_compiler: + return [] + return self.build_unix_rpath_args(build_dir, from_dir, rpath_paths, build_rpath, install_rpath) + + def get_dependency_gen_args(self, outtarget, outfile): + return ['-MD', '-MQ', outtarget, '-MF', outfile] + + def depfile_for_object(self, objfile): + return objfile + '.' + self.get_depfile_suffix() + + def get_depfile_suffix(self): + return 'd' + + def get_exelist(self): + return self.exelist[:] + + def get_linker_exelist(self): + return self.exelist[:] + + def get_preprocess_only_args(self): + return ['-E', '-P'] + + def get_compile_only_args(self): + return ['-c'] + + def get_no_optimization_args(self): + return ['-O0'] + + def get_compiler_check_args(self): + ''' + Get arguments useful for compiler checks such as being permissive in + the code quality and not doing any optimization. + ''' + return self.get_no_optimization_args() + + def get_output_args(self, target): + return ['-o', target] + + def get_linker_output_args(self, outputname): + return ['-o', outputname] + + def get_coverage_args(self): + return ['--coverage'] + + def get_coverage_link_args(self): + return ['--coverage'] + + def get_werror_args(self): + return ['-Werror'] + + def get_std_exe_link_args(self): + return [] + + def get_include_args(self, path, is_system): + if path == '': + path = '.' + if is_system: + return ['-isystem', path] + return ['-I' + path] + + def get_std_shared_lib_link_args(self): + return ['-shared'] + + @functools.lru_cache() + def _get_search_dirs(self, env): + extra_args = ['--print-search-dirs'] + stdo = None + with self._build_wrapper('', env, extra_args=extra_args, + dependencies=None, mode='compile', + want_output=True) as p: + stdo = p.stdo + return stdo + + @staticmethod + def _split_fetch_real_dirs(pathstr, sep=':'): + paths = [] + for p in pathstr.split(sep): + # GCC returns paths like this: + # /usr/lib/gcc/x86_64-linux-gnu/8/../../../../x86_64-linux-gnu/lib + # It would make sense to normalize them to get rid of the .. parts + # Sadly when you are on a merged /usr fs it also kills these: + # /lib/x86_64-linux-gnu + # since /lib is a symlink to /usr/lib. This would mean + # paths under /lib would be considered not a "system path", + # which is wrong and breaks things. Store everything, just to be sure. + pobj = Path(p) + unresolved = pobj.as_posix() + if pobj.exists(): + if unresolved not in paths: + paths.append(unresolved) + try: + resolved = Path(p).resolve().as_posix() + if resolved not in paths: + paths.append(resolved) + except FileNotFoundError: + pass + return tuple(paths) + + def get_compiler_dirs(self, env, name): + ''' + Get dirs from the compiler, either `libraries:` or `programs:` + ''' + stdo = self._get_search_dirs(env) + for line in stdo.split('\n'): + if line.startswith(name + ':'): + return self._split_fetch_real_dirs(line.split('=', 1)[1]) + return () + + @functools.lru_cache() + def get_library_dirs(self, env, elf_class = None): + dirs = self.get_compiler_dirs(env, 'libraries') + if elf_class is None or elf_class == 0: + return dirs + + # if we do have an elf class for 32-bit or 64-bit, we want to check that + # the directory in question contains libraries of the appropriate class. Since + # system directories aren't mixed, we only need to check one file for each + # directory and go by that. If we can't check the file for some reason, assume + # the compiler knows what it's doing, and accept the directory anyway. + retval = [] + for d in dirs: + files = [f for f in os.listdir(d) if f.endswith('.so') and os.path.isfile(os.path.join(d, f))] + # if no files, accept directory and move on + if not files: + retval.append(d) + continue + file_to_check = os.path.join(d, files[0]) + with open(file_to_check, 'rb') as fd: + header = fd.read(5) + # if file is not an ELF file, it's weird, but accept dir + # if it is elf, and the class matches, accept dir + if header[1:4] != b'ELF' or int(header[4]) == elf_class: + retval.append(d) + # at this point, it's an ELF file which doesn't match the + # appropriate elf_class, so skip this one + pass + return tuple(retval) + + @functools.lru_cache() + def get_program_dirs(self, env): + ''' + Programs used by the compiler. Also where toolchain DLLs such as + libstdc++-6.dll are found with MinGW. + ''' + return self.get_compiler_dirs(env, 'programs') + + def get_pic_args(self): + return ['-fPIC'] + + def name_string(self): + return ' '.join(self.exelist) + + def get_pch_use_args(self, pch_dir, header): + return ['-include', os.path.basename(header)] + + def get_pch_name(self, header_name): + return os.path.basename(header_name) + '.' + self.get_pch_suffix() + + def get_linker_search_args(self, dirname): + return ['-L' + dirname] + + def get_default_include_dirs(self): + return [] + + def gen_export_dynamic_link_args(self, env): + if mesonlib.for_windows(env.is_cross_build(), env) or mesonlib.for_cygwin(env.is_cross_build(), env): + return ['-Wl,--export-all-symbols'] + elif mesonlib.for_darwin(env.is_cross_build(), env): + return [] + else: + return ['-Wl,-export-dynamic'] + + def gen_import_library_args(self, implibname): + """ + The name of the outputted import library + + This implementation is used only on Windows by compilers that use GNU ld + """ + return ['-Wl,--out-implib=' + implibname] + + def sanity_check_impl(self, work_dir, environment, sname, code): + mlog.debug('Sanity testing ' + self.get_display_language() + ' compiler:', ' '.join(self.exelist)) + mlog.debug('Is cross compiler: %s.' % str(self.is_cross)) + + source_name = os.path.join(work_dir, sname) + binname = sname.rsplit('.', 1)[0] + mode = 'link' + if self.is_cross: + binname += '_cross' + if self.exe_wrapper is None: + # Linking cross built apps is painful. You can't really + # tell if you should use -nostdlib or not and for example + # on OSX the compiler binary is the same but you need + # a ton of compiler flags to differentiate between + # arm and x86_64. So just compile. + mode = 'compile' + extra_flags = self._get_basic_compiler_args(environment, mode) + + # Is a valid executable output for all toolchains and platforms + binname += '.exe' + # Write binary check source + binary_name = os.path.join(work_dir, binname) + with open(source_name, 'w') as ofile: + ofile.write(code) + # Compile sanity check + cmdlist = self.exelist + extra_flags + [source_name] + self.get_output_args(binary_name) + pc, stdo, stde = mesonlib.Popen_safe(cmdlist, cwd=work_dir) + mlog.debug('Sanity check compiler command line:', ' '.join(cmdlist)) + mlog.debug('Sanity check compile stdout:') + mlog.debug(stdo) + mlog.debug('-----\nSanity check compile stderr:') + mlog.debug(stde) + mlog.debug('-----') + if pc.returncode != 0: + raise mesonlib.EnvironmentException('Compiler {0} can not compile programs.'.format(self.name_string())) + # Run sanity check + if self.is_cross: + if self.exe_wrapper is None: + # Can't check if the binaries run so we have to assume they do + return + cmdlist = self.exe_wrapper + [binary_name] + else: + cmdlist = [binary_name] + mlog.debug('Running test binary command: ' + ' '.join(cmdlist)) + try: + pe = subprocess.Popen(cmdlist) + except Exception as e: + raise mesonlib.EnvironmentException('Could not invoke sanity test executable: %s.' % str(e)) + pe.wait() + if pe.returncode != 0: + raise mesonlib.EnvironmentException('Executables created by {0} compiler {1} are not runnable.'.format(self.language, self.name_string())) + + def sanity_check(self, work_dir, environment): + code = 'int main(int argc, char **argv) { int class=0; return class; }\n' + return self.sanity_check_impl(work_dir, environment, 'sanitycheckc.c', code) + + def check_header(self, hname, prefix, env, *, extra_args=None, dependencies=None): + fargs = {'prefix': prefix, 'header': hname} + code = '''{prefix} + #include <{header}>''' + return self.compiles(code.format(**fargs), env, extra_args=extra_args, + dependencies=dependencies) + + def has_header(self, hname, prefix, env, *, extra_args=None, dependencies=None, disable_cache=False): + fargs = {'prefix': prefix, 'header': hname} + code = '''{prefix} + #ifdef __has_include + #if !__has_include("{header}") + #error "Header '{header}' could not be found" + #endif + #else + #include <{header}> + #endif''' + return self.compiles(code.format(**fargs), env, extra_args=extra_args, + dependencies=dependencies, mode='preprocess', disable_cache=disable_cache) + + def has_header_symbol(self, hname, symbol, prefix, env, *, extra_args=None, dependencies=None): + fargs = {'prefix': prefix, 'header': hname, 'symbol': symbol} + t = '''{prefix} + #include <{header}> + int main () {{ + /* If it's not defined as a macro, try to use as a symbol */ + #ifndef {symbol} + {symbol}; + #endif + }}''' + return self.compiles(t.format(**fargs), env, extra_args=extra_args, + dependencies=dependencies) + + def _get_basic_compiler_args(self, env, mode): + args = [] + # Select a CRT if needed since we're linking + if mode == 'link': + args += self.get_linker_debug_crt_args() + if env.is_cross_build() and not self.is_cross: + for_machine = MachineChoice.BUILD + else: + for_machine = MachineChoice.HOST + if mode in {'compile', 'preprocess'}: + # Add CFLAGS/CXXFLAGS/OBJCFLAGS/OBJCXXFLAGS and CPPFLAGS from the env + sys_args = env.coredata.get_external_args(for_machine, self.language) + # Apparently it is a thing to inject linker flags both + # via CFLAGS _and_ LDFLAGS, even though the former are + # also used during linking. These flags can break + # argument checks. Thanks, Autotools. + cleaned_sys_args = self.remove_linkerlike_args(sys_args) + args += cleaned_sys_args + elif mode == 'link': + # Add LDFLAGS from the env + args += env.coredata.get_external_link_args(for_machine, self.language) + return args + + def _get_compiler_check_args(self, env, extra_args, dependencies, mode='compile'): + if extra_args is None: + extra_args = [] + else: + extra_args = mesonlib.listify(extra_args) + extra_args = mesonlib.listify([e(mode) if callable(e) else e for e in extra_args]) + + if dependencies is None: + dependencies = [] + elif not isinstance(dependencies, list): + dependencies = [dependencies] + # Collect compiler arguments + args = compilers.CompilerArgs(self) + for d in dependencies: + # Add compile flags needed by dependencies + args += d.get_compile_args() + if mode == 'link': + # Add link flags needed to find dependencies + args += d.get_link_args() + + args += self._get_basic_compiler_args(env, mode) + + args += self.get_compiler_check_args() + # extra_args must override all other arguments, so we add them last + args += extra_args + return args + + def compiles(self, code, env, *, extra_args=None, dependencies=None, mode='compile', disable_cache=False): + with self._build_wrapper(code, env, extra_args, dependencies, mode, disable_cache=disable_cache) as p: + return p.returncode == 0, p.cached + + def _build_wrapper(self, code, env, extra_args, dependencies=None, mode='compile', want_output=False, disable_cache=False): + args = self._get_compiler_check_args(env, extra_args, dependencies, mode) + if disable_cache or want_output: + return self.compile(code, extra_args=args, mode=mode, want_output=want_output) + return self.cached_compile(code, env.coredata, extra_args=args, mode=mode) + + def links(self, code, env, *, extra_args=None, dependencies=None, disable_cache=False): + return self.compiles(code, env, extra_args=extra_args, + dependencies=dependencies, mode='link', disable_cache=disable_cache) + + def run(self, code: str, env, *, extra_args=None, dependencies=None): + if self.is_cross and self.exe_wrapper is None: + raise mesonlib.CrossNoRunException('Can not run test applications in this cross environment.') + with self._build_wrapper(code, env, extra_args, dependencies, mode='link', want_output=True) as p: + if p.returncode != 0: + mlog.debug('Could not compile test file %s: %d\n' % ( + p.input_name, + p.returncode)) + return compilers.RunResult(False) + if self.is_cross: + cmdlist = self.exe_wrapper + [p.output_name] + else: + cmdlist = p.output_name + try: + pe, so, se = mesonlib.Popen_safe(cmdlist) + except Exception as e: + mlog.debug('Could not run: %s (error: %s)\n' % (cmdlist, e)) + return compilers.RunResult(False) + + mlog.debug('Program stdout:\n') + mlog.debug(so) + mlog.debug('Program stderr:\n') + mlog.debug(se) + return compilers.RunResult(True, pe.returncode, so, se) + + def _compile_int(self, expression, prefix, env, extra_args, dependencies): + fargs = {'prefix': prefix, 'expression': expression} + t = '''#include <stdio.h> + {prefix} + int main() {{ static int a[1-2*!({expression})]; a[0]=0; return 0; }}''' + return self.compiles(t.format(**fargs), env, extra_args=extra_args, + dependencies=dependencies)[0] + + def cross_compute_int(self, expression, low, high, guess, prefix, env, extra_args, dependencies): + # Try user's guess first + if isinstance(guess, int): + if self._compile_int('%s == %d' % (expression, guess), prefix, env, extra_args, dependencies): + return guess + + # If no bounds are given, compute them in the limit of int32 + maxint = 0x7fffffff + minint = -0x80000000 + if not isinstance(low, int) or not isinstance(high, int): + if self._compile_int('%s >= 0' % (expression), prefix, env, extra_args, dependencies): + low = cur = 0 + while self._compile_int('%s > %d' % (expression, cur), prefix, env, extra_args, dependencies): + low = cur + 1 + if low > maxint: + raise mesonlib.EnvironmentException('Cross-compile check overflowed') + cur = cur * 2 + 1 + if cur > maxint: + cur = maxint + high = cur + else: + low = cur = -1 + while self._compile_int('%s < %d' % (expression, cur), prefix, env, extra_args, dependencies): + high = cur - 1 + if high < minint: + raise mesonlib.EnvironmentException('Cross-compile check overflowed') + cur = cur * 2 + if cur < minint: + cur = minint + low = cur + else: + # Sanity check limits given by user + if high < low: + raise mesonlib.EnvironmentException('high limit smaller than low limit') + condition = '%s <= %d && %s >= %d' % (expression, high, expression, low) + if not self._compile_int(condition, prefix, env, extra_args, dependencies): + raise mesonlib.EnvironmentException('Value out of given range') + + # Binary search + while low != high: + cur = low + int((high - low) / 2) + if self._compile_int('%s <= %d' % (expression, cur), prefix, env, extra_args, dependencies): + high = cur + else: + low = cur + 1 + + return low + + def compute_int(self, expression, low, high, guess, prefix, env, *, extra_args=None, dependencies=None): + if extra_args is None: + extra_args = [] + if self.is_cross: + return self.cross_compute_int(expression, low, high, guess, prefix, env, extra_args, dependencies) + fargs = {'prefix': prefix, 'expression': expression} + t = '''#include<stdio.h> + {prefix} + int main(int argc, char **argv) {{ + printf("%ld\\n", (long)({expression})); + return 0; + }};''' + res = self.run(t.format(**fargs), env, extra_args=extra_args, + dependencies=dependencies) + if not res.compiled: + return -1 + if res.returncode != 0: + raise mesonlib.EnvironmentException('Could not run compute_int test binary.') + return int(res.stdout) + + def cross_sizeof(self, typename, prefix, env, *, extra_args=None, dependencies=None): + if extra_args is None: + extra_args = [] + fargs = {'prefix': prefix, 'type': typename} + t = '''#include <stdio.h> + {prefix} + int main(int argc, char **argv) {{ + {type} something; + }}''' + if not self.compiles(t.format(**fargs), env, extra_args=extra_args, + dependencies=dependencies)[0]: + return -1 + return self.cross_compute_int('sizeof(%s)' % typename, None, None, None, prefix, env, extra_args, dependencies) + + def sizeof(self, typename, prefix, env, *, extra_args=None, dependencies=None): + if extra_args is None: + extra_args = [] + fargs = {'prefix': prefix, 'type': typename} + if self.is_cross: + return self.cross_sizeof(typename, prefix, env, extra_args=extra_args, + dependencies=dependencies) + t = '''#include<stdio.h> + {prefix} + int main(int argc, char **argv) {{ + printf("%ld\\n", (long)(sizeof({type}))); + return 0; + }};''' + res = self.run(t.format(**fargs), env, extra_args=extra_args, + dependencies=dependencies) + if not res.compiled: + return -1 + if res.returncode != 0: + raise mesonlib.EnvironmentException('Could not run sizeof test binary.') + return int(res.stdout) + + def cross_alignment(self, typename, prefix, env, *, extra_args=None, dependencies=None): + if extra_args is None: + extra_args = [] + fargs = {'prefix': prefix, 'type': typename} + t = '''#include <stdio.h> + {prefix} + int main(int argc, char **argv) {{ + {type} something; + }}''' + if not self.compiles(t.format(**fargs), env, extra_args=extra_args, + dependencies=dependencies)[0]: + return -1 + t = '''#include <stddef.h> + {prefix} + struct tmp {{ + char c; + {type} target; + }};''' + return self.cross_compute_int('offsetof(struct tmp, target)', None, None, None, t.format(**fargs), env, extra_args, dependencies) + + def alignment(self, typename, prefix, env, *, extra_args=None, dependencies=None): + if extra_args is None: + extra_args = [] + if self.is_cross: + return self.cross_alignment(typename, prefix, env, extra_args=extra_args, + dependencies=dependencies) + fargs = {'prefix': prefix, 'type': typename} + t = '''#include <stdio.h> + #include <stddef.h> + {prefix} + struct tmp {{ + char c; + {type} target; + }}; + int main(int argc, char **argv) {{ + printf("%d", (int)offsetof(struct tmp, target)); + return 0; + }}''' + res = self.run(t.format(**fargs), env, extra_args=extra_args, + dependencies=dependencies) + if not res.compiled: + raise mesonlib.EnvironmentException('Could not compile alignment test.') + if res.returncode != 0: + raise mesonlib.EnvironmentException('Could not run alignment test binary.') + align = int(res.stdout) + if align == 0: + raise mesonlib.EnvironmentException('Could not determine alignment of %s. Sorry. You might want to file a bug.' % typename) + return align + + def get_define(self, dname, prefix, env, extra_args, dependencies, disable_cache=False): + delim = '"MESON_GET_DEFINE_DELIMITER"' + fargs = {'prefix': prefix, 'define': dname, 'delim': delim} + code = ''' + {prefix} + #ifndef {define} + # define {define} + #endif + {delim}\n{define}''' + args = self._get_compiler_check_args(env, extra_args, dependencies, + mode='preprocess').to_native() + func = lambda: self.cached_compile(code.format(**fargs), env.coredata, extra_args=args, mode='preprocess') + if disable_cache: + func = lambda: self.compile(code.format(**fargs), extra_args=args, mode='preprocess') + with func() as p: + cached = p.cached + if p.returncode != 0: + raise mesonlib.EnvironmentException('Could not get define {!r}'.format(dname)) + # Get the preprocessed value after the delimiter, + # minus the extra newline at the end and + # merge string literals. + return self.concatenate_string_literals(p.stdo.split(delim + '\n')[-1][:-1]), cached + + def get_return_value(self, fname, rtype, prefix, env, extra_args, dependencies): + if rtype == 'string': + fmt = '%s' + cast = '(char*)' + elif rtype == 'int': + fmt = '%lli' + cast = '(long long int)' + else: + raise AssertionError('BUG: Unknown return type {!r}'.format(rtype)) + fargs = {'prefix': prefix, 'f': fname, 'cast': cast, 'fmt': fmt} + code = '''{prefix} + #include <stdio.h> + int main(int argc, char *argv[]) {{ + printf ("{fmt}", {cast} {f}()); + }}'''.format(**fargs) + res = self.run(code, env, extra_args=extra_args, dependencies=dependencies) + if not res.compiled: + m = 'Could not get return value of {}()' + raise mesonlib.EnvironmentException(m.format(fname)) + if rtype == 'string': + return res.stdout + elif rtype == 'int': + try: + return int(res.stdout.strip()) + except ValueError: + m = 'Return value of {}() is not an int' + raise mesonlib.EnvironmentException(m.format(fname)) + + @staticmethod + def _no_prototype_templ(): + """ + Try to find the function without a prototype from a header by defining + our own dummy prototype and trying to link with the C library (and + whatever else the compiler links in by default). This is very similar + to the check performed by Autoconf for AC_CHECK_FUNCS. + """ + # Define the symbol to something else since it is defined by the + # includes or defines listed by the user or by the compiler. This may + # include, for instance _GNU_SOURCE which must be defined before + # limits.h, which includes features.h + # Then, undef the symbol to get rid of it completely. + head = ''' + #define {func} meson_disable_define_of_{func} + {prefix} + #include <limits.h> + #undef {func} + ''' + # Override any GCC internal prototype and declare our own definition for + # the symbol. Use char because that's unlikely to be an actual return + # value for a function which ensures that we override the definition. + head += ''' + #ifdef __cplusplus + extern "C" + #endif + char {func} (); + ''' + # The actual function call + main = ''' + int main () {{ + return {func} (); + }}''' + return head, main + + @staticmethod + def _have_prototype_templ(): + """ + Returns a head-er and main() call that uses the headers listed by the + user for the function prototype while checking if a function exists. + """ + # Add the 'prefix', aka defines, includes, etc that the user provides + # This may include, for instance _GNU_SOURCE which must be defined + # before limits.h, which includes features.h + head = '{prefix}\n#include <limits.h>\n' + # We don't know what the function takes or returns, so return it as an int. + # Just taking the address or comparing it to void is not enough because + # compilers are smart enough to optimize it away. The resulting binary + # is not run so we don't care what the return value is. + main = '''\nint main() {{ + void *a = (void*) &{func}; + long b = (long) a; + return (int) b; + }}''' + return head, main + + def has_function(self, funcname, prefix, env, *, extra_args=None, dependencies=None): + """ + First, this function looks for the symbol in the default libraries + provided by the compiler (stdlib + a few others usually). If that + fails, it checks if any of the headers specified in the prefix provide + an implementation of the function, and if that fails, it checks if it's + implemented as a compiler-builtin. + """ + if extra_args is None: + extra_args = [] + + # Short-circuit if the check is already provided by the cross-info file + varname = 'has function ' + funcname + varname = varname.replace(' ', '_') + if self.is_cross: + val = env.properties.host.get(varname, None) + if val is not None: + if isinstance(val, bool): + return val, False + raise mesonlib.EnvironmentException('Cross variable {0} is not a boolean.'.format(varname)) + + fargs = {'prefix': prefix, 'func': funcname} + + # glibc defines functions that are not available on Linux as stubs that + # fail with ENOSYS (such as e.g. lchmod). In this case we want to fail + # instead of detecting the stub as a valid symbol. + # We already included limits.h earlier to ensure that these are defined + # for stub functions. + stubs_fail = ''' + #if defined __stub_{func} || defined __stub___{func} + fail fail fail this function is not going to work + #endif + ''' + + # If we have any includes in the prefix supplied by the user, assume + # that the user wants us to use the symbol prototype defined in those + # includes. If not, then try to do the Autoconf-style check with + # a dummy prototype definition of our own. + # This is needed when the linker determines symbol availability from an + # SDK based on the prototype in the header provided by the SDK. + # Ignoring this prototype would result in the symbol always being + # marked as available. + if '#include' in prefix: + head, main = self._have_prototype_templ() + else: + head, main = self._no_prototype_templ() + templ = head + stubs_fail + main + + res, cached = self.links(templ.format(**fargs), env, extra_args=extra_args, + dependencies=dependencies) + if res: + return True, cached + + # MSVC does not have compiler __builtin_-s. + if self.get_id() == 'msvc': + return False, False + + # Detect function as a built-in + # + # Some functions like alloca() are defined as compiler built-ins which + # are inlined by the compiler and you can't take their address, so we + # need to look for them differently. On nice compilers like clang, we + # can just directly use the __has_builtin() macro. + fargs['no_includes'] = '#include' not in prefix + t = '''{prefix} + int main() {{ + #ifdef __has_builtin + #if !__has_builtin(__builtin_{func}) + #error "__builtin_{func} not found" + #endif + #elif ! defined({func}) + /* Check for __builtin_{func} only if no includes were added to the + * prefix above, which means no definition of {func} can be found. + * We would always check for this, but we get false positives on + * MSYS2 if we do. Their toolchain is broken, but we can at least + * give them a workaround. */ + #if {no_includes:d} + __builtin_{func}; + #else + #error "No definition for __builtin_{func} found in the prefix" + #endif + #endif + }}''' + return self.links(t.format(**fargs), env, extra_args=extra_args, + dependencies=dependencies) + + def has_members(self, typename, membernames, prefix, env, *, extra_args=None, dependencies=None): + if extra_args is None: + extra_args = [] + fargs = {'prefix': prefix, 'type': typename, 'name': 'foo'} + # Create code that accesses all members + members = '' + for member in membernames: + members += '{}.{};\n'.format(fargs['name'], member) + fargs['members'] = members + t = '''{prefix} + void bar() {{ + {type} {name}; + {members} + }};''' + return self.compiles(t.format(**fargs), env, extra_args=extra_args, + dependencies=dependencies) + + def has_type(self, typename, prefix, env, extra_args, dependencies=None): + fargs = {'prefix': prefix, 'type': typename} + t = '''{prefix} + void bar() {{ + sizeof({type}); + }};''' + return self.compiles(t.format(**fargs), env, extra_args=extra_args, + dependencies=dependencies) + + def symbols_have_underscore_prefix(self, env): + ''' + Check if the compiler prefixes an underscore to global C symbols + ''' + symbol_name = b'meson_uscore_prefix' + code = '''#ifdef __cplusplus + extern "C" { + #endif + void ''' + symbol_name.decode() + ''' () {} + #ifdef __cplusplus + } + #endif + ''' + args = self.get_compiler_check_args() + n = 'symbols_have_underscore_prefix' + with self.compile(code, args, 'compile', want_output=True) as p: + if p.returncode != 0: + m = 'BUG: Unable to compile {!r} check: {}' + raise RuntimeError(m.format(n, p.stdo)) + if not os.path.isfile(p.output_name): + m = 'BUG: Can\'t find compiled test code for {!r} check' + raise RuntimeError(m.format(n)) + with open(p.output_name, 'rb') as o: + for line in o: + # Check if the underscore form of the symbol is somewhere + # in the output file. + if b'_' + symbol_name in line: + return True + # Else, check if the non-underscored form is present + elif symbol_name in line: + return False + raise RuntimeError('BUG: {!r} check failed unexpectedly'.format(n)) + + def _get_patterns(self, env, prefixes, suffixes, shared=False): + patterns = [] + for p in prefixes: + for s in suffixes: + patterns.append(p + '{}.' + s) + if shared and mesonlib.for_openbsd(self.is_cross, env): + # Shared libraries on OpenBSD can be named libfoo.so.X.Y: + # https://www.openbsd.org/faq/ports/specialtopics.html#SharedLibs + # + # This globbing is probably the best matching we can do since regex + # is expensive. It's wrong in many edge cases, but it will match + # correctly-named libraries and hopefully no one on OpenBSD names + # their files libfoo.so.9a.7b.1.0 + for p in prefixes: + patterns.append(p + '{}.so.[0-9]*.[0-9]*') + return patterns + + def get_library_naming(self, env, libtype: LibType, strict=False): + ''' + Get library prefixes and suffixes for the target platform ordered by + priority + ''' + stlibext = ['a'] + # We've always allowed libname to be both `foo` and `libfoo`, and now + # people depend on it. Also, some people use prebuilt `foo.so` instead + # of `libfoo.so` for unknown reasons, and may also want to create + # `foo.so` by setting name_prefix to '' + if strict and not isinstance(self, compilers.VisualStudioLikeCompiler): # lib prefix is not usually used with msvc + prefixes = ['lib'] + else: + prefixes = ['lib', ''] + # Library suffixes and prefixes + if mesonlib.for_darwin(env.is_cross_build(), env): + shlibext = ['dylib', 'so'] + elif mesonlib.for_windows(env.is_cross_build(), env): + # FIXME: .lib files can be import or static so we should read the + # file, figure out which one it is, and reject the wrong kind. + if isinstance(self, compilers.VisualStudioLikeCompiler): + shlibext = ['lib'] + else: + shlibext = ['dll.a', 'lib', 'dll'] + # Yep, static libraries can also be foo.lib + stlibext += ['lib'] + elif mesonlib.for_cygwin(env.is_cross_build(), env): + shlibext = ['dll', 'dll.a'] + prefixes = ['cyg'] + prefixes + else: + # Linux/BSDs + shlibext = ['so'] + # Search priority + if libtype is LibType.PREFER_SHARED: + patterns = self._get_patterns(env, prefixes, shlibext, True) + patterns.extend([x for x in self._get_patterns(env, prefixes, stlibext, False) if x not in patterns]) + elif libtype is LibType.PREFER_STATIC: + patterns = self._get_patterns(env, prefixes, stlibext, False) + patterns.extend([x for x in self._get_patterns(env, prefixes, shlibext, True) if x not in patterns]) + elif libtype is LibType.SHARED: + patterns = self._get_patterns(env, prefixes, shlibext, True) + else: + assert libtype is LibType.STATIC + patterns = self._get_patterns(env, prefixes, stlibext, False) + return tuple(patterns) + + @staticmethod + def _sort_shlibs_openbsd(libs): + filtered = [] + for lib in libs: + # Validate file as a shared library of type libfoo.so.X.Y + ret = lib.rsplit('.so.', maxsplit=1) + if len(ret) != 2: + continue + try: + float(ret[1]) + except ValueError: + continue + filtered.append(lib) + float_cmp = lambda x: float(x.rsplit('.so.', maxsplit=1)[1]) + return sorted(filtered, key=float_cmp, reverse=True) + + @classmethod + def _get_trials_from_pattern(cls, pattern, directory, libname): + f = Path(directory) / pattern.format(libname) + # Globbing for OpenBSD + if '*' in pattern: + # NOTE: globbing matches directories and broken symlinks + # so we have to do an isfile test on it later + return [Path(x) for x in cls._sort_shlibs_openbsd(glob.glob(str(f)))] + return [f] + + @staticmethod + def _get_file_from_list(env, files: typing.List[str]) -> Path: + ''' + We just check whether the library exists. We can't do a link check + because the library might have unresolved symbols that require other + libraries. On macOS we check if the library matches our target + architecture. + ''' + # If not building on macOS for Darwin, do a simple file check + files = [Path(f) for f in files] + if not env.machines.host.is_darwin() or not env.machines.build.is_darwin(): + for f in files: + if f.is_file(): + return f + # Run `lipo` and check if the library supports the arch we want + for f in files: + if not f.is_file(): + continue + archs = mesonlib.darwin_get_object_archs(f) + if archs and env.machines.host.cpu_family in archs: + return f + else: + mlog.debug('Rejected {}, supports {} but need {}' + .format(f, archs, env.machines.host.cpu_family)) + return None + + @functools.lru_cache() + def output_is_64bit(self, env): + ''' + returns true if the output produced is 64-bit, false if 32-bit + ''' + return self.sizeof('void *', '', env) == 8 + + def find_library_real(self, libname, env, extra_dirs, code, libtype: LibType): + # First try if we can just add the library as -l. + # Gcc + co seem to prefer builtin lib dirs to -L dirs. + # Only try to find std libs if no extra dirs specified. + # The built-in search procedure will always favour .so and then always + # search for .a. This is only allowed if libtype is LibType.PREFER_SHARED + if ((not extra_dirs and libtype is LibType.PREFER_SHARED) or + libname in self.internal_libs): + args = ['-l' + libname] + largs = self.linker_to_compiler_args(self.get_allow_undefined_link_args()) + if self.links(code, env, extra_args=(args + largs), disable_cache=True)[0]: + return args + # Don't do a manual search for internal libs + if libname in self.internal_libs: + return None + # Not found or we want to use a specific libtype? Try to find the + # library file itself. + patterns = self.get_library_naming(env, libtype) + # try to detect if we are 64-bit or 32-bit. If we can't + # detect, we will just skip path validity checks done in + # get_library_dirs() call + try: + if self.output_is_64bit(env): + elf_class = 2 + else: + elf_class = 1 + except (mesonlib.MesonException, KeyError): # TODO evaluate if catching KeyError is wanted here + elf_class = 0 + # Search in the specified dirs, and then in the system libraries + for d in itertools.chain(extra_dirs, self.get_library_dirs(env, elf_class)): + for p in patterns: + trial = self._get_trials_from_pattern(p, d, libname) + if not trial: + continue + trial = self._get_file_from_list(env, trial) + if not trial: + continue + return [trial.as_posix()] + return None + + def find_library_impl(self, libname, env, extra_dirs, code, libtype: LibType): + # These libraries are either built-in or invalid + if libname in self.ignore_libs: + return [] + if isinstance(extra_dirs, str): + extra_dirs = [extra_dirs] + key = (tuple(self.exelist), libname, tuple(extra_dirs), code, libtype) + if key not in self.find_library_cache: + value = self.find_library_real(libname, env, extra_dirs, code, libtype) + self.find_library_cache[key] = value + else: + value = self.find_library_cache[key] + if value is None: + return None + return value[:] + + def find_library(self, libname, env, extra_dirs, libtype: LibType = LibType.PREFER_SHARED): + code = 'int main(int argc, char **argv) { return 0; }' + return self.find_library_impl(libname, env, extra_dirs, code, libtype) + + def find_framework_paths(self, env): + ''' + These are usually /Library/Frameworks and /System/Library/Frameworks, + unless you select a particular macOS SDK with the -isysroot flag. + You can also add to this by setting -F in CFLAGS. + ''' + if self.id != 'clang': + raise mesonlib.MesonException('Cannot find framework path with non-clang compiler') + # Construct the compiler command-line + commands = self.get_exelist() + ['-v', '-E', '-'] + commands += self.get_always_args() + # Add CFLAGS/CXXFLAGS/OBJCFLAGS/OBJCXXFLAGS from the env + if env.is_cross_build() and not self.is_cross: + for_machine = MachineChoice.BUILD + else: + for_machine = MachineChoice.HOST + commands += env.coredata.get_external_args(for_machine, self.language) + mlog.debug('Finding framework path by running: ', ' '.join(commands), '\n') + os_env = os.environ.copy() + os_env['LC_ALL'] = 'C' + _, _, stde = mesonlib.Popen_safe(commands, env=os_env, stdin=subprocess.PIPE) + paths = [] + for line in stde.split('\n'): + if '(framework directory)' not in line: + continue + # line is of the form: + # ` /path/to/framework (framework directory)` + paths.append(line[:-21].strip()) + return paths + + def find_framework_real(self, name, env, extra_dirs, allow_system): + code = 'int main(int argc, char **argv) { return 0; }' + link_args = [] + for d in extra_dirs: + link_args += ['-F' + d] + # We can pass -Z to disable searching in the system frameworks, but + # then we must also pass -L/usr/lib to pick up libSystem.dylib + extra_args = [] if allow_system else ['-Z', '-L/usr/lib'] + link_args += ['-framework', name] + if self.links(code, env, extra_args=(extra_args + link_args), disable_cache=True)[0]: + return link_args + + def find_framework_impl(self, name, env, extra_dirs, allow_system): + if isinstance(extra_dirs, str): + extra_dirs = [extra_dirs] + key = (tuple(self.exelist), name, tuple(extra_dirs), allow_system) + if key in self.find_framework_cache: + value = self.find_framework_cache[key] + else: + value = self.find_framework_real(name, env, extra_dirs, allow_system) + self.find_framework_cache[key] = value + if value is None: + return None + return value[:] + + def find_framework(self, name, env, extra_dirs, allow_system=True): + ''' + Finds the framework with the specified name, and returns link args for + the same or returns None when the framework is not found. + ''' + if self.id != 'clang': + raise mesonlib.MesonException('Cannot find frameworks with non-clang compiler') + return self.find_framework_impl(name, env, extra_dirs, allow_system) + + def thread_flags(self, env): + if mesonlib.for_haiku(self.is_cross, env) or mesonlib.for_darwin(self.is_cross, env): + return [] + return ['-pthread'] + + def thread_link_flags(self, env): + if mesonlib.for_haiku(self.is_cross, env) or mesonlib.for_darwin(self.is_cross, env): + return [] + return ['-pthread'] + + def linker_to_compiler_args(self, args): + return args + + def has_arguments(self, args, env, code, mode): + return self.compiles(code, env, extra_args=args, mode=mode) + + def has_multi_arguments(self, args, env): + for arg in args[:]: + # some compilers, e.g. GCC, don't warn for unsupported warning-disable + # flags, so when we are testing a flag like "-Wno-forgotten-towel", also + # check the equivalent enable flag too "-Wforgotten-towel" + if arg.startswith('-Wno-'): + args.append('-W' + arg[5:]) + if arg.startswith('-Wl,'): + mlog.warning('{} looks like a linker argument, ' + 'but has_argument and other similar methods only ' + 'support checking compiler arguments. Using them ' + 'to check linker arguments are never supported, ' + 'and results are likely to be wrong regardless of ' + 'the compiler you are using. has_link_argument or ' + 'other similar method can be used instead.' + .format(arg)) + code = 'int i;\n' + return self.has_arguments(args, env, code, mode='compile') + + def has_multi_link_arguments(self, args, env): + # First time we check for link flags we need to first check if we have + # --fatal-warnings, otherwise some linker checks could give some + # false positive. + fatal_warnings_args = ['-Wl,--fatal-warnings'] + if self.has_fatal_warnings_link_arg is None: + self.has_fatal_warnings_link_arg = False + self.has_fatal_warnings_link_arg = self.has_multi_link_arguments(fatal_warnings_args, env)[0] + + if self.has_fatal_warnings_link_arg: + args = fatal_warnings_args + args + + args = self.linker_to_compiler_args(args) + code = 'int main(int argc, char **argv) { return 0; }' + return self.has_arguments(args, env, code, mode='link') + + @staticmethod + def concatenate_string_literals(s): + pattern = re.compile(r'(?P<pre>.*([^\\]")|^")(?P<str1>([^\\"]|\\.)*)"\s+"(?P<str2>([^\\"]|\\.)*)(?P<post>".*)') + ret = s + m = pattern.match(ret) + while m: + ret = ''.join(m.group('pre', 'str1', 'str2', 'post')) + m = pattern.match(ret) + return ret + + def has_func_attribute(self, name, env): + # Just assume that if we're not on windows that dllimport and dllexport + # don't work + if not (mesonlib.for_windows(env.is_cross_build(), env) or + mesonlib.for_cygwin(env.is_cross_build(), env)): + if name in ['dllimport', 'dllexport']: + return False, False + + # Clang and GCC both return warnings if the __attribute__ is undefined, + # so set -Werror + return self.compiles(self.attribute_check_func(name), env, extra_args='-Werror') diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 2f3c7b7..a3401e8 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -1529,6 +1529,335 @@ def gnulike_default_include_dirs(compiler, lang): return paths +class VisualStudioLikeCompiler(metaclass=abc.ABCMeta): + + """A common interface for all compilers implementing an MSVC-style + interface. + + A number of compilers attempt to mimic MSVC, with varying levels of + success, such as Clang-CL and ICL (the Intel C/C++ Compiler for Windows). + This classs implements as much common logic as possible. + """ + + std_warn_args = ['/W3'] + std_opt_args = ['/O2'] + ignore_libs = unixy_compiler_internal_libs + internal_libs = () + + crt_args = {'none': [], + 'md': ['/MD'], + 'mdd': ['/MDd'], + 'mt': ['/MT'], + 'mtd': ['/MTd'], + } + # /showIncludes is needed for build dependency tracking in Ninja + # See: https://ninja-build.org/manual.html#_deps + always_args = ['/nologo', '/showIncludes'] + warn_args = {'0': ['/W1'], + '1': ['/W2'], + '2': ['/W3'], + '3': ['/W4'], + } + + def __init__(self, target: str): + self.base_options = ['b_pch', 'b_ndebug', 'b_vscrt'] # FIXME add lto, pgo and the like + self.target = target + self.is_64 = ('x64' in target) or ('x86_64' in target) + # do some canonicalization of target machine + if 'x86_64' in target: + self.machine = 'x64' + elif '86' in target: + self.machine = 'x86' + else: + self.machine = target + + # Override CCompiler.get_always_args + def get_always_args(self): + return self.always_args + + def get_linker_debug_crt_args(self): + """ + Arguments needed to select a debug crt for the linker + + Sometimes we need to manually select the CRT (C runtime) to use with + MSVC. One example is when trying to link with static libraries since + MSVC won't auto-select a CRT for us in that case and will error out + asking us to select one. + """ + return ['/MDd'] + + def get_buildtype_args(self, buildtype): + args = msvc_buildtype_args[buildtype] + if self.id == 'msvc' and version_compare(self.version, '<18.0'): + args = [arg for arg in args if arg != '/Gw'] + return args + + def get_buildtype_linker_args(self, buildtype): + return msvc_buildtype_linker_args[buildtype] + + def get_pch_suffix(self): + return 'pch' + + def get_pch_name(self, header): + chopped = os.path.basename(header).split('.')[:-1] + chopped.append(self.get_pch_suffix()) + pchname = '.'.join(chopped) + return pchname + + def get_pch_use_args(self, pch_dir, header): + base = os.path.basename(header) + if self.id == 'clang-cl': + base = header + pchname = self.get_pch_name(header) + return ['/FI' + base, '/Yu' + base, '/Fp' + os.path.join(pch_dir, pchname)] + + def get_preprocess_only_args(self): + return ['/EP'] + + def get_compile_only_args(self): + return ['/c'] + + def get_no_optimization_args(self): + return ['/Od'] + + def get_output_args(self, target): + if target.endswith('.exe'): + return ['/Fe' + target] + return ['/Fo' + target] + + def get_optimization_args(self, optimization_level): + return msvc_optimization_args[optimization_level] + + def get_debug_args(self, is_debug): + return msvc_debug_args[is_debug] + + def get_dependency_gen_args(self, outtarget, outfile): + return [] + + def get_linker_exelist(self): + # FIXME, should have same path as compiler. + # FIXME, should be controllable via cross-file. + if self.id == 'clang-cl': + return ['lld-link'] + else: + return ['link'] + + def get_linker_always_args(self): + return ['/nologo'] + + def get_linker_output_args(self, outputname): + return ['/MACHINE:' + self.machine, '/OUT:' + outputname] + + def get_linker_search_args(self, dirname): + return ['/LIBPATH:' + dirname] + + def linker_to_compiler_args(self, args): + return ['/link'] + args + + def get_gui_app_args(self, value): + # the default is for the linker to guess the subsystem based on presence + # of main or WinMain symbols, so always be explicit + if value: + return ['/SUBSYSTEM:WINDOWS'] + else: + return ['/SUBSYSTEM:CONSOLE'] + + def get_pic_args(self): + return [] # PIC is handled by the loader on Windows + + def gen_export_dynamic_link_args(self, env): + return [] # Not applicable with MSVC + + def get_std_shared_lib_link_args(self): + return ['/DLL'] + + def gen_vs_module_defs_args(self, defsfile): + if not isinstance(defsfile, str): + raise RuntimeError('Module definitions file should be str') + # With MSVC, DLLs only export symbols that are explicitly exported, + # so if a module defs file is specified, we use that to export symbols + return ['/DEF:' + defsfile] + + def gen_pch_args(self, header, source, pchname): + objname = os.path.splitext(pchname)[0] + '.obj' + return objname, ['/Yc' + header, '/Fp' + pchname, '/Fo' + objname] + + def gen_import_library_args(self, implibname): + "The name of the outputted import library" + return ['/IMPLIB:' + implibname] + + def build_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath): + return [] + + def openmp_flags(self): + return ['/openmp'] + + # FIXME, no idea what these should be. + def thread_flags(self, env): + return [] + + def thread_link_flags(self, env): + return [] + + @classmethod + def unix_args_to_native(cls, args): + result = [] + for i in args: + # -mms-bitfields is specific to MinGW-GCC + # -pthread is only valid for GCC + if i in ('-mms-bitfields', '-pthread'): + continue + if i.startswith('-L'): + i = '/LIBPATH:' + i[2:] + # Translate GNU-style -lfoo library name to the import library + elif i.startswith('-l'): + name = i[2:] + if name in cls.ignore_libs: + # With MSVC, these are provided by the C runtime which is + # linked in by default + continue + else: + i = name + '.lib' + # -pthread in link flags is only used on Linux + elif i == '-pthread': + continue + result.append(i) + return result + + def get_werror_args(self): + return ['/WX'] + + def get_include_args(self, path, is_system): + if path == '': + path = '.' + # msvc does not have a concept of system header dirs. + return ['-I' + path] + + def compute_parameters_with_absolute_paths(self, parameter_list, build_dir): + for idx, i in enumerate(parameter_list): + if i[:2] == '-I' or i[:2] == '/I': + parameter_list[idx] = i[:2] + os.path.normpath(os.path.join(build_dir, i[2:])) + elif i[:9] == '/LIBPATH:': + parameter_list[idx] = i[:9] + os.path.normpath(os.path.join(build_dir, i[9:])) + + return parameter_list + + # Visual Studio is special. It ignores some arguments it does not + # understand and you can't tell it to error out on those. + # http://stackoverflow.com/questions/15259720/how-can-i-make-the-microsoft-c-compiler-treat-unknown-flags-as-errors-rather-t + def has_arguments(self, args, env, code, mode): + warning_text = '4044' if mode == 'link' else '9002' + if self.id == 'clang-cl' and mode != 'link': + args = args + ['-Werror=unknown-argument'] + with self._build_wrapper(code, env, extra_args=args, mode=mode) as p: + if p.returncode != 0: + return False, p.cached + return not(warning_text in p.stde or warning_text in p.stdo), p.cached + + def get_compile_debugfile_args(self, rel_obj, pch=False): + pdbarr = rel_obj.split('.')[:-1] + pdbarr += ['pdb'] + args = ['/Fd' + '.'.join(pdbarr)] + # When generating a PDB file with PCH, all compile commands write + # to the same PDB file. Hence, we need to serialize the PDB + # writes using /FS since we do parallel builds. This slows down the + # build obviously, which is why we only do this when PCH is on. + # This was added in Visual Studio 2013 (MSVC 18.0). Before that it was + # always on: https://msdn.microsoft.com/en-us/library/dn502518.aspx + if pch and self.id == 'msvc' and version_compare(self.version, '>=18.0'): + args = ['/FS'] + args + return args + + def get_link_debugfile_args(self, targetfile): + pdbarr = targetfile.split('.')[:-1] + pdbarr += ['pdb'] + return ['/DEBUG', '/PDB:' + '.'.join(pdbarr)] + + def get_link_whole_for(self, args): + # Only since VS2015 + args = mesonlib.listify(args) + return ['/WHOLEARCHIVE:' + x for x in args] + + def get_instruction_set_args(self, instruction_set): + if self.is_64: + return vs64_instruction_set_args.get(instruction_set, None) + if self.id == 'msvc' and self.version.split('.')[0] == '16' and instruction_set == 'avx': + # VS documentation says that this exists and should work, but + # it does not. The headers do not contain AVX intrinsics + # and the can not be called. + return None + return vs32_instruction_set_args.get(instruction_set, None) + + def get_toolset_version(self): + if self.id == 'clang-cl': + # I have no idea + return '14.1' + + # See boost/config/compiler/visualc.cpp for up to date mapping + try: + version = int(''.join(self.version.split('.')[0:2])) + except ValueError: + return None + if version < 1310: + return '7.0' + elif version < 1400: + return '7.1' # (Visual Studio 2003) + elif version < 1500: + return '8.0' # (Visual Studio 2005) + elif version < 1600: + return '9.0' # (Visual Studio 2008) + elif version < 1700: + return '10.0' # (Visual Studio 2010) + elif version < 1800: + return '11.0' # (Visual Studio 2012) + elif version < 1900: + return '12.0' # (Visual Studio 2013) + elif version < 1910: + return '14.0' # (Visual Studio 2015) + elif version < 1920: + return '14.1' # (Visual Studio 2017) + elif version < 1930: + return '14.2' # (Visual Studio 2019) + mlog.warning('Could not find toolset for version {!r}'.format(self.version)) + return None + + def get_default_include_dirs(self): + if 'INCLUDE' not in os.environ: + return [] + return os.environ['INCLUDE'].split(os.pathsep) + + def get_crt_compile_args(self, crt_val, buildtype): + if crt_val in self.crt_args: + return self.crt_args[crt_val] + assert(crt_val == 'from_buildtype') + # Match what build type flags used to do. + if buildtype == 'plain': + return [] + elif buildtype == 'debug': + return self.crt_args['mdd'] + elif buildtype == 'debugoptimized': + return self.crt_args['md'] + elif buildtype == 'release': + return self.crt_args['md'] + elif buildtype == 'minsize': + return self.crt_args['md'] + else: + assert(buildtype == 'custom') + raise mesonlib.EnvironmentException('Requested C runtime based on buildtype, but buildtype is "custom".') + + def has_func_attribute(self, name, env): + # MSVC doesn't have __attribute__ like Clang and GCC do, so just return + # false without compiling anything + return name in ['dllimport', 'dllexport'], False + + def get_argument_syntax(self): + return 'msvc' + + def get_allow_undefined_link_args(self): + # link.exe + return ['/FORCE:UNRESOLVED'] + + class GnuLikeCompiler(abc.ABC): """ GnuLikeCompiler is a common interface to all compilers implementing diff --git a/mesonbuild/compilers/cpp.py b/mesonbuild/compilers/cpp.py index a089a5b..12644a2 100644 --- a/mesonbuild/compilers/cpp.py +++ b/mesonbuild/compilers/cpp.py @@ -14,12 +14,12 @@ import functools import os.path +import typing from .. import coredata from .. import mlog from ..mesonlib import MesonException, version_compare -from .c import CCompiler, VisualStudioCCompiler, ClangClCCompiler, IntelClCCompiler from .compilers import ( gnu_winlibs, msvc_winlibs, @@ -31,20 +31,27 @@ from .compilers import ( ArmCompiler, ArmclangCompiler, CcrxCompiler, + Compiler, + VisualStudioLikeCompiler, ) -from .c_function_attributes import CXX_FUNC_ATTRIBUTES +from .c_function_attributes import CXX_FUNC_ATTRIBUTES, C_FUNC_ATTRIBUTES +from .clike import CLikeCompiler -class CPPCompiler(CCompiler): +class CPPCompiler(CLikeCompiler, Compiler): @classmethod def attribute_check_func(cls, name): - return CXX_FUNC_ATTRIBUTES.get(name, super().attribute_check_func(name)) + try: + return CXX_FUNC_ATTRIBUTES.get(name, C_FUNC_ATTRIBUTES[name]) + except KeyError: + raise MesonException('Unknown function attribute "{}"'.format(name)) - def __init__(self, exelist, version, is_cross, exe_wrap, **kwargs): + def __init__(self, exelist, version, is_cross: bool, + exe_wrap: typing.Optional[str] = None, **kwargs): # If a child ObjCPP class has already set it, don't set it ourselves - if not hasattr(self, 'language'): - self.language = 'cpp' - CCompiler.__init__(self, exelist, version, is_cross, exe_wrap, **kwargs) + self.language = 'cpp' + Compiler.__init__(self, exelist, version, **kwargs) + CLikeCompiler.__init__(self, is_cross, exe_wrap) def get_display_language(self): return 'C++' @@ -350,25 +357,14 @@ class IntelCPPCompiler(IntelCompiler, CPPCompiler): return [] -class VisualStudioCPPCompiler(VisualStudioCCompiler, CPPCompiler): - def __init__(self, exelist, version, is_cross, exe_wrap, target): - CPPCompiler.__init__(self, exelist, version, is_cross, exe_wrap) - VisualStudioCCompiler.__init__(self, exelist, version, is_cross, exe_wrap, target) - self.base_options = ['b_pch', 'b_vscrt'] # FIXME add lto, pgo and the like +class VisualStudioLikeCPPCompilerMixin: - def get_options(self): - cpp_stds = ['none', 'c++11', 'vc++11'] - if self.id == 'clang-cl': - cpp_stds.extend(['c++14', 'vc++14', 'c++17', 'vc++17', 'c++latest']) - else: - # Visual Studio 2015 and later - if version_compare(self.version, '>=19'): - cpp_stds.extend(['c++14', 'vc++14', 'c++latest', 'vc++latest']) - # Visual Studio 2017 and later - if version_compare(self.version, '>=19.11'): - cpp_stds.extend(['c++17', 'vc++17']) + """Mixin for C++ specific method overrides in MSVC-like compilers.""" - opts = CPPCompiler.get_options(self) + def get_option_link_args(self, options): + return options['cpp_winlibs'].value[:] + + def _get_options_impl(self, opts, cpp_stds: typing.List[str]): opts.update({'cpp_eh': coredata.UserComboOption('cpp_eh', 'C++ exception handling type.', ['none', 'a', 's', 'sc', 'default'], @@ -423,24 +419,38 @@ class VisualStudioCPPCompiler(VisualStudioCCompiler, CPPCompiler): return args - def get_option_link_args(self, options): - return options['cpp_winlibs'].value[:] - def get_compiler_check_args(self): - # Visual Studio C++ compiler doesn't support -fpermissive, - # so just use the plain C args. - return VisualStudioCCompiler.get_compiler_check_args(self) + # XXX: this is a hack because so much GnuLike stuff is in the base CPPCompiler class. + return CLikeCompiler.get_compiler_check_args(self) + -class ClangClCPPCompiler(VisualStudioCPPCompiler, ClangClCCompiler): +class VisualStudioCPPCompiler(VisualStudioLikeCPPCompilerMixin, VisualStudioLikeCompiler, CPPCompiler): def __init__(self, exelist, version, is_cross, exe_wrap, target): - VisualStudioCPPCompiler.__init__(self, exelist, version, is_cross, exe_wrap, target) - self.id = 'clang-cl' + CPPCompiler.__init__(self, exelist, version, is_cross, exe_wrap) + VisualStudioLikeCompiler.__init__(self, target) + self.base_options = ['b_pch', 'b_vscrt'] # FIXME add lto, pgo and the like + self.id = 'msvc' + + def get_options(self): + cpp_stds = ['none', 'c++11', 'vc++11'] + # Visual Studio 2015 and later + if version_compare(self.version, '>=19'): + cpp_stds.extend(['c++14', 'vc++14', 'c++latest', 'vc++latest']) + # Visual Studio 2017 and later + if version_compare(self.version, '>=19.11'): + cpp_stds.extend(['c++17', 'vc++17']) + return self._get_options_impl(super().get_options(), cpp_stds) -class IntelClCPPCompiler(VisualStudioCPPCompiler, IntelClCCompiler): +class ClangClCPPCompiler(VisualStudioLikeCPPCompilerMixin, VisualStudioLikeCompiler, CPPCompiler): def __init__(self, exelist, version, is_cross, exe_wrap, target): - VisualStudioCPPCompiler.__init__(self, exelist, version, is_cross, exe_wrap, target) - self.id = 'intel' + CPPCompiler.__init__(self, exelist, version, is_cross, exe_wrap) + VisualStudioLikeCompiler.__init__(self, target) + self.id = 'clang-cl' + + def get_options(self): + cpp_stds = ['none', 'c++11', 'vc++11', 'c++14', 'vc++14', 'c++17', 'vc++17', 'c++latest'] + return self._get_options_impl(super().get_options(), cpp_stds) class ArmCPPCompiler(ArmCompiler, CPPCompiler): diff --git a/mesonbuild/compilers/fortran.py b/mesonbuild/compilers/fortran.py index b4eb327..3fee43b 100644 --- a/mesonbuild/compilers/fortran.py +++ b/mesonbuild/compilers/fortran.py @@ -15,7 +15,6 @@ from typing import List import subprocess, os from pathlib import Path -from .c import CCompiler from .compilers import ( CompilerType, apple_buildtype_linker_args, @@ -28,54 +27,26 @@ from .compilers import ( ClangCompiler, ElbrusCompiler, IntelCompiler, - PGICompiler + PGICompiler, ) +from .clike import CLikeCompiler from mesonbuild.mesonlib import ( EnvironmentException, MachineChoice, is_osx, LibType ) -class FortranCompiler(Compiler): - library_dirs_cache = CCompiler.library_dirs_cache - program_dirs_cache = CCompiler.library_dirs_cache - find_library_cache = CCompiler.library_dirs_cache +class FortranCompiler(CLikeCompiler, Compiler): def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwargs): self.language = 'fortran' Compiler.__init__(self, exelist, version, **kwargs) - cc = CCompiler(exelist, version, is_cross, exe_wrapper, **kwargs) + CLikeCompiler.__init__(self, is_cross, exe_wrapper) self.id = 'unknown' - self.is_cross = cc.is_cross - self.exe_wrapper = cc.exe_wrapper def get_display_language(self): return 'Fortran' - def needs_static_linker(self): - return CCompiler.needs_static_linker(self) - - def get_always_args(self): - return CCompiler.get_always_args(self) - - def get_linker_debug_crt_args(self): - return CCompiler.get_linker_debug_crt_args(self) - - def get_no_stdinc_args(self): - return CCompiler.get_no_stdinc_args(self) - - def get_no_stdlib_link_args(self): - return CCompiler.get_no_stdlib_link_args(self) - - def get_warn_args(self, level): - return CCompiler.get_warn_args(self, level) - - def get_no_warn_args(self): - return CCompiler.get_no_warn_args(self) - - def get_soname_args(self, *args): - return CCompiler.get_soname_args(self, *args) - def sanity_check(self, work_dir: Path, environment): """ Check to be sure a minimal program can compile and execute @@ -133,59 +104,11 @@ class FortranCompiler(Compiler): return apple_buildtype_linker_args[buildtype] return gnulike_buildtype_linker_args[buildtype] - def split_shlib_to_parts(self, fname): - return CCompiler.split_shlib_to_parts(self, fname) - - def build_rpath_args(self, *args): - return CCompiler.build_rpath_args(self, *args) - def get_dependency_gen_args(self, outtarget, outfile): return [] - def depfile_for_object(self, objfile): - return CCompiler.depfile_for_object(self, objfile) - - def get_depfile_suffix(self): - return CCompiler.get_depfile_suffix(self) - - def get_exelist(self): - return CCompiler.get_exelist(self) - - def get_linker_exelist(self): - return CCompiler.get_linker_exelist(self) - def get_preprocess_only_args(self): - return ['-cpp'] + CCompiler.get_preprocess_only_args(self) - - def get_compile_only_args(self): - return CCompiler.get_compile_only_args(self) - - def get_no_optimization_args(self): - return CCompiler.get_no_optimization_args(self) - - def get_compiler_check_args(self): - return CCompiler.get_compiler_check_args(self) - - def get_output_args(self, target): - return CCompiler.get_output_args(self, target) - - def get_linker_output_args(self, outputname): - return CCompiler.get_linker_output_args(self, outputname) - - def get_coverage_args(self): - return CCompiler.get_coverage_args(self) - - def get_coverage_link_args(self): - return CCompiler.get_coverage_link_args(self) - - def get_werror_args(self): - return CCompiler.get_werror_args(self) - - def get_std_exe_link_args(self): - return CCompiler.get_std_exe_link_args(self) - - def get_include_args(self, path, is_system): - return CCompiler.get_include_args(self, path, is_system) + return ['-cpp'] + super().get_preprocess_only_args() def get_module_incdir_args(self): return ('-I', ) @@ -214,102 +137,12 @@ class FortranCompiler(Compiler): return filename - def get_std_shared_lib_link_args(self): - return CCompiler.get_std_shared_lib_link_args(self) - - def _get_search_dirs(self, *args, **kwargs): - return CCompiler._get_search_dirs(self, *args, **kwargs) - - def get_compiler_dirs(self, *args, **kwargs): - return CCompiler.get_compiler_dirs(self, *args, **kwargs) - - def get_library_dirs(self, *args, **kwargs): - return CCompiler.get_library_dirs(self, *args, **kwargs) - - def get_pic_args(self): - return CCompiler.get_pic_args(self) - - def name_string(self): - return CCompiler.name_string(self) - - def get_linker_search_args(self, dirname): - return CCompiler.get_linker_search_args(self, dirname) - - def get_default_include_dirs(self): - return CCompiler.get_default_include_dirs(self) - - def gen_export_dynamic_link_args(self, env): - return CCompiler.gen_export_dynamic_link_args(self, env) - - def gen_import_library_args(self, implibname): - return CCompiler.gen_import_library_args(self, implibname) - - def _get_basic_compiler_args(self, env, mode): - return CCompiler._get_basic_compiler_args(self, env, mode) - - def _get_compiler_check_args(self, env, extra_args, dependencies, mode='compile'): - return CCompiler._get_compiler_check_args(self, env, extra_args, dependencies, mode=mode) - - def compiles(self, code, env, *, extra_args=None, dependencies=None, mode='compile', disable_cache=False): - return CCompiler.compiles(self, code, env, extra_args=extra_args, - dependencies=dependencies, mode=mode, disable_cache=disable_cache) - - def _build_wrapper(self, code, env, extra_args, dependencies=None, mode='compile', want_output=False, disable_cache=False): - return CCompiler._build_wrapper(self, code, env, extra_args, dependencies, mode, want_output, disable_cache=disable_cache) - - def links(self, code, env, *, extra_args=None, dependencies=None, disable_cache=False): - return CCompiler.links(self, code, env, extra_args=extra_args, - dependencies=dependencies, disable_cache=disable_cache) - - def run(self, code, env, *, extra_args=None, dependencies=None): - return CCompiler.run(self, code, env, extra_args=extra_args, dependencies=dependencies) - - def _get_patterns(self, *args, **kwargs): - return CCompiler._get_patterns(self, *args, **kwargs) - - def get_library_naming(self, *args, **kwargs): - return CCompiler.get_library_naming(self, *args, **kwargs) - - def find_library_real(self, *args): - return CCompiler.find_library_real(self, *args) - - def find_library_impl(self, *args): - return CCompiler.find_library_impl(self, *args) - def find_library(self, libname, env, extra_dirs, libtype: LibType = LibType.PREFER_SHARED): code = '''program main call exit(0) end program main''' return self.find_library_impl(libname, env, extra_dirs, code, libtype) - def thread_flags(self, env): - return CCompiler.thread_flags(self, env) - - def thread_link_flags(self, env): - return CCompiler.thread_link_flags(self, env) - - def linker_to_compiler_args(self, args): - return CCompiler.linker_to_compiler_args(self, args) - - def has_arguments(self, args, env, code, mode): - return CCompiler.has_arguments(self, args, env, code, mode) - - def has_multi_arguments(self, args, env): - return CCompiler.has_multi_arguments(self, args, env) - - def has_header(self, hname, prefix, env, *, extra_args=None, dependencies=None, disable_cache=False): - return CCompiler.has_header(self, hname, prefix, env, extra_args=extra_args, dependencies=dependencies, disable_cache=disable_cache) - - def get_define(self, dname, prefix, env, extra_args, dependencies, disable_cache=False): - return CCompiler.get_define(self, dname, prefix, env, extra_args, dependencies, disable_cache=disable_cache) - - @classmethod - def _get_trials_from_pattern(cls, pattern, directory, libname): - return CCompiler._get_trials_from_pattern(pattern, directory, libname) - - @staticmethod - def _get_file_from_list(env, files: List[str]) -> Path: - return CCompiler._get_file_from_list(env, files) class GnuFortranCompiler(GnuCompiler, FortranCompiler): def __init__(self, exelist, version, compiler_type, is_cross, exe_wrapper=None, defines=None, **kwargs): diff --git a/mesonbuild/compilers/objc.py b/mesonbuild/compilers/objc.py index c4a7019..55311ed 100644 --- a/mesonbuild/compilers/objc.py +++ b/mesonbuild/compilers/objc.py @@ -13,16 +13,18 @@ # limitations under the License. import os.path, subprocess +import typing from ..mesonlib import EnvironmentException, MachineChoice -from .c import CCompiler -from .compilers import ClangCompiler, GnuCompiler +from .clike import CLikeCompiler +from .compilers import Compiler, ClangCompiler, GnuCompiler -class ObjCCompiler(CCompiler): - def __init__(self, exelist, version, is_cross, exe_wrap): +class ObjCCompiler(CLikeCompiler, Compiler): + def __init__(self, exelist, version, is_cross: bool, exe_wrap: typing.Optional[str]): self.language = 'objc' - CCompiler.__init__(self, exelist, version, is_cross, exe_wrap) + Compiler.__init__(self, exelist, version) + CLikeCompiler.__init__(self, is_cross, exe_wrap) def get_display_language(self): return 'Objective-C' diff --git a/mesonbuild/compilers/objcpp.py b/mesonbuild/compilers/objcpp.py index 4c23d0a..6743f89 100644 --- a/mesonbuild/compilers/objcpp.py +++ b/mesonbuild/compilers/objcpp.py @@ -13,16 +13,18 @@ # limitations under the License. import os.path, subprocess +import typing from ..mesonlib import EnvironmentException, MachineChoice -from .cpp import CPPCompiler -from .compilers import ClangCompiler, GnuCompiler +from .clike import CLikeCompiler +from .compilers import Compiler, ClangCompiler, GnuCompiler -class ObjCPPCompiler(CPPCompiler): - def __init__(self, exelist, version, is_cross, exe_wrap): +class ObjCPPCompiler(CLikeCompiler, Compiler): + def __init__(self, exelist, version, is_cross: bool, exe_wrap: typing.Optional[str]): self.language = 'objcpp' - CPPCompiler.__init__(self, exelist, version, is_cross, exe_wrap) + Compiler.__init__(self, exelist, version) + CLikeCompiler.__init__(self, is_cross, exe_wrap) def get_display_language(self): return 'Objective-C++' diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index 462672e..dd1d4cf 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -61,8 +61,6 @@ from .compilers import ( IntelCCompiler, IntelCPPCompiler, IntelFortranCompiler, - IntelClCCompiler, - IntelClCPPCompiler, JavaCompiler, MonoCompiler, CudaCompiler, @@ -681,7 +679,6 @@ class Environment: arg = '-v' else: arg = '--version' - try: p, out, err = Popen_safe(compiler + [arg]) except OSError as e: @@ -690,11 +687,6 @@ class Environment: if 'ccrx' in compiler[0]: out = err - if 'icl' in compiler[0]: - # https://software.intel.com/en-us/cpp-compiler-developer-guide-and-reference-alphabetical-list-of-compiler-options - # https://software.intel.com/en-us/fortran-compiler-developer-guide-and-reference-logo - # most consistent way for ICL is to just let compiler error and tell version - out = err full_version = out.split('\n', 1)[0] version = search_version(out) @@ -782,9 +774,9 @@ class Environment: return cls(compiler, version, is_cross, exe_wrap, target) if 'PGI Compilers' in out: - if mesonlib.for_darwin(is_cross, self): + if mesonlib.for_darwin(want_cross, self): compiler_type = CompilerType.PGI_OSX - elif mesonlib.for_windows(is_cross, self): + elif mesonlib.for_windows(want_cross, self): compiler_type = CompilerType.PGI_WIN else: compiler_type = CompilerType.PGI_STANDARD @@ -794,15 +786,12 @@ class Environment: if mesonlib.for_darwin(want_cross, self): compiler_type = CompilerType.ICC_OSX elif mesonlib.for_windows(want_cross, self): - raise EnvironmentException('At the time of authoring, there was no ICC for Windows') + # TODO: fix ICC on Windows + compiler_type = CompilerType.ICC_WIN else: compiler_type = CompilerType.ICC_STANDARD cls = IntelCCompiler if lang == 'c' else IntelCPPCompiler return cls(ccache + compiler, version, compiler_type, is_cross, exe_wrap, full_version=full_version) - if out.startswith('Intel(R) C++') and mesonlib.for_windows(want_cross, self): - cls = IntelClCCompiler if lang == 'c' else IntelClCPPCompiler - target = 'x64' if 'Intel(R) 64 Compiler' in out else 'x86' - return cls(compiler, version, is_cross, exe_wrap, target) if 'ARM' in out: compiler_type = CompilerType.ARM_WIN cls = ArmCCompiler if lang == 'c' else ArmCPPCompiler @@ -867,13 +856,6 @@ class Environment: popen_exceptions[' '.join(compiler + [arg])] = e continue - if mesonlib.for_windows(is_cross, self): - if 'ifort' in compiler[0]: - # https://software.intel.com/en-us/cpp-compiler-developer-guide-and-reference-alphabetical-list-of-compiler-options - # https://software.intel.com/en-us/fortran-compiler-developer-guide-and-reference-logo - # most consistent way for ICL is to just let compiler error and tell version - out = err - version = search_version(out) full_version = out.split('\n', 1)[0] @@ -904,16 +886,16 @@ class Environment: version = search_version(err) return SunFortranCompiler(compiler, version, is_cross, exe_wrap, full_version=full_version) - if 'ifort (IFORT)' in out or out.startswith('Intel(R) Visual Fortran'): + if 'ifort (IFORT)' in out: return IntelFortranCompiler(compiler, version, is_cross, exe_wrap, full_version=full_version) if 'PathScale EKOPath(tm)' in err: return PathScaleFortranCompiler(compiler, version, is_cross, exe_wrap, full_version=full_version) if 'PGI Compilers' in out: - if mesonlib.for_darwin(is_cross, self): + if mesonlib.for_darwin(want_cross, self): compiler_type = CompilerType.PGI_OSX - elif mesonlib.for_windows(is_cross, self): + elif mesonlib.for_windows(want_cross, self): compiler_type = CompilerType.PGI_WIN else: compiler_type = CompilerType.PGI_STANDARD @@ -1073,8 +1055,9 @@ class Environment: # up to date language version at time (2016). if exelist is not None: if os.path.basename(exelist[-1]).startswith(('ldmd', 'gdmd')): - raise EnvironmentException('Meson does not support {} as it is only a DMD frontend for another compiler.' - 'Please provide a valid value for DC or unset it so that Meson can resolve the compiler by itself.'.format(exelist[-1])) + raise EnvironmentException( + 'Meson does not support {} as it is only a DMD frontend for another compiler.' + 'Please provide a valid value for DC or unset it so that Meson can resolve the compiler by itself.'.format(exelist[-1])) else: for d in self.default_d: if shutil.which(d): @@ -1175,7 +1158,7 @@ class Environment: linkers = [self.cuda_static_linker, self.default_static_linker] elif evar in os.environ: linkers = [shlex.split(os.environ[evar])] - elif isinstance(compiler, compilers.VisualStudioCCompiler): + elif isinstance(compiler, compilers.VisualStudioLikeCompiler): linkers = [self.vs_static_linker, self.clang_cl_static_linker] elif isinstance(compiler, compilers.GnuCompiler): # Use gcc-ar if available; needed for LTO diff --git a/run_unittests.py b/run_unittests.py index 3a473ea..7a7c006 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -1939,8 +1939,8 @@ class AllPlatformTests(BasePlatformTests): gnu = mesonbuild.compilers.GnuCompiler clang = mesonbuild.compilers.ClangCompiler intel = mesonbuild.compilers.IntelCompiler - msvc = mesonbuild.compilers.VisualStudioCCompiler - clangcl = mesonbuild.compilers.ClangClCCompiler + msvc = (mesonbuild.compilers.VisualStudioCCompiler, mesonbuild.compilers.VisualStudioCPPCompiler) + clangcl = (mesonbuild.compilers.ClangClCCompiler, mesonbuild.compilers.ClangClCPPCompiler) ar = mesonbuild.linkers.ArLinker lib = mesonbuild.linkers.VisualStudioLinker langs = [('c', 'CC'), ('cpp', 'CXX')] |