diff options
36 files changed, 597 insertions, 396 deletions
diff --git a/data/macros.meson b/data/macros.meson index 05d21e5..c5b90de 100644 --- a/data/macros.meson +++ b/data/macros.meson @@ -2,6 +2,12 @@ %__meson_wrap_mode nodownload %__meson_auto_features enabled +%_smp_mesonflags %([ -z "$MESON_BUILD_NCPUS" ] \\\ + && MESON_BUILD_NCPUS="`/usr/bin/getconf _NPROCESSORS_ONLN`"; \\\ + ncpus_max=%{?_smp_ncpus_max}; \\\ + if [ -n "$ncpus_max" ] && [ "$ncpus_max" -gt 0 ] && [ "$MESON_BUILD_NCPUS" -gt "$ncpus_max" ]; then MESON_BUILD_NCPUS="$ncpus_max"; fi; \\\ + if [ "$MESON_BUILD_NCPUS" -gt 1 ]; then echo "--num-processes $MESON_BUILD_NCPUS"; fi) + %meson \ %set_build_flags \ %{shrink:%{__meson} \ @@ -31,4 +37,8 @@ %ninja_install -C %{_vpath_builddir} %meson_test \ - %ninja_test -C %{_vpath_builddir} + %{shrink: %{__meson} test \ + -C %{_vpath_builddir} \ + %{?_smp_mesonflags} \ + --print-errorlogs \ + %{nil}} diff --git a/docs/markdown/Feature-autodetection.md b/docs/markdown/Feature-autodetection.md index f865174..f50309e 100644 --- a/docs/markdown/Feature-autodetection.md +++ b/docs/markdown/Feature-autodetection.md @@ -4,16 +4,31 @@ short-description: Auto-detection of features like ccache and code coverage # Feature autodetection -Meson is designed for high productivity. It tries to do as many things automatically as it possibly can. +Meson is designed for high productivity. It tries to do as many things +automatically as it possibly can. CCache -- -[CCache](https://ccache.samba.org/) is a cache system designed to make compiling faster. When you run Meson for the first time for a given project, it checks if CCache is installed. If it is, Meson will use it automatically. +[CCache](https://ccache.samba.org/) is a cache system designed to make +compiling faster. When you run Meson for the first time for a given +project, it checks if CCache is installed. If it is, Meson will use it +automatically. -If you do not wish to use CCache for some reason, just specify your compiler with environment variables `CC` and/or `CXX` when first running Meson (remember that once specified the compiler can not be changed). Meson will then use the specified compiler without CCache. +If you do not wish to use CCache for some reason, just specify your +compiler with environment variables `CC` and/or `CXX` when first +running Meson (remember that once specified the compiler can not be +changed). Meson will then use the specified compiler without CCache. Coverage -- -When doing a code coverage build, Meson will check the existence of binaries `gcovr`, `lcov` and `genhtml`. If the first one is found, it will create targets called *coverage-text* and *coverage-xml*. If the latter two or a new enough `gcovr` is found, it generates the target *coverage-html*. You can then generate coverage reports just by calling e.g. `ninja coverage-xml`. +When doing a code coverage build, Meson will check for existence of +the binaries `gcovr`, `lcov` and `genhtml`. If version 3.3 or higher +of the first is found, targets called *coverage-text*, *coverage-xml* +and *coverage-html* are generated. Alternatively, if the latter two +are found, only the target *coverage-html* is generated. Coverage +reports can then be produced simply by calling e.g. `ninja +coverage-xml`. As a convenience, a high-level *coverage* target is +also generated which will produce all 3 coverage report types, if +possible. diff --git a/docs/markdown/Unit-tests.md b/docs/markdown/Unit-tests.md index 9e61739..694c190 100644 --- a/docs/markdown/Unit-tests.md +++ b/docs/markdown/Unit-tests.md @@ -30,7 +30,7 @@ Note how you need to specify multiple values as an array. Coverage -- -If you enable coverage measurements by giving Meson the command line flag `-Db_coverage=true`, you can generate coverage reports. Meson will autodetect what coverage generator tools you have installed and will generate the corresponding targets. These targets are `coverage-xml` and `coverage-text` which are both provided by [Gcovr](http://gcovr.com) and `coverage-html`, which requires [Lcov](https://ltp.sourceforge.io/coverage/lcov.php) and [GenHTML](https://linux.die.net/man/1/genhtml) or [Gcovr](http://gcovr.com) with html support. +If you enable coverage measurements by giving Meson the command line flag `-Db_coverage=true`, you can generate coverage reports. Meson will autodetect what coverage generator tools you have installed and will generate the corresponding targets. These targets are `coverage-xml` and `coverage-text` which are both provided by [Gcovr](http://gcovr.com) (version 3.3 or higher) and `coverage-html`, which requires [Lcov](https://ltp.sourceforge.io/coverage/lcov.php) and [GenHTML](https://linux.die.net/man/1/genhtml) or [Gcovr](http://gcovr.com). As a convenience, a high-level `coverage` target is also generated which will produce all 3 coverage report types, if possible. The output of these commands is written to the log directory `meson-logs` in your build directory. diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py index a75c0b7..eb9cb9f 100644 --- a/mesonbuild/ast/interpreter.py +++ b/mesonbuild/ast/interpreter.py @@ -20,9 +20,22 @@ from .. import interpreterbase, mparser, mesonlib from .. import environment from ..interpreterbase import InvalidArguments, BreakRequest, ContinueRequest +from ..mparser import ( + ArgumentNode, + ArithmeticNode, + ArrayNode, + AssignmentNode, + BaseNode, + ElementaryNode, + EmptyNode, + IdNode, + MethodNode, + PlusAssignmentNode, + TernaryNode, +) import os, sys -from typing import List, Optional +from typing import List, Any, Optional class DontCareObject(interpreterbase.InterpreterObject): pass @@ -162,13 +175,13 @@ class AstInterpreter(interpreterbase.InterpreterBase): return 0 def evaluate_ternary(self, node): - assert(isinstance(node, mparser.TernaryNode)) + assert(isinstance(node, TernaryNode)) self.evaluate_statement(node.condition) self.evaluate_statement(node.trueblock) self.evaluate_statement(node.falseblock) def evaluate_plusassign(self, node): - assert(isinstance(node, mparser.PlusAssignmentNode)) + assert(isinstance(node, PlusAssignmentNode)) if node.var_name not in self.assignments: self.assignments[node.var_name] = [] self.assign_vals[node.var_name] = [] @@ -184,10 +197,12 @@ class AstInterpreter(interpreterbase.InterpreterBase): pass def reduce_arguments(self, args): - assert(isinstance(args, mparser.ArgumentNode)) - if args.incorrect_order(): - raise InvalidArguments('All keyword arguments must be after positional arguments.') - return args.arguments, args.kwargs + if isinstance(args, ArgumentNode): + if args.incorrect_order(): + raise InvalidArguments('All keyword arguments must be after positional arguments.') + return self.flatten_args(args.arguments), args.kwargs + else: + return self.flatten_args(args), {} def evaluate_comparison(self, node): self.evaluate_statement(node.left) @@ -215,45 +230,95 @@ class AstInterpreter(interpreterbase.InterpreterBase): def evaluate_if(self, node): for i in node.ifs: self.evaluate_codeblock(i.block) - if not isinstance(node.elseblock, mparser.EmptyNode): + if not isinstance(node.elseblock, EmptyNode): self.evaluate_codeblock(node.elseblock) def get_variable(self, varname): return 0 def assignment(self, node): - assert(isinstance(node, mparser.AssignmentNode)) + assert(isinstance(node, AssignmentNode)) self.assignments[node.var_name] = [node.value] # Save a reference to the value node if hasattr(node.value, 'ast_id'): self.reverse_assignment[node.value.ast_id] = node self.assign_vals[node.var_name] = [self.evaluate_statement(node.value)] # Evaluate the value just in case - def flatten_args(self, args, include_unknown_args: bool = False): - # Resolve mparser.ArrayNode if needed + def flatten_args(self, args: Any, include_unknown_args: bool = False, id_loop_detect: Optional[List[str]] = None) -> List[str]: + def quick_resolve(n: BaseNode, loop_detect: Optional[List[str]] = None) -> Any: + if loop_detect is None: + loop_detect = [] + if isinstance(n, IdNode): + if n.value in loop_detect or n.value not in self.assignments: + return [] + return quick_resolve(self.assignments[n.value][0], loop_detect = loop_detect + [n.value]) + elif isinstance(n, ElementaryNode): + return n.value + else: + return n + + if id_loop_detect is None: + id_loop_detect = [] flattend_args = [] - temp_args = [] - if isinstance(args, mparser.ArrayNode): + + if isinstance(args, ArrayNode): args = [x for x in args.args.arguments] - elif isinstance(args, mparser.ArgumentNode): + + elif isinstance(args, ArgumentNode): args = [x for x in args.arguments] - for i in args: - if isinstance(i, mparser.ArrayNode): - temp_args += [x for x in i.args.arguments] + + elif isinstance(args, ArithmeticNode): + if args.operation != 'add': + return [] # Only handle string and array concats + l = quick_resolve(args.left) + r = quick_resolve(args.right) + if isinstance(l, str) and isinstance(r, str): + args = [l + r] # String concatination detected else: - temp_args += [i] - for i in temp_args: - if isinstance(i, mparser.ElementaryNode) and not isinstance(i, mparser.IdNode): + args = self.flatten_args(l, include_unknown_args, id_loop_detect) + self.flatten_args(r, include_unknown_args, id_loop_detect) + + elif isinstance(args, MethodNode): + src = quick_resolve(args.source_object) + margs = self.flatten_args(args.args, include_unknown_args, id_loop_detect) + try: + if isinstance(src, str): + args = [self.string_method_call(src, args.name, margs)] + elif isinstance(src, bool): + args = [self.bool_method_call(src, args.name, margs)] + elif isinstance(src, int): + args = [self.int_method_call(src, args.name, margs)] + elif isinstance(src, list): + args = [self.array_method_call(src, args.name, margs)] + elif isinstance(src, dict): + args = [self.dict_method_call(src, args.name, margs)] + else: + return [] + except mesonlib.MesonException: + return [] + + # Make sure we are always dealing with lists + if not isinstance(args, list): + args = [args] + + # Resolve the contents of args + for i in args: + if isinstance(i, IdNode) and i.value not in id_loop_detect: + flattend_args += self.flatten_args(quick_resolve(i), include_unknown_args, id_loop_detect + [i.value]) + elif isinstance(i, (ArrayNode, ArgumentNode, ArithmeticNode, MethodNode)): + flattend_args += self.flatten_args(i, include_unknown_args, id_loop_detect) + elif isinstance(i, mparser.ElementaryNode): flattend_args += [i.value] - elif isinstance(i, (str, bool, int, float)) or include_unknown_args: + elif isinstance(i, (str, bool, int, float)): + flattend_args += [i] + elif include_unknown_args: flattend_args += [i] return flattend_args def flatten_kwargs(self, kwargs: object, include_unknown_args: bool = False): flattend_kwargs = {} for key, val in kwargs.items(): - if isinstance(val, mparser.ElementaryNode): + if isinstance(val, ElementaryNode): flattend_kwargs[key] = val.value - elif isinstance(val, (mparser.ArrayNode, mparser.ArgumentNode)): + elif isinstance(val, (ArrayNode, ArgumentNode)): flattend_kwargs[key] = self.flatten_args(val, include_unknown_args) elif isinstance(val, (str, bool, int, float)) or include_unknown_args: flattend_kwargs[key] = val diff --git a/mesonbuild/ast/introspection.py b/mesonbuild/ast/introspection.py index f8fb2e8..49d531f 100644 --- a/mesonbuild/ast/introspection.py +++ b/mesonbuild/ast/introspection.py @@ -182,11 +182,12 @@ class IntrospectionInterpreter(AstInterpreter): srcqueue += [curr.left, curr.right] if arg_node is None: continue - elemetary_nodes = list(filter(lambda x: isinstance(x, (str, StringNode)), arg_node.arguments)) - srcqueue += list(filter(lambda x: isinstance(x, (FunctionNode, ArrayNode, IdNode, ArithmeticNode)), arg_node.arguments)) + arg_nodes = arg_node.arguments.copy() # Pop the first element if the function is a build target function if isinstance(curr, FunctionNode) and curr.func_name in build_target_functions: - elemetary_nodes.pop(0) + arg_nodes.pop(0) + elemetary_nodes = [x for x in arg_nodes if isinstance(x, (str, StringNode))] + srcqueue += [x for x in arg_nodes if isinstance(x, (FunctionNode, ArrayNode, IdNode, ArithmeticNode))] if elemetary_nodes: source_nodes += [curr] diff --git a/mesonbuild/ast/printer.py b/mesonbuild/ast/printer.py index 60e0b0d..2de1d0c 100644 --- a/mesonbuild/ast/printer.py +++ b/mesonbuild/ast/printer.py @@ -179,7 +179,7 @@ class AstPrinter(AstVisitor): node.falseblock.accept(self) def visit_ArgumentNode(self, node: mparser.ArgumentNode): - break_args = True if (len(node.arguments) + len(node.kwargs)) > self.arg_newline_cutoff else False + break_args = (len(node.arguments) + len(node.kwargs)) > self.arg_newline_cutoff for i in node.arguments + list(node.kwargs.values()): if not isinstance(i, mparser.ElementaryNode): break_args = True diff --git a/mesonbuild/backend/xcodebackend.py b/mesonbuild/backend/xcodebackend.py index 990b824..7dd3674 100644 --- a/mesonbuild/backend/xcodebackend.py +++ b/mesonbuild/backend/xcodebackend.py @@ -748,7 +748,7 @@ class XCodeBackend(backends.Backend): gargs = self.build.global_args.get(lang, []) targs = target.get_extra_args(lang) args = pargs + gargs + targs - if len(args) > 0: + if args: langargs[langnamemap[lang]] = args symroot = os.path.join(self.environment.get_build_dir(), target.subdir) self.write_line('%s /* %s */ = {' % (valid, buildtype)) @@ -783,7 +783,7 @@ class XCodeBackend(backends.Backend): self.write_line('GCC_PREFIX_HEADER = "$(PROJECT_DIR)/%s";' % relative_pch_path) self.write_line('GCC_PREPROCESSOR_DEFINITIONS = "";') self.write_line('GCC_SYMBOLS_PRIVATE_EXTERN = NO;') - if len(headerdirs) > 0: + if headerdirs: quotedh = ','.join(['"\\"%s\\""' % i for i in headerdirs]) self.write_line('HEADER_SEARCH_PATHS=(%s);' % quotedh) self.write_line('INSTALL_PATH = "%s";' % install_path) diff --git a/mesonbuild/compilers/__init__.py b/mesonbuild/compilers/__init__.py index 5de0e59..4cb7ebf 100644 --- a/mesonbuild/compilers/__init__.py +++ b/mesonbuild/compilers/__init__.py @@ -69,6 +69,8 @@ __all__ = [ 'IntelCCompiler', 'IntelCPPCompiler', 'IntelFortranCompiler', + 'IntelClCCompiler', + 'IntelClCPPCompiler', 'JavaCompiler', 'LLVMDCompiler', 'MonoCompiler', @@ -130,6 +132,7 @@ from .c import ( GnuCCompiler, ElbrusCCompiler, IntelCCompiler, + IntelClCCompiler, PGICCompiler, CcrxCCompiler, VisualStudioCCompiler, @@ -143,6 +146,7 @@ from .cpp import ( GnuCPPCompiler, ElbrusCPPCompiler, IntelCPPCompiler, + IntelClCPPCompiler, PGICPPCompiler, CcrxCPPCompiler, VisualStudioCPPCompiler, diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py index da9a5dc..54ca894 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py @@ -248,7 +248,7 @@ class CCompiler(Compiler): for d in dirs: files = [f for f in os.listdir(d) if f.endswith('.so') and os.path.isfile(os.path.join(d, f))] # if no files, accept directory and move on - if len(files) == 0: + if not files: retval.append(d) continue file_to_check = os.path.join(d, files[0]) @@ -1324,9 +1324,9 @@ class GnuCCompiler(GnuCompiler, CCompiler): class PGICCompiler(PGICompiler, CCompiler): - def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwargs): + def __init__(self, exelist, version, compiler_type, is_cross, exe_wrapper=None, **kwargs): CCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwargs) - PGICompiler.__init__(self, CompilerType.PGI_STANDARD) + PGICompiler.__init__(self, compiler_type) class ElbrusCCompiler(GnuCCompiler, ElbrusCompiler): @@ -1722,6 +1722,12 @@ class ClangClCCompiler(VisualStudioCCompiler): self.id = 'clang-cl' +class IntelClCCompiler(VisualStudioCCompiler): + def __init__(self, exelist, version, is_cross, exe_wrap, target): + super().__init__(exelist, version, is_cross, exe_wrap, target) + self.id = 'intel' + + class ArmCCompiler(ArmCompiler, CCompiler): def __init__(self, exelist, version, compiler_type, is_cross, exe_wrapper=None, **kwargs): CCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwargs) diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 79eefd1..04cc31a 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -1397,18 +1397,20 @@ class CompilerType(enum.Enum): CCRX_WIN = 40 PGI_STANDARD = 50 + PGI_OSX = 51 + PGI_WIN = 52 @property def is_standard_compiler(self): - return self.name in ('GCC_STANDARD', 'CLANG_STANDARD', 'ICC_STANDARD') + return self.name in ('GCC_STANDARD', 'CLANG_STANDARD', 'ICC_STANDARD', 'PGI_STANDARD') @property def is_osx_compiler(self): - return self.name in ('GCC_OSX', 'CLANG_OSX', 'ICC_OSX') + return self.name in ('GCC_OSX', 'CLANG_OSX', 'ICC_OSX', 'PGI_OSX') @property def is_windows_compiler(self): - return self.name in ('GCC_MINGW', 'GCC_CYGWIN', 'CLANG_MINGW', 'ICC_WIN', 'ARM_WIN', 'CCRX_WIN') + return self.name in ('GCC_MINGW', 'GCC_CYGWIN', 'CLANG_MINGW', 'ICC_WIN', 'ARM_WIN', 'CCRX_WIN', 'PGI_WIN') def get_macos_dylib_install_name(prefix, shlib_name, suffix, soversion): @@ -1506,7 +1508,7 @@ def gnulike_default_include_dirs(compiler, lang): break else: paths.append(line[1:]) - if len(paths) == 0: + if not paths: mlog.warning('No include directory found parsing "{cmd}" output'.format(cmd=" ".join(cmd))) return paths @@ -1690,7 +1692,7 @@ class GnuCompiler(GnuLikeCompiler): class PGICompiler: - def __init__(self, compiler_type=None): + def __init__(self, compiler_type): self.id = 'pgi' self.compiler_type = compiler_type @@ -1706,6 +1708,11 @@ class PGICompiler: def get_no_warn_args(self) -> List[str]: return ['-silent'] + def get_pic_args(self) -> List[str]: + if self.compiler_type.is_osx_compiler or self.compiler_type.is_windows_compiler: + return [] # PGI -fPIC is Linux only. + return ['-fPIC'] + def openmp_flags(self) -> List[str]: return ['-mp'] diff --git a/mesonbuild/compilers/cpp.py b/mesonbuild/compilers/cpp.py index 87e6ffc..7c2253d 100644 --- a/mesonbuild/compilers/cpp.py +++ b/mesonbuild/compilers/cpp.py @@ -19,7 +19,7 @@ from .. import coredata from .. import mlog from ..mesonlib import MesonException, version_compare -from .c import CCompiler, VisualStudioCCompiler, ClangClCCompiler +from .c import CCompiler, VisualStudioCCompiler, ClangClCCompiler, IntelClCCompiler from .compilers import ( gnu_winlibs, msvc_winlibs, @@ -238,9 +238,9 @@ class GnuCPPCompiler(GnuCompiler, CPPCompiler): class PGICPPCompiler(PGICompiler, CPPCompiler): - def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwargs): + def __init__(self, exelist, version, compiler_type, is_cross, exe_wrapper=None, **kwargs): CPPCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwargs) - PGICompiler.__init__(self, CompilerType.PGI_STANDARD) + PGICompiler.__init__(self, compiler_type) class ElbrusCPPCompiler(GnuCPPCompiler, ElbrusCompiler): @@ -406,6 +406,13 @@ class ClangClCPPCompiler(VisualStudioCPPCompiler, ClangClCCompiler): VisualStudioCPPCompiler.__init__(self, exelist, version, is_cross, exe_wrap, target) self.id = 'clang-cl' + +class IntelClCPPCompiler(VisualStudioCPPCompiler, IntelClCCompiler): + def __init__(self, exelist, version, is_cross, exe_wrap, target): + VisualStudioCPPCompiler.__init__(self, exelist, version, is_cross, exe_wrap, target) + self.id = 'intel' + + class ArmCPPCompiler(ArmCompiler, CPPCompiler): def __init__(self, exelist, version, compiler_type, is_cross, exe_wrap=None, **kwargs): CPPCompiler.__init__(self, exelist, version, is_cross, exe_wrap, **kwargs) diff --git a/mesonbuild/compilers/fortran.py b/mesonbuild/compilers/fortran.py index 0f7c38e..dd54fd0 100644 --- a/mesonbuild/compilers/fortran.py +++ b/mesonbuild/compilers/fortran.py @@ -423,9 +423,9 @@ class PathScaleFortranCompiler(FortranCompiler): class PGIFortranCompiler(PGICompiler, FortranCompiler): - def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwags): + def __init__(self, exelist, version, compiler_type, is_cross, exe_wrapper=None, **kwags): FortranCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwags) - PGICompiler.__init__(self, CompilerType.PGI_STANDARD) + PGICompiler.__init__(self, compiler_type) class FlangFortranCompiler(ClangCompiler, FortranCompiler): diff --git a/mesonbuild/dependencies/boost.py b/mesonbuild/dependencies/boost.py index 62a652a..381824c 100644 --- a/mesonbuild/dependencies/boost.py +++ b/mesonbuild/dependencies/boost.py @@ -209,7 +209,7 @@ class BoostDependency(ExternalDependency): for root in self.boost_roots: globtext = os.path.join(root, 'include', 'boost-*') incdirs = glob.glob(globtext) - if len(incdirs) > 0: + if incdirs: return incdirs[0] incboostdir = os.path.join(root, 'include', 'boost') if os.path.isdir(incboostdir): @@ -427,7 +427,7 @@ class BoostDependency(ExternalDependency): for entry in globber2_matches: fname = os.path.basename(entry) self.lib_modules[self.modname_from_filename(fname)] = [fname] - if len(globber2_matches) == 0: + if not globber2_matches: # FIXME - why are we only looking for *.lib? Mingw provides *.dll.a and *.a for entry in glob.glob(os.path.join(self.libdir, globber1 + '.lib')): if self.static: 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/environment.py b/mesonbuild/environment.py index c296573..bf2cce9 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -12,14 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -import configparser, os, platform, re, sys, shlex, shutil, subprocess -import typing +import os, platform, re, sys, shlex, shutil, subprocess from . import coredata from .linkers import ArLinker, ArmarLinker, VisualStudioLinker, DLinker, CcrxLinker from . import mesonlib from .mesonlib import ( - MesonException, EnvironmentException, MachineChoice, PerMachine, Popen_safe, + MesonException, EnvironmentException, MachineChoice, Popen_safe, ) from . import mlog @@ -38,7 +37,6 @@ from .compilers import ( is_source, ) from .compilers import ( - Compiler, ArmCCompiler, ArmCPPCompiler, ArmclangCCompiler, @@ -62,6 +60,8 @@ from .compilers import ( IntelCCompiler, IntelCPPCompiler, IntelFortranCompiler, + IntelClCCompiler, + IntelClCPPCompiler, JavaCompiler, MonoCompiler, CudaCompiler, @@ -83,7 +83,7 @@ from .compilers import ( build_filename = 'meson.build' -def detect_gcovr(version='3.1', log=False): +def detect_gcovr(min_version='3.3', new_rootdir_version='4.2', log=False): gcovr_exe = 'gcovr' try: p, found = Popen_safe([gcovr_exe, '--version'])[0:2] @@ -91,10 +91,10 @@ def detect_gcovr(version='3.1', log=False): # Doesn't exist in PATH or isn't executable return None, None found = search_version(found) - if p.returncode == 0: + if p.returncode == 0 and mesonlib.version_compare(found, '>=' + min_version): if log: mlog.log('Found gcovr-{} at {}'.format(found, shlex.quote(shutil.which(gcovr_exe)))) - return gcovr_exe, mesonlib.version_compare(found, '>=' + version) + return gcovr_exe, mesonlib.version_compare(found, '>=' + new_rootdir_version) return None, None def find_coverage_tools(): @@ -182,7 +182,7 @@ def detect_windows_arch(compilers): # 32-bit and pretend like we're running under WOW64. Else, return the # actual Windows architecture that we deduced above. for compiler in compilers.values(): - if compiler.id == 'msvc' and compiler.target == 'x86': + if compiler.id == 'msvc' and (compiler.target == 'x86' or compiler.target == '80x86'): return 'x86' if compiler.id == 'clang-cl' and compiler.target == 'x86': return 'x86' @@ -455,11 +455,13 @@ class Environment: # List of potential compilers. if mesonlib.is_windows(): - self.default_c = ['cl', 'cc', 'gcc', 'clang', 'clang-cl', 'pgcc'] - self.default_cpp = ['cl', 'c++', 'g++', 'clang++', 'clang-cl', 'pgc++'] + # Intel C and C++ compiler is icl on Windows, but icc and icpc elsewhere. + self.default_c = ['cl', 'cc', 'gcc', 'clang', 'clang-cl', 'pgcc', 'icl'] + # There is currently no pgc++ for Windows, only for Mac and Linux. + self.default_cpp = ['cl', 'c++', 'g++', 'clang++', 'clang-cl', 'icl'] else: - self.default_c = ['cc', 'gcc', 'clang', 'pgcc'] - self.default_cpp = ['c++', 'g++', 'clang++', 'pgc++'] + self.default_c = ['cc', 'gcc', 'clang', 'pgcc', 'icc'] + self.default_cpp = ['c++', 'g++', 'clang++', 'pgc++', 'icpc'] if mesonlib.is_windows(): self.default_cs = ['csc', 'mcs'] else: @@ -467,7 +469,7 @@ class Environment: self.default_objc = ['cc'] self.default_objcpp = ['c++'] self.default_d = ['ldc2', 'ldc', 'gdc', 'dmd'] - self.default_fortran = ['gfortran', 'g95', 'f95', 'f90', 'f77', 'ifort', 'pgfortran'] + self.default_fortran = ['gfortran', 'flang', 'pgfortran', 'ifort', 'g95'] self.default_java = ['javac'] self.default_cuda = ['nvcc'] self.default_rust = ['rustc'] @@ -676,6 +678,7 @@ class Environment: arg = '-v' else: arg = '--version' + try: p, out, err = Popen_safe(compiler + [arg]) except OSError as e: @@ -684,6 +687,11 @@ class Environment: if 'ccrx' in compiler[0]: out = err + if 'icl' in compiler[0]: + # https://software.intel.com/en-us/cpp-compiler-developer-guide-and-reference-alphabetical-list-of-compiler-options + # https://software.intel.com/en-us/fortran-compiler-developer-guide-and-reference-logo + # most consistent way for ICL is to just let compiler error and tell version + out = err full_version = out.split('\n', 1)[0] version = search_version(out) @@ -769,19 +777,29 @@ class Environment: target = 'x86' cls = VisualStudioCCompiler if lang == 'c' else VisualStudioCPPCompiler return cls(compiler, version, is_cross, exe_wrap, target) + if 'PGI Compilers' in out: + if mesonlib.for_darwin(is_cross, self): + compiler_type = CompilerType.PGI_OSX + elif mesonlib.for_windows(is_cross, self): + compiler_type = CompilerType.PGI_WIN + else: + compiler_type = CompilerType.PGI_STANDARD cls = PGICCompiler if lang == 'c' else PGICPPCompiler - return cls(ccache + compiler, version, is_cross, exe_wrap) + return cls(ccache + compiler, version, compiler_type, is_cross, exe_wrap) if '(ICC)' in out: if mesonlib.for_darwin(want_cross, self): compiler_type = CompilerType.ICC_OSX elif mesonlib.for_windows(want_cross, self): - # TODO: fix ICC on Windows - compiler_type = CompilerType.ICC_WIN + raise EnvironmentException('At the time of authoring, there was no ICC for Windows') else: compiler_type = CompilerType.ICC_STANDARD cls = IntelCCompiler if lang == 'c' else IntelCPPCompiler return cls(ccache + compiler, version, compiler_type, is_cross, exe_wrap, full_version=full_version) + if out.startswith('Intel(R) C++') and mesonlib.for_windows(want_cross, self): + cls = IntelClCCompiler if lang == 'c' else IntelClCPPCompiler + target = 'x64' if 'Intel(R) 64 Compiler' in out else 'x86' + return cls(compiler, version, is_cross, exe_wrap, target) if 'ARM' in out: compiler_type = CompilerType.ARM_WIN cls = ArmCCompiler if lang == 'c' else ArmCPPCompiler @@ -846,6 +864,13 @@ class Environment: popen_exceptions[' '.join(compiler + [arg])] = e continue + if mesonlib.for_windows(is_cross, self): + if 'ifort' in compiler[0]: + # https://software.intel.com/en-us/cpp-compiler-developer-guide-and-reference-alphabetical-list-of-compiler-options + # https://software.intel.com/en-us/fortran-compiler-developer-guide-and-reference-logo + # most consistent way for ICL is to just let compiler error and tell version + out = err + version = search_version(out) full_version = out.split('\n', 1)[0] @@ -876,14 +901,20 @@ class Environment: version = search_version(err) return SunFortranCompiler(compiler, version, is_cross, exe_wrap, full_version=full_version) - if 'ifort (IFORT)' in out: + if 'ifort (IFORT)' in out or out.startswith('Intel(R) Visual Fortran'): return IntelFortranCompiler(compiler, version, is_cross, exe_wrap, full_version=full_version) if 'PathScale EKOPath(tm)' in err: return PathScaleFortranCompiler(compiler, version, is_cross, exe_wrap, full_version=full_version) if 'PGI Compilers' in out: - return PGIFortranCompiler(compiler, version, is_cross, exe_wrap, full_version=full_version) + if mesonlib.for_darwin(is_cross, self): + compiler_type = CompilerType.PGI_OSX + elif mesonlib.for_windows(is_cross, self): + compiler_type = CompilerType.PGI_WIN + else: + compiler_type = CompilerType.PGI_STANDARD + return PGIFortranCompiler(compiler, version, compiler_type, is_cross, exe_wrap, full_version=full_version) if 'flang' in out or 'clang' in out: return FlangFortranCompiler(compiler, version, is_cross, exe_wrap, full_version=full_version) @@ -1039,7 +1070,8 @@ class Environment: # up to date language version at time (2016). if exelist is not None: if os.path.basename(exelist[-1]).startswith(('ldmd', 'gdmd')): - raise EnvironmentException('Meson doesn\'t support %s as it\'s only a DMD frontend for another compiler. Please provide a valid value for DC or unset it so that Meson can resolve the compiler by itself.' % exelist[-1]) + raise EnvironmentException('Meson does not support {} as it is only a DMD frontend for another compiler.' + 'Please provide a valid value for DC or unset it so that Meson can resolve the compiler by itself.'.format(exelist[-1])) else: for d in self.default_d: if shutil.which(d): diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index b107a25..8ca7758 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -4076,24 +4076,6 @@ This will become a hard error in the future.''', location=self.current_node) if not os.path.isfile(fname): raise InterpreterException('Tried to add non-existing source file %s.' % s) - def format_string(self, templ, args): - if isinstance(args, mparser.ArgumentNode): - args = args.arguments - arg_strings = [] - for arg in args: - arg = self.evaluate_statement(arg) - if isinstance(arg, bool): # Python boolean is upper case. - arg = str(arg).lower() - arg_strings.append(str(arg)) - - def arg_replace(match): - idx = int(match.group(1)) - if idx >= len(arg_strings): - raise InterpreterException('Format placeholder @{}@ out of range.'.format(idx)) - return arg_strings[idx] - - return re.sub(r'@(\d+)@', arg_replace, templ) - # Only permit object extraction from the same subproject def validate_extraction(self, buildtarget): if not self.subdir.startswith(self.subproject_dir): diff --git a/mesonbuild/interpreterbase.py b/mesonbuild/interpreterbase.py index 650d1e0..c148cbd 100644 --- a/mesonbuild/interpreterbase.py +++ b/mesonbuild/interpreterbase.py @@ -920,6 +920,24 @@ The result of this is undefined and will become a hard error in a future Meson r return mesonlib.version_compare(obj, cmpr) raise InterpreterException('Unknown method "%s" for a string.' % method_name) + def format_string(self, templ, args): + if isinstance(args, mparser.ArgumentNode): + args = args.arguments + arg_strings = [] + for arg in args: + arg = self.evaluate_statement(arg) + if isinstance(arg, bool): # Python boolean is upper case. + arg = str(arg).lower() + arg_strings.append(str(arg)) + + def arg_replace(match): + idx = int(match.group(1)) + if idx >= len(arg_strings): + raise InterpreterException('Format placeholder @{}@ out of range.'.format(idx)) + return arg_strings[idx] + + return re.sub(r'@(\d+)@', arg_replace, templ) + def unknown_function_called(self, func_name): raise InvalidCode('Unknown function "%s".' % func_name) diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index 25e15e4..7219946 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -15,7 +15,6 @@ """A library of random helper functionality.""" from pathlib import Path from typing import List -import functools import sys import stat import time @@ -23,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} @@ -320,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', @@ -506,7 +511,6 @@ def detect_vcs(source_dir): return None # a helper class which implements the same version ordering as RPM -@functools.total_ordering class Version: def __init__(self, s): self._s = s @@ -515,8 +519,8 @@ class Version: sequences = re.finditer(r'(\d+|[a-zA-Z]+|[^a-zA-Z\d]+)', s) # non-alphanumeric separators are discarded sequences = [m for m in sequences if not re.match(r'[^a-zA-Z\d]+', m.group(1))] - # numeric sequences have leading zeroes discarded - sequences = [re.sub(r'^0+(\d)', r'\1', m.group(1), 1) for m in sequences] + # numeric sequences are converted from strings to ints + sequences = [int(m.group(1)) if m.group(1).isdigit() else m.group(1) for m in sequences] self._v = sequences @@ -527,38 +531,50 @@ class Version: return '<Version: {}>'.format(self._s) def __lt__(self, other): - return self.__cmp__(other) == -1 + if isinstance(other, Version): + return self.__cmp(other, operator.lt) + return NotImplemented + + def __gt__(self, other): + if isinstance(other, Version): + return self.__cmp(other, operator.gt) + return NotImplemented + + def __le__(self, other): + if isinstance(other, Version): + return self.__cmp(other, operator.le) + return NotImplemented + + def __ge__(self, other): + if isinstance(other, Version): + return self.__cmp(other, operator.ge) + return NotImplemented def __eq__(self, other): - return self.__cmp__(other) == 0 + if isinstance(other, Version): + return self._v == other._v + return NotImplemented - def __cmp__(self, other): - def cmp(a, b): - return (a > b) - (a < b) + def __ne__(self, other): + if isinstance(other, Version): + return self._v != other._v + return NotImplemented + def __cmp(self, other, comparator): # compare each sequence in order - for i in range(0, min(len(self._v), len(other._v))): + for ours, theirs in zip(self._v, other._v): # sort a non-digit sequence before a digit sequence - if self._v[i].isdigit() != other._v[i].isdigit(): - return 1 if self._v[i].isdigit() else -1 - - # compare as numbers - if self._v[i].isdigit(): - # because leading zeros have already been removed, if one number - # has more digits, it is greater - c = cmp(len(self._v[i]), len(other._v[i])) - if c != 0: - return c - # fallthrough - - # compare lexicographically - c = cmp(self._v[i], other._v[i]) - if c != 0: - return c + ours_is_int = isinstance(ours, int) + theirs_is_int = isinstance(theirs, int) + if ours_is_int != theirs_is_int: + return comparator(ours_is_int, theirs_is_int) + + if ours != theirs: + return comparator(ours, theirs) # if equal length, all components have matched, so equal # otherwise, the version with a suffix remaining is greater - return cmp(len(self._v), len(other._v)) + return comparator(len(self._v), len(other._v)) def _version_extract_cmpop(vstr2): if vstr2.startswith('>='): @@ -906,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: @@ -921,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): @@ -1192,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/mesonmain.py b/mesonbuild/mesonmain.py index f80dfdd..4326c20 100644 --- a/mesonbuild/mesonmain.py +++ b/mesonbuild/mesonmain.py @@ -110,7 +110,7 @@ class CommandLineParser: # If first arg is not a known command, assume user wants to run the setup # command. known_commands = list(self.commands.keys()) + ['-h', '--help'] - if len(args) == 0 or args[0] not in known_commands: + if not args or args[0] not in known_commands: args = ['setup'] + args # Hidden commands have their own parser instead of using the global one diff --git a/mesonbuild/minit.py b/mesonbuild/minit.py index 394fe40..4ae0ae3 100644 --- a/mesonbuild/minit.py +++ b/mesonbuild/minit.py @@ -442,7 +442,7 @@ def add_arguments(parser): parser.add_argument('--version', default='0.1') def run(options): - if len(glob('*')) == 0: + if not glob('*'): autodetect_options(options, sample=True) if not options.language: print('Defaulting to generating a C language project.') diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py index 32931b6..a4a6978 100644 --- a/mesonbuild/mintro.py +++ b/mesonbuild/mintro.py @@ -343,7 +343,7 @@ def list_projinfo_from_source(sourcedir: str, intr: IntrospectionInterpreter): return intr.project_data def print_results(options, results, indent): - if len(results) == 0 and not options.force_dict: + if not results and not options.force_dict: print('No command specified') return 1 elif len(results) == 1 and not options.force_dict: @@ -487,7 +487,7 @@ def write_meson_info_file(builddata: build.Build, errors: list, build_files_upda 'build_files_updated': build_files_updated, } - if len(errors) > 0: + if errors: info_data['error'] = True info_data['error_list'] = [x if isinstance(x, str) else str(x) for x in errors] else: 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: diff --git a/mesonbuild/modules/cmake.py b/mesonbuild/modules/cmake.py index d98213d..6af4adb 100644 --- a/mesonbuild/modules/cmake.py +++ b/mesonbuild/modules/cmake.py @@ -72,20 +72,12 @@ class CmakeModule(ExtensionModule): mlog.log('error retrieving cmake informations: returnCode={0} stdout={1} stderr={2}'.format(p.returncode, stdout, stderr)) return False - match = re.search('\n_INCLUDED_FILE \\"([^"]+)"\n', stdout.strip()) + match = re.search('\nCMAKE_ROOT \\"([^"]+)"\n', stdout.strip()) if not match: mlog.log('unable to determine cmake root') return False - # compilerpath is something like '/usr/share/cmake-3.5/Modules/Platform/Linux-GNU-CXX.cmake' - #Â or 'C:/Program Files (x86)/CMake 2.8/share/cmake-2.8/Modules/Platform/Windows-MSVC-CXX.cmake' under windows - compilerpath = match.group(1) - pos = compilerpath.find('/Modules/Platform/') - if pos < 0: - mlog.log('unknown _INCLUDED_FILE path scheme') - return False - - cmakePath = pathlib.PurePath(compilerpath[0:pos]) + cmakePath = pathlib.PurePath(match.group(1)) self.cmake_root = os.path.join(*cmakePath.parts) self.cmake_detected = True return True @@ -158,7 +150,7 @@ class CmakeModule(ExtensionModule): @permittedKwargs({'input', 'name', 'install_dir', 'configuration'}) def configure_package_config_file(self, interpreter, state, args, kwargs): - if len(args) > 0: + if args: raise mesonlib.MesonException('configure_package_config_file takes only keyword arguments.') if 'input' not in kwargs: diff --git a/mesonbuild/modules/hotdoc.py b/mesonbuild/modules/hotdoc.py index 5064803..02e07a9 100644 --- a/mesonbuild/modules/hotdoc.py +++ b/mesonbuild/modules/hotdoc.py @@ -76,7 +76,8 @@ class HotdocTargetBuilder: return if isinstance(value, bool): - self.cmd.append(option) + if value: + self.cmd.append(option) elif isinstance(value, list): # Do not do anything on empty lists if value: @@ -308,6 +309,9 @@ class HotdocTargetBuilder: for path in self.include_paths.keys(): self.cmd.extend(['--include-path', path]) + + if self.state.environment.coredata.get_builtin_option('werror'): + self.cmd.append('--fatal-warning') self.generate_hotdoc_config() target_cmd = self.build_command + ["--internal", "hotdoc"] + \ diff --git a/mesonbuild/modules/qt.py b/mesonbuild/modules/qt.py index 0b252ac..11289c4 100644 --- a/mesonbuild/modules/qt.py +++ b/mesonbuild/modules/qt.py @@ -129,13 +129,13 @@ class QtBaseModule(ExtensionModule): self._detect_tools(state.environment, method) err_msg = "{0} sources specified and couldn't find {1}, " \ "please check your qt{2} installation" - if len(moc_headers) + len(moc_sources) > 0 and not self.moc.found(): + if (moc_headers or moc_sources) and not self.moc.found(): raise MesonException(err_msg.format('MOC', 'moc-qt{}'.format(self.qt_version), self.qt_version)) - if len(rcc_files) > 0: + if rcc_files: if not self.rcc.found(): raise MesonException(err_msg.format('RCC', 'rcc-qt{}'.format(self.qt_version), self.qt_version)) # custom output name set? -> one output file, multiple otherwise - if len(args) > 0: + if args: qrc_deps = [] for i in rcc_files: qrc_deps += self.parse_qrc(state, i) @@ -160,7 +160,7 @@ class QtBaseModule(ExtensionModule): 'depend_files': qrc_deps} res_target = build.CustomTarget(name, state.subdir, state.subproject, rcc_kwargs) sources.append(res_target) - if len(ui_files) > 0: + if ui_files: if not self.uic.found(): raise MesonException(err_msg.format('UIC', 'uic-qt{}'.format(self.qt_version), self.qt_version)) arguments = uic_extra_arguments + ['-o', '@OUTPUT@', '@INPUT@'] @@ -183,14 +183,14 @@ class QtBaseModule(ExtensionModule): 'either an external dependency (returned by find_library() or ' 'dependency()) or an internal dependency (returned by ' 'declare_dependency()).'.format(type(dep).__name__)) - if len(moc_headers) > 0: + if moc_headers: arguments = moc_extra_arguments + inc + compile_args + ['@INPUT@', '-o', '@OUTPUT@'] moc_kwargs = {'output': 'moc_@BASENAME@.cpp', 'arguments': arguments} moc_gen = build.Generator([self.moc], moc_kwargs) moc_output = moc_gen.process_files('Qt{} moc header'.format(self.qt_version), moc_headers, state) sources.append(moc_output) - if len(moc_sources) > 0: + if moc_sources: arguments = moc_extra_arguments + inc + compile_args + ['@INPUT@', '-o', '@OUTPUT@'] moc_kwargs = {'output': '@BASENAME@.moc', 'arguments': arguments} diff --git a/mesonbuild/modules/rpm.py b/mesonbuild/modules/rpm.py index 9774286..7c1cefb 100644 --- a/mesonbuild/modules/rpm.py +++ b/mesonbuild/modules/rpm.py @@ -58,7 +58,7 @@ class RPMModule(ExtensionModule): elif isinstance(target, TypelibTarget) and target.should_install(): files.add('%%{_libdir}/girepository-1.0/%s' % target.get_filename()[0]) for header in coredata.headers: - if len(header.get_install_subdir()) > 0: + if header.get_install_subdir(): files_devel.add('%%{_includedir}/%s/' % header.get_install_subdir()) else: for hdr_src in header.get_sources(): @@ -66,7 +66,7 @@ class RPMModule(ExtensionModule): for man in coredata.man: for man_file in man.get_sources(): files.add('%%{_mandir}/man%u/%s.*' % (int(man_file.split('.')[-1]), man_file)) - if len(files_devel) > 0: + if files_devel: devel_subpkg = True filename = os.path.join(coredata.environment.get_build_dir(), @@ -122,7 +122,7 @@ class RPMModule(ExtensionModule): fn.write('\n') fn.write('%install\n') fn.write('%meson_install\n') - if len(to_delete) > 0: + if to_delete: fn.write('rm -vf %s\n' % ' '.join(to_delete)) fn.write('\n') fn.write('%check\n') diff --git a/mesonbuild/modules/unstable_icestorm.py b/mesonbuild/modules/unstable_icestorm.py index 051dc5f..268c394 100644 --- a/mesonbuild/modules/unstable_icestorm.py +++ b/mesonbuild/modules/unstable_icestorm.py @@ -36,7 +36,7 @@ class IceStormModule(ExtensionModule): def project(self, interpreter, state, args, kwargs): if not self.yosys_bin: self.detect_binaries(interpreter) - if not len(args): + if not args: raise mesonlib.MesonException('Project requires at least one argument, which is the project name.') proj_name = args[0] arg_sources = args[1:] diff --git a/mesonbuild/modules/unstable_simd.py b/mesonbuild/modules/unstable_simd.py index b64242a..18a1099 100644 --- a/mesonbuild/modules/unstable_simd.py +++ b/mesonbuild/modules/unstable_simd.py @@ -65,7 +65,7 @@ class SimdModule(ExtensionModule): if args is None: mlog.log('Compiler supports %s:' % iset, mlog.red('NO')) continue - if len(args) > 0: + if args: if not compiler.has_multi_arguments(args, state.environment): mlog.log('Compiler supports %s:' % iset, mlog.red('NO')) continue diff --git a/mesonbuild/rewriter.py b/mesonbuild/rewriter.py index 6205eae..1558cdd 100644 --- a/mesonbuild/rewriter.py +++ b/mesonbuild/rewriter.py @@ -874,11 +874,16 @@ target_operation_map = { } def list_to_dict(in_list: List[str]) -> Dict[str, str]: - if len(in_list) % 2 != 0: - raise TypeError('An even ammount of arguments are required') result = {} - for i in range(0, len(in_list), 2): - result[in_list[i]] = in_list[i + 1] + it = iter(in_list) + try: + for i in it: + # calling next(it) is not a mistake, we're taking the next element from + # the iterator, avoiding te need to preprocess it into a sequence of + # key value pairs. + result[i] = next(it) + except StopIteration: + raise TypeError('in_list parameter of list_to_dict must have an even length.') return result def generate_target(options) -> List[dict]: diff --git a/mesonbuild/scripts/coverage.py b/mesonbuild/scripts/coverage.py index 0e203f9..4bd41fe 100644 --- a/mesonbuild/scripts/coverage.py +++ b/mesonbuild/scripts/coverage.py @@ -22,35 +22,33 @@ def coverage(outputs, source_root, subproject_root, build_root, log_dir): (gcovr_exe, gcovr_new_rootdir, lcov_exe, genhtml_exe) = environment.find_coverage_tools() - # gcovr >= 3.1 interprets rootdir differently + # gcovr >= 4.2 requires a different syntax for out of source builds if gcovr_new_rootdir: - gcovr_rootdir = build_root + gcovr_base_cmd = [gcovr_exe, '-r', source_root, build_root] else: - gcovr_rootdir = source_root + gcovr_base_cmd = [gcovr_exe, '-r', build_root] if not outputs or 'xml' in outputs: if gcovr_exe: - subprocess.check_call([gcovr_exe, - '-x', - '-r', gcovr_rootdir, + subprocess.check_call(gcovr_base_cmd + + ['-x', '-e', subproject_root, '-o', os.path.join(log_dir, 'coverage.xml'), ]) outfiles.append(('Xml', pathlib.Path(log_dir, 'coverage.xml'))) elif outputs: - print('gcovr needed to generate Xml coverage report') + print('gcovr >= 3.3 needed to generate Xml coverage report') exitcode = 1 if not outputs or 'text' in outputs: if gcovr_exe: - subprocess.check_call([gcovr_exe, - '-r', gcovr_rootdir, - '-e', subproject_root, + subprocess.check_call(gcovr_base_cmd + + ['-e', subproject_root, '-o', os.path.join(log_dir, 'coverage.txt'), ]) outfiles.append(('Text', pathlib.Path(log_dir, 'coverage.txt'))) elif outputs: - print('gcovr needed to generate text coverage report') + print('gcovr >= 3.3 needed to generate text coverage report') exitcode = 1 if not outputs or 'html' in outputs: @@ -101,21 +99,20 @@ def coverage(outputs, source_root, subproject_root, build_root, log_dir): '--branch-coverage', covinfo]) outfiles.append(('Html', pathlib.Path(htmloutdir, 'index.html'))) - elif gcovr_exe and gcovr_new_rootdir: + elif gcovr_exe: htmloutdir = os.path.join(log_dir, 'coveragereport') if not os.path.isdir(htmloutdir): os.mkdir(htmloutdir) - subprocess.check_call([gcovr_exe, - '--html', + subprocess.check_call(gcovr_base_cmd + + ['--html', '--html-details', '--print-summary', - '-r', build_root, '-e', subproject_root, '-o', os.path.join(htmloutdir, 'index.html'), ]) outfiles.append(('Html', pathlib.Path(htmloutdir, 'index.html'))) elif outputs: - print('lcov/genhtml or gcovr >= 3.2 needed to generate Html coverage report') + print('lcov/genhtml or gcovr >= 3.3 needed to generate Html coverage report') exitcode = 1 if not outputs and not outfiles: diff --git a/mesonbuild/scripts/dist.py b/mesonbuild/scripts/dist.py index 309a032..47fde23 100644 --- a/mesonbuild/scripts/dist.py +++ b/mesonbuild/scripts/dist.py @@ -133,7 +133,7 @@ def create_dist_hg(dist_name, src_root, bld_root, dist_sub, dist_scripts): tarname = os.path.join(dist_sub, dist_name + '.tar') xzname = tarname + '.xz' subprocess.check_call(['hg', 'archive', '-R', src_root, '-S', '-t', 'tar', tarname]) - if len(dist_scripts) > 0: + if dist_scripts: mlog.warning('dist scripts are not supported in Mercurial projects') with lzma.open(xzname, 'wb') as xf, open(tarname, 'rb') as tf: shutil.copyfileobj(tf, xf) diff --git a/mesonbuild/scripts/gtkdochelper.py b/mesonbuild/scripts/gtkdochelper.py index 01ced5b..11d31b6 100644 --- a/mesonbuild/scripts/gtkdochelper.py +++ b/mesonbuild/scripts/gtkdochelper.py @@ -175,7 +175,7 @@ def build_gtkdoc(source_root, build_root, doc_subdir, src_subdirs, mkdb_cmd.append('--name-space=' + namespace) if modeflag: mkdb_cmd.append(modeflag) - if len(main_file) > 0: + if main_file: # Yes, this is the flag even if the file is in xml. mkdb_cmd.append('--main-sgml-file=' + main_file) # Add user-specified arguments @@ -187,7 +187,7 @@ def build_gtkdoc(source_root, build_root, doc_subdir, src_subdirs, '--path=' + ':'.join((doc_src, abs_out)), module, ] + html_args - if len(main_file) > 0: + if main_file: mkhtml_cmd.append('../' + main_file) else: mkhtml_cmd.append('%s-docs.xml' % module) @@ -212,23 +212,23 @@ def install_gtkdoc(build_root, doc_subdir, install_prefix, datadir, module): def run(args): options = parser.parse_args(args) - if len(options.htmlargs) > 0: + if options.htmlargs: htmlargs = options.htmlargs.split('@@') else: htmlargs = [] - if len(options.scanargs) > 0: + if options.scanargs: scanargs = options.scanargs.split('@@') else: scanargs = [] - if len(options.scanobjsargs) > 0: + if options.scanobjsargs: scanobjsargs = options.scanobjsargs.split('@@') else: scanobjsargs = [] - if len(options.fixxrefargs) > 0: + if options.fixxrefargs: fixxrefargs = options.fixxrefargs.split('@@') else: fixxrefargs = [] - if len(options.mkdbargs) > 0: + if options.mkdbargs: mkdbargs = options.mkdbargs.split('@@') else: mkdbargs = [] diff --git a/mesonbuild/scripts/meson_exe.py b/mesonbuild/scripts/meson_exe.py index 23c7334..a862ec5 100644 --- a/mesonbuild/scripts/meson_exe.py +++ b/mesonbuild/scripts/meson_exe.py @@ -60,7 +60,7 @@ def run_exe(exe): cmd = exe.fname child_env = os.environ.copy() child_env.update(exe.env) - if len(exe.extra_paths) > 0: + if exe.extra_paths: child_env['PATH'] = (os.pathsep.join(exe.extra_paths + ['']) + child_env['PATH']) if exe.exe_runner and mesonlib.substring_is_in_list('wine', exe.exe_runner.get_command()): diff --git a/mesonbuild/scripts/symbolextractor.py b/mesonbuild/scripts/symbolextractor.py index 95ea0ec..410cb33 100644 --- a/mesonbuild/scripts/symbolextractor.py +++ b/mesonbuild/scripts/symbolextractor.py @@ -69,7 +69,7 @@ def linux_syms(libfilename, outfilename): if pnm.returncode != 0: raise RuntimeError('nm does not work.') for line in output.split('\n'): - if len(line) == 0: + if not line: continue line_split = line.split() entry = line_split[0:2] @@ -91,7 +91,7 @@ def osx_syms(libfilename, outfilename): pnm, output = Popen_safe(['nm', '-g', '-P', libfilename])[0:2] if pnm.returncode != 0: raise RuntimeError('nm does not work.') - result += [' '.join(x.split()[0:2]) for x in output.split('\n') if len(x) > 0 and not x.endswith('U')] + result += [' '.join(x.split()[0:2]) for x in output.split('\n') if x and not x.endswith('U')] write_if_changed('\n'.join(result) + '\n', outfilename) def gen_symbols(libfilename, outfilename, cross_host): diff --git a/run_unittests.py b/run_unittests.py index d37e805..2457a50 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -28,6 +28,7 @@ import platform import pickle import functools import io +import operator from itertools import chain from unittest import mock from configparser import ConfigParser @@ -856,107 +857,118 @@ class InternalTests(unittest.TestCase): ]: self.assertEqual(comparefunc(a, b)[0], result) - for (a, b, result) in [ + for (a, b, op) in [ # examples from https://fedoraproject.org/wiki/Archive:Tools/RPM/VersionComparison - ("1.0010", "1.9", 1), - ("1.05", "1.5", 0), - ("1.0", "1", 1), - ("2.50", "2.5", 1), - ("fc4", "fc.4", 0), - ("FC5", "fc4", -1), - ("2a", "2.0", -1), - ("1.0", "1.fc4", 1), - ("3.0.0_fc", "3.0.0.fc", 0), + ("1.0010", "1.9", operator.gt), + ("1.05", "1.5", operator.eq), + ("1.0", "1", operator.gt), + ("2.50", "2.5", operator.gt), + ("fc4", "fc.4", operator.eq), + ("FC5", "fc4", operator.lt), + ("2a", "2.0", operator.lt), + ("1.0", "1.fc4", operator.gt), + ("3.0.0_fc", "3.0.0.fc", operator.eq), # from RPM tests - ("1.0", "1.0", 0), - ("1.0", "2.0", -1), - ("2.0", "1.0", 1), - ("2.0.1", "2.0.1", 0), - ("2.0", "2.0.1", -1), - ("2.0.1", "2.0", 1), - ("2.0.1a", "2.0.1a", 0), - ("2.0.1a", "2.0.1", 1), - ("2.0.1", "2.0.1a", -1), - ("5.5p1", "5.5p1", 0), - ("5.5p1", "5.5p2", -1), - ("5.5p2", "5.5p1", 1), - ("5.5p10", "5.5p10", 0), - ("5.5p1", "5.5p10", -1), - ("5.5p10", "5.5p1", 1), - ("10xyz", "10.1xyz", -1), - ("10.1xyz", "10xyz", 1), - ("xyz10", "xyz10", 0), - ("xyz10", "xyz10.1", -1), - ("xyz10.1", "xyz10", 1), - ("xyz.4", "xyz.4", 0), - ("xyz.4", "8", -1), - ("8", "xyz.4", 1), - ("xyz.4", "2", -1), - ("2", "xyz.4", 1), - ("5.5p2", "5.6p1", -1), - ("5.6p1", "5.5p2", 1), - ("5.6p1", "6.5p1", -1), - ("6.5p1", "5.6p1", 1), - ("6.0.rc1", "6.0", 1), - ("6.0", "6.0.rc1", -1), - ("10b2", "10a1", 1), - ("10a2", "10b2", -1), - ("1.0aa", "1.0aa", 0), - ("1.0a", "1.0aa", -1), - ("1.0aa", "1.0a", 1), - ("10.0001", "10.0001", 0), - ("10.0001", "10.1", 0), - ("10.1", "10.0001", 0), - ("10.0001", "10.0039", -1), - ("10.0039", "10.0001", 1), - ("4.999.9", "5.0", -1), - ("5.0", "4.999.9", 1), - ("20101121", "20101121", 0), - ("20101121", "20101122", -1), - ("20101122", "20101121", 1), - ("2_0", "2_0", 0), - ("2.0", "2_0", 0), - ("2_0", "2.0", 0), - ("a", "a", 0), - ("a+", "a+", 0), - ("a+", "a_", 0), - ("a_", "a+", 0), - ("+a", "+a", 0), - ("+a", "_a", 0), - ("_a", "+a", 0), - ("+_", "+_", 0), - ("_+", "+_", 0), - ("_+", "_+", 0), - ("+", "_", 0), - ("_", "+", 0), + ("1.0", "1.0", operator.eq), + ("1.0", "2.0", operator.lt), + ("2.0", "1.0", operator.gt), + ("2.0.1", "2.0.1", operator.eq), + ("2.0", "2.0.1", operator.lt), + ("2.0.1", "2.0", operator.gt), + ("2.0.1a", "2.0.1a", operator.eq), + ("2.0.1a", "2.0.1", operator.gt), + ("2.0.1", "2.0.1a", operator.lt), + ("5.5p1", "5.5p1", operator.eq), + ("5.5p1", "5.5p2", operator.lt), + ("5.5p2", "5.5p1", operator.gt), + ("5.5p10", "5.5p10", operator.eq), + ("5.5p1", "5.5p10", operator.lt), + ("5.5p10", "5.5p1", operator.gt), + ("10xyz", "10.1xyz", operator.lt), + ("10.1xyz", "10xyz", operator.gt), + ("xyz10", "xyz10", operator.eq), + ("xyz10", "xyz10.1", operator.lt), + ("xyz10.1", "xyz10", operator.gt), + ("xyz.4", "xyz.4", operator.eq), + ("xyz.4", "8", operator.lt), + ("8", "xyz.4", operator.gt), + ("xyz.4", "2", operator.lt), + ("2", "xyz.4", operator.gt), + ("5.5p2", "5.6p1", operator.lt), + ("5.6p1", "5.5p2", operator.gt), + ("5.6p1", "6.5p1", operator.lt), + ("6.5p1", "5.6p1", operator.gt), + ("6.0.rc1", "6.0", operator.gt), + ("6.0", "6.0.rc1", operator.lt), + ("10b2", "10a1", operator.gt), + ("10a2", "10b2", operator.lt), + ("1.0aa", "1.0aa", operator.eq), + ("1.0a", "1.0aa", operator.lt), + ("1.0aa", "1.0a", operator.gt), + ("10.0001", "10.0001", operator.eq), + ("10.0001", "10.1", operator.eq), + ("10.1", "10.0001", operator.eq), + ("10.0001", "10.0039", operator.lt), + ("10.0039", "10.0001", operator.gt), + ("4.999.9", "5.0", operator.lt), + ("5.0", "4.999.9", operator.gt), + ("20101121", "20101121", operator.eq), + ("20101121", "20101122", operator.lt), + ("20101122", "20101121", operator.gt), + ("2_0", "2_0", operator.eq), + ("2.0", "2_0", operator.eq), + ("2_0", "2.0", operator.eq), + ("a", "a", operator.eq), + ("a+", "a+", operator.eq), + ("a+", "a_", operator.eq), + ("a_", "a+", operator.eq), + ("+a", "+a", operator.eq), + ("+a", "_a", operator.eq), + ("_a", "+a", operator.eq), + ("+_", "+_", operator.eq), + ("_+", "+_", operator.eq), + ("_+", "_+", operator.eq), + ("+", "_", operator.eq), + ("_", "+", operator.eq), # other tests - ('0.99.beta19', '0.99.beta14', 1), - ("1.0.0", "2.0.0", -1), - (".0.0", "2.0.0", -1), - ("alpha", "beta", -1), - ("1.0", "1.0.0", -1), - ("2.456", "2.1000", -1), - ("2.1000", "3.111", -1), - ("2.001", "2.1", 0), - ("2.34", "2.34", 0), - ("6.1.2", "6.3.8", -1), - ("1.7.3.0", "2.0.0", -1), - ("2.24.51", "2.25", -1), - ("2.1.5+20120813+gitdcbe778", "2.1.5", 1), - ("3.4.1", "3.4b1", 1), - ("041206", "200090325", -1), - ("0.6.2+git20130413", "0.6.2", 1), - ("2.6.0+bzr6602", "2.6.0", 1), - ("2.6.0", "2.6b2", 1), - ("2.6.0+bzr6602", "2.6b2x", 1), - ("0.6.7+20150214+git3a710f9", "0.6.7", 1), - ("15.8b", "15.8.0.1", -1), - ("1.2rc1", "1.2.0", -1), + ('0.99.beta19', '0.99.beta14', operator.gt), + ("1.0.0", "2.0.0", operator.lt), + (".0.0", "2.0.0", operator.lt), + ("alpha", "beta", operator.lt), + ("1.0", "1.0.0", operator.lt), + ("2.456", "2.1000", operator.lt), + ("2.1000", "3.111", operator.lt), + ("2.001", "2.1", operator.eq), + ("2.34", "2.34", operator.eq), + ("6.1.2", "6.3.8", operator.lt), + ("1.7.3.0", "2.0.0", operator.lt), + ("2.24.51", "2.25", operator.lt), + ("2.1.5+20120813+gitdcbe778", "2.1.5", operator.gt), + ("3.4.1", "3.4b1", operator.gt), + ("041206", "200090325", operator.lt), + ("0.6.2+git20130413", "0.6.2", operator.gt), + ("2.6.0+bzr6602", "2.6.0", operator.gt), + ("2.6.0", "2.6b2", operator.gt), + ("2.6.0+bzr6602", "2.6b2x", operator.gt), + ("0.6.7+20150214+git3a710f9", "0.6.7", operator.gt), + ("15.8b", "15.8.0.1", operator.lt), + ("1.2rc1", "1.2.0", operator.lt), ]: ver_a = Version(a) ver_b = Version(b) - self.assertEqual(ver_a.__cmp__(ver_b), result) - self.assertEqual(ver_b.__cmp__(ver_a), -result) + if op is operator.eq: + for o, name in [(op, 'eq'), (operator.ge, 'ge'), (operator.le, 'le')]: + self.assertTrue(o(ver_a, ver_b), '{} {} {}'.format(ver_a, name, ver_b)) + if op is operator.lt: + for o, name in [(op, 'lt'), (operator.le, 'le'), (operator.ne, 'ne')]: + self.assertTrue(o(ver_a, ver_b), '{} {} {}'.format(ver_a, name, ver_b)) + for o, name in [(operator.gt, 'gt'), (operator.ge, 'ge'), (operator.eq, 'eq')]: + self.assertFalse(o(ver_a, ver_b), '{} {} {}'.format(ver_a, name, ver_b)) + if op is operator.gt: + for o, name in [(op, 'gt'), (operator.ge, 'ge'), (operator.ne, 'ne')]: + self.assertTrue(o(ver_a, ver_b), '{} {} {}'.format(ver_a, name, ver_b)) + for o, name in [(operator.lt, 'lt'), (operator.le, 'le'), (operator.eq, 'eq')]: + self.assertFalse(o(ver_a, ver_b), '{} {} {}'.format(ver_a, name, ver_b)) def test_msvc_toolset_version(self): ''' diff --git a/test cases/unit/55 introspection/meson.build b/test cases/unit/55 introspection/meson.build index 95a7c5f..f11d64d 100644 --- a/test cases/unit/55 introspection/meson.build +++ b/test cases/unit/55 introspection/meson.build @@ -20,9 +20,19 @@ endif subdir('sharedlib') subdir('staticlib') -t1 = executable('test1', 't1.cpp', link_with: [sharedlib], install: true, build_by_default: get_option('test_opt2')) -t2 = executable('test2', 't2.cpp', link_with: [staticlib]) -t3 = executable('test3', 't3.cpp', link_with: [sharedlib, staticlib], dependencies: [dep1]) +var1 = '1' +var2 = 2.to_string() +var3 = 'test3' + +t1 = executable('test' + var1, ['t1.cpp'], link_with: [sharedlib], install: true, build_by_default: get_option('test_opt2')) +t2 = executable('test@0@'.format('@0@'.format(var2)), 't2.cpp', link_with: [staticlib]) +t3 = executable(var3, 't3.cpp', link_with: [sharedlib, staticlib], dependencies: [dep1]) + +### BEGIN: Test inspired by taisei: https://github.com/taisei-project/taisei/blob/master/meson.build#L293 +systype = '@0@'.format(host_machine.system()) +systype = '@0@, @1@, @2@'.format(systype, host_machine.cpu_family(), host_machine.cpu()) +message(systype) +### END: Test inspired by taisei test('test case 1', t1) test('test case 2', t2) |