aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJussi Pakkanen <jpakkane@gmail.com>2019-04-28 20:20:25 +0300
committerGitHub <noreply@github.com>2019-04-28 20:20:25 +0300
commit60969d99d33001317c569a9b37d3b9efae08d387 (patch)
tree57a9eb3a4d51194b14b8c4a9c06b501dd118d1a4
parentcb1ef0c44990a710f6c78c5f808bf6e04985f59b (diff)
parent891136178defbf876ca3880b7f33fd04d32bf631 (diff)
downloadmeson-60969d99d33001317c569a9b37d3b9efae08d387.zip
meson-60969d99d33001317c569a9b37d3b9efae08d387.tar.gz
meson-60969d99d33001317c569a9b37d3b9efae08d387.tar.bz2
Merge pull request #5265 from dcbaker/more-annotations
Fully annotate the envconfig and mlog modules
-rw-r--r--mesonbuild/envconfig.py122
-rw-r--r--mesonbuild/mesonlib.py30
-rw-r--r--mesonbuild/mlog.py124
3 files changed, 150 insertions, 126 deletions
diff --git a/mesonbuild/envconfig.py b/mesonbuild/envconfig.py
index e211945..f4c371f 100644
--- a/mesonbuild/envconfig.py
+++ b/mesonbuild/envconfig.py
@@ -19,6 +19,8 @@ from . import mesonlib
from .mesonlib import EnvironmentException, MachineChoice, PerMachine
from . import mlog
+_T = typing.TypeVar('_T')
+
# These classes contains all the data pulled from configuration files (native
# and cross file currently), and also assists with the reading environment
@@ -69,7 +71,7 @@ CPU_FAMILES_64_BIT = [
class MesonConfigFile:
@classmethod
- def from_config_parser(cls, parser: configparser.ConfigParser):
+ def from_config_parser(cls, parser: configparser.ConfigParser) -> typing.Dict[str, typing.Dict[str, typing.Dict[str, str]]]:
out = {}
# This is a bit hackish at the moment.
for s in parser.sections():
@@ -106,55 +108,58 @@ class HasEnvVarFallback:
that we deal with environment variables will become more structured, and
this can be starting point.
"""
- def __init__(self, fallback = True):
+ def __init__(self, fallback: bool = True):
self.fallback = fallback
class Properties(HasEnvVarFallback):
def __init__(
self,
properties: typing.Optional[typing.Dict[str, typing.Union[str, typing.List[str]]]] = None,
- fallback = True):
+ fallback: bool = True):
super().__init__(fallback)
- self.properties = properties or {}
+ self.properties = properties or {} # type: typing.Dict[str, typing.Union[str, typing.List[str]]]
- def has_stdlib(self, language):
+ def has_stdlib(self, language: str) -> bool:
return language + '_stdlib' in self.properties
- def get_stdlib(self, language):
+ # Some of get_stdlib, get_root, get_sys_root are wider than is actually
+ # true, but without heterogenious dict annotations it's not practical to
+ # narrow them
+ def get_stdlib(self, language: str) -> typing.Union[str, typing.List[str]]:
return self.properties[language + '_stdlib']
- def get_root(self):
+ def get_root(self) -> typing.Optional[typing.Union[str, typing.List[str]]]:
return self.properties.get('root', None)
- def get_sys_root(self):
+ def get_sys_root(self) -> typing.Optional[typing.Union[str, typing.List[str]]]:
return self.properties.get('sys_root', None)
- def __eq__(self, other):
+ def __eq__(self, other: typing.Any) -> typing.Union[bool, 'NotImplemented']:
if isinstance(other, type(self)):
return self.properties == other.properties
return NotImplemented
# TODO consider removing so Properties is less freeform
- def __getitem__(self, key):
+ def __getitem__(self, key: str) -> typing.Any:
return self.properties[key]
# TODO consider removing so Properties is less freeform
- def __contains__(self, item):
+ def __contains__(self, item: typing.Any) -> bool:
return item in self.properties
# TODO consider removing, for same reasons as above
- def get(self, key, default=None):
+ def get(self, key: str, default: typing.Any = None) -> typing.Any:
return self.properties.get(key, default)
class MachineInfo:
- def __init__(self, system, cpu_family, cpu, endian):
+ def __init__(self, system: str, cpu_family: str, cpu: str, endian: str):
self.system = system
self.cpu_family = cpu_family
self.cpu = cpu
self.endian = endian
- self.is_64_bit = cpu_family in CPU_FAMILES_64_BIT
+ self.is_64_bit = cpu_family in CPU_FAMILES_64_BIT # type: bool
- def __eq__(self, other):
+ def __eq__(self, other: typing.Any) -> typing.Union[bool, 'NotImplemented']:
if self.__class__ is not other.__class__:
return NotImplemented
return \
@@ -163,16 +168,16 @@ class MachineInfo:
self.cpu == other.cpu and \
self.endian == other.endian
- def __ne__(self, other):
+ def __ne__(self, other: typing.Any) -> typing.Union[bool, 'NotImplemented']:
if self.__class__ is not other.__class__:
return NotImplemented
return not self.__eq__(other)
- def __repr__(self):
+ def __repr__(self) -> str:
return '<MachineInfo: {} {} ({})>'.format(self.system, self.cpu_family, self.cpu)
- @staticmethod
- def from_literal(literal):
+ @classmethod
+ def from_literal(cls, literal: typing.Dict[str, str]) -> 'MachineInfo':
minimum_literal = {'cpu', 'cpu_family', 'endian', 'system'}
if set(literal) < minimum_literal:
raise EnvironmentException(
@@ -187,49 +192,45 @@ class MachineInfo:
if endian not in ('little', 'big'):
mlog.warning('Unknown endian %s' % endian)
- return MachineInfo(
- literal['system'],
- cpu_family,
- literal['cpu'],
- endian)
+ return cls(literal['system'], cpu_family, literal['cpu'], endian)
- def is_windows(self):
+ def is_windows(self) -> bool:
"""
Machine is windows?
"""
return self.system == 'windows'
- def is_cygwin(self):
+ def is_cygwin(self) -> bool:
"""
Machine is cygwin?
"""
return self.system == 'cygwin'
- def is_linux(self):
+ def is_linux(self) -> bool:
"""
Machine is linux?
"""
return self.system == 'linux'
- def is_darwin(self):
+ def is_darwin(self) -> bool:
"""
Machine is Darwin (iOS/OS X)?
"""
return self.system in ('darwin', 'ios')
- def is_android(self):
+ def is_android(self) -> bool:
"""
Machine is Android?
"""
return self.system == 'android'
- def is_haiku(self):
+ def is_haiku(self) -> bool:
"""
Machine is Haiku?
"""
return self.system == 'haiku'
- def is_openbsd(self):
+ def is_openbsd(self) -> bool:
"""
Machine is OpenBSD?
"""
@@ -239,29 +240,28 @@ class MachineInfo:
# static libraries, and executables.
# Versioning is added to these names in the backends as-needed.
- def get_exe_suffix(self):
+ def get_exe_suffix(self) -> str:
if self.is_windows() or self.is_cygwin():
return 'exe'
else:
return ''
- def get_object_suffix(self):
+ def get_object_suffix(self) -> str:
if self.is_windows():
return 'obj'
else:
return 'o'
- def libdir_layout_is_win(self):
- return self.is_windows() \
- or self.is_cygwin()
+ def libdir_layout_is_win(self) -> bool:
+ return self.is_windows() or self.is_cygwin()
-class PerMachineDefaultable(PerMachine):
+class PerMachineDefaultable(PerMachine[_T]):
"""Extends `PerMachine` with the ability to default from `None`s.
"""
- def __init__(self):
+ def __init__(self) -> None:
super().__init__(None, None, None)
- def default_missing(self):
+ def default_missing(self) -> None:
"""Default host to buid and target to host.
This allows just specifying nothing in the native case, just host in the
@@ -273,7 +273,7 @@ class PerMachineDefaultable(PerMachine):
if self.target is None:
self.target = self.host
- def miss_defaulting(self):
+ def miss_defaulting(self) -> None:
"""Unset definition duplicated from their previous to None
This is the inverse of ''default_missing''. By removing defaulted
@@ -285,18 +285,17 @@ class PerMachineDefaultable(PerMachine):
if self.host == self.build:
self.host = None
-class MachineInfos(PerMachineDefaultable):
- def matches_build_machine(self, machine: MachineChoice):
+class MachineInfos(PerMachineDefaultable[typing.Optional[MachineInfo]]):
+ def matches_build_machine(self, machine: MachineChoice) -> bool:
return self.build == self[machine]
class BinaryTable(HasEnvVarFallback):
def __init__(
self,
binaries: typing.Optional[typing.Dict[str, typing.Union[str, typing.List[str]]]] = None,
-
- fallback = True):
+ fallback: bool = True):
super().__init__(fallback)
- self.binaries = binaries or {}
+ self.binaries = binaries or {} # type: typing.Dict[str, typing.Union[str, typing.List[str]]]
for name, command in self.binaries.items():
if not isinstance(command, (list, str)):
# TODO generalize message
@@ -325,29 +324,25 @@ class BinaryTable(HasEnvVarFallback):
'cmake': 'CMAKE',
'qmake': 'QMAKE',
'pkgconfig': 'PKG_CONFIG',
- }
+ } # type: typing.Dict[str, str]
- @classmethod
- def detect_ccache(cls):
+ @staticmethod
+ def detect_ccache() -> typing.List[str]:
try:
- has_ccache = subprocess.call(['ccache', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- except OSError:
- has_ccache = 1
- if has_ccache == 0:
- cmdlist = ['ccache']
- else:
- cmdlist = []
- return cmdlist
+ subprocess.check_call(['ccache', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ except (OSError, subprocess.CalledProcessError):
+ return []
+ return ['ccache']
@classmethod
- def _warn_about_lang_pointing_to_cross(cls, compiler_exe, evar):
+ def _warn_about_lang_pointing_to_cross(cls, compiler_exe: str, evar: str) -> None:
evar_str = os.environ.get(evar, 'WHO_WOULD_CALL_THEIR_COMPILER_WITH_THIS_NAME')
if evar_str == compiler_exe:
mlog.warning('''Env var %s seems to point to the cross compiler.
This is probably wrong, it should always point to the native compiler.''' % evar)
@classmethod
- def parse_entry(cls, entry):
+ def parse_entry(cls, entry: typing.Union[str, typing.List[str]]) -> typing.Tuple[typing.List[str], typing.List[str]]:
compiler = mesonlib.stringlistify(entry)
# Ensure ccache exists and remove it if it doesn't
if compiler[0] == 'ccache':
@@ -358,8 +353,8 @@ This is probably wrong, it should always point to the native compiler.''' % evar
# Return value has to be a list of compiler 'choices'
return compiler, ccache
- def lookup_entry(self, name):
- """Lookup binary
+ def lookup_entry(self, name: str) -> typing.Optional[typing.List[str]]:
+ """Lookup binaryk
Returns command with args as list if found, Returns `None` if nothing is
found.
@@ -408,11 +403,12 @@ class Directories:
self.sharedstatedir = sharedstatedir
self.sysconfdir = sysconfdir
- def __contains__(self, key: str) -> str:
+ def __contains__(self, key: str) -> bool:
return hasattr(self, key)
- def __getitem__(self, key: str) -> str:
- return getattr(self, key)
+ def __getitem__(self, key: str) -> typing.Optional[str]:
+ # Mypy can't figure out what to do with getattr here, so we'll case for it
+ return typing.cast(typing.Optional[str], getattr(self, key))
def __setitem__(self, key: str, value: typing.Optional[str]) -> None:
setattr(self, key, value)
diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py
index f233730..7219946 100644
--- a/mesonbuild/mesonlib.py
+++ b/mesonbuild/mesonlib.py
@@ -22,9 +22,13 @@ import platform, subprocess, operator, os, shutil, re
import collections
from enum import Enum
from functools import lru_cache
+import typing
from mesonbuild import mlog
+_T = typing.TypeVar('_T')
+_U = typing.TypeVar('_U')
+
have_fcntl = False
have_msvcrt = False
# {subproject: project_meson_version}
@@ -319,20 +323,22 @@ class MachineChoice(OrderedEnum):
HOST = 1
TARGET = 2
-class PerMachine:
- def __init__(self, build, host, target):
+_T = typing.TypeVar('_T')
+
+class PerMachine(typing.Generic[_T]):
+ def __init__(self, build: typing.Optional[_T], host: typing.Optional[_T], target: typing.Optional[_T]):
self.build = build
self.host = host
self.target = target
- def __getitem__(self, machine: MachineChoice):
+ def __getitem__(self, machine: MachineChoice) -> typing.Optional[_T]:
return {
MachineChoice.BUILD: self.build,
MachineChoice.HOST: self.host,
MachineChoice.TARGET: self.target
}[machine]
- def __setitem__(self, machine: MachineChoice, val):
+ def __setitem__(self, machine: MachineChoice, val: typing.Optional[_T]) -> None:
key = {
MachineChoice.BUILD: 'build',
MachineChoice.HOST: 'host',
@@ -916,14 +922,13 @@ def extract_as_list(dict_object, *keys, pop=False, **kwargs):
result.append(listify(fetch(key, []), **kwargs))
return result
-
-def typeslistify(item, types):
+def typeslistify(item: typing.Union[_T, typing.List[_T]], types: typing.Union[typing.Type[_T], typing.Tuple[typing.Type[_T]]]) -> typing.List[_T]:
'''
Ensure that type(@item) is one of @types or a
list of items all of which are of type @types
'''
if isinstance(item, types):
- item = [item]
+ item = typing.cast(typing.List[_T], [item])
if not isinstance(item, list):
raise MesonException('Item must be a list or one of {!r}'.format(types))
for i in item:
@@ -931,7 +936,7 @@ def typeslistify(item, types):
raise MesonException('List item must be one of {!r}'.format(types))
return item
-def stringlistify(item):
+def stringlistify(item: typing.Union[str, typing.List[str]]) -> typing.List[str]:
return typeslistify(item, str)
def expand_arguments(args):
@@ -1202,7 +1207,14 @@ def detect_subprojects(spdir_name, current_dir='', result=None):
result[basename] = [trial]
return result
-def get_error_location_string(fname, lineno):
+# This isn't strictly correct. What we really want here is something like:
+# class StringProtocol(typing_extensions.Protocol):
+#
+# def __str__(self) -> str: ...
+#
+# This would more accurately embody what this funcitonc an handle, but we
+# don't have that yet, so instead we'll do some casting to work around it
+def get_error_location_string(fname: str, lineno: str) -> str:
return '{}:{}:'.format(fname, lineno)
def substring_is_in_list(substr, strlist):
diff --git a/mesonbuild/mlog.py b/mesonbuild/mlog.py
index 0434274..e8ee6c8 100644
--- a/mesonbuild/mlog.py
+++ b/mesonbuild/mlog.py
@@ -18,13 +18,15 @@ import sys
import time
import platform
from contextlib import contextmanager
+import typing
"""This is (mostly) a standalone module used to write logging
information about Meson runs. Some output goes to screen,
some to logging dir and some goes to both."""
-def _windows_ansi():
- from ctypes import windll, byref
+def _windows_ansi() -> bool:
+ # windll only exists on windows, so mypy will get mad
+ from ctypes import windll, byref # type: ignore
from ctypes.wintypes import DWORD
kernel = windll.kernel32
@@ -35,48 +37,48 @@ def _windows_ansi():
# ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0x4
# If the call to enable VT processing fails (returns 0), we fallback to
# original behavior
- return kernel.SetConsoleMode(stdout, mode.value | 0x4) or os.environ.get('ANSICON')
+ return bool(kernel.SetConsoleMode(stdout, mode.value | 0x4) or os.environ.get('ANSICON'))
if platform.system().lower() == 'windows':
- colorize_console = os.isatty(sys.stdout.fileno()) and _windows_ansi()
+ colorize_console = os.isatty(sys.stdout.fileno()) and _windows_ansi() # type: bool
else:
colorize_console = os.isatty(sys.stdout.fileno()) and os.environ.get('TERM') != 'dumb'
-log_dir = None
-log_file = None
-log_fname = 'meson-log.txt'
-log_depth = 0
-log_timestamp_start = None
-log_fatal_warnings = False
-log_disable_stdout = False
-log_errors_only = False
-
-def disable():
+log_dir = None # type: typing.Optional[str]
+log_file = None # type: typing.Optional[typing.TextIO]
+log_fname = 'meson-log.txt' # type: str
+log_depth = 0 # type: int
+log_timestamp_start = None # type: typing.Optional[float]
+log_fatal_warnings = False # type: bool
+log_disable_stdout = False # type: bool
+log_errors_only = False # type: bool
+
+def disable() -> None:
global log_disable_stdout
log_disable_stdout = True
-def enable():
+def enable() -> None:
global log_disable_stdout
log_disable_stdout = False
-def set_quiet():
+def set_quiet() -> None:
global log_errors_only
log_errors_only = True
-def set_verbose():
+def set_verbose() -> None:
global log_errors_only
log_errors_only = False
-def initialize(logdir, fatal_warnings=False):
+def initialize(logdir: str, fatal_warnings: bool = False) -> None:
global log_dir, log_file, log_fatal_warnings
log_dir = logdir
log_file = open(os.path.join(logdir, log_fname), 'w', encoding='utf8')
log_fatal_warnings = fatal_warnings
-def set_timestamp_start(start):
+def set_timestamp_start(start: float) -> None:
global log_timestamp_start
log_timestamp_start = start
-def shutdown():
+def shutdown() -> typing.Optional[str]:
global log_file
if log_file is not None:
path = log_file.name
@@ -89,12 +91,12 @@ def shutdown():
class AnsiDecorator:
plain_code = "\033[0m"
- def __init__(self, text, code, quoted=False):
+ def __init__(self, text: str, code: str, quoted: bool = False):
self.text = text
self.code = code
self.quoted = quoted
- def get_text(self, with_codes):
+ def get_text(self, with_codes: bool) -> str:
text = self.text
if with_codes:
text = self.code + self.text + AnsiDecorator.plain_code
@@ -102,26 +104,28 @@ class AnsiDecorator:
text = '"{}"'.format(text)
return text
-def bold(text, quoted=False):
+def bold(text: str, quoted: bool = False) -> AnsiDecorator:
return AnsiDecorator(text, "\033[1m", quoted=quoted)
-def red(text):
+def red(text: str) -> AnsiDecorator:
return AnsiDecorator(text, "\033[1;31m")
-def green(text):
+def green(text: str) -> AnsiDecorator:
return AnsiDecorator(text, "\033[1;32m")
-def yellow(text):
+def yellow(text: str) -> AnsiDecorator:
return AnsiDecorator(text, "\033[1;33m")
-def blue(text):
+def blue(text: str) -> AnsiDecorator:
return AnsiDecorator(text, "\033[1;34m")
-def cyan(text):
+def cyan(text: str) -> AnsiDecorator:
return AnsiDecorator(text, "\033[1;36m")
-def process_markup(args, keep):
- arr = []
+# This really should be AnsiDecorator or anything that implements
+# __str__(), but that requires protocols from typing_extensions
+def process_markup(args: typing.Sequence[typing.Union[AnsiDecorator, str]], keep: bool) -> typing.List[str]:
+ arr = [] # type: typing.List[str]
if log_timestamp_start is not None:
arr = ['[{:.3f}]'.format(time.monotonic() - log_timestamp_start)]
for arg in args:
@@ -135,7 +139,7 @@ def process_markup(args, keep):
arr.append(str(arg))
return arr
-def force_print(*args, **kwargs):
+def force_print(*args: str, **kwargs: typing.Any) -> None:
global log_disable_stdout
if log_disable_stdout:
return
@@ -155,41 +159,51 @@ def force_print(*args, **kwargs):
cleaned = raw.encode('ascii', 'replace').decode('ascii')
print(cleaned, end='')
-def debug(*args, **kwargs):
+# We really want a heterogenous dict for this, but that's in typing_extensions
+def debug(*args: typing.Union[str, AnsiDecorator], **kwargs: typing.Any) -> None:
arr = process_markup(args, False)
if log_file is not None:
- print(*arr, file=log_file, **kwargs) # Log file never gets ANSI codes.
+ print(*arr, file=log_file, **kwargs)
log_file.flush()
-def log(*args, is_error=False, **kwargs):
+def log(*args: typing.Union[str, AnsiDecorator], is_error: bool = False,
+ **kwargs: typing.Any) -> None:
global log_errors_only
arr = process_markup(args, False)
if log_file is not None:
- print(*arr, file=log_file, **kwargs) # Log file never gets ANSI codes.
+ print(*arr, file=log_file, **kwargs)
log_file.flush()
if colorize_console:
arr = process_markup(args, True)
if not log_errors_only or is_error:
force_print(*arr, **kwargs)
-def _log_error(severity, *args, **kwargs):
+def _log_error(severity: str, *rargs: typing.Union[str, AnsiDecorator], **kwargs: typing.Any) -> None:
from .mesonlib import get_error_location_string
from .environment import build_filename
from .mesonlib import MesonException
+
+ # The tping requirements here are non-obvious. Lists are invariant,
+ # therefore List[A] and List[Union[A, B]] are not able to be joined
if severity == 'warning':
- args = (yellow('WARNING:'),) + args
+ label = [yellow('WARNING:')] # type: typing.List[typing.Union[str, AnsiDecorator]]
elif severity == 'error':
- args = (red('ERROR:'),) + args
+ label = [red('ERROR:')]
elif severity == 'deprecation':
- args = (red('DEPRECATION:'),) + args
+ label = [red('DEPRECATION:')]
else:
- assert False, 'Invalid severity ' + severity
+ raise MesonException('Invalid severity ' + severity)
+ # rargs is a tuple, not a list
+ args = label + list(rargs)
location = kwargs.pop('location', None)
if location is not None:
location_file = os.path.join(location.subdir, build_filename)
location_str = get_error_location_string(location_file, location.lineno)
- args = (location_str,) + args
+ # Unions are frankly awful, and we have to cast here to get mypy
+ # to understand that the list concatenation is safe
+ location_list = typing.cast(typing.List[typing.Union[str, AnsiDecorator]], [location_str])
+ args = location_list + args
log(*args, **kwargs)
@@ -197,40 +211,42 @@ def _log_error(severity, *args, **kwargs):
if log_fatal_warnings:
raise MesonException("Fatal warnings enabled, aborting")
-def error(*args, **kwargs):
+def error(*args: typing.Union[str, AnsiDecorator], **kwargs: typing.Any) -> None:
return _log_error('error', *args, **kwargs, is_error=True)
-def warning(*args, **kwargs):
+def warning(*args: typing.Union[str, AnsiDecorator], **kwargs: typing.Any) -> None:
return _log_error('warning', *args, **kwargs, is_error=True)
-def deprecation(*args, **kwargs):
+def deprecation(*args: typing.Union[str, AnsiDecorator], **kwargs: typing.Any) -> None:
return _log_error('deprecation', *args, **kwargs, is_error=True)
-def exception(e, prefix=red('ERROR:')):
+def exception(e: Exception, prefix: AnsiDecorator = red('ERROR:')) -> None:
log()
- args = []
+ args = [] # type: typing.List[typing.Union[AnsiDecorator, str]]
if hasattr(e, 'file') and hasattr(e, 'lineno') and hasattr(e, 'colno'):
- args.append('%s:%d:%d:' % (e.file, e.lineno, e.colno))
+ # Mypy can't figure this out, and it's pretty easy to vidual inspect
+ # that this is correct, so we'll just ignore it.
+ args.append('%s:%d:%d:' % (e.file, e.lineno, e.colno)) # type: ignore
if prefix:
args.append(prefix)
- args.append(e)
+ args.append(str(e))
log(*args)
# Format a list for logging purposes as a string. It separates
# all but the last item with commas, and the last with 'and'.
-def format_list(list):
- l = len(list)
+def format_list(list_: typing.List[str]) -> str:
+ l = len(list_)
if l > 2:
- return ' and '.join([', '.join(list[:-1]), list[-1]])
+ return ' and '.join([', '.join(list_[:-1]), list_[-1]])
elif l == 2:
- return ' and '.join(list)
+ return ' and '.join(list_)
elif l == 1:
- return list[0]
+ return list_[0]
else:
return ''
@contextmanager
-def nested():
+def nested() -> typing.Generator[None, None, None]:
global log_depth
log_depth += 1
try: