diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 66fc46489..f89b91725 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -2647,10 +2647,10 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) guessed_dependencies = [] # TODO The get_library_naming requirement currently excludes link targets that use d or fortran as their main linker - if hasattr(linker, 'get_library_naming'): - search_dirs = tuple(search_dirs) + tuple(linker.get_library_dirs(self.environment)) + try: static_patterns = linker.get_library_naming(self.environment, LibType.STATIC, strict=True) shared_patterns = linker.get_library_naming(self.environment, LibType.SHARED, strict=True) + search_dirs = tuple(search_dirs) + tuple(linker.get_library_dirs(self.environment)) for libname in libs: # be conservative and record most likely shared and static resolution, because we don't know exactly # which one the linker will prefer @@ -2662,6 +2662,9 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) guessed_dependencies.append(staticlibs.resolve().as_posix()) if sharedlibs: guessed_dependencies.append(sharedlibs.resolve().as_posix()) + except (mesonlib.MesonException, AttributeError) as e: + if 'get_library_naming' not in str(e): + raise return guessed_dependencies + absolute_libs diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 5b2e15da1..edd1c5d6c 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -512,27 +512,27 @@ class Compiler(metaclass=abc.ABCMeta): return self.default_suffix def get_define(self, dname: str, prefix: str, env: 'Environment', - extra_args: T.Sequence[str], dependencies: T.Sequence['Dependency'], + extra_args: T.List[str], dependencies: T.List['Dependency'], disable_cache: bool = False) -> T.Tuple[str, bool]: raise EnvironmentException('%s does not support get_define ' % self.get_id()) def compute_int(self, expression: str, low: T.Optional[int], high: T.Optional[int], guess: T.Optional[int], prefix: str, env: 'Environment', - extra_args: T.Sequence[str], dependencies: T.Sequence['Dependency']) -> int: + extra_args: T.List[str], dependencies: T.List['Dependency']) -> int: raise EnvironmentException('%s does not support compute_int ' % self.get_id()) - def compute_parameters_with_absolute_paths(self, parameter_list: T.Sequence[str], + def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], build_dir: str) -> T.List[str]: raise EnvironmentException('%s does not support compute_parameters_with_absolute_paths ' % self.get_id()) - def has_members(self, typename: str, membernames: T.Sequence[str], + def has_members(self, typename: str, membernames: T.Iterable[str], prefix: str, env: 'Environment', *, - extra_args: T.Optional[T.Sequence[str]] = None, - dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: + extra_args: T.Optional[T.Iterable[str]] = None, + dependencies: T.Optional[T.Iterable['Dependency']] = None) -> T.Tuple[bool, bool]: raise EnvironmentException('%s does not support has_member(s) ' % self.get_id()) def has_type(self, typename: str, prefix: str, env: 'Environment', - extra_args: T.Sequence[str], *, + extra_args: T.List[str], *, dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: raise EnvironmentException('%s does not support has_type ' % self.get_id()) @@ -632,14 +632,14 @@ class Compiler(metaclass=abc.ABCMeta): raise EnvironmentException('Language %s does not support header symbol checks.' % self.get_display_language()) def compiles(self, code: str, env: 'Environment', *, - extra_args: T.Sequence[T.Union[T.Sequence[str], str]] = None, + extra_args: T.Union[None, T.List[str], CompilerArgs] = None, dependencies: T.Optional[T.List['Dependency']] = None, mode: str = 'compile', disable_cache: bool = False) -> T.Tuple[bool, bool]: raise EnvironmentException('Language %s does not support compile checks.' % self.get_display_language()) def links(self, code: str, env: 'Environment', *, - extra_args: T.Sequence[T.Union[T.Sequence[str], str]] = None, + extra_args: T.Union[None, T.List[str], CompilerArgs] = None, dependencies: T.Optional[T.List['Dependency']] = None, mode: str = 'compile', disable_cache: bool = False) -> T.Tuple[bool, bool]: @@ -686,8 +686,10 @@ class Compiler(metaclass=abc.ABCMeta): raise EnvironmentException('Language {} does not support library finding.'.format(self.get_display_language())) def get_library_naming(self, env: 'Environment', libtype: LibType, - strict: bool = False) -> T.Tuple[str, ...]: - return () + strict: bool = False) -> T.Optional[T.Tuple[str, ...]]: + raise EnvironmentException( + 'Language {} does not support get_library_naming.'.format( + self.get_display_language())) def get_program_dirs(self, env: 'Environment') -> T.List[str]: return [] @@ -728,9 +730,11 @@ class Compiler(metaclass=abc.ABCMeta): return CompilerArgs(self, args) @contextlib.contextmanager - def compile(self, code: 'mesonlib.FileOrString', extra_args: T.Optional[T.List[str]] = None, + def compile(self, code: 'mesonlib.FileOrString', + extra_args: T.Union[None, CompilerArgs, T.List[str]] = None, *, mode: str = 'link', want_output: bool = False, temp_dir: T.Optional[str] = None) -> T.Iterator[T.Optional[CompileResult]]: + # TODO: there isn't really any reason for this to be a contextmanager if extra_args is None: extra_args = [] try: @@ -787,9 +791,11 @@ class Compiler(metaclass=abc.ABCMeta): @contextlib.contextmanager def cached_compile(self, code: str, cdata: coredata.CoreData, *, - extra_args: T.Optional[T.List[str]] = None, + extra_args: T.Union[None, T.List[str], CompilerArgs] = None, mode: str = 'link', temp_dir: T.Optional[str] = None) -> T.Iterator[T.Optional[CompileResult]]: + # TODO: There's isn't really any reason for this to be a context manager + # Calculate the key textra_args = tuple(extra_args) if extra_args is not None else tuple() # type: T.Tuple[str, ...] key = (tuple(self.exelist), self.version, code, textra_args, mode) # type: coredata.CompilerCheckCacheKey @@ -994,27 +1000,38 @@ class Compiler(metaclass=abc.ABCMeta): def get_preprocess_only_args(self) -> T.List[str]: raise EnvironmentError('This compiler does not have a preprocessor') + def get_default_include_dirs(self) -> T.List[str]: + return [] + + def get_largefile_args(self) -> T.List[str]: + '''Enable transparent large-file-support for 32-bit UNIX systems''' + if not (self.get_argument_syntax() == 'msvc' or self.info.is_darwin()): + # Enable large-file support unconditionally on all platforms other + # than macOS and MSVC. macOS is now 64-bit-only so it doesn't + # need anything special, and MSVC doesn't have automatic LFS. + # You must use the 64-bit counterparts explicitly. + # glibc, musl, and uclibc, and all BSD libcs support this. On Android, + # support for transparent LFS is available depending on the version of + # Bionic: https://github.com/android/platform_bionic#32-bit-abi-bugs + # https://code.google.com/p/android/issues/detail?id=64613 + # + # If this breaks your code, fix it! It's been 20+ years! + return ['-D_FILE_OFFSET_BITS=64'] + # We don't enable -D_LARGEFILE64_SOURCE since that enables + # transitionary features and must be enabled by programs that use + # those features explicitly. + return [] + + def get_library_dirs(self, env: 'Environment', + elf_class: T.Optional[int] = None) -> T.List[str]: + return [] + + def find_framework_paths(self, env: 'Environment') -> T.List[str]: + raise EnvironmentException('{} does not support find_framework_paths'.format(self.id)) + + def attribute_check_func(self, name: str) -> str: + raise EnvironmentException('{} does not support attribute checks'.format(self.id)) -def get_largefile_args(compiler: Compiler) -> T.List[str]: - ''' - Enable transparent large-file-support for 32-bit UNIX systems - ''' - if not (compiler.get_argument_syntax() == 'msvc' or compiler.info.is_darwin()): - # Enable large-file support unconditionally on all platforms other - # than macOS and MSVC. macOS is now 64-bit-only so it doesn't - # need anything special, and MSVC doesn't have automatic LFS. - # You must use the 64-bit counterparts explicitly. - # glibc, musl, and uclibc, and all BSD libcs support this. On Android, - # support for transparent LFS is available depending on the version of - # Bionic: https://github.com/android/platform_bionic#32-bit-abi-bugs - # https://code.google.com/p/android/issues/detail?id=64613 - # - # If this breaks your code, fix it! It's been 20+ years! - return ['-D_FILE_OFFSET_BITS=64'] - # We don't enable -D_LARGEFILE64_SOURCE since that enables - # transitionary features and must be enabled by programs that use - # those features explicitly. - return [] def get_args_from_envvars(lang: str, diff --git a/mesonbuild/compilers/fortran.py b/mesonbuild/compilers/fortran.py index 7ca307381..e928a0478 100644 --- a/mesonbuild/compilers/fortran.py +++ b/mesonbuild/compilers/fortran.py @@ -143,7 +143,7 @@ class FortranCompiler(CLikeCompiler, Compiler): def find_library(self, libname, env, extra_dirs, libtype: LibType = LibType.PREFER_SHARED): code = 'stop; end program' - return self.find_library_impl(libname, env, extra_dirs, code, libtype) + return self._find_library_impl(libname, env, extra_dirs, code, libtype) def has_multi_arguments(self, args: T.Sequence[str], env): for arg in args[:]: diff --git a/mesonbuild/compilers/mixins/clike.py b/mesonbuild/compilers/mixins/clike.py index 6a069b6a8..662c63488 100644 --- a/mesonbuild/compilers/mixins/clike.py +++ b/mesonbuild/compilers/mixins/clike.py @@ -20,6 +20,8 @@ of this is to have mixin's, which are classes that are designed *not* to be standalone, they only work through inheritance. """ +import contextlib +import collections import functools import glob import itertools @@ -38,7 +40,10 @@ from .. import compilers from .visualstudio import VisualStudioLikeCompiler if T.TYPE_CHECKING: + from ...coredata import CoreData + from ...dependencies import Dependency, ExternalProgram from ...environment import Environment + from ...linkers import DynamicLinker GROUP_FLAGS = re.compile(r'''\.so (?:\.[0-9]+)? (?:\.[0-9]+)? (?:\.[0-9]+)?$ | ^(?:-Wl,)?-l | @@ -55,6 +60,9 @@ class CLikeCompilerArgs(arglist.CompilerArgs): dedup1_args = ('-c', '-S', '-E', '-pipe', '-pthread') def to_native(self, copy: bool = False) -> T.List[str]: + # This seems to be allowed, but could never work? + assert isinstance(self.compiler, compilers.Compiler), 'How did you get here' + # Check if we need to add --start/end-group for circular dependencies # between static libraries, and for recursively searching for symbols # needed by static libraries that are provided by object files or @@ -82,8 +90,8 @@ class CLikeCompilerArgs(arglist.CompilerArgs): new.insert(group_end + 1, '-Wl,--end-group') new.insert(group_start, '-Wl,--start-group') # Remove system/default include paths added with -isystem - if hasattr(self.compiler, 'get_default_include_dirs'): - default_dirs = self.compiler.get_default_include_dirs() + default_dirs = self.compiler.get_default_include_dirs() + if default_dirs: bad_idx_list = [] # type: T.List[int] for i, each in enumerate(new): if not each.startswith('-isystem'): @@ -111,14 +119,47 @@ class CLikeCompiler: """Shared bits for the C and CPP Compilers.""" + if T.TYPE_CHECKING: + can_compile_suffixes = set() # type: T.Set[str] + exelist = [] # type: T.List[str] + for_machine = mesonlib.MachineChoice.HOST + id = '' + ignore_libs = () # type: T.Tuple[str, ...] + language = '' + linker = SolarisDynamicLinker([], mesonlib.MachineChoice.HOST, [], []) # type: DynamicLinker + warn_args = {} # type: T.Dict[str, T.List[str]] + + @staticmethod + def attribute_check_func(name: str) -> str:... + def get_allow_undefined_link_args(self) -> T.List[str]: ... + def get_compiler_args_for_mode(self, mode: str) -> T.List[str]: ... + def get_display_language(self) -> str: ... + def get_largefile_args(self) -> T.List[str]: ... + def get_linker_always_args(self) -> T.List[str]: ... + def get_pch_suffix(self) -> str: ... + def get_id(self) -> str: ... + def remove_linkerlike_args(self, args: T.List[str]) -> T.List[str]: ... + @classmethod + def use_linker_args(cls, linker: str) -> T.List[str]: ... + + @contextlib.contextmanager + def compile(self, code: 'mesonlib.FileOrString', + extra_args: T.Union[None, arglist.CompilerArgs, T.List[str]] = None, + *, mode: str = 'link', want_output: bool = False, + temp_dir: T.Optional[str] = None) -> T.Iterator[T.Optional[compilers.CompileResult]]: ... + + @contextlib.contextmanager + def cached_compile(self, code: str, cdata: 'CoreData', *, + extra_args: T.Union[None, arglist.CompilerArgs, T.List[str]] = None, + mode: str = 'link', + temp_dir: T.Optional[str] = None) -> T.Iterator[T.Optional[compilers.CompileResult]]: ... + # TODO: Replace this manual cache with functools.lru_cache - library_dirs_cache = {} - program_dirs_cache = {} - find_library_cache = {} - find_framework_cache = {} + find_library_cache = {} # type: T.Dict[T.Tuple[T.Tuple[str, ...], str, T.Tuple[str, ...], str, LibType], T.Optional[T.List[str]]] + find_framework_cache = {} # type: T.Dict[T.Tuple[T.Tuple[str, ...], str, T.Tuple[str, ...], bool], T.Optional[T.List[str]]] internal_libs = arglist.UNIXY_COMPILER_INTERNAL_LIBS - def __init__(self, is_cross: bool, exe_wrapper: T.Optional[str] = None): + def __init__(self, is_cross: bool, exe_wrapper: T.Optional['ExternalProgram'] = 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') @@ -129,70 +170,72 @@ class CLikeCompiler: else: self.exe_wrapper = exe_wrapper.get_command() - def compiler_args(self, args: T.Optional[T.Iterable[str]] = None) -> CLikeCompilerArgs: - return CLikeCompilerArgs(self, args) + def compiler_args(self, args: T.Optional[T.List[str]] = None) -> CLikeCompilerArgs: + # This is correct, mypy just doesn't understand co-operative inheritance + return CLikeCompilerArgs(self, args) # type: ignore - def needs_static_linker(self): + def needs_static_linker(self) -> bool: return True # When compiling static libraries, so yes. - def get_always_args(self): + def get_always_args(self) -> T.List[str]: ''' Args that are always-on for all C compilers other than MSVC ''' - return ['-pipe'] + compilers.get_largefile_args(self) + return ['-pipe'] + self.get_largefile_args() - def get_no_stdinc_args(self): + def get_no_stdinc_args(self) -> T.List[str]: return ['-nostdinc'] - def get_no_stdlib_link_args(self): + def get_no_stdlib_link_args(self) -> T.List[str]: return ['-nostdlib'] - def get_warn_args(self, level): + def get_warn_args(self, level: str) -> T.List[str]: + # TODO: this should be an enum return self.warn_args[level] - def get_no_warn_args(self): + def get_no_warn_args(self) -> T.List[str]: # Almost every compiler uses this for disabling warnings return ['-w'] - def split_shlib_to_parts(self, fname): + def split_shlib_to_parts(self, fname: str) -> T.Tuple[T.Optional[str], str]: return None, fname - def depfile_for_object(self, objfile): + def depfile_for_object(self, objfile: str) -> str: return objfile + '.' + self.get_depfile_suffix() - def get_depfile_suffix(self): + def get_depfile_suffix(self) -> str: return 'd' - def get_exelist(self): - return self.exelist[:] + def get_exelist(self) -> T.List[str]: + return self.exelist.copy() - def get_preprocess_only_args(self): + def get_preprocess_only_args(self) -> T.List[str]: return ['-E', '-P'] - def get_compile_only_args(self): + def get_compile_only_args(self) -> T.List[str]: return ['-c'] - def get_no_optimization_args(self): + def get_no_optimization_args(self) -> T.List[str]: return ['-O0'] - def get_compiler_check_args(self): + def get_compiler_check_args(self) -> T.List[str]: ''' 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): + def get_output_args(self, target: str) -> T.List[str]: return ['-o', target] - def get_werror_args(self): + def get_werror_args(self) -> T.List[str]: return ['-Werror'] - def get_std_exe_link_args(self): + def get_std_exe_link_args(self) -> T.List[str]: # TODO: is this a linker property? return [] - def get_include_args(self, path, is_system): + def get_include_args(self, path: str, is_system: bool) -> T.List[str]: if path == '': path = '.' if is_system: @@ -206,7 +249,9 @@ class CLikeCompiler: return [] @functools.lru_cache() - def get_library_dirs(self, env, elf_class = None): + def _get_library_dirs(self, env: 'Environment', + elf_class: T.Optional[int] = None) -> T.List[str]: + # TODO: replace elf_class with enum dirs = self.get_compiler_dirs(env, 'libraries') if elf_class is None or elf_class == 0: return dirs @@ -241,10 +286,17 @@ class CLikeCompiler: # Skip the file if we can't read it pass - return tuple(retval) + return retval + + def get_library_dirs(self, env: 'Environment', + elf_class: T.Optional[int] = None) -> T.List[str]: + """Wrap the lru_cache so that we return a new copy and don't allow + mutation of the cached value. + """ + return self._get_library_dirs(env, elf_class).copy() @functools.lru_cache() - def get_program_dirs(self, env): + def get_program_dirs(self, env: 'Environment') -> T.List[str]: ''' Programs used by the compiler. Also where toolchain DLLs such as libstdc++-6.dll are found with MinGW. @@ -266,7 +318,7 @@ class CLikeCompiler: def get_linker_search_args(self, dirname: str) -> T.List[str]: return self.linker.get_search_args(dirname) - def get_default_include_dirs(self): + def get_default_include_dirs(self) -> T.List[str]: return [] def gen_export_dynamic_link_args(self, env: 'Environment') -> T.List[str]: @@ -275,7 +327,8 @@ class CLikeCompiler: def gen_import_library_args(self, implibname: str) -> T.List[str]: return self.linker.import_library_args(implibname) - def sanity_check_impl(self, work_dir, environment, sname, code): + def sanity_check_impl(self, work_dir: str, environment: 'Environment', + sname: str, code: str) -> None: mlog.debug('Sanity testing ' + self.get_display_language() + ' compiler:', ' '.join(self.exelist)) mlog.debug('Is cross compiler: %s.' % str(self.is_cross)) @@ -330,18 +383,23 @@ class CLikeCompiler: 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): + def sanity_check(self, work_dir: str, environment: 'Environment') -> None: code = 'int main(void) { int class=0; return class; }\n' return self.sanity_check_impl(work_dir, environment, 'sanitycheckc.c', code) - def check_header(self, hname: str, prefix: str, env, *, extra_args=None, dependencies=None): + def check_header(self, hname: str, prefix: str, env: 'Environment', *, + extra_args: T.Optional[T.List[str]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: 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: str, prefix: str, env, *, extra_args=None, dependencies=None, disable_cache: bool = False): + def has_header(self, hname: str, prefix: str, env: 'Environment', *, + extra_args: T.Optional[T.List[str]] = None, + dependencies: T.Optional[T.List['Dependency']] = None, + disable_cache: bool = False) -> T.Tuple[bool, bool]: fargs = {'prefix': prefix, 'header': hname} code = '''{prefix} #ifdef __has_include @@ -354,7 +412,10 @@ class CLikeCompiler: 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: str, symbol: str, prefix: str, env, *, extra_args=None, dependencies=None): + def has_header_symbol(self, hname: str, symbol: str, prefix: str, + env: 'Environment', *, + extra_args: T.Optional[T.List[str]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: fargs = {'prefix': prefix, 'header': hname, 'symbol': symbol} t = '''{prefix} #include <{header}> @@ -368,8 +429,9 @@ class CLikeCompiler: return self.compiles(t.format(**fargs), env, extra_args=extra_args, dependencies=dependencies) - def _get_basic_compiler_args(self, env, mode: str): - cargs, largs = [], [] + def _get_basic_compiler_args(self, env: 'Environment', mode: str) -> T.Tuple[T.List[str], T.List[str]]: + cargs = [] # type: T.List[str] + largs = [] # type: T.List[str] if mode == 'link': # Sometimes we need to manually select the CRT to use with MSVC. # One example is when trying to do a compiler check that involves @@ -405,20 +467,26 @@ class CLikeCompiler: cargs += self.get_compiler_args_for_mode(mode) return cargs, largs - def _get_compiler_check_args(self, env, extra_args: list, dependencies, mode: str = 'compile') -> T.List[str]: + def _get_compiler_check_args(self, env: 'Environment', + extra_args: T.Union[None, arglist.CompilerArgs, T.List[str]], + dependencies: T.Optional[T.List['Dependency']], + mode: str = 'compile') -> arglist.CompilerArgs: + # TODO: the caller should handle the listfing of these arguments if extra_args is None: extra_args = [] else: + # TODO: we want to do this in the caller 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] + elif not isinstance(dependencies, collections.abc.Iterable): + # TODO: we want to ensure the front end does the listifing here + dependencies = [dependencies] # type: ignore # Collect compiler arguments - cargs = self.compiler_args() - largs = [] + cargs = self.compiler_args() # type: arglist.CompilerArgs + largs = [] # type: T.List[str] for d in dependencies: # Add compile flags needed by dependencies cargs += d.get_compile_args() @@ -440,23 +508,40 @@ class CLikeCompiler: args = cargs + extra_args + largs return args - def compiles(self, code: str, env, *, - extra_args: T.Sequence[T.Union[T.Sequence[str], str]] = None, - dependencies=None, mode: str = 'compile', disable_cache: bool = False) -> T.Tuple[bool, bool]: + def compiles(self, code: str, env: 'Environment', *, + extra_args: T.Optional[T.List[str]] = None, + dependencies: T.Optional[T.List['Dependency']] = None, + mode: str = 'compile', + disable_cache: bool = False) -> T.Tuple[bool, bool]: 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: str, env, extra_args, dependencies=None, mode: str = 'compile', want_output: bool = False, disable_cache: bool = False, temp_dir: str = None) -> T.Iterator[compilers.CompileResult]: + @contextlib.contextmanager + def _build_wrapper(self, code: str, env: 'Environment', + extra_args: T.Union[None, arglist.CompilerArgs, T.List[str]] = None, + dependencies: T.Optional[T.List['Dependency']] = None, + mode: str = 'compile', want_output: bool = False, + disable_cache: bool = False, + temp_dir: str = None) -> T.Iterator[T.Optional[compilers.CompileResult]]: 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, temp_dir=env.scratch_dir) - return self.cached_compile(code, env.coredata, extra_args=args, mode=mode, temp_dir=env.scratch_dir) - - def links(self, code, env, *, extra_args=None, dependencies=None, disable_cache=False): + with self.compile(code, extra_args=args, mode=mode, want_output=want_output, temp_dir=env.scratch_dir) as r: + yield r + else: + with self.cached_compile(code, env.coredata, extra_args=args, mode=mode, temp_dir=env.scratch_dir) as r: + yield r + + def links(self, code: str, env: 'Environment', *, + extra_args: T.Optional[T.List[str]] = None, + dependencies: T.Optional[T.List['Dependency']] = None, + mode: str = 'compile', + disable_cache: bool = False) -> T.Tuple[bool, bool]: 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): + def run(self, code: str, env: 'Environment', *, + extra_args: T.Optional[T.List[str]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> compilers.RunResult: need_exe_wrapper = env.need_exe_wrapper(self.for_machine) if need_exe_wrapper and self.exe_wrapper is None: raise compilers.CrossNoRunException('Can not run test applications in this cross environment.') @@ -469,7 +554,7 @@ class CLikeCompiler: if need_exe_wrapper: cmdlist = self.exe_wrapper + [p.output_name] else: - cmdlist = p.output_name + cmdlist = [p.output_name] try: pe, so, se = mesonlib.Popen_safe(cmdlist) except Exception as e: @@ -482,7 +567,9 @@ class CLikeCompiler: mlog.debug(se) return compilers.RunResult(True, pe.returncode, so, se) - def _compile_int(self, expression, prefix, env, extra_args, dependencies): + def _compile_int(self, expression: str, prefix: str, env: 'Environment', + extra_args: T.Optional[T.List[str]], + dependencies: T.Optional[T.List['Dependency']]) -> bool: fargs = {'prefix': prefix, 'expression': expression} t = '''#include {prefix} @@ -490,7 +577,10 @@ class CLikeCompiler: 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): + def cross_compute_int(self, expression: str, low: T.Optional[int], high: T.Optional[int], + guess: T.Optional[int], prefix: str, env: 'Environment', + extra_args: T.Optional[T.List[str]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> int: # Try user's guess first if isinstance(guess, int): if self._compile_int('%s == %d' % (expression, guess), prefix, env, extra_args, dependencies): @@ -538,7 +628,10 @@ class CLikeCompiler: return low - def compute_int(self, expression, low, high, guess, prefix, env, *, extra_args=None, dependencies=None): + def compute_int(self, expression: str, low: T.Optional[int], high: T.Optional[int], + guess: T.Optional[int], prefix: str, env: 'Environment', *, + extra_args: T.Optional[T.List[str]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> int: if extra_args is None: extra_args = [] if self.is_cross: @@ -558,7 +651,9 @@ class CLikeCompiler: 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): + def cross_sizeof(self, typename: str, prefix: str, env: 'Environment', *, + extra_args: T.Optional[T.List[str]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> int: if extra_args is None: extra_args = [] fargs = {'prefix': prefix, 'type': typename} @@ -573,7 +668,9 @@ class CLikeCompiler: 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): + def sizeof(self, typename: str, prefix: str, env: 'Environment', *, + extra_args: T.Optional[T.List[str]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> int: if extra_args is None: extra_args = [] fargs = {'prefix': prefix, 'type': typename} @@ -594,7 +691,9 @@ class CLikeCompiler: 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): + def cross_alignment(self, typename: str, prefix: str, env: 'Environment', *, + extra_args: T.Optional[T.List[str]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> int: if extra_args is None: extra_args = [] fargs = {'prefix': prefix, 'type': typename} @@ -615,7 +714,9 @@ class CLikeCompiler: }};''' 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): + def alignment(self, typename: str, prefix: str, env: 'Environment', *, + extra_args: T.Optional[T.List[str]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> int: if extra_args is None: extra_args = [] if self.is_cross: @@ -644,7 +745,10 @@ class CLikeCompiler: 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): + def get_define(self, dname: str, prefix: str, env: 'Environment', + extra_args: T.Optional[T.List[str]], + dependencies: T.Optional[T.List['Dependency']], + disable_cache: bool = False) -> T.Tuple[str, bool]: delim = '"MESON_GET_DEFINE_DELIMITER"' fargs = {'prefix': prefix, 'define': dname, 'delim': delim} code = ''' @@ -655,9 +759,9 @@ class CLikeCompiler: {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') + func = functools.partial(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', temp_dir=env.scratch_dir) + func = functools.partial(self.compile, code.format(**fargs), extra_args=args, mode='preprocess', temp_dir=env.scratch_dir) with func() as p: cached = p.cached if p.returncode != 0: @@ -665,9 +769,13 @@ class CLikeCompiler: # Get the preprocessed value after the delimiter, # minus the extra newline at the end and # merge string literals. - return self.concatenate_string_literals(p.stdout.split(delim + '\n')[-1][:-1]), cached + return self._concatenate_string_literals(p.stdout.split(delim + '\n')[-1][:-1]), cached - def get_return_value(self, fname, rtype, prefix, env, extra_args, dependencies): + def get_return_value(self, fname: str, rtype: str, prefix: str, + env: 'Environment', extra_args: T.Optional[T.List[str]], + dependencies: T.Optional[T.List['Dependency']]) -> T.Union[str, int]: + # TODO: rtype should be an enum. + # TODO: maybe we can use overload to tell mypy when this will return int vs str? if rtype == 'string': fmt = '%s' cast = '(char*)' @@ -695,9 +803,10 @@ class CLikeCompiler: except ValueError: m = 'Return value of {}() is not an int' raise mesonlib.EnvironmentException(m.format(fname)) + assert False, 'Unreachable' @staticmethod - def _no_prototype_templ(): + def _no_prototype_templ() -> T.Tuple[str, str]: """ 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 @@ -732,7 +841,7 @@ class CLikeCompiler: return head, main @staticmethod - def _have_prototype_templ(): + def _have_prototype_templ() -> T.Tuple[str, str]: """ 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. @@ -752,8 +861,11 @@ class CLikeCompiler: }}''' return head, main - def has_function(self, funcname, prefix, env, *, extra_args=None, dependencies=None): - """ + def has_function(self, funcname: str, prefix: str, env: 'Environment', *, + extra_args: T.Optional[T.List[str]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: + """Determine if a function exists. + 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 @@ -773,7 +885,11 @@ class CLikeCompiler: return val, False raise mesonlib.EnvironmentException('Cross variable {0} is not a boolean.'.format(varname)) - fargs = {'prefix': prefix, 'func': funcname} + # TODO: we really need a protocol for this, + # + # class StrProto(typing.Protocol): + # def __str__(self) -> str: ... + fargs = {'prefix': prefix, 'func': funcname} # type: T.Dict[str, T.Union[str, bool, int]] # 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 @@ -845,7 +961,10 @@ class CLikeCompiler: 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): + def has_members(self, typename: str, membernames: T.List[str], + prefix: str, env: 'Environment', *, + extra_args: T.Optional[T.List[str]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: if extra_args is None: extra_args = [] fargs = {'prefix': prefix, 'type': typename, 'name': 'foo'} @@ -862,7 +981,8 @@ class CLikeCompiler: return self.compiles(t.format(**fargs), env, extra_args=extra_args, dependencies=dependencies) - def has_type(self, typename, prefix, env, extra_args, dependencies=None): + def has_type(self, typename: str, prefix: str, env: 'Environment', extra_args: T.List[str], + dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: fargs = {'prefix': prefix, 'type': typename} t = '''{prefix} void bar(void) {{ @@ -871,7 +991,7 @@ class CLikeCompiler: return self.compiles(t.format(**fargs), env, extra_args=extra_args, dependencies=dependencies) - def symbols_have_underscore_prefix(self, env): + def symbols_have_underscore_prefix(self, env: 'Environment') -> bool: ''' Check if the compiler prefixes an underscore to global C symbols ''' @@ -906,8 +1026,8 @@ class CLikeCompiler: return False raise RuntimeError('BUG: {!r} check failed unexpectedly'.format(n)) - def _get_patterns(self, env, prefixes, suffixes, shared=False): - patterns = [] + def _get_patterns(self, env: 'Environment', prefixes: T.List[str], suffixes: T.List[str], shared: bool = False) -> T.List[str]: + patterns = [] # type: T.List[str] for p in prefixes: for s in suffixes: patterns.append(p + '{}.' + s) @@ -923,7 +1043,7 @@ class CLikeCompiler: patterns.append(p + '{}.so.[0-9]*.[0-9]*') return patterns - def get_library_naming(self, env, libtype: LibType, strict=False): + def get_library_naming(self, env: 'Environment', libtype: LibType, strict: bool = False) -> T.Tuple[str, ...]: ''' Get library prefixes and suffixes for the target platform ordered by priority @@ -970,8 +1090,8 @@ class CLikeCompiler: return tuple(patterns) @staticmethod - def _sort_shlibs_openbsd(libs): - filtered = [] + def _sort_shlibs_openbsd(libs: T.List[str]) -> T.List[str]: + filtered = [] # type: T.List[str] for lib in libs: # Validate file as a shared library of type libfoo.so.X.Y ret = lib.rsplit('.so.', maxsplit=1) @@ -986,7 +1106,7 @@ class CLikeCompiler: return sorted(filtered, key=float_cmp, reverse=True) @classmethod - def _get_trials_from_pattern(cls, pattern, directory, libname): + def _get_trials_from_pattern(cls, pattern: str, directory: str, libname: str) -> T.List[Path]: f = Path(directory) / pattern.format(libname) # Globbing for OpenBSD if '*' in pattern: @@ -996,7 +1116,7 @@ class CLikeCompiler: return [f] @staticmethod - def _get_file_from_list(env, files: T.List[str]) -> Path: + def _get_file_from_list(env: 'Environment', paths: T.List[Path]) -> 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 @@ -1004,7 +1124,6 @@ class CLikeCompiler: architecture. ''' # If not building on macOS for Darwin, do a simple file check - paths = [Path(f) for f in files] if not env.machines.host.is_darwin() or not env.machines.build.is_darwin(): for p in paths: if p.is_file(): @@ -1022,13 +1141,13 @@ class CLikeCompiler: return None @functools.lru_cache() - def output_is_64bit(self, env): + def output_is_64bit(self, env: 'Environment') -> bool: ''' 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): + def _find_library_real(self, libname: str, env: 'Environment', extra_dirs: T.List[str], code: str, libtype: LibType) -> T.Optional[T.List[str]]: # 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. @@ -1061,16 +1180,17 @@ class CLikeCompiler: # 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: + trials = self._get_trials_from_pattern(p, d, libname) + if not trials: continue - trial = self._get_file_from_list(env, trial) + trial = self._get_file_from_list(env, trials) if not trial: continue return [trial.as_posix()] return None - def find_library_impl(self, libname, env, extra_dirs, code, libtype: LibType): + def _find_library_impl(self, libname: str, env: 'Environment', extra_dirs: T.List[str], + code: str, libtype: LibType) -> T.Optional[T.List[str]]: # These libraries are either built-in or invalid if libname in self.ignore_libs: return [] @@ -1078,24 +1198,26 @@ class CLikeCompiler: 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) + 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[:] + return value.copy() - def find_library(self, libname, env, extra_dirs, libtype: LibType = LibType.PREFER_SHARED): + def find_library(self, libname: str, env: 'Environment', extra_dirs: T.List[str], + libtype: LibType = LibType.PREFER_SHARED) -> T.Optional[T.List[str]]: code = 'int main(void) { return 0; }\n' - return self.find_library_impl(libname, env, extra_dirs, code, libtype) + return self._find_library_impl(libname, env, extra_dirs, code, libtype) - def find_framework_paths(self, env): + def find_framework_paths(self, env: 'Environment') -> T.List[str]: ''' 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. ''' + # TODO: this really needs to be *AppleClang*, not just any clang. if self.id != 'clang': raise mesonlib.MesonException('Cannot find framework path with non-clang compiler') # Construct the compiler command-line @@ -1107,7 +1229,7 @@ class CLikeCompiler: os_env = os.environ.copy() os_env['LC_ALL'] = 'C' _, _, stde = mesonlib.Popen_safe(commands, env=os_env, stdin=subprocess.PIPE) - paths = [] + paths = [] # T.List[str] for line in stde.split('\n'): if '(framework directory)' not in line: continue @@ -1116,7 +1238,7 @@ class CLikeCompiler: paths.append(line[:-21].strip()) return paths - def find_framework_real(self, name, env, extra_dirs, allow_system): + def _find_framework_real(self, name: str, env: 'Environment', extra_dirs: T.List[str], allow_system: bool) -> T.Optional[T.List[str]]: code = 'int main(void) { return 0; }' link_args = [] for d in extra_dirs: @@ -1127,36 +1249,43 @@ class CLikeCompiler: link_args += ['-framework', name] if self.links(code, env, extra_args=(extra_args + link_args), disable_cache=True)[0]: return link_args + return None - def find_framework_impl(self, name, env, extra_dirs, allow_system): + def _find_framework_impl(self, name: str, env: 'Environment', extra_dirs: T.List[str], + allow_system: bool) -> T.Optional[T.List[str]]: 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) + 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[:] + return value.copy() - def find_framework(self, name, env, extra_dirs, allow_system=True): + def find_framework(self, name: str, env: 'Environment', extra_dirs: T.List[str], + allow_system: bool = True) -> T.Optional[T.List[str]]: ''' Finds the framework with the specified name, and returns link args for the same or returns None when the framework is not found. ''' + # TODO: maybe this belongs in clang? also, should probably check for macOS? 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) + return self._find_framework_impl(name, env, extra_dirs, allow_system) def get_crt_compile_args(self, crt_val: str, buildtype: str) -> T.List[str]: + # TODO: does this belong here or in GnuLike or maybe PosixLike? return [] def get_crt_link_args(self, crt_val: str, buildtype: str) -> T.List[str]: + # TODO: does this belong here or in GnuLike or maybe PosixLike? return [] - def thread_flags(self, env): + def thread_flags(self, env: 'Environment') -> T.List[str]: + # TODO: does this belong here or in GnuLike or maybe PosixLike? host_m = env.machines[self.for_machine] if host_m.is_haiku() or host_m.is_darwin(): return [] @@ -1165,19 +1294,21 @@ class CLikeCompiler: def thread_link_flags(self, env: 'Environment') -> T.List[str]: return self.linker.thread_flags(env) - def linker_to_compiler_args(self, args): - return args + def linker_to_compiler_args(self, args: T.List[str]) -> T.List[str]: + return args.copy() - def has_arguments(self, args: T.Sequence[str], env, code: str, mode: str) -> T.Tuple[bool, bool]: + def has_arguments(self, args: T.List[str], env: 'Environment', code: str, + mode: str) -> T.Tuple[bool, bool]: return self.compiles(code, env, extra_args=args, mode=mode) - def has_multi_arguments(self, args, env): - for arg in args[:]: + def has_multi_arguments(self, args: T.List[str], env: 'Environment') -> T.Tuple[bool, bool]: + new_args = [] # type: T.List[str] + 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:]) + new_args.append('-W' + arg[5:]) if arg.startswith('-Wl,'): mlog.warning('{} looks like a linker argument, ' 'but has_argument and other similar methods only ' @@ -1187,10 +1318,11 @@ class CLikeCompiler: 'the compiler you are using. has_link_argument or ' 'other similar method can be used instead.' .format(arg)) + new_args.append(arg) code = 'extern int i;\nint i;\n' - return self.has_arguments(args, env, code, mode='compile') + return self.has_arguments(new_args, env, code, mode='compile') - def has_multi_link_arguments(self, args, env): + def has_multi_link_arguments(self, args: T.List[str], env: 'Environment') -> T.Tuple[bool, bool]: # 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. @@ -1200,7 +1332,7 @@ class CLikeCompiler: return self.has_arguments(args, env, code, mode='link') @staticmethod - def concatenate_string_literals(s): + def _concatenate_string_literals(s: str) -> str: pattern = re.compile(r'(?P
.*([^\\]")|^")(?P([^\\"]|\\.)*)"\s+"(?P([^\\"]|\\.)*)(?P".*)')
         ret = s
         m = pattern.match(ret)
@@ -1209,13 +1341,13 @@ class CLikeCompiler:
             m = pattern.match(ret)
         return ret
 
-    def get_has_func_attribute_extra_args(self, name):
+    def get_has_func_attribute_extra_args(self, name: str) -> T.List[str]:
         # Most compilers (such as GCC and Clang) only warn about unknown or
         # ignored attributes, so force an error. Overriden in GCC and Clang
         # mixins.
         return ['-Werror']
 
-    def has_func_attribute(self, name, env):
+    def has_func_attribute(self, name: str, env: 'Environment') -> T.Tuple[bool, bool]:
         # Just assume that if we're not on windows that dllimport and dllexport
         # don't work
         m = env.machines[self.for_machine]
diff --git a/run_mypy.py b/run_mypy.py
index bf4389904..31b0678fc 100755
--- a/run_mypy.py
+++ b/run_mypy.py
@@ -18,6 +18,7 @@ modules = [
   'mesonbuild/compilers/mixins/arm.py',
   'mesonbuild/compilers/mixins/c2000.py',
   'mesonbuild/compilers/mixins/ccrx.py',
+  'mesonbuild/compilers/mixins/clike.py',
   # 'mesonbuild/compilers/mixins/intel.py',
   # 'mesonbuild/coredata.py',
   'mesonbuild/dependencies/boost.py',
diff --git a/run_tests.py b/run_tests.py
index 6e9c6ce9d..0f02636f5 100755
--- a/run_tests.py
+++ b/run_tests.py
@@ -263,8 +263,6 @@ def run_mtest_inprocess(commandlist):
     return returncode, stdout.getvalue(), stderr.getvalue()
 
 def clear_meson_configure_class_caches():
-    compilers.CCompiler.library_dirs_cache = {}
-    compilers.CCompiler.program_dirs_cache = {}
     compilers.CCompiler.find_library_cache = {}
     compilers.CCompiler.find_framework_cache = {}
     dependencies.PkgConfigDependency.pkgbin_cache = {}
diff --git a/run_unittests.py b/run_unittests.py
index d8a2a7b0b..db61ca6b1 100755
--- a/run_unittests.py
+++ b/run_unittests.py
@@ -792,7 +792,7 @@ class InternalTests(unittest.TestCase):
                 f.write('')
             with open(os.path.join(tmpdir, 'libfoo.so.70.0.so.1'), 'w') as f:
                 f.write('')
-            found = cc.find_library_real('foo', env, [tmpdir], '', LibType.PREFER_SHARED)
+            found = cc._find_library_real('foo', env, [tmpdir], '', LibType.PREFER_SHARED)
             self.assertEqual(os.path.basename(found[0]), 'libfoo.so.54.0')
 
     def test_find_library_patterns(self):