From 458827a5b2fabe7d15f23f39f97626efe6f5a5fc Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Mon, 20 Nov 2023 23:29:20 -0800 Subject: [PATCH 1/9] depscan: use a defaultdict to simplify append action --- mesonbuild/scripts/depscan.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/mesonbuild/scripts/depscan.py b/mesonbuild/scripts/depscan.py index d5b8a0734..3a61370a9 100644 --- a/mesonbuild/scripts/depscan.py +++ b/mesonbuild/scripts/depscan.py @@ -1,8 +1,10 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright 2020 The Meson development team +# Copyright © 2023 Intel Corporation from __future__ import annotations +import collections import json import os import pathlib @@ -37,7 +39,7 @@ class DependencyScanner: self.sources = sources self.provided_by: T.Dict[str, str] = {} self.exports: T.Dict[str, str] = {} - self.needs: T.Dict[str, T.List[str]] = {} + self.needs: collections.defaultdict[str, T.List[str]] = collections.defaultdict(list) self.sources_with_exports: T.List[str] = [] def scan_file(self, fname: str) -> None: @@ -63,10 +65,7 @@ class DependencyScanner: # In Fortran you have an using declaration also for the module # you define in the same file. Prevent circular dependencies. if needed not in modules_in_this_file: - if fname in self.needs: - self.needs[fname].append(needed) - else: - self.needs[fname] = [needed] + self.needs[fname].append(needed) if export_match: exported_module = export_match.group(1).lower() assert exported_module not in modules_in_this_file @@ -97,10 +96,7 @@ class DependencyScanner: # submodule (a1:a2) a3 <- requires a1@a2.smod # # a3 does not depend on the a1 parent module directly, only transitively. - if fname in self.needs: - self.needs[fname].append(parent_module_name_full) - else: - self.needs[fname] = [parent_module_name_full] + self.needs[fname].append(parent_module_name_full) def scan_cpp_file(self, fname: str) -> None: fpath = pathlib.Path(fname) @@ -109,10 +105,7 @@ class DependencyScanner: export_match = CPP_EXPORT_RE.match(line) if import_match: needed = import_match.group(1) - if fname in self.needs: - self.needs[fname].append(needed) - else: - self.needs[fname] = [needed] + self.needs[fname].append(needed) if export_match: exported_module = export_match.group(1) if exported_module in self.provided_by: From 2812b21de56c578ec552e182a071dbaa7f2463ee Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Mon, 20 Nov 2023 23:31:13 -0800 Subject: [PATCH 2/9] backend/ninja: use A dataclass for TargetDependencyScannerInfo --- mesonbuild/backend/ninjabackend.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index f7ba6468a..e808b0c92 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -1,5 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright 2012-2017 The Meson development team +# Copyright © 2023 Intel Corporation from __future__ import annotations @@ -134,10 +135,20 @@ Please report this error with a test case to the Meson bug tracker.''' raise MesonException(errmsg) return quote_re.sub(r'$\g<0>', text) + +@dataclass class TargetDependencyScannerInfo: - def __init__(self, private_dir: str, source2object: T.Dict[str, str]): - self.private_dir = private_dir - self.source2object = source2object + + """Information passed to the depscanner about a target. + + :param private_dir: The private scratch directory for the target. + :param source2object: A mapping of source file names to the objects that + will be created from them. + """ + + private_dir: str + source2object: T.Dict[str, str] + @unique class Quoting(Enum): From 875a9b789f2f53fe0bd35d2bbe8277e8294e59f6 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Mon, 20 Nov 2023 23:40:36 -0800 Subject: [PATCH 3/9] backend/ninja: remove duplicate isinstance() check --- mesonbuild/backend/ninjabackend.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index e808b0c92..4987d75a9 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -2782,10 +2782,11 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) rel_obj = os.path.join(self.get_target_private_dir(target), obj_basename) rel_obj += '.' + self.environment.machines[target.for_machine].get_object_suffix() commands += self.get_compile_debugfile_args(compiler, target, rel_obj) - if isinstance(src, File) and src.is_built: - rel_src = src.fname - elif isinstance(src, File): - rel_src = src.rel_to_builddir(self.build_to_src) + if isinstance(src, File): + if src.is_built: + rel_src = src.fname + else: + rel_src = src.rel_to_builddir(self.build_to_src) else: raise InvalidArguments(f'Invalid source type: {src!r}') # Write the Ninja build command From 934c9074bdd81c31a67d87a290247007332cdbcf Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Mon, 20 Nov 2023 23:41:12 -0800 Subject: [PATCH 4/9] backend/ninja: add missing typing annotations --- mesonbuild/backend/ninjabackend.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 4987d75a9..d729d1989 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -855,8 +855,8 @@ class NinjaBackend(backends.Backend): self.generate_custom_target(target) if isinstance(target, build.RunTarget): self.generate_run_target(target) - compiled_sources = [] - source2object = {} + compiled_sources: T.List[str] = [] + source2object: T.Dict[str, str] = {} name = target.get_id() if name in self.processed_targets: return @@ -939,7 +939,7 @@ class NinjaBackend(backends.Backend): # this target's sources (generated sources and preexisting sources). # This will be set as dependencies of all the target's sources. At the # same time, also deal with generated sources that need to be compiled. - generated_source_files = [] + generated_source_files: T.List[File] = [] for rel_src in generated_sources.keys(): raw_src = File.from_built_relative(rel_src) if self.environment.is_source(rel_src): @@ -1084,7 +1084,10 @@ class NinjaBackend(backends.Backend): return False return True - def generate_dependency_scan_target(self, target: build.BuildTarget, compiled_sources, source2object, generated_source_files: T.List[mesonlib.File], + def generate_dependency_scan_target(self, target: build.BuildTarget, + compiled_sources: T.List[str], + source2object: T.Dict[str, str], + generated_source_files: T.List[mesonlib.File], object_deps: T.List['mesonlib.FileOrString']) -> None: if not self.should_use_dyndeps_for_target(target): return @@ -1113,7 +1116,7 @@ class NinjaBackend(backends.Backend): pickle.dump(scaninfo, p) self.add_build(elem) - def select_sources_to_scan(self, compiled_sources): + def select_sources_to_scan(self, compiled_sources: T.List[str]) -> T.List[str]: # in practice pick up C++ and Fortran files. If some other language # requires scanning (possibly Java to deal with inner class files) # then add them here. @@ -2763,7 +2766,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) def get_link_debugfile_args(self, linker, target): return linker.get_link_debugfile_args(self.get_target_debug_filename(target)) - def generate_llvm_ir_compile(self, target, src): + def generate_llvm_ir_compile(self, target, src: mesonlib.FileOrString): base_proxy = target.get_options() compiler = get_compiler_for_source(target.compilers.values(), src) commands = compiler.compiler_args() @@ -2925,7 +2928,8 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) is_generated: bool = False, header_deps=None, order_deps: T.Optional[T.List['mesonlib.FileOrString']] = None, extra_args: T.Optional[T.List[str]] = None, - unity_sources: T.Optional[T.List[mesonlib.FileOrString]] = None) -> None: + unity_sources: T.Optional[T.List[mesonlib.FileOrString]] = None, + ) -> T.Tuple[str, str]: """ Compiles C/C++, ObjC/ObjC++, Fortran, and D sources """ From fae1363bd3d4b6aec4b2de41f58385e277d91eca Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Mon, 11 Dec 2023 10:01:10 -0800 Subject: [PATCH 5/9] scripts/depscan: combine pickle and JSON data into a single file We don't need to write and pass two separate files to the depscanner, I've used the pickle because the pickle serializer/deserializer should be faster than JSON, thought I haven't tested. --- mesonbuild/backend/ninjabackend.py | 19 ++++++++----------- mesonbuild/scripts/depscan.py | 13 +++++-------- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index d729d1989..77e1b04db 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -144,10 +144,12 @@ class TargetDependencyScannerInfo: :param private_dir: The private scratch directory for the target. :param source2object: A mapping of source file names to the objects that will be created from them. + :param sources: A list of all the sources in this target """ private_dir: str source2object: T.Dict[str, str] + sources: T.List[str] @unique @@ -1095,25 +1097,20 @@ class NinjaBackend(backends.Backend): pickle_base = target.name + '.dat' pickle_file = os.path.join(self.get_target_private_dir(target), pickle_base).replace('\\', '/') pickle_abs = os.path.join(self.get_target_private_dir_abs(target), pickle_base).replace('\\', '/') - json_abs = os.path.join(self.get_target_private_dir_abs(target), f'{target.name}-deps.json').replace('\\', '/') rule_name = 'depscan' scan_sources = self.select_sources_to_scan(compiled_sources) - # Dump the sources as a json list. This avoids potential problems where - # the number of sources passed to depscan exceeds the limit imposed by - # the OS. - with open(json_abs, 'w', encoding='utf-8') as f: - json.dump(scan_sources, f) - elem = NinjaBuildElement(self.all_outputs, depscan_file, rule_name, json_abs) - elem.add_item('picklefile', pickle_file) + scaninfo = TargetDependencyScannerInfo( + self.get_target_private_dir(target), source2object, scan_sources) + with open(pickle_abs, 'wb') as p: + pickle.dump(scaninfo, p) + + elem = NinjaBuildElement(self.all_outputs, depscan_file, rule_name, pickle_file) # Add any generated outputs to the order deps of the scan target, so # that those sources are present for g in generated_source_files: elem.orderdeps.add(g.relative_name()) elem.orderdeps.update(object_deps) - scaninfo = TargetDependencyScannerInfo(self.get_target_private_dir(target), source2object) - with open(pickle_abs, 'wb') as p: - pickle.dump(scaninfo, p) self.add_build(elem) def select_sources_to_scan(self, compiled_sources: T.List[str]) -> T.List[str]: diff --git a/mesonbuild/scripts/depscan.py b/mesonbuild/scripts/depscan.py index 3a61370a9..c0ac09b52 100644 --- a/mesonbuild/scripts/depscan.py +++ b/mesonbuild/scripts/depscan.py @@ -5,7 +5,6 @@ from __future__ import annotations import collections -import json import os import pathlib import pickle @@ -32,11 +31,11 @@ FORTRAN_SUBMOD_RE = re.compile(FORTRAN_SUBMOD_PAT, re.IGNORECASE) FORTRAN_USE_RE = re.compile(FORTRAN_USE_PAT, re.IGNORECASE) class DependencyScanner: - def __init__(self, pickle_file: str, outfile: str, sources: T.List[str]): + def __init__(self, pickle_file: str, outfile: str): with open(pickle_file, 'rb') as pf: self.target_data: TargetDependencyScannerInfo = pickle.load(pf) self.outfile = outfile - self.sources = sources + self.sources = self.target_data.sources self.provided_by: T.Dict[str, str] = {} self.exports: T.Dict[str, str] = {} self.needs: collections.defaultdict[str, T.List[str]] = collections.defaultdict(list) @@ -183,9 +182,7 @@ class DependencyScanner: return 0 def run(args: T.List[str]) -> int: - assert len(args) == 3, 'got wrong number of arguments!' - pickle_file, outfile, jsonfile = args - with open(jsonfile, encoding='utf-8') as f: - sources = json.load(f) - scanner = DependencyScanner(pickle_file, outfile, sources) + assert len(args) == 2, 'got wrong number of arguments!' + outfile, pickle_file = args + scanner = DependencyScanner(pickle_file, outfile) return scanner.scan() From 433117fc5a604e34a847b3480bbec15e59585b96 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Mon, 20 Nov 2023 23:56:06 -0800 Subject: [PATCH 6/9] scripts/depscan: pick language once, at configure time We already have to decide whether to scan a file at configure time, so we don't want to have to do it again at compile time, every time the depscan rule is run. We can do this by saving and passing the language to use in the pickle, so depscan doesn't have to re-calculate it. As an added bonus, this removes an import from depscan --- mesonbuild/backend/ninjabackend.py | 21 +++++++++--------- mesonbuild/scripts/depscan.py | 34 +++++++++++------------------- 2 files changed, 22 insertions(+), 33 deletions(-) diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 77e1b04db..462ea27fe 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -144,12 +144,13 @@ class TargetDependencyScannerInfo: :param private_dir: The private scratch directory for the target. :param source2object: A mapping of source file names to the objects that will be created from them. - :param sources: A list of all the sources in this target + :param sources: a list of sources mapping them to the language rules to use + to scan them. """ private_dir: str source2object: T.Dict[str, str] - sources: T.List[str] + sources: T.List[T.Tuple[str, Literal['cpp', 'fortran']]] @unique @@ -1098,7 +1099,7 @@ class NinjaBackend(backends.Backend): pickle_file = os.path.join(self.get_target_private_dir(target), pickle_base).replace('\\', '/') pickle_abs = os.path.join(self.get_target_private_dir_abs(target), pickle_base).replace('\\', '/') rule_name = 'depscan' - scan_sources = self.select_sources_to_scan(compiled_sources) + scan_sources = list(self.select_sources_to_scan(compiled_sources)) scaninfo = TargetDependencyScannerInfo( self.get_target_private_dir(target), source2object, scan_sources) @@ -1113,19 +1114,17 @@ class NinjaBackend(backends.Backend): elem.orderdeps.update(object_deps) self.add_build(elem) - def select_sources_to_scan(self, compiled_sources: T.List[str]) -> T.List[str]: + def select_sources_to_scan(self, compiled_sources: T.List[str] + ) -> T.Iterable[T.Tuple[str, Literal['cpp', 'fortran']]]: # in practice pick up C++ and Fortran files. If some other language # requires scanning (possibly Java to deal with inner class files) # then add them here. - all_suffixes = set(compilers.lang_suffixes['cpp']) | set(compilers.lang_suffixes['fortran']) - selected_sources = [] for source in compiled_sources: ext = os.path.splitext(source)[1][1:] - if ext != 'C': - ext = ext.lower() - if ext in all_suffixes: - selected_sources.append(source) - return selected_sources + if ext.lower() in compilers.lang_suffixes['cpp'] or ext == 'C': + yield source, 'cpp' + elif ext.lower() in compilers.lang_suffixes['fortran']: + yield source, 'fortran' def process_target_dependencies(self, target): for t in target.get_dependencies(): diff --git a/mesonbuild/scripts/depscan.py b/mesonbuild/scripts/depscan.py index c0ac09b52..79934fb5f 100644 --- a/mesonbuild/scripts/depscan.py +++ b/mesonbuild/scripts/depscan.py @@ -9,13 +9,12 @@ import os import pathlib import pickle import re -import sys import typing as T from ..backend.ninjabackend import ninja_quote -from ..compilers.compilers import lang_suffixes if T.TYPE_CHECKING: + from typing_extensions import Literal from ..backend.ninjabackend import TargetDependencyScannerInfo CPP_IMPORT_RE = re.compile(r'\w*import ([a-zA-Z0-9]+);') @@ -41,16 +40,11 @@ class DependencyScanner: self.needs: collections.defaultdict[str, T.List[str]] = collections.defaultdict(list) self.sources_with_exports: T.List[str] = [] - def scan_file(self, fname: str) -> None: - suffix = os.path.splitext(fname)[1][1:] - if suffix != 'C': - suffix = suffix.lower() - if suffix in lang_suffixes['fortran']: + def scan_file(self, fname: str, lang: Literal['cpp', 'fortran']) -> None: + if lang == 'fortran': self.scan_fortran_file(fname) - elif suffix in lang_suffixes['cpp']: - self.scan_cpp_file(fname) else: - sys.exit(f'Can not scan files with suffix .{suffix}.') + self.scan_cpp_file(fname) def scan_fortran_file(self, fname: str) -> None: fpath = pathlib.Path(fname) @@ -118,9 +112,8 @@ class DependencyScanner: assert isinstance(objname, str) return objname - def module_name_for(self, src: str) -> str: - suffix = os.path.splitext(src)[1][1:].lower() - if suffix in lang_suffixes['fortran']: + def module_name_for(self, src: str, lang: Literal['cpp', 'fortran']) -> str: + if lang == 'fortran': exported = self.exports[src] # Module foo:bar goes to a file name foo@bar.smod # Module Foo goes to a file name foo.mod @@ -130,23 +123,20 @@ class DependencyScanner: else: extension = 'mod' return os.path.join(self.target_data.private_dir, f'{namebase}.{extension}') - elif suffix in lang_suffixes['cpp']: - return '{}.ifc'.format(self.exports[src]) - else: - raise RuntimeError('Unreachable code.') + return '{}.ifc'.format(self.exports[src]) def scan(self) -> int: - for s in self.sources: - self.scan_file(s) + for s, lang in self.sources: + self.scan_file(s, lang) with open(self.outfile, 'w', encoding='utf-8') as ofile: ofile.write('ninja_dyndep_version = 1\n') - for src in self.sources: + for src, lang in self.sources: objfilename = self.objname_for(src) mods_and_submods_needed = [] module_files_generated = [] module_files_needed = [] if src in self.sources_with_exports: - module_files_generated.append(self.module_name_for(src)) + module_files_generated.append(self.module_name_for(src, lang)) if src in self.needs: for modname in self.needs[src]: if modname not in self.provided_by: @@ -159,7 +149,7 @@ class DependencyScanner: for modname in mods_and_submods_needed: provider_src = self.provided_by[modname] - provider_modfile = self.module_name_for(provider_src) + provider_modfile = self.module_name_for(provider_src, lang) # Prune self-dependencies if provider_src != src: module_files_needed.append(provider_modfile) From 3e9021a4c421d88501ee04aaaebb0e03f1f05540 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Tue, 21 Nov 2023 00:01:37 -0800 Subject: [PATCH 7/9] scripts/depscan: remove unnecessary function This basically existed for an assert which we don't need, as mypy would catch that issue for us anyway. Removing the function entirely has some small performance advantages --- mesonbuild/scripts/depscan.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/mesonbuild/scripts/depscan.py b/mesonbuild/scripts/depscan.py index 79934fb5f..44e805447 100644 --- a/mesonbuild/scripts/depscan.py +++ b/mesonbuild/scripts/depscan.py @@ -107,11 +107,6 @@ class DependencyScanner: self.provided_by[exported_module] = fname self.exports[fname] = exported_module - def objname_for(self, src: str) -> str: - objname = self.target_data.source2object[src] - assert isinstance(objname, str) - return objname - def module_name_for(self, src: str, lang: Literal['cpp', 'fortran']) -> str: if lang == 'fortran': exported = self.exports[src] @@ -131,7 +126,7 @@ class DependencyScanner: with open(self.outfile, 'w', encoding='utf-8') as ofile: ofile.write('ninja_dyndep_version = 1\n') for src, lang in self.sources: - objfilename = self.objname_for(src) + objfilename = self.target_data.source2object[src] mods_and_submods_needed = [] module_files_generated = [] module_files_needed = [] From 2f8d51c833546b048f453947278fe03f33b26e59 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Thu, 28 Mar 2024 13:57:18 -0700 Subject: [PATCH 8/9] backend/ninja: don't rewrite the pickle data if it hasn't changed Which prevents spurious rebuilds of dyndeps --- mesonbuild/backend/ninjabackend.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 462ea27fe..a2e7c2bf5 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -1103,8 +1103,16 @@ class NinjaBackend(backends.Backend): scaninfo = TargetDependencyScannerInfo( self.get_target_private_dir(target), source2object, scan_sources) - with open(pickle_abs, 'wb') as p: - pickle.dump(scaninfo, p) + + write = True + if os.path.exists(pickle_abs): + with open(pickle_abs, 'rb') as p: + old = pickle.load(p) + write = old != scaninfo + + if write: + with open(pickle_abs, 'wb') as p: + pickle.dump(scaninfo, p) elem = NinjaBuildElement(self.all_outputs, depscan_file, rule_name, pickle_file) # Add any generated outputs to the order deps of the scan target, so From 2171a017be671283c7d68b60efba4404bedf5e64 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Thu, 28 Mar 2024 16:38:14 -0700 Subject: [PATCH 9/9] backend/ninja: Don't run -t cleandead when using dyndeps There's a known ninja bug (https://github.com/ninja-build/ninja/issues/1952) that running this with dyndeps will result in Ninja deleting implicit outputs from the dyndeps, leading to pointless rebuilds. For reference, this is what CMake does as well. --- mesonbuild/backend/ninjabackend.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index a2e7c2bf5..88e880bca 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -494,6 +494,7 @@ class NinjaBackend(backends.Backend): self.created_llvm_ir_rule = PerMachine(False, False) self.rust_crates: T.Dict[str, RustCrate] = {} self.implicit_meson_outs = [] + self._uses_dyndeps = False def create_phony_target(self, dummy_outfile: str, rulename: str, phony_infilename: str) -> NinjaBuildElement: ''' @@ -669,7 +670,8 @@ class NinjaBackend(backends.Backend): os.replace(tempfilename, outfilename) mlog.cmd_ci_include(outfilename) # For CI debugging # Refresh Ninja's caches. https://github.com/ninja-build/ninja/pull/1685 - if mesonlib.version_compare(self.ninja_version, '>=1.10.0') and os.path.exists(os.path.join(self.environment.build_dir, '.ninja_log')): + # Cannot use when running with dyndeps: https://github.com/ninja-build/ninja/issues/1952 + if mesonlib.version_compare(self.ninja_version, '>=1.10.0') and os.path.exists(os.path.join(self.environment.build_dir, '.ninja_log')) and not self._uses_dyndeps: subprocess.call(self.ninja_command + ['-t', 'restat'], cwd=self.environment.build_dir) subprocess.call(self.ninja_command + ['-t', 'cleandead'], cwd=self.environment.build_dir) self.generate_compdb() @@ -1094,6 +1096,7 @@ class NinjaBackend(backends.Backend): object_deps: T.List['mesonlib.FileOrString']) -> None: if not self.should_use_dyndeps_for_target(target): return + self._uses_dyndeps = True depscan_file = self.get_dep_scan_file_for(target) pickle_base = target.name + '.dat' pickle_file = os.path.join(self.get_target_private_dir(target), pickle_base).replace('\\', '/')