From f6f35aa906c8ebac21af07c17449df65d08aeaeb Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Tue, 10 Aug 2021 12:10:09 -0700 Subject: backend/backends: Add type annotations to Backend --- mesonbuild/backend/backends.py | 370 +++++++++++++++++++++++++---------------- 1 file changed, 223 insertions(+), 147 deletions(-) diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 5749a8c..21e45bd 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -39,8 +39,12 @@ if T.TYPE_CHECKING: from .._typing import ImmutableListProtocol from ..arglist import CompilerArgs from ..compilers import Compiler + from ..environment import Environment from ..interpreter import Interpreter, Test - from ..mesonlib import FileMode + from ..linkers import StaticLinker + from ..mesonlib import FileMode, FileOrString + from ..wrap import WrapMode + # Languages that can mix with C or C++ but don't support unity builds yet # because the syntax we use for unity builds is specific to C/++/ObjC/++. @@ -241,6 +245,9 @@ def get_backend_from_name(backend: str, build: T.Optional[build.Build] = None, i # This class contains the basic functionality that is needed by all backends. # Feel free to move stuff in and out of it as you see fit. class Backend: + + environment: T.Optional['Environment'] + def __init__(self, build: T.Optional[build.Build], interpreter: T.Optional['Interpreter']): # Make it possible to construct a dummy backend # This is used for introspection without a build directory @@ -262,7 +269,7 @@ class Backend: def generate(self) -> None: raise RuntimeError(f'generate is not implemented in {type(self).__name__}') - def get_target_filename(self, t: T.Union[build.Target, build.CustomTargetIndex], *, warn_multi_output: bool = True): + def get_target_filename(self, t: T.Union[build.Target, build.CustomTargetIndex], *, warn_multi_output: bool = True) -> str: if isinstance(t, build.CustomTarget): if warn_multi_output and len(t.get_outputs()) != 1: mlog.warning(f'custom_target {t.name!r} has more than one output! ' @@ -288,13 +295,19 @@ class Backend: comp_override = target.option_overrides_compiler return OptionOverrideProxy(comp_override, comp_reg) - def get_option_for_target(self, option_name: 'OptionKey', target: build.BuildTarget): + def get_option_for_target(self, option_name: 'OptionKey', target: build.BuildTarget) -> T.Union[str, int, bool, 'WrapMode']: if option_name in target.option_overrides_base: override = target.option_overrides_base[option_name] - return self.environment.coredata.validate_option_value(option_name, override) - return self.environment.coredata.get_option(option_name.evolve(subproject=target.subproject)) - - def get_source_dir_include_args(self, target, compiler, *, absolute_path=False): + v = self.environment.coredata.validate_option_value(option_name, override) + else: + v = self.environment.coredata.get_option(option_name.evolve(subproject=target.subproject)) + # We don't actually have wrapmode here to do an assert, so just do a + # cast, we know what's in coredata anyway. + # TODO: if it's possible to annotate get_option or validate_option_value + # in the future we might be able to remove the cast here + return T.cast(T.Union[str, int, bool, 'WrapMode'], v) + + def get_source_dir_include_args(self, target: build.BuildTarget, compiler: 'Compiler', *, absolute_path: bool = False) -> T.List[str]: curdir = target.get_subdir() if absolute_path: lead = self.source_dir @@ -303,7 +316,7 @@ class Backend: tmppath = os.path.normpath(os.path.join(lead, curdir)) return compiler.get_include_args(tmppath, False) - def get_build_dir_include_args(self, target, compiler, *, absolute_path=False): + def get_build_dir_include_args(self, target: build.BuildTarget, compiler: 'Compiler', *, absolute_path: bool = False) -> T.List[str]: if absolute_path: curdir = os.path.join(self.build_dir, target.get_subdir()) else: @@ -312,7 +325,7 @@ class Backend: curdir = '.' return compiler.get_include_args(curdir, False) - def get_target_filename_for_linking(self, target): + def get_target_filename_for_linking(self, target: T.Union[build.Target, build.CustomTargetIndex]) -> T.Optional[str]: # On some platforms (msvc for instance), the file that is used for # dynamic linking is not the same as the dynamic library itself. This # file is called an import library, and we want to link against that. @@ -334,20 +347,20 @@ class Backend: raise AssertionError(f'BUG: Tried to link to {target!r} which is not linkable') @lru_cache(maxsize=None) - def get_target_dir(self, target: build.Target) -> str: + def get_target_dir(self, target: T.Union[build.Target, build.CustomTargetIndex]) -> str: if self.environment.coredata.get_option(OptionKey('layout')) == 'mirror': dirname = target.get_subdir() else: dirname = 'meson-out' return dirname - def get_target_dir_relative_to(self, t, o): + def get_target_dir_relative_to(self, t: build.Target, o: build.Target) -> str: '''Get a target dir relative to another target's directory''' target_dir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(t)) othert_dir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(o)) return os.path.relpath(target_dir, othert_dir) - def get_target_source_dir(self, target): + def get_target_source_dir(self, target: build.Target) -> str: # if target dir is empty, avoid extraneous trailing / from os.path.join() target_dir = self.get_target_dir(target) if target_dir: @@ -357,11 +370,14 @@ class Backend: def get_target_private_dir(self, target: build.Target) -> str: return os.path.join(self.get_target_filename(target, warn_multi_output=False) + '.p') - def get_target_private_dir_abs(self, target): + def get_target_private_dir_abs(self, target: build.Target) -> str: return os.path.join(self.environment.get_build_dir(), self.get_target_private_dir(target)) @lru_cache(maxsize=None) - def get_target_generated_dir(self, target, gensrc, src): + def get_target_generated_dir( + self, target: build.Target, + gensrc: T.Union[build.CustomTarget, build.CustomTargetIndex, build.GeneratedList], + src: str) -> str: """ Takes a BuildTarget, a generator source (CustomTarget or GeneratedList), and a generated source filename. @@ -374,19 +390,20 @@ class Backend: # target that the GeneratedList is used in return os.path.join(self.get_target_private_dir(target), src) - def get_unity_source_file(self, target, suffix, number): + def get_unity_source_file(self, target: build.Target, suffix: str, number: int) -> mesonlib.File: # There is a potential conflict here, but it is unlikely that # anyone both enables unity builds and has a file called foo-unity.cpp. osrc = f'{target.name}-unity{number}.{suffix}' return mesonlib.File.from_built_file(self.get_target_private_dir(target), osrc) - def generate_unity_files(self, target, unity_src): - abs_files = [] - result = [] + def generate_unity_files(self, target: build.BuildTarget, unity_src: str) -> T.List[mesonlib.File]: + abs_files: T.List[str] = [] + result: T.List[mesonlib.File] = [] compsrcs = classify_unity_sources(target.compilers.values(), unity_src) unity_size = self.get_option_for_target(OptionKey('unity_size'), target) + assert isinstance(unity_size, int), 'for mypy' - def init_language_file(suffix, unity_file_number): + def init_language_file(suffix: str, unity_file_number: int) -> T.TextIO: unity_src = self.get_unity_source_file(target, suffix, unity_file_number) outfileabs = unity_src.absolute_path(self.environment.get_source_dir(), self.environment.get_build_dir()) @@ -402,6 +419,8 @@ class Backend: for comp, srcs in compsrcs.items(): files_in_current = unity_size + 1 unity_file_number = 0 + # TODO: this could be simplified with an algorithm that pre-sorts + # the sources into the size of chunks we want ofile = None for src in srcs: if files_in_current >= unity_size: @@ -415,19 +434,23 @@ class Backend: if ofile: ofile.close() - [mesonlib.replace_if_different(x, x + '.tmp') for x in abs_files] + for x in abs_files: + mesonlib.replace_if_different(x, x + '.tmp') return result - def relpath(self, todir, fromdir): + @staticmethod + def relpath(todir: str, fromdir: str) -> str: return os.path.relpath(os.path.join('dummyprefixdir', todir), os.path.join('dummyprefixdir', fromdir)) - def flatten_object_list(self, target, proj_dir_to_build_root=''): + def flatten_object_list(self, target: build.BuildTarget, proj_dir_to_build_root: str = '') -> T.List[str]: obj_list = self._flatten_object_list(target, target.get_objects(), proj_dir_to_build_root) return list(dict.fromkeys(obj_list)) - def _flatten_object_list(self, target, objects, proj_dir_to_build_root): - obj_list = [] + def _flatten_object_list(self, target: build.BuildTarget, + objects: T.Sequence[T.Union[str, 'File', build.ExtractedObjects]], + proj_dir_to_build_root: str) -> T.List[str]: + obj_list: T.List[str] = [] for obj in objects: if isinstance(obj, str): o = os.path.join(proj_dir_to_build_root, @@ -450,24 +473,31 @@ class Backend: raise MesonException('Unknown data type in object list.') return obj_list - def is_swift_target(self, target): + @staticmethod + def is_swift_target(target: build.BuildTarget) -> bool: for s in target.sources: if s.endswith('swift'): return True return False - def determine_swift_dep_dirs(self, target): - result = [] + def determine_swift_dep_dirs(self, target: build.BuildTarget) -> T.List[str]: + result: T.List[str] = [] for l in target.link_targets: result.append(self.get_target_private_dir_abs(l)) return result - def get_executable_serialisation(self, cmd, workdir=None, - extra_bdeps=None, capture=None, feed=None, - env: T.Optional[build.EnvironmentVariables] = None, - tag: T.Optional[str] = None): - exe = cmd[0] - cmd_args = cmd[1:] + def get_executable_serialisation( + self, cmd: T.Sequence[T.Union[programs.ExternalProgram, build.BuildTarget, build.CustomTarget, File, str]], + workdir: T.Optional[str] = None, + extra_bdeps: T.Optional[T.List[build.BuildTarget]] = None, + capture: T.Optional[bool] = None, + feed: T.Optional[bool] = None, + env: T.Optional[build.EnvironmentVariables] = None, + tag: T.Optional[str] = None, + verbose: bool = False) -> 'ExecutableSerialisation': + + # XXX: cmd_args either need to be lowered to strings, or need to be checked for non-string arguments, right? + exe, *raw_cmd_args = cmd if isinstance(exe, programs.ExternalProgram): exe_cmd = exe.get_command() exe_for_machine = exe.for_machine @@ -489,6 +519,19 @@ class Backend: exe_cmd = [exe] exe_for_machine = MachineChoice.BUILD + cmd_args: T.List[str] = [] + for c in raw_cmd_args: + if isinstance(c, programs.ExternalProgram): + p = c.get_path() + assert isinstance(p, str) + cmd_args.append(p) + elif isinstance(c, (build.BuildTarget, build.CustomTarget)): + cmd_args.append(self.get_target_filename_abs(c)) + elif isinstance(c, mesonlib.File): + cmd_args.append(c.rel_to_builddir(self.environment.source_dir)) + else: + cmd_args.append(c) + machine = self.environment.machines[exe_for_machine] if machine.is_windows() or machine.is_cygwin(): extra_paths = self.determine_windows_extra_paths(exe, extra_bdeps or []) @@ -512,20 +555,25 @@ class Backend: workdir = workdir or self.environment.get_build_dir() return ExecutableSerialisation(exe_cmd + cmd_args, env, exe_wrapper, workdir, - extra_paths, capture, feed, tag) - - def as_meson_exe_cmdline(self, exe, cmd_args, workdir=None, - extra_bdeps=None, capture=None, feed=None, - force_serialize=False, + extra_paths, capture, feed, tag, verbose) + + def as_meson_exe_cmdline(self, exe: T.Union[str, mesonlib.File, build.BuildTarget, build.CustomTarget, programs.ExternalProgram], + cmd_args: T.Sequence[T.Union[str, mesonlib.File, build.BuildTarget, build.CustomTarget, programs.ExternalProgram]], + workdir: T.Optional[str] = None, + extra_bdeps: T.Optional[T.List[build.BuildTarget]] = None, + capture: T.Optional[bool] = None, + feed: T.Optional[bool] = None, + force_serialize: bool = False, env: T.Optional[build.EnvironmentVariables] = None, - verbose: bool = False): + verbose: bool = False) -> T.Tuple[T.Sequence[T.Union[str, File, build.Target, programs.ExternalProgram]], str]: ''' Serialize an executable for running with a generator or a custom target ''' - cmd = [exe] + cmd_args - es = self.get_executable_serialisation(cmd, workdir, extra_bdeps, capture, feed, env) - es.verbose = verbose - reasons = [] + cmd: T.List[T.Union[str, mesonlib.File, build.BuildTarget, build.CustomTarget, programs.ExternalProgram]] = [] + cmd.append(exe) + cmd.extend(cmd_args) + es = self.get_executable_serialisation(cmd, workdir, extra_bdeps, capture, feed, env, verbose=verbose) + reasons: T.List[str] = [] if es.extra_paths: reasons.append('to set PATH') @@ -551,14 +599,16 @@ class Backend: if not force_serialize: if not capture and not feed: return es.cmd_args, '' - args = [] + args: T.List[str] = [] if capture: - args += ['--capture', capture] + args += ['--capture', str(capture)] if feed: - args += ['--feed', feed] - return ((self.environment.get_build_command() + - ['--internal', 'exe'] + args + ['--'] + es.cmd_args), - ', '.join(reasons)) + args += ['--feed', str(feed)] + + return ( + self.environment.get_build_command() + ['--internal', 'exe'] + args + ['--'] + es.cmd_args, + ', '.join(reasons) + ) if isinstance(exe, (programs.ExternalProgram, build.BuildTarget, build.CustomTarget)): @@ -583,7 +633,7 @@ class Backend: return (self.environment.get_build_command() + ['--internal', 'exe', '--unpickle', exe_data], ', '.join(reasons)) - def serialize_tests(self): + def serialize_tests(self) -> T.Tuple[str, str]: test_data = os.path.join(self.environment.get_scratch_dir(), 'meson_test_setup.dat') with open(test_data, 'wb') as datafile: self.write_test_file(datafile) @@ -592,7 +642,7 @@ class Backend: self.write_benchmark_file(datafile) return test_data, benchmark_data - def determine_linker_and_stdlib_args(self, target): + def determine_linker_and_stdlib_args(self, target: build.BuildTarget) -> T.Tuple[T.Union['Compiler', 'StaticLinker'], T.List[str]]: ''' If we're building a static library, there is only one static linker. Otherwise, we query the target for the dynamic linker. @@ -603,19 +653,23 @@ class Backend: return l, stdlib_args @staticmethod - def _libdir_is_system(libdir, compilers, env): + def _libdir_is_system(libdir: str, compilers: T.Mapping[str, 'Compiler'], env: 'Environment') -> bool: libdir = os.path.normpath(libdir) for cc in compilers.values(): if libdir in cc.get_library_dirs(env): return True return False - def get_external_rpath_dirs(self, target): - dirs = set() - args = [] + def get_external_rpath_dirs(self, target: build.BuildTarget) -> T.Set[str]: + dirs: T.Set[str] = set() + args: T.List[str] = [] for lang in LANGUAGES_USING_LDFLAGS: try: - args.extend(self.environment.coredata.get_external_link_args(target.for_machine, lang)) + e = self.environment.coredata.get_external_link_args(target.for_machine, lang) + if isinstance(e, str): + args.append(e) + else: + args.extend(e) except Exception: pass # Match rpath formats: @@ -649,8 +703,8 @@ class Backend: raise MesonException(f'Invalid arg for --just-symbols, {dir} is a directory.') return dirs - def rpaths_for_bundled_shared_libraries(self, target, exclude_system=True): - paths = [] + def rpaths_for_bundled_shared_libraries(self, target: build.BuildTarget, exclude_system: bool = True) -> T.List[str]: + paths: T.List[str] = [] for dep in target.external_deps: if not isinstance(dep, (dependencies.ExternalLibrary, dependencies.PkgConfigDependency)): continue @@ -679,8 +733,10 @@ class Backend: return paths def determine_rpath_dirs(self, target: build.BuildTarget) -> T.Tuple[str, ...]: + result: OrderedSet[str] if self.environment.coredata.get_option(OptionKey('layout')) == 'mirror': - result: OrderedSet[str] = target.get_link_dep_subdirs() + # NEed a copy here + result = OrderedSet(target.get_link_dep_subdirs()) else: result = OrderedSet() result.add('meson-out') @@ -689,12 +745,12 @@ class Backend: return tuple(result) @staticmethod - def canonicalize_filename(fname): + def canonicalize_filename(fname: str) -> str: for ch in ('/', '\\', ':'): fname = fname.replace(ch, '_') return fname - def object_filename_from_source(self, target, source): + def object_filename_from_source(self, target: build.BuildTarget, source: 'FileOrString') -> str: assert isinstance(source, mesonlib.File) build_dir = self.environment.get_build_dir() rel_src = source.rel_to_builddir(self.build_to_src) @@ -709,42 +765,41 @@ class Backend: else: rel_src = os.path.basename(rel_src) # A meson- prefixed directory is reserved; hopefully no-one creates a file name with such a weird prefix. - source = 'meson-generated_' + rel_src[:-5] + '.c' + gen_source = 'meson-generated_' + rel_src[:-5] + '.c' elif source.is_built: if os.path.isabs(rel_src): rel_src = rel_src[len(build_dir) + 1:] targetdir = self.get_target_private_dir(target) # A meson- prefixed directory is reserved; hopefully no-one creates a file name with such a weird prefix. - source = 'meson-generated_' + os.path.relpath(rel_src, targetdir) + gen_source = 'meson-generated_' + os.path.relpath(rel_src, targetdir) else: if os.path.isabs(rel_src): # Use the absolute path directly to avoid file name conflicts - source = rel_src + gen_source = rel_src else: - source = os.path.relpath(os.path.join(build_dir, rel_src), - os.path.join(self.environment.get_source_dir(), target.get_subdir())) + gen_source = os.path.relpath(os.path.join(build_dir, rel_src), + os.path.join(self.environment.get_source_dir(), target.get_subdir())) machine = self.environment.machines[target.for_machine] - return self.canonicalize_filename(source) + '.' + machine.get_object_suffix() + return self.canonicalize_filename(gen_source) + '.' + machine.get_object_suffix() - def determine_ext_objs(self, extobj, proj_dir_to_build_root): - result = [] + def determine_ext_objs(self, extobj: 'build.ExtractedObjects', proj_dir_to_build_root: str) -> T.List[str]: + result: T.List[str] = [] # Merge sources and generated sources - sources = list(extobj.srclist) + raw_sources = list(extobj.srclist) for gensrc in extobj.genlist: - for s in gensrc.get_outputs(): - path = self.get_target_generated_dir(extobj.target, gensrc, s) + for r in gensrc.get_outputs(): + path = self.get_target_generated_dir(extobj.target, gensrc, r) dirpart, fnamepart = os.path.split(path) - sources.append(File(True, dirpart, fnamepart)) + raw_sources.append(File(True, dirpart, fnamepart)) # Filter out headers and all non-source files - filtered_sources = [] - for s in sources: + sources: T.List['FileOrString'] = [] + for s in raw_sources: if self.environment.is_source(s) and not self.environment.is_header(s): - filtered_sources.append(s) + sources.append(s) elif self.environment.is_object(s): result.append(s.relative_name()) - sources = filtered_sources # extobj could contain only objects and no sources if not sources: @@ -759,15 +814,16 @@ class Backend: compsrcs = classify_unity_sources(extobj.target.compilers.values(), sources) sources = [] unity_size = self.get_option_for_target(OptionKey('unity_size'), extobj.target) + assert isinstance(unity_size, int), 'for mypy' for comp, srcs in compsrcs.items(): if comp.language in LANGS_CANT_UNITY: sources += srcs continue for i in range(len(srcs) // unity_size + 1): - osrc = self.get_unity_source_file(extobj.target, + _src = self.get_unity_source_file(extobj.target, comp.get_default_suffix(), i) - sources.append(osrc) + sources.append(_src) for osrc in sources: objname = self.object_filename_from_source(extobj.target, osrc) @@ -776,8 +832,8 @@ class Backend: return result - def get_pch_include_args(self, compiler, target): - args = [] + def get_pch_include_args(self, compiler: 'Compiler', target: build.BuildTarget) -> T.List[str]: + args: T.List[str] = [] pchpath = self.get_target_private_dir(target) includeargs = compiler.get_include_args(pchpath, False) p = target.get_pch(compiler.get_language()) @@ -785,7 +841,7 @@ class Backend: args += compiler.get_pch_use_args(pchpath, p[0]) return includeargs + args - def create_msvc_pch_implementation(self, target, lang, pch_header): + def create_msvc_pch_implementation(self, target: build.BuildTarget, lang: str, pch_header: str) -> str: # We have to include the language in the file name, otherwise # pch.c and pch.cpp will both end up as pch.obj in VS backends. impl_name = f'meson_pch-{lang}.{lang}' @@ -836,7 +892,8 @@ class Backend: if no_warn_args: commands += compiler.get_no_warn_args() else: - commands += compiler.get_warn_args(self.get_option_for_target(OptionKey('warning_level'), target)) + # warning_level is a string, but mypy can't determine that + commands += compiler.get_warn_args(T.cast(str, self.get_option_for_target(OptionKey('warning_level'), target))) # Add -Werror if werror=true is set in the build options set on the # command-line or default_options inside project(). This only sets the # action to be done for warnings if/when they are emitted, so it's ok @@ -846,10 +903,20 @@ class Backend: # Add compile args for c_* or cpp_* build options set on the # command-line or default_options inside project(). commands += compiler.get_option_compile_args(copt_proxy) + # Add buildtype args: optimization level, debugging, etc. - commands += compiler.get_buildtype_args(self.get_option_for_target(OptionKey('buildtype'), target)) - commands += compiler.get_optimization_args(self.get_option_for_target(OptionKey('optimization'), target)) - commands += compiler.get_debug_args(self.get_option_for_target(OptionKey('debug'), target)) + buildtype = self.get_option_for_target(OptionKey('buildtype'), target) + assert isinstance(buildtype, str), 'for mypy' + commands += compiler.get_buildtype_args(buildtype) + + optimization = self.get_option_for_target(OptionKey('optimization'), target) + assert isinstance(optimization, str), 'for mypy' + commands += compiler.get_optimization_args(optimization) + + debug = self.get_option_for_target(OptionKey('debug'), target) + assert isinstance(debug, bool), 'for mypy' + commands += compiler.get_debug_args(debug) + # Add compile args added using add_project_arguments() commands += self.build.get_project_args(compiler, target.subproject, target.for_machine) # Add compile args added using add_global_arguments() @@ -905,8 +972,8 @@ class Backend: commands += compiler.get_include_args(priv_dir, False) return commands - def build_target_link_arguments(self, compiler, deps): - args = [] + def build_target_link_arguments(self, compiler: 'Compiler', deps: T.List[build.Target]) -> T.List[str]: + args: T.List[str] = [] for d in deps: if not (d.is_linkable_target()): raise RuntimeError(f'Tried to link with a non-library target "{d.get_basename()}".') @@ -920,8 +987,8 @@ class Backend: args.append(arg) return args - def get_mingw_extra_paths(self, target): - paths = OrderedSet() + def get_mingw_extra_paths(self, target: build.BuildTarget) -> T.List[str]: + paths: OrderedSet[str] = OrderedSet() # The cross bindir root = self.environment.properties[target.for_machine].get_root() if root: @@ -937,13 +1004,17 @@ class Backend: paths.update(cc.get_library_dirs(self.environment)) return list(paths) - def determine_windows_extra_paths(self, target: T.Union[build.BuildTarget, str], extra_bdeps): - '''On Windows there is no such thing as an rpath. + def determine_windows_extra_paths( + self, target: T.Union[build.BuildTarget, build.CustomTarget, programs.ExternalProgram, mesonlib.File, str], + extra_bdeps: T.Sequence[T.Union[build.BuildTarget, build.CustomTarget]]) -> T.List[str]: + """On Windows there is no such thing as an rpath. + We must determine all locations of DLLs that this exe links to and return them so they can be used in unit - tests.''' - result = set() - prospectives = set() + tests. + """ + result: T.Set[str] = set() + prospectives: T.Set[build.Target] = set() if isinstance(target, build.BuildTarget): prospectives.update(target.get_transitive_link_deps()) # External deps @@ -951,11 +1022,10 @@ class Backend: result.add(os.path.normpath(os.path.join(self.environment.get_build_dir(), deppath))) for bdep in extra_bdeps: prospectives.add(bdep) - prospectives.update(bdep.get_transitive_link_deps()) + if isinstance(bdep, build.BuildTarget): + prospectives.update(bdep.get_transitive_link_deps()) # Internal deps for ld in prospectives: - if ld == '' or ld == '.': - continue dirseg = os.path.join(self.environment.get_build_dir(), self.get_target_dir(ld)) result.add(dirseg) if (isinstance(target, build.BuildTarget) and @@ -963,20 +1033,20 @@ class Backend: result.update(self.get_mingw_extra_paths(target)) return list(result) - def write_benchmark_file(self, datafile): + def write_benchmark_file(self, datafile: T.BinaryIO) -> None: self.write_test_serialisation(self.build.get_benchmarks(), datafile) - def write_test_file(self, datafile): + def write_test_file(self, datafile: T.BinaryIO) -> None: self.write_test_serialisation(self.build.get_tests(), datafile) def create_test_serialisation(self, tests: T.List['Test']) -> T.List[TestSerialisation]: - arr = [] + arr: T.List[TestSerialisation] = [] for t in sorted(tests, key=lambda tst: -1 * tst.priority): exe = t.get_exe() if isinstance(exe, programs.ExternalProgram): cmd = exe.get_command() else: - cmd = [os.path.join(self.environment.get_build_dir(), self.get_target_filename(t.get_exe()))] + cmd = [os.path.join(self.environment.get_build_dir(), self.get_target_filename(exe))] if isinstance(exe, (build.BuildTarget, programs.ExternalProgram)): test_for_machine = exe.for_machine else: @@ -999,15 +1069,15 @@ class Backend: exe_wrapper = None machine = self.environment.machines[exe.for_machine] if machine.is_windows() or machine.is_cygwin(): - extra_bdeps = [] + extra_bdeps: T.List[T.Union[build.BuildTarget, build.CustomTarget]] = [] if isinstance(exe, build.CustomTarget): - extra_bdeps = exe.get_transitive_build_target_deps() + extra_bdeps = list(exe.get_transitive_build_target_deps()) extra_paths = self.determine_windows_extra_paths(exe, extra_bdeps) else: extra_paths = [] - cmd_args = [] - depends = set(t.depends) + cmd_args: T.List[str] = [] + depends: T.Set[build.Target] = set(t.depends) if isinstance(exe, build.Target): depends.add(exe) for a in t.cmd_args: @@ -1015,6 +1085,7 @@ class Backend: depends.add(a) if isinstance(a, build.BuildTarget): extra_paths += self.determine_windows_extra_paths(a, []) + if isinstance(a, mesonlib.File): a = os.path.join(self.environment.get_build_dir(), a.rel_to_builddir(self.build_to_src)) cmd_args.append(a) @@ -1040,10 +1111,10 @@ class Backend: arr.append(ts) return arr - def write_test_serialisation(self, tests: T.List['Test'], datafile: str): + def write_test_serialisation(self, tests: T.List['Test'], datafile: T.BinaryIO) -> None: pickle.dump(self.create_test_serialisation(tests), datafile) - def construct_target_rel_path(self, a, workdir): + def construct_target_rel_path(self, a: build.Target, workdir: T.Optional[str]) -> str: if workdir is None: return self.get_target_filename(a) assert(os.path.isabs(workdir)) @@ -1061,7 +1132,7 @@ class Backend: # Copy file from, to, and with mode unchanged d.data.append(InstallDataBase(ifilename, ofilename, None, '', tag='devel')) - def get_regen_filelist(self): + def get_regen_filelist(self) -> T.List[str]: '''List of all files whose alteration means that the build definition needs to be regenerated.''' deps = [str(Path(self.build_to_src) / df) @@ -1073,7 +1144,7 @@ class Backend: self.check_clock_skew(deps) return deps - def generate_regen_info(self): + def generate_regen_info(self) -> None: deps = self.get_regen_filelist() regeninfo = RegenInfo(self.environment.get_source_dir(), self.environment.get_build_dir(), @@ -1083,7 +1154,7 @@ class Backend: with open(filename, 'wb') as f: pickle.dump(regeninfo, f) - def check_clock_skew(self, file_list): + def check_clock_skew(self, file_list: T.List[str]) -> None: # If a file that leads to reconfiguration has a time # stamp in the future, it will trigger an eternal reconfigure # loop. @@ -1099,15 +1170,15 @@ class Backend: if delta > 0.001: raise MesonException(f'Clock skew detected. File {absf} has a time stamp {delta:.4f}s in the future.') - def build_target_to_cmd_array(self, bt): + def build_target_to_cmd_array(self, bt: T.Union[build.BuildTarget, programs.ExternalProgram]) -> T.List[str]: if isinstance(bt, build.BuildTarget): arr = [os.path.join(self.environment.get_build_dir(), self.get_target_filename(bt))] else: arr = bt.get_command() return arr - def replace_extra_args(self, args, genlist): - final_args = [] + def replace_extra_args(self, args: T.List[str], genlist: 'build.GeneratedList') -> T.List[str]: + final_args: T.List[str] = [] for a in args: if a == '@EXTRA_ARGS@': final_args += genlist.get_extra_args() @@ -1115,8 +1186,8 @@ class Backend: final_args.append(a) return final_args - def replace_outputs(self, args, private_dir, output_list): - newargs = [] + def replace_outputs(self, args: T.List[str], private_dir: str, output_list: T.List[str]) -> T.List[str]: + newargs: T.List[str] = [] regex = re.compile(r'@OUTPUT(\d+)@') for arg in args: m = regex.search(arg) @@ -1128,12 +1199,12 @@ class Backend: newargs.append(arg) return newargs - def get_build_by_default_targets(self): - result = OrderedDict() + def get_build_by_default_targets(self) -> 'T.OrderedDict[str, T.Union[build.BuildTarget, build.CustomTarget]]': + result: 'T.OrderedDict[str, T.Union[build.BuildTarget, build.CustomTarget]]' = OrderedDict() # Get all build and custom targets that must be built by default - for name, t in self.build.get_targets().items(): - if t.build_by_default: - result[name] = t + for name, b in self.build.get_targets().items(): + if b.build_by_default: + result[name] = b # Get all targets used as test executables and arguments. These must # also be built by default. XXX: Sometime in the future these should be # built only before running tests. @@ -1167,19 +1238,17 @@ class Backend: libs.extend(self.get_custom_target_provided_by_generated_source(t)) return libs - def is_unity(self, target): + def is_unity(self, target: build.BuildTarget) -> bool: optval = self.get_option_for_target(OptionKey('unity'), target) - if optval == 'on' or (optval == 'subprojects' and target.subproject != ''): - return True - return False + return optval == 'on' or (optval == 'subprojects' and target.subproject != '') - def get_custom_target_sources(self, target): + def get_custom_target_sources(self, target: build.CustomTarget) -> T.List[str]: ''' Custom target sources can be of various object types; strings, File, BuildTarget, even other CustomTargets. Returns the path to them relative to the build root directory. ''' - srcs = [] + srcs: T.List[str] = [] for i in target.get_sources(): if isinstance(i, str): fname = [os.path.join(self.build_to_src, target.subdir, i)] @@ -1198,8 +1267,8 @@ class Backend: srcs += fname return srcs - def get_custom_target_depend_files(self, target, absolute_paths=False): - deps = [] + def get_custom_target_depend_files(self, target: build.CustomTarget, absolute_paths: bool = False) -> T.List[str]: + deps: T.List[str] = [] for i in target.depend_files: if isinstance(i, mesonlib.File): if absolute_paths: @@ -1214,7 +1283,7 @@ class Backend: deps.append(os.path.join(self.build_to_src, target.subdir, i)) return deps - def get_custom_target_output_dir(self, target): + def get_custom_target_output_dir(self, target: T.Union[build.Target, build.CustomTargetIndex]) -> str: # The XCode backend is special. A target foo/bar does # not go to ${BUILDDIR}/foo/bar but instead to # ${BUILDDIR}/${BUILDTYPE}/foo/bar. @@ -1225,11 +1294,12 @@ class Backend: return self.get_target_dir(target) @lru_cache(maxsize=None) - def get_normpath_target(self, source) -> str: + def get_normpath_target(self, source: str) -> str: return os.path.normpath(source) - def get_custom_target_dirs(self, target, compiler, *, absolute_path=False): - custom_target_include_dirs = [] + def get_custom_target_dirs(self, target: build.CustomTarget, compiler: 'Compiler', *, + absolute_path: bool = False) -> T.List[str]: + custom_target_include_dirs: T.List[str] = [] for i in target.get_generated_sources(): # Generator output goes into the target private dir which is # already in the include paths list. Only custom targets have their @@ -1245,14 +1315,18 @@ class Backend: custom_target_include_dirs.append(idir) return custom_target_include_dirs - def get_custom_target_dir_include_args(self, target, compiler, *, absolute_path=False): - incs = [] + def get_custom_target_dir_include_args( + self, target: build.CustomTarget, compiler: 'Compiler', *, + absolute_path: bool = False) -> T.List[str]: + incs: T.List[str] = [] for i in self.get_custom_target_dirs(target, compiler, absolute_path=absolute_path): incs += compiler.get_include_args(i, False) return incs - def eval_custom_target_command(self, target, absolute_outputs=False): + def eval_custom_target_command( + self, target: build.CustomTarget, absolute_outputs: bool = False) -> \ + T.Tuple[T.List[str], T.List[str], T.List[str]]: # We want the outputs to be absolute only when using the VS backend # XXX: Maybe allow the vs backend to use relative paths too? source_root = self.build_to_src @@ -1262,12 +1336,10 @@ class Backend: source_root = self.environment.get_source_dir() build_root = self.environment.get_build_dir() outdir = os.path.join(self.environment.get_build_dir(), outdir) - outputs = [] - for i in target.get_outputs(): - outputs.append(os.path.join(outdir, i)) + outputs = [os.path.join(outdir, i) for i in target.get_outputs()] inputs = self.get_custom_target_sources(target) # Evaluate the command list - cmd = [] + cmd: T.List[str] = [] for i in target.command: if isinstance(i, build.BuildTarget): cmd += self.build_target_to_cmd_array(i) @@ -1360,11 +1432,15 @@ class Backend: else: # TODO go through all candidates, like others strip_bin = [detect.defaults['strip'][0]] + + umask = self.environment.coredata.get_option(OptionKey('install_umask')) + assert isinstance(umask, (str, int)), 'for mypy' + d = InstallData(self.environment.get_source_dir(), self.environment.get_build_dir(), self.environment.get_prefix(), strip_bin, - self.environment.coredata.get_option(OptionKey('install_umask')), + umask, self.environment.get_build_command() + ['introspect'], self.environment.coredata.version) self.generate_depmf_install(d) @@ -1376,7 +1452,7 @@ class Backend: self.generate_subdir_install(d) return d - def create_install_data_files(self): + def create_install_data_files(self) -> None: install_data_file = os.path.join(self.environment.get_scratch_dir(), 'install.dat') with open(install_data_file, 'wb') as ofile: pickle.dump(self.create_install_data(), ofile) @@ -1386,7 +1462,9 @@ class Backend: bindir = Path(prefix, self.environment.get_bindir()) libdir = Path(prefix, self.environment.get_libdir()) incdir = Path(prefix, self.environment.get_includedir()) - localedir = Path(prefix, self.environment.coredata.get_option(mesonlib.OptionKey('localedir'))) + _ldir = self.environment.coredata.get_option(mesonlib.OptionKey('localedir')) + assert isinstance(_ldir, str), 'for mypy' + localedir = Path(prefix, _ldir) dest_path = Path(prefix, outdir, Path(fname).name) if outdir else Path(prefix, fname) if bindir in dest_path.parents: return 'runtime' @@ -1556,7 +1634,7 @@ class Backend: i = InstallDataBase(srcabs, dstabs, m.get_custom_install_mode(), m.subproject, tag='man') d.man.append(i) - def generate_data_install(self, d: InstallData): + def generate_data_install(self, d: InstallData) -> None: data = self.build.get_data() srcdir = self.environment.get_source_dir() builddir = self.environment.get_build_dir() @@ -1615,11 +1693,9 @@ class Backend: source_list += [os.path.join(self.build_dir, j.get_subdir(), o) for o in j.get_outputs()] source_list = list(map(lambda x: os.path.normpath(x), source_list)) - compiler = [] + compiler: T.List[str] = [] if isinstance(target, build.CustomTarget): tmp_compiler = target.command - if not isinstance(compiler, list): - tmp_compiler = [compiler] for j in tmp_compiler: if isinstance(j, mesonlib.File): compiler += [j.absolute_path(self.source_dir, self.build_dir)] -- cgit v1.1