diff options
42 files changed, 898 insertions, 550 deletions
@@ -7,9 +7,19 @@ score=no [MESSAGES CONTROL] disable=all enable= + abstract-class-instantiated, assert-on-tuple, + bad-indentation, + compare-to-zero, dangerous-default-value, deprecated-lambda, - compare-to-zero, len-as-condition, + literal-comparison, + missing-kwoa, + mixed-indentation, + no-value-for-parameter, + redundant-keyword-arg, + singleton-comparison, + too-many-function-args, + unexpected-keyword-arg, unreachable diff --git a/mesonbuild/ast/introspection.py b/mesonbuild/ast/introspection.py index f03f7d2..9cfdded 100644 --- a/mesonbuild/ast/introspection.py +++ b/mesonbuild/ast/introspection.py @@ -151,14 +151,15 @@ class IntrospectionInterpreter(AstInterpreter): for for_machine in [MachineChoice.BUILD, MachineChoice.HOST]: self._add_languages(args, for_machine) - def _add_languages(self, langs: T.List[TYPE_nvar], for_machine: MachineChoice) -> None: - langs = self.flatten_args(langs) + def _add_languages(self, raw_langs: T.List[TYPE_nvar], for_machine: MachineChoice) -> None: + langs = [] # type: T.List[str] + for l in self.flatten_args(raw_langs): + if isinstance(l, str): + langs.append(l) + elif isinstance(l, StringNode): + langs.append(l.value) + for lang in sorted(langs, key=compilers.sort_clink): - if isinstance(lang, StringNode): - assert isinstance(lang.value, str) - lang = lang.value - if not isinstance(lang, str): - continue lang = lang.lower() if lang not in self.coredata.compilers[for_machine]: self.environment.detect_compiler_for(lang, for_machine) diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 66fc464..f89b917 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/build.py b/mesonbuild/build.py index 30fa77a..be97634 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -462,10 +462,10 @@ a hard error in the future.'''.format(name)) for k, v in option_overrides.items(): if '_' in k: - lang, k2 = k.split('_', 1) - if lang in all_languages: - self.option_overrides_compiler[lang][k2] = v - continue + lang, k2 = k.split('_', 1) + if lang in all_languages: + self.option_overrides_compiler[lang][k2] = v + continue self.option_overrides_base[k] = v def parse_overrides(self, kwargs) -> dict: diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py index 5cfa06c..091a000 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py @@ -56,15 +56,15 @@ class CCompiler(CLikeCompiler, Compiler): def __init__(self, exelist, version, for_machine: MachineChoice, is_cross: bool, info: 'MachineInfo', exe_wrapper: T.Optional[str] = None, **kwargs): # If a child ObjC or CPP class has already set it, don't set it ourselves - Compiler.__init__(self, exelist, version, for_machine, info, **kwargs) - CLikeCompiler.__init__(self, is_cross, exe_wrapper) + Compiler.__init__(self, exelist, version, for_machine, info, is_cross=is_cross, **kwargs) + CLikeCompiler.__init__(self, exe_wrapper) def get_no_stdinc_args(self): return ['-nostdinc'] def sanity_check(self, work_dir, environment): code = 'int main(void) { int class=0; return class; }\n' - return self.sanity_check_impl(work_dir, environment, 'sanitycheckc.c', code) + return self._sanity_check_impl(work_dir, environment, 'sanitycheckc.c', code) def has_header_symbol(self, hname, symbol, prefix, env, *, extra_args=None, dependencies=None): fargs = {'prefix': prefix, 'header': hname, 'symbol': symbol} diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index c4bd7c2..12643b0 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -24,28 +24,32 @@ from .. import mesonlib from ..linkers import LinkerEnvVarsMixin from ..mesonlib import ( EnvironmentException, MachineChoice, MesonException, - Popen_safe, split_args + Popen_safe, split_args, LibType ) from ..envconfig import ( Properties, get_env_var ) + from ..arglist import CompilerArgs if T.TYPE_CHECKING: + from ..build import BuildTarget from ..coredata import OptionDictType from ..envconfig import MachineInfo from ..environment import Environment from ..linkers import DynamicLinker # noqa: F401 + from ..dependencies import Dependency CompilerType = T.TypeVar('CompilerType', bound=Compiler) + _T = T.TypeVar('_T') """This file contains the data files of all compilers Meson knows about. To support a new compiler, add its information below. Also add corresponding autodetection code in environment.py.""" -header_suffixes = ('h', 'hh', 'hpp', 'hxx', 'H', 'ipp', 'moc', 'vapi', 'di') -obj_suffixes = ('o', 'obj', 'res') -lib_suffixes = ('a', 'lib', 'dll', 'dll.a', 'dylib', 'so') +header_suffixes = ('h', 'hh', 'hpp', 'hxx', 'H', 'ipp', 'moc', 'vapi', 'di') # type: T.Tuple[str, ...] +obj_suffixes = ('o', 'obj', 'res') # type: T.Tuple[str, ...] +lib_suffixes = ('a', 'lib', 'dll', 'dll.a', 'dylib', 'so') # type: T.Tuple[str, ...] # Mapping of language to suffixes of files that should always be in that language # This means we can't include .h headers here since they could be C, C++, ObjC, etc. lang_suffixes = { @@ -63,26 +67,26 @@ lang_suffixes = { 'cs': ('cs',), 'swift': ('swift',), 'java': ('java',), -} +} # type: T.Dict[str, T.Tuple[str, ...]] all_languages = lang_suffixes.keys() -cpp_suffixes = lang_suffixes['cpp'] + ('h',) -c_suffixes = lang_suffixes['c'] + ('h',) +cpp_suffixes = lang_suffixes['cpp'] + ('h',) # type: T.Tuple[str, ...] +c_suffixes = lang_suffixes['c'] + ('h',) # type: T.Tuple[str, ...] # List of languages that by default consume and output libraries following the # C ABI; these can generally be used interchangebly -clib_langs = ('objcpp', 'cpp', 'objc', 'c', 'fortran',) +clib_langs = ('objcpp', 'cpp', 'objc', 'c', 'fortran',) # type: T.Tuple[str, ...] # List of languages that can be linked with C code directly by the linker # used in build.py:process_compilers() and build.py:get_dynamic_linker() -clink_langs = ('d', 'cuda') + clib_langs -clink_suffixes = () +clink_langs = ('d', 'cuda') + clib_langs # type: T.Tuple[str, ...] +clink_suffixes = tuple() # type: T.Tuple[str, ...] for _l in clink_langs + ('vala',): clink_suffixes += lang_suffixes[_l] clink_suffixes += ('h', 'll', 's') -all_suffixes = set(itertools.chain(*lang_suffixes.values(), clink_suffixes)) +all_suffixes = set(itertools.chain(*lang_suffixes.values(), clink_suffixes)) # type: T.Set[str] # Languages that should use LDFLAGS arguments when linking. -languages_using_ldflags = {'objcpp', 'cpp', 'objc', 'c', 'fortran', 'd', 'cuda'} +languages_using_ldflags = {'objcpp', 'cpp', 'objc', 'c', 'fortran', 'd', 'cuda'} # type: T.Set[str] # Languages that should use CPPFLAGS arguments when linking. -languages_using_cppflags = {'c', 'cpp', 'objc', 'objcpp'} +languages_using_cppflags = {'c', 'cpp', 'objc', 'objcpp'} # type: T.Set[str] soregex = re.compile(r'.*\.so(\.[0-9]+)?(\.[0-9]+)?(\.[0-9]+)?$') # Environment variables that each lang uses. @@ -94,14 +98,14 @@ cflags_mapping = {'c': 'CFLAGS', 'fortran': 'FFLAGS', 'd': 'DFLAGS', 'vala': 'VALAFLAGS', - 'rust': 'RUSTFLAGS'} + 'rust': 'RUSTFLAGS'} # type: T.Dict[str, str] cexe_mapping = {'c': 'CC', 'cpp': 'CXX'} # All these are only for C-linkable languages; see `clink_langs` above. -def sort_clink(lang): +def sort_clink(lang: str) -> int: ''' Sorting function to sort the list of languages according to reversed(compilers.clink_langs) and append the unknown langs in the end. @@ -112,40 +116,40 @@ def sort_clink(lang): return 1 return -clink_langs.index(lang) -def is_header(fname): - if hasattr(fname, 'fname'): +def is_header(fname: 'mesonlib.FileOrString') -> bool: + if isinstance(fname, mesonlib.File): fname = fname.fname suffix = fname.split('.')[-1] return suffix in header_suffixes -def is_source(fname): - if hasattr(fname, 'fname'): +def is_source(fname: 'mesonlib.FileOrString') -> bool: + if isinstance(fname, mesonlib.File): fname = fname.fname suffix = fname.split('.')[-1].lower() return suffix in clink_suffixes -def is_assembly(fname): - if hasattr(fname, 'fname'): +def is_assembly(fname: 'mesonlib.FileOrString') -> bool: + if isinstance(fname, mesonlib.File): fname = fname.fname return fname.split('.')[-1].lower() == 's' -def is_llvm_ir(fname): - if hasattr(fname, 'fname'): +def is_llvm_ir(fname: 'mesonlib.FileOrString') -> bool: + if isinstance(fname, mesonlib.File): fname = fname.fname return fname.split('.')[-1] == 'll' @lru_cache(maxsize=None) -def cached_by_name(fname): +def cached_by_name(fname: 'mesonlib.FileOrString') -> bool: suffix = fname.split('.')[-1] return suffix in obj_suffixes -def is_object(fname): - if hasattr(fname, 'fname'): +def is_object(fname: 'mesonlib.FileOrString') -> bool: + if isinstance(fname, mesonlib.File): fname = fname.fname return cached_by_name(fname) -def is_library(fname): - if hasattr(fname, 'fname'): +def is_library(fname: 'mesonlib.FileOrString') -> bool: + if isinstance(fname, mesonlib.File): fname = fname.fname if soregex.match(fname): @@ -154,8 +158,8 @@ def is_library(fname): suffix = fname.split('.')[-1] return suffix in lib_suffixes -def is_known_suffix(fname): - if hasattr(fname, 'fname'): +def is_known_suffix(fname: 'mesonlib.FileOrString') -> bool: + if isinstance(fname, mesonlib.File): fname = fname.fname suffix = fname.split('.')[-1] @@ -166,14 +170,14 @@ cuda_buildtype_args = {'plain': [], 'debugoptimized': [], 'release': [], 'minsize': [], - } + } # type: T.Dict[str, T.List[str]] java_buildtype_args = {'plain': [], 'debug': ['-g'], 'debugoptimized': ['-g'], 'release': [], 'minsize': [], 'custom': [], - } + } # type: T.Dict[str, T.List[str]] rust_buildtype_args = {'plain': [], 'debug': [], @@ -181,7 +185,7 @@ rust_buildtype_args = {'plain': [], 'release': [], 'minsize': [], 'custom': [], - } + } # type: T.Dict[str, T.List[str]] d_gdc_buildtype_args = {'plain': [], 'debug': [], @@ -189,7 +193,7 @@ d_gdc_buildtype_args = {'plain': [], 'release': ['-finline-functions'], 'minsize': [], 'custom': [], - } + } # type: T.Dict[str, T.List[str]] d_ldc_buildtype_args = {'plain': [], 'debug': [], @@ -197,7 +201,7 @@ d_ldc_buildtype_args = {'plain': [], 'release': ['-enable-inlining', '-Hkeep-all-bodies'], 'minsize': [], 'custom': [], - } + } # type: T.Dict[str, T.List[str]] d_dmd_buildtype_args = {'plain': [], 'debug': [], @@ -205,7 +209,7 @@ d_dmd_buildtype_args = {'plain': [], 'release': ['-inline'], 'minsize': [], 'custom': [], - } + } # type: T.Dict[str, T.List[str]] mono_buildtype_args = {'plain': [], 'debug': [], @@ -213,7 +217,7 @@ mono_buildtype_args = {'plain': [], 'release': ['-optimize+'], 'minsize': [], 'custom': [], - } + } # type: T.Dict[str, T.List[str]] swift_buildtype_args = {'plain': [], 'debug': [], @@ -221,14 +225,14 @@ swift_buildtype_args = {'plain': [], 'release': [], 'minsize': [], 'custom': [], - } + } # type: T.Dict[str, T.List[str]] gnu_winlibs = ['-lkernel32', '-luser32', '-lgdi32', '-lwinspool', '-lshell32', - '-lole32', '-loleaut32', '-luuid', '-lcomdlg32', '-ladvapi32'] + '-lole32', '-loleaut32', '-luuid', '-lcomdlg32', '-ladvapi32'] # type: T.List[str] msvc_winlibs = ['kernel32.lib', 'user32.lib', 'gdi32.lib', 'winspool.lib', 'shell32.lib', 'ole32.lib', 'oleaut32.lib', - 'uuid.lib', 'comdlg32.lib', 'advapi32.lib'] + 'uuid.lib', 'comdlg32.lib', 'advapi32.lib'] # type: T.List[str] clike_optimization_args = {'0': [], 'g': [], @@ -236,7 +240,7 @@ clike_optimization_args = {'0': [], '2': ['-O2'], '3': ['-O3'], 's': ['-Os'], - } + } # type: T.Dict[str, T.List[str]] cuda_optimization_args = {'0': [], 'g': ['-O0'], @@ -244,13 +248,13 @@ cuda_optimization_args = {'0': [], '2': ['-O2'], '3': ['-O3'], 's': ['-O3'] - } + } # type: T.Dict[str, T.List[str]] cuda_debug_args = {False: [], - True: ['-g']} + True: ['-g']} # type: T.Dict[bool, T.List[str]] clike_debug_args = {False: [], - True: ['-g']} + True: ['-g']} # type: T.Dict[bool, T.List[str]] base_options = {'b_pch': coredata.UserBooleanOption('Use precompiled headers', True), 'b_lto': coredata.UserBooleanOption('Use link time optimization', False), @@ -278,18 +282,21 @@ base_options = {'b_pch': coredata.UserBooleanOption('Use precompiled headers', T 'b_vscrt': coredata.UserComboOption('VS run-time library type to use.', ['none', 'md', 'mdd', 'mt', 'mtd', 'from_buildtype'], 'from_buildtype'), - } + } # type: OptionDictType -def option_enabled(boptions, options, option): +def option_enabled(boptions: T.List[str], options: 'OptionDictType', + option: str) -> bool: try: if option not in boptions: return False - return options[option].value + ret = options[option].value + assert isinstance(ret, bool), 'must return bool' # could also be str + return ret except KeyError: return False -def get_base_compile_args(options, compiler): - args = [] +def get_base_compile_args(options: 'OptionDictType', compiler: 'Compiler') -> T.List[str]: + args = [] # type T.List[str] try: if options['b_lto'].value: args.extend(compiler.get_lto_compile_args()) @@ -337,8 +344,9 @@ def get_base_compile_args(options, compiler): pass return args -def get_base_link_args(options, linker, is_shared_module): - args = [] +def get_base_link_args(options: 'OptionDictType', linker: 'Compiler', + is_shared_module: bool) -> T.List[str]: + args = [] # type: T.List[str] try: if options['b_lto'].value: args.extend(linker.get_lto_link_args()) @@ -398,32 +406,58 @@ class CrossNoRunException(MesonException): pass class RunResult: - def __init__(self, compiled, returncode=999, stdout='UNDEFINED', stderr='UNDEFINED'): + def __init__(self, compiled: bool, returncode: int = 999, + stdout: str = 'UNDEFINED', stderr: str = 'UNDEFINED'): self.compiled = compiled self.returncode = returncode self.stdout = stdout self.stderr = stderr +class CompileResult: + + """The result of Compiler.compiles (and friends).""" + + def __init__(self, stdo: T.Optional[str] = None, stde: T.Optional[str] = None, + args: T.Optional[T.List[str]] = None, + returncode: int = 999, pid: int = -1, + text_mode: bool = True, + input_name: T.Optional[str] = None, + output_name: T.Optional[str] = None, + command: T.Optional[T.List[str]] = None, cached: bool = False): + self.stdout = stdo + self.stderr = stde + self.input_name = input_name + self.output_name = output_name + self.command = command or [] + self.args = args or [] + self.cached = cached + self.returncode = returncode + self.pid = pid + self.text_mode = text_mode + + class Compiler(metaclass=abc.ABCMeta): # Libraries to ignore in find_library() since they are provided by the # compiler or the C library. Currently only used for MSVC. - ignore_libs = () + ignore_libs = [] # type: T.List[str] # Libraries that are internal compiler implementations, and must not be # manually searched. - internal_libs = () + internal_libs = [] # type: T.List[str] LINKER_PREFIX = None # type: T.Union[None, str, T.List[str]] INVOKES_LINKER = True - def __init__(self, exelist, version, for_machine: MachineChoice, info: 'MachineInfo', - linker: T.Optional['DynamicLinker'] = None, **kwargs): - if isinstance(exelist, str): - self.exelist = [exelist] - elif isinstance(exelist, list): - self.exelist = exelist - else: - raise TypeError('Unknown argument to Compiler') + # TODO: these could be forward declarations once we drop 3.5 support + if T.TYPE_CHECKING: + language = 'unset' + id = '' + + def __init__(self, exelist: T.List[str], version: str, + for_machine: MachineChoice, info: 'MachineInfo', + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None, is_cross: bool = False): + self.exelist = exelist # In case it's been overridden by a child class already if not hasattr(self, 'file_suffixes'): self.file_suffixes = lang_suffixes[self.language] @@ -431,28 +465,24 @@ class Compiler(metaclass=abc.ABCMeta): self.can_compile_suffixes = set(self.file_suffixes) self.default_suffix = self.file_suffixes[0] self.version = version - if 'full_version' in kwargs: - self.full_version = kwargs['full_version'] - else: - self.full_version = None + self.full_version = full_version self.for_machine = for_machine - self.base_options = [] + self.base_options = [] # type: T.List[str] self.linker = linker self.info = info + self.is_cross = is_cross - def __repr__(self): + def __repr__(self) -> str: repr_str = "<{0}: v{1} `{2}`>" return repr_str.format(self.__class__.__name__, self.version, ' '.join(self.exelist)) @lru_cache(maxsize=None) - def can_compile(self, src) -> bool: - if hasattr(src, 'fname'): + def can_compile(self, src: 'mesonlib.FileOrString') -> bool: + if isinstance(src, mesonlib.File): src = src.fname suffix = os.path.splitext(src)[1].lower() - if suffix and suffix[1:] in self.can_compile_suffixes: - return True - return False + return bool(suffix) and suffix[1:] in self.can_compile_suffixes def get_id(self) -> str: return self.id @@ -482,42 +512,54 @@ class Compiler(metaclass=abc.ABCMeta): def get_default_suffix(self) -> str: return self.default_suffix - def get_define(self, dname, prefix, env, extra_args, dependencies) -> T.Tuple[str, bool]: + def get_define(self, dname: str, prefix: str, env: 'Environment', + 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, low, high, guess, prefix, env, extra_args, dependencies) -> int: + 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]], dependencies: T.Optional[T.List['Dependency']]) -> int: raise EnvironmentException('%s does not support compute_int ' % self.get_id()) - def compute_parameters_with_absolute_paths(self, parameter_list, build_dir): + 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, membernames, prefix, env, *, - extra_args=None, dependencies=None) -> T.Tuple[bool, bool]: + 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]: raise EnvironmentException('%s does not support has_member(s) ' % self.get_id()) - def has_type(self, typename, prefix, env, extra_args, *, - dependencies=None) -> T.Tuple[bool, bool]: + 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]: raise EnvironmentException('%s does not support has_type ' % self.get_id()) - def symbols_have_underscore_prefix(self, env) -> bool: + def symbols_have_underscore_prefix(self, env: 'Environment') -> bool: raise EnvironmentException('%s does not support symbols_have_underscore_prefix ' % self.get_id()) - def get_exelist(self): - return self.exelist[:] + def get_exelist(self) -> T.List[str]: + return self.exelist.copy() def get_linker_exelist(self) -> T.List[str]: return self.linker.get_exelist() + @abc.abstractmethod + def get_output_args(self, outputname: str) -> T.List[str]: + pass + def get_linker_output_args(self, outputname: str) -> T.List[str]: return self.linker.get_output_args(outputname) - def get_builtin_define(self, *args, **kwargs): + def get_builtin_define(self, define: str) -> T.Optional[str]: raise EnvironmentException('%s does not support get_builtin_define.' % self.id) - def has_builtin_define(self, *args, **kwargs): + def has_builtin_define(self, define: str) -> bool: raise EnvironmentException('%s does not support has_builtin_define.' % self.id) - def get_always_args(self): + def get_always_args(self) -> T.List[str]: return [] def can_linker_accept_rsp(self) -> bool: @@ -529,10 +571,10 @@ class Compiler(metaclass=abc.ABCMeta): def get_linker_always_args(self) -> T.List[str]: return self.linker.get_always_args() - def get_linker_lib_prefix(self): + def get_linker_lib_prefix(self) -> str: return self.linker.get_lib_prefix() - def gen_import_library_args(self, implibname): + def gen_import_library_args(self, implibname: str) -> T.List[str]: """ Used only on Windows for libraries that need an import library. This currently means C, C++, Fortran. @@ -544,62 +586,115 @@ class Compiler(metaclass=abc.ABCMeta): is_cross: bool) -> T.List[str]: return self.linker.get_args_from_envvars(for_machine, is_cross) - def get_options(self) -> T.Dict[str, coredata.UserOption]: + def get_options(self) -> 'OptionDictType': return {} - def get_option_compile_args(self, options): + def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: return [] def get_option_link_args(self, options: 'OptionDictType') -> T.List[str]: return self.linker.get_option_args(options) - def check_header(self, *args, **kwargs) -> T.Tuple[bool, bool]: + 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]: + """Check that header is usable. + + Returns a two item tuple of bools. The first bool is whether the + check succeeded, the second is whether the result was cached (True) + or run fresh (False). + """ raise EnvironmentException('Language %s does not support header checks.' % self.get_display_language()) - def has_header(self, *args, **kwargs) -> T.Tuple[bool, bool]: + 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]: + """Check that header is exists. + + This check will return true if the file exists, even if it contains: + + ```c + # error "You thought you could use this, LOLZ!" + ``` + + Use check_header if your header only works in some cases. + + Returns a two item tuple of bools. The first bool is whether the + check succeeded, the second is whether the result was cached (True) + or run fresh (False). + """ raise EnvironmentException('Language %s does not support header checks.' % self.get_display_language()) - def has_header_symbol(self, *args, **kwargs) -> T.Tuple[bool, bool]: + 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]: raise EnvironmentException('Language %s does not support header symbol checks.' % self.get_display_language()) - def compiles(self, *args, **kwargs) -> T.Tuple[bool, bool]: + def compiles(self, code: str, env: 'Environment', *, + 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, *args, **kwargs) -> T.Tuple[bool, bool]: + def links(self, code: str, env: 'Environment', *, + 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 link checks.' % self.get_display_language()) - def run(self, *args, **kwargs) -> RunResult: + def run(self, code: str, env: 'Environment', *, + extra_args: T.Optional[T.List[str]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> RunResult: raise EnvironmentException('Language %s does not support run checks.' % self.get_display_language()) - def sizeof(self, *args, **kwargs) -> int: + 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: raise EnvironmentException('Language %s does not support sizeof checks.' % self.get_display_language()) - def alignment(self, *args, **kwargs) -> int: + 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: raise EnvironmentException('Language %s does not support alignment checks.' % self.get_display_language()) - def has_function(self, *args, **kwargs) -> T.Tuple[bool, bool]: + 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]: + """See if a function exists. + + Returns a two item tuple of bools. The first bool is whether the + check succeeded, the second is whether the result was cached (True) + or run fresh (False). + """ raise EnvironmentException('Language %s does not support function checks.' % self.get_display_language()) - @classmethod - def unix_args_to_native(cls, args: T.List[str]) -> T.List[str]: + def unix_args_to_native(self, args: T.List[str]) -> T.List[str]: "Always returns a copy that can be independently mutated" - return args[:] + return args.copy() @classmethod def native_args_to_unix(cls, args: T.List[str]) -> T.List[str]: "Always returns a copy that can be independently mutated" - return args[:] + return args.copy() - def find_library(self, *args, **kwargs): + def find_library(self, libname: str, env: 'Environment', extra_dirs: T.List[str], + libtype: LibType = LibType.PREFER_SHARED) -> T.Optional[T.List[str]]: raise EnvironmentException('Language {} does not support library finding.'.format(self.get_display_language())) - def get_library_dirs(self, *args, **kwargs): - return () + def get_library_naming(self, env: 'Environment', libtype: LibType, + 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, *args, **kwargs): + def get_program_dirs(self, env: 'Environment') -> T.List[str]: return [] - def has_multi_arguments(self, args, env) -> T.Tuple[bool, bool]: + def has_multi_arguments(self, args: T.List[str], env: 'Environment') -> T.Tuple[bool, bool]: raise EnvironmentException( 'Language {} does not support has_multi_arguments.'.format( self.get_display_language())) @@ -607,7 +702,8 @@ class Compiler(metaclass=abc.ABCMeta): def has_multi_link_arguments(self, args: T.List[str], env: 'Environment') -> T.Tuple[bool, bool]: return self.linker.has_multi_arguments(args, env) - def _get_compile_output(self, dirname, mode): + def _get_compile_output(self, dirname: str, mode: str) -> str: + # TODO: mode should really be an enum # In pre-processor mode, the output is sent to stdout and discarded if mode == 'preprocess': return None @@ -619,8 +715,9 @@ class Compiler(metaclass=abc.ABCMeta): suffix = 'obj' return os.path.join(dirname, 'output.' + suffix) - def get_compiler_args_for_mode(self, mode): - args = [] + def get_compiler_args_for_mode(self, mode: str) -> T.List[str]: + # TODO: mode should really be an enum + args = [] # type: T.List[str] args += self.get_always_args() if mode == 'compile': args += self.get_compile_only_args() @@ -633,7 +730,11 @@ class Compiler(metaclass=abc.ABCMeta): return CompilerArgs(self, args) @contextlib.contextmanager - def compile(self, code: str, extra_args: list = None, *, mode: str = 'link', want_output: bool = False, temp_dir: 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: @@ -646,8 +747,11 @@ class Compiler(metaclass=abc.ABCMeta): ofile.write(code) # ccache would result in a cache miss no_ccache = True + contents = code elif isinstance(code, mesonlib.File): srcname = code.fname + with open(code.fname, 'r') as f: + contents = f.read() # Construct the compiler command-line commands = self.compiler_args() @@ -662,69 +766,62 @@ class Compiler(metaclass=abc.ABCMeta): # in the command line after '/link' is given to the linker. commands += extra_args # Generate full command-line with the exelist - commands = self.get_exelist() + commands.to_native() + command_list = self.get_exelist() + commands.to_native() mlog.debug('Running compile:') mlog.debug('Working directory: ', tmpdirname) - mlog.debug('Command line: ', ' '.join(commands), '\n') - mlog.debug('Code:\n', code) + mlog.debug('Command line: ', ' '.join(command_list), '\n') + mlog.debug('Code:\n', contents) os_env = os.environ.copy() os_env['LC_ALL'] = 'C' if no_ccache: os_env['CCACHE_DISABLE'] = '1' - p, p.stdo, p.stde = Popen_safe(commands, cwd=tmpdirname, env=os_env) - mlog.debug('Compiler stdout:\n', p.stdo) - mlog.debug('Compiler stderr:\n', p.stde) - p.commands = commands - p.input_name = srcname + p, stdo, stde = Popen_safe(command_list, cwd=tmpdirname, env=os_env) + mlog.debug('Compiler stdout:\n', stdo) + mlog.debug('Compiler stderr:\n', stde) + + result = CompileResult(stdo, stde, list(commands), p.returncode, p.pid, input_name=srcname) if want_output: - p.output_name = output - p.cached = False # Make sure that the cached attribute always exists - yield p + result.output_name = output + yield result except OSError: # On Windows antivirus programs and the like hold on to files so # they can't be deleted. There's not much to do in this case. Also, # catch OSError because the directory is then no longer empty. - pass + yield None @contextlib.contextmanager - def cached_compile(self, code, cdata: coredata.CoreData, *, extra_args=None, mode: str = 'link', temp_dir=None): - assert(isinstance(cdata, coredata.CoreData)) + def cached_compile(self, code: str, cdata: coredata.CoreData, *, + 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 None - key = (tuple(self.exelist), self.version, code, textra_args, mode) - - # Check if not cached - if key not in cdata.compiler_check_cache: + 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 + + # Check if not cached, and generate, otherwise get from the cache + if key in cdata.compiler_check_cache: + p = cdata.compiler_check_cache[key] # type: CompileResult + p.cached = True + mlog.debug('Using cached compile:') + mlog.debug('Cached command line: ', ' '.join(p.command), '\n') + mlog.debug('Code:\n', code) + mlog.debug('Cached compiler stdout:\n', p.stdout) + mlog.debug('Cached compiler stderr:\n', p.stderr) + yield p + else: with self.compile(code, extra_args=extra_args, mode=mode, want_output=False, temp_dir=temp_dir) as p: - # Remove all attributes except the following - # This way the object can be serialized - tokeep = ['args', 'commands', 'input_name', 'output_name', - 'pid', 'returncode', 'stdo', 'stde', 'text_mode'] - todel = [x for x in vars(p).keys() if x not in tokeep] - for i in todel: - delattr(p, i) - p.cached = False cdata.compiler_check_cache[key] = p yield p - return - - # Return cached - p = cdata.compiler_check_cache[key] - p.cached = True - mlog.debug('Using cached compile:') - mlog.debug('Cached command line: ', ' '.join(p.commands), '\n') - mlog.debug('Code:\n', code) - mlog.debug('Cached compiler stdout:\n', p.stdo) - mlog.debug('Cached compiler stderr:\n', p.stde) - yield p - - def get_colorout_args(self, colortype): + + def get_colorout_args(self, colortype: str) -> T.List[str]: + # TODO: colortype can probably be an emum return [] # Some compilers (msvc) write debug info to a separate file. # These args specify where it should be written. - def get_compile_debugfile_args(self, rel_obj, **kwargs): + def get_compile_debugfile_args(self, rel_obj: str, pch: bool = False) -> T.List[str]: return [] def get_link_debugfile_name(self, targetfile: str) -> str: @@ -748,10 +845,12 @@ class Compiler(metaclass=abc.ABCMeta): def no_undefined_link_args(self) -> T.List[str]: return self.linker.no_undefined_args() - # Compiler arguments needed to enable the given instruction set. - # May be [] meaning nothing needed or None meaning the given set - # is not supported. - def get_instruction_set_args(self, instruction_set): + def get_instruction_set_args(self, instruction_set: str) -> T.Optional[T.List[str]]: + """Compiler arguments needed to enable the given instruction set. + + Return type ay be an empty list meaning nothing needed or None + meaning the given set is not supported. + """ return None def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, @@ -760,40 +859,40 @@ class Compiler(metaclass=abc.ABCMeta): return self.linker.build_rpath_args( env, build_dir, from_dir, rpath_paths, build_rpath, install_rpath) - def thread_flags(self, env): + def thread_flags(self, env: 'Environment') -> T.List[str]: return [] - def openmp_flags(self): + def openmp_flags(self) -> T.List[str]: raise EnvironmentException('Language %s does not support OpenMP flags.' % self.get_display_language()) - def openmp_link_flags(self): + def openmp_link_flags(self) -> T.List[str]: return self.openmp_flags() - def language_stdlib_only_link_flags(self): + def language_stdlib_only_link_flags(self) -> T.List[str]: return [] - def gnu_symbol_visibility_args(self, vistype): + def gnu_symbol_visibility_args(self, vistype: str) -> T.List[str]: return [] - def get_gui_app_args(self, value): + def get_gui_app_args(self, value: bool) -> T.List[str]: return [] - def has_func_attribute(self, name, env): + def has_func_attribute(self, name: str, env: 'Environment') -> T.Tuple[bool, bool]: raise EnvironmentException( 'Language {} does not support function attributes.'.format(self.get_display_language())) - def get_pic_args(self): + def get_pic_args(self) -> T.List[str]: m = 'Language {} does not support position-independent code' raise EnvironmentException(m.format(self.get_display_language())) - def get_pie_args(self): + def get_pie_args(self) -> T.List[str]: m = 'Language {} does not support position-independent executable' raise EnvironmentException(m.format(self.get_display_language())) def get_pie_link_args(self) -> T.List[str]: return self.linker.get_pie_args() - def get_argument_syntax(self): + def get_argument_syntax(self) -> str: """Returns the argument family type. Compilers fall into families if they try to emulate the command line @@ -804,22 +903,19 @@ class Compiler(metaclass=abc.ABCMeta): """ return 'other' - def get_profile_generate_args(self): + def get_profile_generate_args(self) -> T.List[str]: raise EnvironmentException( '%s does not support get_profile_generate_args ' % self.get_id()) - def get_profile_use_args(self): + def get_profile_use_args(self) -> T.List[str]: raise EnvironmentException( '%s does not support get_profile_use_args ' % self.get_id()) - def get_undefined_link_args(self) -> T.List[str]: - return self.linker.get_undefined_link_args() - - def remove_linkerlike_args(self, args): + def remove_linkerlike_args(self, args: T.List[str]) -> T.List[str]: rm_exact = ('-headerpad_max_install_names',) rm_prefixes = ('-Wl,', '-L',) rm_next = ('-L', '-framework',) - ret = [] + ret = [] # T.List[str] iargs = iter(args) for arg in iargs: # Remove this argument @@ -868,13 +964,13 @@ class Compiler(metaclass=abc.ABCMeta): env, prefix, shlib_name, suffix, soversion, darwin_versions, is_shared_module) - def get_target_link_args(self, target): + def get_target_link_args(self, target: 'BuildTarget') -> T.List[str]: return target.link_args - def get_dependency_compile_args(self, dep): + def get_dependency_compile_args(self, dep: 'Dependency') -> T.List[str]: return dep.get_compile_args() - def get_dependency_link_args(self, dep): + def get_dependency_link_args(self, dep: 'Dependency') -> T.List[str]: return dep.get_link_args() @classmethod @@ -883,33 +979,73 @@ class Compiler(metaclass=abc.ABCMeta): """ return [] + def get_coverage_args(self) -> T.List[str]: + return [] + def get_coverage_link_args(self) -> T.List[str]: return self.linker.get_coverage_args() def get_disable_assert_args(self) -> T.List[str]: return [] + def get_crt_compile_args(self, crt_val: str, buildtype: str) -> T.List[str]: + raise EnvironmentError('This compiler does not support Windows CRT selection') -def get_largefile_args(compiler): - ''' - 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_crt_link_args(self, crt_val: str, buildtype: str) -> T.List[str]: + raise EnvironmentError('This compiler does not support Windows CRT selection') + + def get_compile_only_args(self) -> T.List[str]: + return [] + + 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_pch_suffix(self) -> str: + raise EnvironmentException('{} does not support pre compiled headers'.format(self.id)) + + def get_pch_name(self, name: str) -> str: + raise EnvironmentException('{} does not support pre compiled headers'.format(self.id)) + + def get_pch_use_args(self, pch_dir: str, header: str) -> T.List[str]: + raise EnvironmentException('{} does not support pre compiled headers'.format(self.id)) + + def get_has_func_attribute_extra_args(self, name: str) -> T.List[str]: + raise EnvironmentException('{} does not support function attributes'.format(self.id)) + + def name_string(self) -> str: + return ' '.join(self.exelist) def get_args_from_envvars(lang: str, @@ -953,7 +1089,7 @@ def get_global_options(lang: str, comp: T.Type[Compiler], for_machine: MachineChoice, is_cross: bool, - properties: Properties) -> T.Dict[str, coredata.UserOption]: + properties: Properties) -> 'OptionDictType': """Retreive options that apply to all compilers for a given language.""" description = 'Extra arguments passed to the {}'.format(lang) opts = { @@ -963,7 +1099,7 @@ def get_global_options(lang: str, 'link_args': coredata.UserArrayOption( description + ' linker', [], split_args=True, user_input=True, allow_dups=True), - } + } # type: OptionDictType # Get from env vars. compile_args, link_args = get_args_from_envvars( diff --git a/mesonbuild/compilers/cpp.py b/mesonbuild/compilers/cpp.py index 698c71a..b5dbdda 100644 --- a/mesonbuild/compilers/cpp.py +++ b/mesonbuild/compilers/cpp.py @@ -65,8 +65,8 @@ class CPPCompiler(CLikeCompiler, Compiler): def __init__(self, exelist, version, for_machine: MachineChoice, is_cross: bool, info: 'MachineInfo', exe_wrap: T.Optional[str] = None, **kwargs): # If a child ObjCPP class has already set it, don't set it ourselves - Compiler.__init__(self, exelist, version, for_machine, info, **kwargs) - CLikeCompiler.__init__(self, is_cross, exe_wrap) + Compiler.__init__(self, exelist, version, for_machine, info, is_cross=is_cross, **kwargs) + CLikeCompiler.__init__(self, exe_wrap) @staticmethod def get_display_language(): @@ -77,7 +77,7 @@ class CPPCompiler(CLikeCompiler, Compiler): def sanity_check(self, work_dir, environment): code = 'class breakCCompiler;int main(void) { return 0; }\n' - return self.sanity_check_impl(work_dir, environment, 'sanitycheckcpp.cc', code) + return self._sanity_check_impl(work_dir, environment, 'sanitycheckcpp.cc', code) def get_compiler_check_args(self): # -fpermissive allows non-conforming code to compile which is necessary diff --git a/mesonbuild/compilers/cs.py b/mesonbuild/compilers/cs.py index 843348e..b269aec 100644 --- a/mesonbuild/compilers/cs.py +++ b/mesonbuild/compilers/cs.py @@ -40,7 +40,6 @@ class CsCompiler(BasicLinkerIsCompilerMixin, Compiler): info: 'MachineInfo', comp_id, runner=None): super().__init__(exelist, version, for_machine, info) self.id = comp_id - self.is_cross = False self.runner = runner @classmethod @@ -95,9 +94,6 @@ class CsCompiler(BasicLinkerIsCompilerMixin, Compiler): return parameter_list - def name_string(self): - return ' '.join(self.exelist) - def get_pch_use_args(self, pch_dir, header): return [] diff --git a/mesonbuild/compilers/cuda.py b/mesonbuild/compilers/cuda.py index 934ad12..482d504 100644 --- a/mesonbuild/compilers/cuda.py +++ b/mesonbuild/compilers/cuda.py @@ -161,7 +161,7 @@ class CudaCompiler(Compiler): mlog.debug('cudaGetDeviceCount() returned ' + stde) def has_header_symbol(self, hname, symbol, prefix, env, extra_args=None, dependencies=None): - result, cached = super().has_header_symbol(hname, symbol, prefix, env, extra_args, dependencies) + result, cached = super().has_header_symbol(hname, symbol, prefix, env, extra_args=extra_args, dependencies=dependencies) if result: return True, cached if extra_args is None: @@ -171,7 +171,7 @@ class CudaCompiler(Compiler): #include <{header}> using {symbol}; int main(void) {{ return 0; }}''' - return self.compiles(t.format(**fargs), env, extra_args, dependencies) + return self.compiles(t.format(**fargs), env, extra_args=extra_args, dependencies=dependencies) def get_options(self): opts = super().get_options() @@ -210,9 +210,6 @@ class CudaCompiler(Compiler): def get_option_link_args(self, options): return self._cook_link_args(self.host_compiler.get_option_link_args(self._to_host_compiler_options(options))) - def name_string(self): - return ' '.join(self.exelist) - def get_soname_args(self, *args): return self._cook_link_args(self.host_compiler.get_soname_args(*args)) diff --git a/mesonbuild/compilers/d.py b/mesonbuild/compilers/d.py index a74dc95..ca7f80d 100644 --- a/mesonbuild/compilers/d.py +++ b/mesonbuild/compilers/d.py @@ -30,6 +30,7 @@ from .compilers import ( from .mixins.gnu import GnuCompiler if T.TYPE_CHECKING: + from ..dependencies import ExternalProgram from ..envconfig import MachineInfo d_feature_args = {'gcc': {'unittest': '-funittest', @@ -442,13 +443,13 @@ class DCompiler(Compiler): language = 'd' - def __init__(self, exelist, version, for_machine: MachineChoice, - info: 'MachineInfo', arch, is_cross, exe_wrapper, **kwargs): + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, + info: 'MachineInfo', arch: str, exe_wrapper: T.Optional['ExternalProgram'] = None, + **kwargs): super().__init__(exelist, version, for_machine, info, **kwargs) self.id = 'unknown' self.arch = arch self.exe_wrapper = exe_wrapper - self.is_cross = is_cross def sanity_check(self, work_dir, environment): source_name = os.path.join(work_dir, 'sanity.d') @@ -633,18 +634,16 @@ class DCompiler(Compiler): def thread_link_flags(self, env): return self.linker.thread_flags(env) - def name_string(self): - return ' '.join(self.exelist) - class GnuDCompiler(GnuCompiler, DCompiler): # we mostly want DCompiler, but that gives us the Compiler.LINKER_PREFIX instead LINKER_PREFIX = GnuCompiler.LINKER_PREFIX - def __init__(self, exelist, version, for_machine: MachineChoice, - info: 'MachineInfo', is_cross, exe_wrapper, arch, **kwargs): - DCompiler.__init__(self, exelist, version, for_machine, info, is_cross, exe_wrapper, arch, **kwargs) + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, + info: 'MachineInfo', arch: str, *, exe_wrapper: T.Optional['ExternalProgram'] = None, + **kwargs): + DCompiler.__init__(self, exelist, version, for_machine, info, arch, exe_wrapper=exe_wrapper, **kwargs) GnuCompiler.__init__(self, {}) self.id = 'gcc' default_warn_args = ['-Wall', '-Wdeprecated'] @@ -698,9 +697,9 @@ class GnuDCompiler(GnuCompiler, DCompiler): class LLVMDCompiler(DmdLikeCompilerMixin, DCompiler): - def __init__(self, exelist, version, for_machine: MachineChoice, - info: 'MachineInfo', arch, **kwargs): - DCompiler.__init__(self, exelist, version, for_machine, info, arch, False, None, **kwargs) + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, + info: 'MachineInfo', arch: str, **kwargs): + DCompiler.__init__(self, exelist, version, for_machine, info, arch, **kwargs) self.id = 'llvm' self.base_options = ['b_coverage', 'b_colorout', 'b_vscrt', 'b_ndebug'] @@ -750,9 +749,9 @@ class LLVMDCompiler(DmdLikeCompilerMixin, DCompiler): class DmdDCompiler(DmdLikeCompilerMixin, DCompiler): - def __init__(self, exelist, version, for_machine: MachineChoice, - info: 'MachineInfo', arch, **kwargs): - DCompiler.__init__(self, exelist, version, for_machine, info, arch, False, None, **kwargs) + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, + info: 'MachineInfo', arch: str, **kwargs): + DCompiler.__init__(self, exelist, version, for_machine, info, arch, **kwargs) self.id = 'dmd' self.base_options = ['b_coverage', 'b_colorout', 'b_vscrt', 'b_ndebug'] diff --git a/mesonbuild/compilers/fortran.py b/mesonbuild/compilers/fortran.py index 7ca3073..9c2f5bf 100644 --- a/mesonbuild/compilers/fortran.py +++ b/mesonbuild/compilers/fortran.py @@ -44,9 +44,9 @@ class FortranCompiler(CLikeCompiler, Compiler): language = 'fortran' def __init__(self, exelist, version, for_machine: MachineChoice, - is_cross, info: 'MachineInfo', exe_wrapper=None, **kwargs): - Compiler.__init__(self, exelist, version, for_machine, info, **kwargs) - CLikeCompiler.__init__(self, is_cross, exe_wrapper) + is_cross: bool, info: 'MachineInfo', exe_wrapper=None, **kwargs): + Compiler.__init__(self, exelist, version, for_machine, info, is_cross=is_cross, **kwargs) + CLikeCompiler.__init__(self, exe_wrapper) self.id = 'unknown' def has_function(self, funcname, prefix, env, *, extra_args=None, dependencies=None): @@ -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/java.py b/mesonbuild/compilers/java.py index 5aeb250..8405b43 100644 --- a/mesonbuild/compilers/java.py +++ b/mesonbuild/compilers/java.py @@ -32,7 +32,6 @@ class JavaCompiler(BasicLinkerIsCompilerMixin, Compiler): info: 'MachineInfo'): super().__init__(exelist, version, for_machine, info) self.id = 'unknown' - self.is_cross = False self.javarunner = 'java' def get_werror_args(self): @@ -64,9 +63,6 @@ class JavaCompiler(BasicLinkerIsCompilerMixin, Compiler): def get_pic_args(self): return [] - def name_string(self): - return ' '.join(self.exelist) - def get_pch_use_args(self, pch_dir, header): return [] diff --git a/mesonbuild/compilers/mixins/arm.py b/mesonbuild/compilers/mixins/arm.py index b331d8f..25fb545 100644 --- a/mesonbuild/compilers/mixins/arm.py +++ b/mesonbuild/compilers/mixins/arm.py @@ -1,4 +1,4 @@ -# Copyright 2012-2019 The Meson development team +# Copyright 2012-2020 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. @@ -15,15 +15,22 @@ """Representations specific to the arm family of compilers.""" import os -import re import typing as T from ... import mesonlib +from ...linkers import ArmClangDynamicLinker from ..compilers import clike_debug_args from .clang import clang_color_args if T.TYPE_CHECKING: from ...environment import Environment + from ...compilers.compilers import Compiler +else: + # This is a bit clever, for mypy we pretend that these mixins descend from + # Compiler, so we get all of the methods and attributes defined for us, but + # for runtime we make them descend from object (which all classes normally + # do). This gives up DRYer type checking, with no runtime impact + Compiler = object arm_buildtype_args = { 'plain': [], @@ -62,9 +69,11 @@ armclang_optimization_args = { } # type: T.Dict[str, T.List[str]] -class ArmCompiler: - # Functionality that is common to all ARM family compilers. - def __init__(self): +class ArmCompiler(Compiler): + + """Functionality that is common to all ARM family compilers.""" + + def __init__(self) -> None: if not self.is_cross: raise mesonlib.EnvironmentException('armcc supports only cross-compilation.') self.id = 'arm' @@ -126,32 +135,15 @@ class ArmCompiler: return parameter_list -class ArmclangCompiler: - def __init__(self): +class ArmclangCompiler(Compiler): + + def __init__(self) -> None: if not self.is_cross: raise mesonlib.EnvironmentException('armclang supports only cross-compilation.') # Check whether 'armlink' is available in path - self.linker_exe = 'armlink' - args = '--vsn' - try: - p, stdo, stderr = mesonlib.Popen_safe(self.linker_exe, args) - except OSError as e: - err_msg = 'Unknown linker\nRunning "{0}" gave \n"{1}"'.format(' '.join([self.linker_exe] + [args]), e) - raise mesonlib.EnvironmentException(err_msg) - # Verify the armlink version - ver_str = re.search('.*Component.*', stdo) - if ver_str: - ver_str = ver_str.group(0) - else: - raise mesonlib.EnvironmentException('armlink version string not found') - assert ver_str # makes mypy happy - # Using the regular expression from environment.search_version, - # which is used for searching compiler version - version_regex = r'(?<!(\d|\.))(\d{1,2}(\.\d+)+(-[a-zA-Z0-9]+)?)' - linker_ver = re.search(version_regex, ver_str) - if linker_ver: - linker_ver = linker_ver.group(0) - if not mesonlib.version_compare(self.version, '==' + linker_ver): + if not isinstance(self.linker, ArmClangDynamicLinker): + raise mesonlib.EnvironmentException('Unsupported Linker {}, must be armlink'.format(self.linker.exelist)) + if not mesonlib.version_compare(self.version, '==' + self.linker.version): raise mesonlib.EnvironmentException('armlink version does not match with compiler version') self.id = 'armclang' self.base_options = ['b_pch', 'b_lto', 'b_pgo', 'b_sanitize', 'b_coverage', diff --git a/mesonbuild/compilers/mixins/c2000.py b/mesonbuild/compilers/mixins/c2000.py index 65a2cea..aca1ee8 100644 --- a/mesonbuild/compilers/mixins/c2000.py +++ b/mesonbuild/compilers/mixins/c2000.py @@ -21,6 +21,13 @@ from ...mesonlib import EnvironmentException if T.TYPE_CHECKING: from ...environment import Environment + from ...compilers.compilers import Compiler +else: + # This is a bit clever, for mypy we pretend that these mixins descend from + # Compiler, so we get all of the methods and attributes defined for us, but + # for runtime we make them descend from object (which all classes normally + # do). This gives up DRYer type checking, with no runtime impact + Compiler = object c2000_buildtype_args = { 'plain': [], @@ -46,8 +53,9 @@ c2000_debug_args = { } # type: T.Dict[bool, T.List[str]] -class C2000Compiler: - def __init__(self): +class C2000Compiler(Compiler): + + def __init__(self) -> None: if not self.is_cross: raise EnvironmentException('c2000 supports only cross-compilation.') self.id = 'c2000' diff --git a/mesonbuild/compilers/mixins/ccrx.py b/mesonbuild/compilers/mixins/ccrx.py index b859215..fb82797 100644 --- a/mesonbuild/compilers/mixins/ccrx.py +++ b/mesonbuild/compilers/mixins/ccrx.py @@ -21,6 +21,13 @@ from ...mesonlib import EnvironmentException if T.TYPE_CHECKING: from ...environment import Environment + from ...compilers.compilers import Compiler +else: + # This is a bit clever, for mypy we pretend that these mixins descend from + # Compiler, so we get all of the methods and attributes defined for us, but + # for runtime we make them descend from object (which all classes normally + # do). This gives up DRYer type checking, with no runtime impact + Compiler = object ccrx_buildtype_args = { 'plain': [], @@ -46,8 +53,13 @@ ccrx_debug_args = { } # type: T.Dict[bool, T.List[str]] -class CcrxCompiler: - def __init__(self): +class CcrxCompiler(Compiler): + + if T.TYPE_CHECKING: + is_cross = True + can_compile_suffixes = set() # type: T.Set[str] + + def __init__(self) -> None: if not self.is_cross: raise EnvironmentException('ccrx supports only cross-compilation.') self.id = 'ccrx' diff --git a/mesonbuild/compilers/mixins/clang.py b/mesonbuild/compilers/mixins/clang.py index 7525c12..8c85944 100644 --- a/mesonbuild/compilers/mixins/clang.py +++ b/mesonbuild/compilers/mixins/clang.py @@ -42,6 +42,7 @@ clang_optimization_args = { } # type: T.Dict[str, T.List[str]] class ClangCompiler(GnuLikeCompiler): + def __init__(self, defines: T.Optional[T.Dict[str, str]]): super().__init__() self.id = 'clang' @@ -75,17 +76,15 @@ class ClangCompiler(GnuLikeCompiler): # so it might change semantics at any time. return ['-include-pch', os.path.join(pch_dir, self.get_pch_name(header))] - def has_multi_arguments(self, args: T.List[str], env: 'Environment') -> T.List[str]: + def has_multi_arguments(self, args: T.List[str], env: 'Environment') -> T.Tuple[bool, bool]: myargs = ['-Werror=unknown-warning-option', '-Werror=unused-command-line-argument'] if mesonlib.version_compare(self.version, '>=3.6.0'): myargs.append('-Werror=ignored-optimization-argument') - return super().has_multi_arguments( - myargs + args, - env) + return super().has_multi_arguments(myargs + args, env) 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) -> bool: + dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: if extra_args is None: extra_args = [] # Starting with XCode 8, we need to pass this to force linker @@ -96,7 +95,7 @@ class ClangCompiler(GnuLikeCompiler): if isinstance(self.linker, AppleDynamicLinker) and mesonlib.version_compare(self.version, '>=8.0'): extra_args.append('-Wl,-no_weak_imports') return super().has_function(funcname, prefix, env, extra_args=extra_args, - dependencies=dependencies) + dependencies=dependencies) def openmp_flags(self) -> T.List[str]: if mesonlib.version_compare(self.version, '>=3.8.0'): @@ -125,7 +124,7 @@ class ClangCompiler(GnuLikeCompiler): return ['-fuse-ld={}'.format(linker)] return super().use_linker_args(linker) - def get_has_func_attribute_extra_args(self, name): + def get_has_func_attribute_extra_args(self, name: str) -> T.List[str]: # Clang only warns about unknown or ignored attributes, so force an # error. return ['-Werror=attributes'] diff --git a/mesonbuild/compilers/mixins/clike.py b/mesonbuild/compilers/mixins/clike.py index b2942d3..e146f5f 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,15 @@ from .. import compilers from .visualstudio import VisualStudioLikeCompiler if T.TYPE_CHECKING: + from ...dependencies import Dependency, ExternalProgram from ...environment import Environment + from ...compilers.compilers import Compiler +else: + # This is a bit clever, for mypy we pretend that these mixins descend from + # Compiler, so we get all of the methods and attributes defined for us, but + # for runtime we make them descend from object (which all classes normally + # do). This gives up DRYer type checking, with no runtime impact + Compiler = object GROUP_FLAGS = re.compile(r'''\.so (?:\.[0-9]+)? (?:\.[0-9]+)? (?:\.[0-9]+)?$ | ^(?:-Wl,)?-l | @@ -55,6 +65,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 +95,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'): @@ -107,20 +120,20 @@ class CLikeCompilerArgs(arglist.CompilerArgs): return 'CLikeCompilerArgs({!r}, {!r})'.format(self.compiler, self._container) -class CLikeCompiler: +class CLikeCompiler(Compiler): """Shared bits for the C and CPP Compilers.""" + if T.TYPE_CHECKING: + warn_args = {} # type: T.Dict[str, T.List[str]] + # 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, 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') # If the exe wrapper was not found, pretend it wasn't set so that the # sanity check is skipped and compiler checks use fallbacks. @@ -130,69 +143,71 @@ class CLikeCompiler: self.exe_wrapper = exe_wrapper.get_command() def compiler_args(self, args: T.Optional[T.Iterable[str]] = None) -> CLikeCompilerArgs: + # This is correct, mypy just doesn't understand co-operative inheritance return CLikeCompilerArgs(self, args) - 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 +221,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,22 +258,29 @@ 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. ''' return self.get_compiler_dirs(env, 'programs') + def get_program_dirs(self, env: 'Environment') -> T.List[str]: + return self._get_program_dirs(env).copy() + def get_pic_args(self) -> T.List[str]: return ['-fPIC'] - def name_string(self) -> str: - return ' '.join(self.exelist) - def get_pch_use_args(self, pch_dir: str, header: str) -> T.List[str]: return ['-include', os.path.basename(header)] @@ -266,7 +290,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 +299,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 +355,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) + 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 +384,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 +401,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 +439,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 +480,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.Union[None, T.List[str], arglist.CompilerArgs] = 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.Tuple[bool, bool]: + @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.Union[None, T.List[str], arglist.CompilerArgs] = 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 +526,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 +539,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 <stdio.h> {prefix} @@ -490,7 +549,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 +600,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 +623,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 +640,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 +663,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 +686,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 +717,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 +731,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 +741,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.stdo.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 +775,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 +813,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 +833,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 +857,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 +933,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 +953,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 +963,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 ''' @@ -889,7 +981,7 @@ class CLikeCompiler: with self._build_wrapper(code, env, extra_args=args, mode='compile', want_output=True, temp_dir=env.scratch_dir) as p: if p.returncode != 0: m = 'BUG: Unable to compile {!r} check: {}' - raise RuntimeError(m.format(n, p.stdo)) + raise RuntimeError(m.format(n, p.stdout)) if not os.path.isfile(p.output_name): m = 'BUG: Can\'t find compiled test code for {!r} check' raise RuntimeError(m.format(n)) @@ -906,8 +998,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 +1015,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 +1062,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 +1078,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 +1088,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 +1096,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 +1113,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 +1152,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 +1170,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 +1201,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 +1210,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 +1221,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 +1266,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 +1290,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 +1304,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<pre>.*([^\\]")|^")(?P<str1>([^\\"]|\\.)*)"\s+"(?P<str2>([^\\"]|\\.)*)(?P<post>".*)') ret = s m = pattern.match(ret) @@ -1209,13 +1313,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/mesonbuild/compilers/mixins/compcert.py b/mesonbuild/compilers/mixins/compcert.py index ebcccf5..0f816a8 100644 --- a/mesonbuild/compilers/mixins/compcert.py +++ b/mesonbuild/compilers/mixins/compcert.py @@ -20,6 +20,13 @@ import typing as T if T.TYPE_CHECKING: from ...environment import Environment + from ...compilers.compilers import Compiler +else: + # This is a bit clever, for mypy we pretend that these mixins descend from + # Compiler, so we get all of the methods and attributes defined for us, but + # for runtime we make them descend from object (which all classes normally + # do). This gives up DRYer type checking, with no runtime impact + Compiler = object ccomp_buildtype_args = { 'plain': [''], @@ -51,7 +58,8 @@ ccomp_args_to_wul = [ r"^-r$" ] # type: T.List[str] -class CompCertCompiler: +class CompCertCompiler(Compiler): + def __init__(self) -> None: self.id = 'ccomp' # Assembly @@ -78,9 +86,9 @@ class CompCertCompiler: def get_pch_use_args(self, pch_dir: str, header: str) -> T.List[str]: return [] - def unix_args_to_native(self, args): + def unix_args_to_native(self, args: T.List[str]) -> T.List[str]: "Always returns a copy that can be independently mutated" - patched_args = [] + patched_args = [] # type: T.List[str] for arg in args: added = 0 for ptrn in ccomp_args_to_wul: diff --git a/mesonbuild/compilers/mixins/elbrus.py b/mesonbuild/compilers/mixins/elbrus.py index db743d8..2ea3599 100644 --- a/mesonbuild/compilers/mixins/elbrus.py +++ b/mesonbuild/compilers/mixins/elbrus.py @@ -30,7 +30,8 @@ if T.TYPE_CHECKING: class ElbrusCompiler(GnuLikeCompiler): # Elbrus compiler is nearly like GCC, but does not support # PCH, LTO, sanitizers and color output as of version 1.21.x. - def __init__(self): + + def __init__(self) -> None: super().__init__() self.id = 'lcc' self.base_options = ['b_pgo', 'b_coverage', diff --git a/mesonbuild/compilers/mixins/emscripten.py b/mesonbuild/compilers/mixins/emscripten.py index 08b2205..87bc40c 100644 --- a/mesonbuild/compilers/mixins/emscripten.py +++ b/mesonbuild/compilers/mixins/emscripten.py @@ -20,12 +20,19 @@ import typing as T from ... import coredata if T.TYPE_CHECKING: - from ..environment import Environment + from ...environment import Environment + from ...compilers.compilers import Compiler +else: + # This is a bit clever, for mypy we pretend that these mixins descend from + # Compiler, so we get all of the methods and attributes defined for us, but + # for runtime we make them descend from object (which all classes normally + # do). This gives up DRYer type checking, with no runtime impact + Compiler = object -class EmscriptenMixin: +class EmscriptenMixin(Compiler): - def _get_compile_output(self, dirname, mode): + def _get_compile_output(self, dirname: str, mode: str) -> str: # In pre-processor mode, the output is sent to stdout and discarded if mode == 'preprocess': return None @@ -48,7 +55,7 @@ class EmscriptenMixin: args.extend(['-s', 'PTHREAD_POOL_SIZE={}'.format(count)]) return args - def get_options(self): + def get_options(self) -> 'coredata.OptionDictType': opts = super().get_options() opts.update({ '{}_thread_count'.format(self.language): coredata.UserIntegerOption( diff --git a/mesonbuild/compilers/mixins/gnu.py b/mesonbuild/compilers/mixins/gnu.py index 83f7047..9c60fcb 100644 --- a/mesonbuild/compilers/mixins/gnu.py +++ b/mesonbuild/compilers/mixins/gnu.py @@ -26,8 +26,14 @@ from ... import mesonlib from ... import mlog if T.TYPE_CHECKING: - from ...coredata import UserOption # noqa: F401 from ...environment import Environment + from .clike import CLikeCompiler as Compiler +else: + # This is a bit clever, for mypy we pretend that these mixins descend from + # Compiler, so we get all of the methods and attributes defined for us, but + # for runtime we make them descend from object (which all classes normally + # do). This gives up DRYer type checking, with no runtime impact + Compiler = object # XXX: prevent circular references. # FIXME: this really is a posix interface not a c-like interface @@ -106,7 +112,7 @@ def gnulike_default_include_dirs(compiler: T.Tuple[str], lang: str) -> T.List[st ) stdout = p.stdout.read().decode('utf-8', errors='replace') parse_state = 0 - paths = [] + paths = [] # type: T.List[str] for line in stdout.split('\n'): line = line.strip(' \n\r\t') if parse_state == 0: @@ -129,7 +135,7 @@ def gnulike_default_include_dirs(compiler: T.Tuple[str], lang: str) -> T.List[st return paths -class GnuLikeCompiler(metaclass=abc.ABCMeta): +class GnuLikeCompiler(Compiler, metaclass=abc.ABCMeta): """ GnuLikeCompiler is a common interface to all compilers implementing the GNU-style commandline interface. This includes GCC, Clang @@ -139,7 +145,7 @@ class GnuLikeCompiler(metaclass=abc.ABCMeta): LINKER_PREFIX = '-Wl,' - def __init__(self): + def __init__(self) -> None: self.base_options = ['b_pch', 'b_lto', 'b_pgo', 'b_coverage', 'b_ndebug', 'b_staticpic', 'b_pie'] if not (self.info.is_windows() or self.info.is_cygwin() or self.info.is_openbsd()): @@ -164,14 +170,14 @@ class GnuLikeCompiler(metaclass=abc.ABCMeta): @abc.abstractmethod def get_optimization_args(self, optimization_level: str) -> T.List[str]: - raise NotImplementedError("get_optimization_args not implemented") + pass def get_debug_args(self, is_debug: bool) -> T.List[str]: return clike_debug_args[is_debug] @abc.abstractmethod def get_pch_suffix(self) -> str: - raise NotImplementedError("get_pch_suffix not implemented") + pass def split_shlib_to_parts(self, fname: str) -> T.Tuple[str, str]: return os.path.dirname(fname), fname @@ -184,7 +190,7 @@ class GnuLikeCompiler(metaclass=abc.ABCMeta): @abc.abstractmethod def openmp_flags(self) -> T.List[str]: - raise NotImplementedError("openmp_flags not implemented") + pass def gnu_symbol_visibility_args(self, vistype: str) -> T.List[str]: return gnu_symbol_visibility_args[vistype] @@ -223,12 +229,10 @@ class GnuLikeCompiler(metaclass=abc.ABCMeta): @functools.lru_cache() def _get_search_dirs(self, env: 'Environment') -> str: 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 + return p.stdout def _split_fetch_real_dirs(self, pathstr: str) -> T.List[str]: # We need to use the path separator used by the compiler for printing @@ -291,7 +295,7 @@ class GnuLikeCompiler(metaclass=abc.ABCMeta): def get_output_args(self, target: str) -> T.List[str]: return ['-o', target] - def get_dependency_gen_args(self, outtarget, outfile): + def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]: return ['-MD', '-MQ', outtarget, '-MF', outfile] def get_compile_only_args(self) -> T.List[str]: @@ -322,7 +326,7 @@ class GnuCompiler(GnuLikeCompiler): Compilers imitating GCC (Clang/Intel) should use the GnuLikeCompiler ABC. """ - def __init__(self, defines: T.Dict[str, str]): + def __init__(self, defines: T.Optional[T.Dict[str, str]]): super().__init__() self.id = 'gcc' self.defines = defines or {} @@ -334,6 +338,7 @@ class GnuCompiler(GnuLikeCompiler): return [] def get_warn_args(self, level: str) -> T.List[str]: + # Mypy doesn't understand cooperative inheritance args = super().get_warn_args(level) if mesonlib.version_compare(self.version, '<4.8.0') and '-Wpedantic' in args: # -Wpedantic was added in 4.8.0 @@ -358,19 +363,20 @@ class GnuCompiler(GnuLikeCompiler): def openmp_flags(self) -> T.List[str]: return ['-fopenmp'] - def has_arguments(self, args, env, code, mode): + def has_arguments(self, args: T.List[str], env: 'Environment', code: str, + mode: str) -> T.Tuple[bool, bool]: # For some compiler command line arguments, the GNU compilers will # emit a warning on stderr indicating that an option is valid for a # another language, but still complete with exit_success with self._build_wrapper(code, env, args, None, mode) as p: result = p.returncode == 0 - if self.language in {'cpp', 'objcpp'} and 'is valid for C/ObjC' in p.stde: + if self.language in {'cpp', 'objcpp'} and 'is valid for C/ObjC' in p.stderr: result = False - if self.language in {'c', 'objc'} and 'is valid for C++/ObjC++' in p.stde: + if self.language in {'c', 'objc'} and 'is valid for C++/ObjC++' in p.stderr: result = False return result, p.cached - def get_has_func_attribute_extra_args(self, name): + def get_has_func_attribute_extra_args(self, name: str) -> T.List[str]: # GCC only warns about unknown or ignored attributes, so force an # error. return ['-Werror=attributes'] diff --git a/mesonbuild/compilers/mixins/intel.py b/mesonbuild/compilers/mixins/intel.py index eb4b10d..b83e5c4 100644 --- a/mesonbuild/compilers/mixins/intel.py +++ b/mesonbuild/compilers/mixins/intel.py @@ -28,7 +28,9 @@ from .gnu import GnuLikeCompiler from .visualstudio import VisualStudioLikeCompiler if T.TYPE_CHECKING: - import subprocess # noqa: F401 + from ...arglist import CompilerArgs + from ...dependencies import Dependency + from ...environment import Environment # XXX: avoid circular dependencies # TODO: this belongs in a posix compiler class @@ -97,21 +99,21 @@ class IntelGnuLikeCompiler(GnuLikeCompiler): else: return ['-openmp'] - def compiles(self, *args, **kwargs) -> T.Tuple[bool, bool]: - # This covers a case that .get('foo', []) doesn't, that extra_args is - # defined and is None - extra_args = kwargs.get('extra_args') or [] - kwargs['extra_args'] = [ - extra_args, + def compiles(self, code: str, env: 'Environment', *, + 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]: + extra_args = extra_args.copy() if extra_args is not None else [] + extra_args += [ '-diag-error', '10006', # ignoring unknown option '-diag-error', '10148', # Option not supported '-diag-error', '10155', # ignoring argument required '-diag-error', '10156', # ignoring not argument allowed '-diag-error', '10157', # Ignoring argument of the wrong type '-diag-error', '10158', # Argument must be separate. Can be hit by trying an option like -foo-bar=foo when -foo=bar is a valid option but -foo-bar isn't - '-diag-error', '1292', # unknown __attribute__ ] - return super().compiles(*args, **kwargs) + return super().compiles(code, env, extra_args=extra_args, dependencies=dependencies, mode=mode, disable_cache=disable_cache) def get_profile_generate_args(self) -> T.List[str]: return ['-prof-gen=threadsafe'] @@ -125,6 +127,9 @@ class IntelGnuLikeCompiler(GnuLikeCompiler): def get_optimization_args(self, optimization_level: str) -> T.List[str]: return self.OPTIM_ARGS[optimization_level] + def get_has_func_attribute_extra_args(self, name: str) -> T.List[str]: + return ['-diag-error', '1292'] + class IntelVisualStudioLikeCompiler(VisualStudioLikeCompiler): @@ -148,13 +153,17 @@ class IntelVisualStudioLikeCompiler(VisualStudioLikeCompiler): 's': ['/Os'], } - def __init__(self, target: str): + def __init__(self, target: str) -> None: super().__init__(target) self.id = 'intel-cl' - def compile(self, code: str, *, extra_args: T.Optional[T.List[str]] = None, **kwargs) -> T.Iterator['subprocess.Popen']: + def compiles(self, code: str, env: 'Environment', *, + 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]: # This covers a case that .get('foo', []) doesn't, that extra_args is - if kwargs.get('mode', 'compile') != 'link': + if mode != 'link': extra_args = extra_args.copy() if extra_args is not None else [] extra_args.extend([ '/Qdiag-error:10006', # ignoring unknown option @@ -164,7 +173,7 @@ class IntelVisualStudioLikeCompiler(VisualStudioLikeCompiler): '/Qdiag-error:10157', # Ignoring argument of the wrong type '/Qdiag-error:10158', # Argument must be separate. Can be hit by trying an option like -foo-bar=foo when -foo=bar is a valid option but -foo-bar isn't ]) - return super().compile(code, extra_args, **kwargs) + return super().compiles(code, env, extra_args=extra_args, dependencies=dependencies, mode=mode, disable_cache=disable_cache) def get_toolset_version(self) -> T.Optional[str]: # Avoid circular dependencies.... diff --git a/mesonbuild/compilers/mixins/islinker.py b/mesonbuild/compilers/mixins/islinker.py index a9967d6..ce7a8af 100644 --- a/mesonbuild/compilers/mixins/islinker.py +++ b/mesonbuild/compilers/mixins/islinker.py @@ -27,9 +27,16 @@ from ... import mesonlib if T.TYPE_CHECKING: from ...coredata import OptionDictType from ...environment import Environment + from ...compilers.compilers import Compiler +else: + # This is a bit clever, for mypy we pretend that these mixins descend from + # Compiler, so we get all of the methods and attributes defined for us, but + # for runtime we make them descend from object (which all classes normally + # do). This gives up DRYer type checking, with no runtime impact + Compiler = object -class BasicLinkerIsCompilerMixin: +class BasicLinkerIsCompilerMixin(Compiler): """Provides a baseline of methods that a linker would implement. @@ -99,8 +106,8 @@ class BasicLinkerIsCompilerMixin: def bitcode_args(self) -> T.List[str]: raise mesonlib.MesonException("This linker doesn't support bitcode bundles") - def get_soname_args(self, for_machine: 'mesonlib.MachineChoice', - prefix: str, shlib_name: str, suffix: str, soversion: str, + def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, + suffix: str, soversion: str, darwin_versions: T.Tuple[str, str], is_shared_module: bool) -> T.List[str]: raise mesonlib.MesonException("This linker doesn't support soname args") diff --git a/mesonbuild/compilers/mixins/pgi.py b/mesonbuild/compilers/mixins/pgi.py index 77a7a28..f6ad279 100644 --- a/mesonbuild/compilers/mixins/pgi.py +++ b/mesonbuild/compilers/mixins/pgi.py @@ -20,6 +20,16 @@ from pathlib import Path from ..compilers import clike_debug_args, clike_optimization_args +if T.TYPE_CHECKING: + from ...environment import Environment + from ...compilers.compilers import Compiler +else: + # This is a bit clever, for mypy we pretend that these mixins descend from + # Compiler, so we get all of the methods and attributes defined for us, but + # for runtime we make them descend from object (which all classes normally + # do). This gives up DRYer type checking, with no runtime impact + Compiler = object + pgi_buildtype_args = { 'plain': [], 'debug': [], @@ -30,8 +40,9 @@ pgi_buildtype_args = { } # type: T.Dict[str, T.List[str]] -class PGICompiler: - def __init__(self): +class PGICompiler(Compiler): + + def __init__(self) -> None: self.base_options = ['b_pch'] self.id = 'pgi' @@ -94,6 +105,6 @@ class PGICompiler: else: return [] - def thread_flags(self, env): + def thread_flags(self, env: 'Environment') -> T.List[str]: # PGI cannot accept -pthread, it's already threaded return [] diff --git a/mesonbuild/compilers/mixins/visualstudio.py b/mesonbuild/compilers/mixins/visualstudio.py index 0ea03f0..d9abb95 100644 --- a/mesonbuild/compilers/mixins/visualstudio.py +++ b/mesonbuild/compilers/mixins/visualstudio.py @@ -20,11 +20,19 @@ import abc import os import typing as T +from ... import arglist from ... import mesonlib from ... import mlog if T.TYPE_CHECKING: from ...environment import Environment + from .clike import CLikeCompiler as Compiler +else: + # This is a bit clever, for mypy we pretend that these mixins descend from + # Compiler, so we get all of the methods and attributes defined for us, but + # for runtime we make them descend from object (which all classes normally + # do). This gives up DRYer type checking, with no runtime impact + Compiler = object vs32_instruction_set_args = { 'mmx': ['/arch:SSE'], # There does not seem to be a flag just for MMX @@ -85,7 +93,7 @@ msvc_debug_args = { } # type: T.Dict[bool, T.List[str]] -class VisualStudioLikeCompiler(metaclass=abc.ABCMeta): +class VisualStudioLikeCompiler(Compiler, metaclass=abc.ABCMeta): """A common interface for all compilers implementing an MSVC-style interface. @@ -97,10 +105,8 @@ class VisualStudioLikeCompiler(metaclass=abc.ABCMeta): std_warn_args = ['/W3'] std_opt_args = ['/O2'] - # XXX: this is copied in this patch only to avoid circular dependencies - #ignore_libs = unixy_compiler_internal_libs - ignore_libs = ('m', 'c', 'pthread', 'dl', 'rt', 'execinfo') - internal_libs = () + ignore_libs = arglist.UNIXY_COMPILER_INTERNAL_LIBS + ['execinfo'] + internal_libs = [] # type: T.List[str] crt_args = { 'none': [], @@ -137,6 +143,7 @@ class VisualStudioLikeCompiler(metaclass=abc.ABCMeta): self.machine = 'arm' else: self.machine = target + assert self.linker is not None self.linker.machine = self.machine # Override CCompiler.get_always_args @@ -291,12 +298,12 @@ class VisualStudioLikeCompiler(metaclass=abc.ABCMeta): # 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: T.List[str], env: 'Environment', code, mode: str) -> T.Tuple[bool, bool]: + def has_arguments(self, args: T.List[str], env: 'Environment', code: str, mode: str) -> T.Tuple[bool, bool]: warning_text = '4044' if mode == 'link' else '9002' 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 + return not(warning_text in p.stderr or warning_text in p.stdout), p.cached def get_compile_debugfile_args(self, rel_obj: str, pch: bool = False) -> T.List[str]: pdbarr = rel_obj.split('.')[:-1] @@ -420,7 +427,7 @@ class ClangClCompiler(VisualStudioLikeCompiler): super().__init__(target) self.id = 'clang-cl' - def has_arguments(self, args: T.List[str], env: 'Environment', code, mode: str) -> T.Tuple[bool, bool]: + def has_arguments(self, args: T.List[str], env: 'Environment', code: str, mode: str) -> T.Tuple[bool, bool]: if mode != 'link': args = args + ['-Werror=unknown-argument'] return super().has_arguments(args, env, code, mode) diff --git a/mesonbuild/compilers/mixins/xc16.py b/mesonbuild/compilers/mixins/xc16.py index f12cccc..edc5f2c 100644 --- a/mesonbuild/compilers/mixins/xc16.py +++ b/mesonbuild/compilers/mixins/xc16.py @@ -21,6 +21,13 @@ from ...mesonlib import EnvironmentException if T.TYPE_CHECKING: from ...environment import Environment + from ...compilers.compilers import Compiler +else: + # This is a bit clever, for mypy we pretend that these mixins descend from + # Compiler, so we get all of the methods and attributes defined for us, but + # for runtime we make them descend from object (which all classes normally + # do). This gives up DRYer type checking, with no runtime impact + Compiler = object xc16_buildtype_args = { 'plain': [], @@ -46,8 +53,9 @@ xc16_debug_args = { } # type: T.Dict[bool, T.List[str]] -class Xc16Compiler: - def __init__(self): +class Xc16Compiler(Compiler): + + def __init__(self) -> None: if not self.is_cross: raise EnvironmentException('xc16 supports only cross-compilation.') self.id = 'xc16' @@ -85,7 +93,7 @@ class Xc16Compiler: def get_coverage_args(self) -> T.List[str]: return [] - + def get_no_stdinc_args(self) -> T.List[str]: return ['-nostdinc'] diff --git a/mesonbuild/compilers/objc.py b/mesonbuild/compilers/objc.py index 254a609..1b280eb 100644 --- a/mesonbuild/compilers/objc.py +++ b/mesonbuild/compilers/objc.py @@ -23,24 +23,31 @@ from .mixins.gnu import GnuCompiler from .mixins.clang import ClangCompiler if T.TYPE_CHECKING: + from ..dependencies import ExternalProgram from ..envconfig import MachineInfo + from ..environment import Environment + from ..linkers import DynamicLinker class ObjCCompiler(CLikeCompiler, Compiler): language = 'objc' - def __init__(self, exelist, version, for_machine: MachineChoice, + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, info: 'MachineInfo', - exe_wrap: T.Optional[str], **kwargs): - Compiler.__init__(self, exelist, version, for_machine, info, **kwargs) - CLikeCompiler.__init__(self, is_cross, exe_wrap) + exe_wrap: T.Optional['ExternalProgram'], + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + Compiler.__init__(self, exelist, version, for_machine, info, + is_cross=is_cross, full_version=full_version, + linker=linker) + CLikeCompiler.__init__(self, exe_wrap) @staticmethod - def get_display_language(): + def get_display_language() -> str: return 'Objective-C' - def sanity_check(self, work_dir, environment): + def sanity_check(self, work_dir: str, environment: 'Environment') -> None: # TODO try to use sanity_check_impl instead of duplicated code source_name = os.path.join(work_dir, 'sanitycheckobjc.m') binary_name = os.path.join(work_dir, 'sanitycheckobjc') @@ -67,11 +74,14 @@ class ObjCCompiler(CLikeCompiler, Compiler): class GnuObjCCompiler(GnuCompiler, ObjCCompiler): - def __init__(self, exelist, version, for_machine: MachineChoice, - is_cross, info: 'MachineInfo', exe_wrapper=None, - defines=None, **kwargs): + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, + is_cross: bool, info: 'MachineInfo', + exe_wrapper: T.Optional['ExternalProgram'] = None, + defines: T.Optional[T.Dict[str, str]] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): ObjCCompiler.__init__(self, exelist, version, for_machine, is_cross, - info, exe_wrapper, **kwargs) + info, exe_wrapper, linker=linker, full_version=full_version) GnuCompiler.__init__(self, defines) default_warn_args = ['-Wall', '-Winvalid-pch'] self.warn_args = {'0': [], @@ -81,12 +91,15 @@ class GnuObjCCompiler(GnuCompiler, ObjCCompiler): class ClangObjCCompiler(ClangCompiler, ObjCCompiler): - def __init__(self, exelist, version, for_machine: MachineChoice, - is_cross, info: 'MachineInfo', exe_wrapper=None, - **kwargs): + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, + is_cross: bool, info: 'MachineInfo', + exe_wrapper: T.Optional['ExternalProgram'] = None, + defines: T.Optional[T.Dict[str, str]] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): ObjCCompiler.__init__(self, exelist, version, for_machine, is_cross, - info, exe_wrapper, **kwargs) - ClangCompiler.__init__(self, []) + info, exe_wrapper, linker=linker, full_version=full_version) + ClangCompiler.__init__(self, defines) default_warn_args = ['-Wall', '-Winvalid-pch'] self.warn_args = {'0': [], '1': default_warn_args, diff --git a/mesonbuild/compilers/objcpp.py b/mesonbuild/compilers/objcpp.py index 3197abc..16ba77e 100644 --- a/mesonbuild/compilers/objcpp.py +++ b/mesonbuild/compilers/objcpp.py @@ -23,23 +23,30 @@ from .mixins.gnu import GnuCompiler from .mixins.clang import ClangCompiler if T.TYPE_CHECKING: + from ..dependencies import ExternalProgram from ..envconfig import MachineInfo + from ..environment import Environment + from ..linkers import DynamicLinker class ObjCPPCompiler(CLikeCompiler, Compiler): language = 'objcpp' - def __init__(self, exelist, version, for_machine: MachineChoice, + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, info: 'MachineInfo', - exe_wrap: T.Optional[str], **kwargs): - Compiler.__init__(self, exelist, version, for_machine, info, **kwargs) - CLikeCompiler.__init__(self, is_cross, exe_wrap) + exe_wrap: T.Optional['ExternalProgram'], + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + Compiler.__init__(self, exelist, version, for_machine, info, + is_cross=is_cross, full_version=full_version, + linker=linker) + CLikeCompiler.__init__(self, exe_wrap) @staticmethod - def get_display_language(): + def get_display_language() -> str: return 'Objective-C++' - def sanity_check(self, work_dir, environment): + def sanity_check(self, work_dir: str, environment: 'Environment') -> None: # TODO try to use sanity_check_impl instead of duplicated code source_name = os.path.join(work_dir, 'sanitycheckobjcpp.mm') binary_name = os.path.join(work_dir, 'sanitycheckobjcpp') @@ -67,10 +74,14 @@ class ObjCPPCompiler(CLikeCompiler, Compiler): class GnuObjCPPCompiler(GnuCompiler, ObjCPPCompiler): - def __init__(self, exelist, version, for_machine: MachineChoice, - is_cross, info: 'MachineInfo', exe_wrapper=None, - defines=None, **kwargs): - ObjCPPCompiler.__init__(self, exelist, version, for_machine, is_cross, info, exe_wrapper, **kwargs) + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, + is_cross: bool, info: 'MachineInfo', + exe_wrapper: T.Optional['ExternalProgram'] = None, + defines: T.Optional[T.Dict[str, str]] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + ObjCPPCompiler.__init__(self, exelist, version, for_machine, is_cross, + info, exe_wrapper, linker=linker, full_version=full_version) GnuCompiler.__init__(self, defines) default_warn_args = ['-Wall', '-Winvalid-pch', '-Wnon-virtual-dtor'] self.warn_args = {'0': [], @@ -80,11 +91,16 @@ class GnuObjCPPCompiler(GnuCompiler, ObjCPPCompiler): class ClangObjCPPCompiler(ClangCompiler, ObjCPPCompiler): - def __init__(self, exelist, version, for_machine: MachineChoice, - is_cross, info: 'MachineInfo', exe_wrapper=None, - **kwargs): - ObjCPPCompiler.__init__(self, exelist, version, for_machine, is_cross, info, exe_wrapper, **kwargs) - ClangCompiler.__init__(self, []) + + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, + is_cross: bool, info: 'MachineInfo', + exe_wrapper: T.Optional['ExternalProgram'] = None, + defines: T.Optional[T.Dict[str, str]] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + ObjCPPCompiler.__init__(self, exelist, version, for_machine, is_cross, + info, exe_wrapper, linker=linker, full_version=full_version) + ClangCompiler.__init__(self, defines) default_warn_args = ['-Wall', '-Winvalid-pch', '-Wnon-virtual-dtor'] self.warn_args = {'0': [], '1': default_warn_args, diff --git a/mesonbuild/compilers/rust.py b/mesonbuild/compilers/rust.py index c2e21c4..baa7272 100644 --- a/mesonbuild/compilers/rust.py +++ b/mesonbuild/compilers/rust.py @@ -36,18 +36,14 @@ class RustCompiler(Compiler): language = 'rust' def __init__(self, exelist, version, for_machine: MachineChoice, - is_cross, info: 'MachineInfo', exe_wrapper=None, **kwargs): - super().__init__(exelist, version, for_machine, info, **kwargs) + is_cross: bool, info: 'MachineInfo', exe_wrapper=None, **kwargs): + super().__init__(exelist, version, for_machine, info, is_cross=is_cross, **kwargs) self.exe_wrapper = exe_wrapper self.id = 'rustc' - self.is_cross = is_cross def needs_static_linker(self): return False - def name_string(self): - return ' '.join(self.exelist) - def sanity_check(self, work_dir, environment): source_name = os.path.join(work_dir, 'sanity.rs') output_name = os.path.join(work_dir, 'rusttest') @@ -110,6 +106,9 @@ class RustCompiler(Compiler): def get_std_exe_link_args(self): return [] + def get_output_args(self, outputname: str) -> T.List[str]: + return ['-o', outputname] + # Rust does not have a use_linker_args because it dispatches to a gcc-like # C compiler for dynamic linking, as such we invoke the C compiler's # use_linker_args method instead. diff --git a/mesonbuild/compilers/swift.py b/mesonbuild/compilers/swift.py index 1942120..55f2761 100644 --- a/mesonbuild/compilers/swift.py +++ b/mesonbuild/compilers/swift.py @@ -36,14 +36,10 @@ class SwiftCompiler(Compiler): language = 'swift' def __init__(self, exelist, version, for_machine: MachineChoice, - is_cross, info: 'MachineInfo', **kwargs): - super().__init__(exelist, version, for_machine, info, **kwargs) + is_cross: bool, info: 'MachineInfo', **kwargs): + super().__init__(exelist, version, for_machine, info, is_cross=is_cross, **kwargs) self.version = version self.id = 'llvm' - self.is_cross = is_cross - - def name_string(self): - return ' '.join(self.exelist) def needs_static_linker(self): return True diff --git a/mesonbuild/compilers/vala.py b/mesonbuild/compilers/vala.py index a5d49b6..f31a294 100644 --- a/mesonbuild/compilers/vala.py +++ b/mesonbuild/compilers/vala.py @@ -28,16 +28,12 @@ class ValaCompiler(Compiler): language = 'vala' def __init__(self, exelist, version, for_machine: MachineChoice, - is_cross, info: 'MachineInfo'): - super().__init__(exelist, version, for_machine, info) + is_cross: bool, info: 'MachineInfo'): + super().__init__(exelist, version, for_machine, info, is_cross=is_cross) self.version = version - self.is_cross = is_cross self.id = 'valac' self.base_options = ['b_colorout'] - def name_string(self): - return ' '.join(self.exelist) - def needs_static_linker(self): return False # Because compiles into C. diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index bba3942..5827a4e 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -32,10 +32,11 @@ import typing as T if T.TYPE_CHECKING: from . import dependencies - from .compilers import Compiler # noqa: F401 + from .compilers.compilers import Compiler, CompileResult # noqa: F401 from .environment import Environment OptionDictType = T.Dict[str, 'UserOption[T.Any]'] + CompilerCheckCacheKey = T.Tuple[T.Tuple[str, ...], str, str, T.Tuple[str, ...], str] version = '0.55.999' backendlist = ['ninja', 'vs', 'vs2010', 'vs2015', 'vs2017', 'vs2019', 'xcode'] @@ -394,7 +395,7 @@ class CoreData: build_cache = DependencyCache(self.builtins_per_machine, MachineChoice.BUILD) host_cache = DependencyCache(self.builtins_per_machine, MachineChoice.BUILD) self.deps = PerMachine(build_cache, host_cache) # type: PerMachine[DependencyCache] - self.compiler_check_cache = OrderedDict() + self.compiler_check_cache = OrderedDict() # type: T.Dict[CompilerCheckCacheKey, compiler.CompileResult] # Only to print a warning if it changes between Meson invocations. self.config_files = self.__load_config_files(options, scratch_dir, 'native') @@ -771,7 +772,7 @@ class CoreData: lang, key = k.split('_', 1) for machine in MachineChoice: if key not in env.compiler_options[machine][lang]: - env.compiler_options[machine][lang][key] = v + env.compiler_options[machine][lang][key] = v elif k in base_options: if not subproject and k not in env.base_options: env.base_options[k] = v diff --git a/mesonbuild/dependencies/scalapack.py b/mesonbuild/dependencies/scalapack.py index 8774746..0147e0b 100644 --- a/mesonbuild/dependencies/scalapack.py +++ b/mesonbuild/dependencies/scalapack.py @@ -109,10 +109,10 @@ class MKLPkgConfigDependency(PkgConfigDependency): if self.clib_compiler.id == 'gcc': for i, a in enumerate(self.link_args): # only replace in filename, not in directory names - parts = list(os.path.split(a)) - if 'mkl_intel_lp64' in parts[-1]: - parts[-1] = parts[-1].replace('intel', 'gf') - self.link_args[i] = '/' + os.path.join(*parts) + dirname, basename = os.path.split(a) + if 'mkl_intel_lp64' in basename: + basename = basename.replace('intel', 'gf') + self.link_args[i] = '/' + os.path.join(dirname, basename) # MKL pkg-config omits scalapack # be sure "-L" and "-Wl" are first if present i = 0 diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index 4623049..513a03c 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -1588,6 +1588,10 @@ class Environment: exe_wrap, defines, linker=linker) if 'clang' in out: linker = None + defines = self.get_clang_compiler_defines(compiler) + if not defines: + popen_exceptions[' '.join(compiler)] = 'no pre-processor defines' + continue if 'Apple' in out: comp = AppleClangObjCCompiler if objc else AppleClangObjCPPCompiler else: @@ -1604,7 +1608,7 @@ class Environment: compiler, comp, for_machine) return comp( ccache + compiler, version, for_machine, - is_cross, info, exe_wrap, linker=linker) + is_cross, info, exe_wrap, linker=linker, defines=defines) self._handle_exceptions(popen_exceptions, compilers) def detect_java_compiler(self, for_machine): @@ -1821,7 +1825,8 @@ class Environment: elif 'gdc' in out: linker = self._guess_nix_linker(exelist, compilers.GnuDCompiler, for_machine) return compilers.GnuDCompiler( - exelist, version, for_machine, info, arch, is_cross, exe_wrap, + exelist, version, for_machine, info, arch, + exe_wrapper=exe_wrap, is_cross=is_cross, full_version=full_version, linker=linker) elif 'The D Language Foundation' in out or 'Digital Mars' in out: # DMD seems to require a file diff --git a/mesonbuild/linkers.py b/mesonbuild/linkers.py index d85c88e..ed28fa3 100644 --- a/mesonbuild/linkers.py +++ b/mesonbuild/linkers.py @@ -351,6 +351,7 @@ class DynamicLinker(LinkerEnvVarsMixin, metaclass=abc.ABCMeta): self.version = version self.prefix_arg = prefix_arg self.always_args = always_args + self.machine = None # type: T.Optional[str] def __repr__(self) -> str: return '<{}: v{} `{}`>'.format(type(self).__name__, self.version, ' '.join(self.exelist)) diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index c6cbbd6..17d2733 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -34,6 +34,8 @@ if T.TYPE_CHECKING: from .compilers.compilers import CompilerType from .interpreterbase import ObjectHolder + FileOrString = T.Union['File', str] + _T = T.TypeVar('_T') _U = T.TypeVar('_U') @@ -946,7 +948,7 @@ def do_define(regex: T.Pattern[str], line: str, confdata: 'ConfigurationData', v arr = line.split() if variable_format == 'meson' and len(arr) != 2: - raise MesonException('#mesondefine does not contain exactly two tokens: %s' % line.strip()) + raise MesonException('#mesondefine does not contain exactly two tokens: %s' % line.strip()) varname = arr[1] try: diff --git a/mesonbuild/minstall.py b/mesonbuild/minstall.py index 7a56275..0eaa54f 100644 --- a/mesonbuild/minstall.py +++ b/mesonbuild/minstall.py @@ -483,7 +483,7 @@ class Installer: set_mode(outname, install_mode, d.install_umask) if should_strip and d.strip_bin is not None: if fname.endswith('.jar'): - self.log('Not stripping jar target:', os.path.basename(fname)) + self.log('Not stripping jar target: {}'.format(os.path.basename(fname))) continue self.log('Stripping target {!r} using {}.'.format(fname, d.strip_bin[0])) ps, stdo, stde = Popen_safe(d.strip_bin + [outname]) diff --git a/mesonbuild/modules/qt.py b/mesonbuild/modules/qt.py index c7a1c46..32ff6af 100644 --- a/mesonbuild/modules/qt.py +++ b/mesonbuild/modules/qt.py @@ -87,29 +87,29 @@ class QtBaseModule(ExtensionModule): rcc_dirname, nodes = self.qrc_nodes(state, rcc_file) result = [] for resource_path in nodes: - # We need to guess if the pointed resource is: - # a) in build directory -> implies a generated file - # b) in source directory - # c) somewhere else external dependency file to bundle - # - # Also from qrc documentation: relative path are always from qrc file - # So relative path must always be computed from qrc file ! - if os.path.isabs(resource_path): - # a) - if resource_path.startswith(os.path.abspath(state.environment.build_dir)): - resource_relpath = os.path.relpath(resource_path, state.environment.build_dir) - result.append(File(is_built=True, subdir='', fname=resource_relpath)) - # either b) or c) - else: - result.append(File(is_built=False, subdir=state.subdir, fname=resource_path)) - else: - path_from_rcc = os.path.normpath(os.path.join(rcc_dirname, resource_path)) - # a) - if path_from_rcc.startswith(state.environment.build_dir): - result.append(File(is_built=True, subdir=state.subdir, fname=resource_path)) - # b) - else: - result.append(File(is_built=False, subdir=state.subdir, fname=path_from_rcc)) + # We need to guess if the pointed resource is: + # a) in build directory -> implies a generated file + # b) in source directory + # c) somewhere else external dependency file to bundle + # + # Also from qrc documentation: relative path are always from qrc file + # So relative path must always be computed from qrc file ! + if os.path.isabs(resource_path): + # a) + if resource_path.startswith(os.path.abspath(state.environment.build_dir)): + resource_relpath = os.path.relpath(resource_path, state.environment.build_dir) + result.append(File(is_built=True, subdir='', fname=resource_relpath)) + # either b) or c) + else: + result.append(File(is_built=False, subdir=state.subdir, fname=resource_path)) + else: + path_from_rcc = os.path.normpath(os.path.join(rcc_dirname, resource_path)) + # a) + if path_from_rcc.startswith(state.environment.build_dir): + result.append(File(is_built=True, subdir=state.subdir, fname=resource_path)) + # b) + else: + result.append(File(is_built=False, subdir=state.subdir, fname=path_from_rcc)) return result @noPosargs diff --git a/mesonbuild/optinterpreter.py b/mesonbuild/optinterpreter.py index 75011e7..ec329a8 100644 --- a/mesonbuild/optinterpreter.py +++ b/mesonbuild/optinterpreter.py @@ -210,7 +210,7 @@ class OptionInterpreter: (posargs, kwargs) = self.reduce_arguments(node.args) if 'yield' in kwargs: - FeatureNew.single_use('option yield', '0.45.0', self.subproject) + FeatureNew.single_use('option yield', '0.45.0', self.subproject) if 'type' not in kwargs: raise OptionException('Option call missing mandatory "type" keyword argument') diff --git a/run_mypy.py b/run_mypy.py index 953c20d..db38112 100755 --- a/run_mypy.py +++ b/run_mypy.py @@ -9,12 +9,16 @@ import typing as T modules = [ # fully typed submodules 'mesonbuild/ast', + 'mesonbuild/compilers/mixins', 'mesonbuild/scripts', 'mesonbuild/wrap', # specific files 'mesonbuild/arglist.py', - # 'mesonbuild/compilers/mixins/intel.py', + 'mesonbuild/compilers/compilers.py', + 'mesonbuild/compilers/c_function_attributes.py', + 'mesonbuild/compilers/objc.py', + 'mesonbuild/compilers/objcpp.py', # 'mesonbuild/coredata.py', 'mesonbuild/dependencies/boost.py', 'mesonbuild/dependencies/hdf5.py', diff --git a/run_tests.py b/run_tests.py index 6e9c6ce..0f02636 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 6b817bb..db61ca6 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -377,7 +377,7 @@ class InternalTests(unittest.TestCase): self.assertEqual(a, ['-I.', '-I./tests2/', '-I./tests/', '-I..']) def test_compiler_args_class_d(self): - d = mesonbuild.compilers.DCompiler([], 'fake', MachineChoice.HOST, 'info', 'arch', False, None) + d = mesonbuild.compilers.DmdDCompiler([], 'fake', MachineChoice.HOST, 'info', 'arch') # check include order is kept when deduplicating a = d.compiler_args(['-Ifirst', '-Isecond', '-Ithird']) a += ['-Ifirst'] @@ -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): |