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 d5b8a0734c30..3a61370a92b6 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 @@ def __init__(self, pickle_file: str, outfile: str, sources: T.List[str]): 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 @@ def scan_fortran_file(self, fname: str) -> None: # 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 @@ def scan_fortran_file(self, fname: str) -> None: # 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 @@ def scan_cpp_file(self, fname: str) -> None: 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 f7ba6468a495..e808b0c92eab 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 @@ def ninja_quote(text: str, is_build_line: bool = False) -> str: 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 e808b0c92eab..4987d75a9087 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -2782,10 +2782,11 @@ def generate_llvm_ir_compile(self, target, src): 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 4987d75a9087..d729d19892a8 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -855,8 +855,8 @@ def generate_target(self, target): 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 @@ def generate_target(self, target): # 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 @@ def should_use_dyndeps_for_target(self, target: 'build.BuildTarget') -> bool: 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 @@ def generate_dependency_scan_target(self, target: build.BuildTarget, compiled_so 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 @@ def get_link_debugfile_name(self, linker, target) -> T.Optional[str]: 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 @@ def generate_single_compile(self, target: build.BuildTarget, src, 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 d729d19892a8..77e1b04db465 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 @@ def generate_dependency_scan_target(self, target: build.BuildTarget, 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 3a61370a92b6..c0ac09b52ef1 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_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 @@ def scan(self) -> int: 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 77e1b04db465..462ea27fe268 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 @@ def generate_dependency_scan_target(self, target: build.BuildTarget, 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 @@ def generate_dependency_scan_target(self, target: build.BuildTarget, 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 c0ac09b52ef1..79934fb5fae4 100644 --- a/mesonbuild/scripts/depscan.py +++ b/mesonbuild/scripts/depscan.py @@ -9,13 +9,12 @@ 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 @@ def __init__(self, pickle_file: str, outfile: 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: - 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 @@ def objname_for(self, src: str) -> str: 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 @@ def module_name_for(self, src: str) -> str: 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 @@ def scan(self) -> int: 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 79934fb5fae4..44e805447713 100644 --- a/mesonbuild/scripts/depscan.py +++ b/mesonbuild/scripts/depscan.py @@ -107,11 +107,6 @@ def scan_cpp_file(self, fname: str) -> None: 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 @@ def scan(self) -> int: 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 462ea27fe268..a2e7c2bf58d0 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -1103,8 +1103,16 @@ def generate_dependency_scan_target(self, target: build.BuildTarget, 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 a2e7c2bf58d0..88e880bca4e5 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -494,6 +494,7 @@ def __init__(self, build: T.Optional[build.Build], interpreter: T.Optional[Inter 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 @@ def generate(self, capture: bool = False, vslite_ctx: dict = None) -> T.Optional 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 @@ def generate_dependency_scan_target(self, target: build.BuildTarget, 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('\\', '/')