diff --git a/analyzer/codechecker_analyzer/cli/analyze.py b/analyzer/codechecker_analyzer/cli/analyze.py index c805149d38..2f43f4a137 100644 --- a/analyzer/codechecker_analyzer/cli/analyze.py +++ b/analyzer/codechecker_analyzer/cli/analyze.py @@ -1012,13 +1012,15 @@ def is_checker_config_valid( def get_affected_file_paths( file_filters: List[str], - compile_commands: tu_collector.CompilationDB + compile_commands: tu_collector.CompilationDB, + jobs: int ) -> List[str]: """ Returns a list of source files for existing header file otherwise returns with the same file path expression. """ file_paths = [] # Use list to keep the order of the file paths. + dependencies: tu_collector.Dependencies = collections.defaultdict(set) for file_filter in file_filters: file_paths.append(str(Path(file_filter).resolve()) if '*' not in file_filter else file_filter) @@ -1027,7 +1029,7 @@ def get_affected_file_paths( file_filter.endswith(header_file_extensions): LOG.info("Get dependent source files for '%s'...", file_filter) dependent_sources = tu_collector.get_dependent_sources( - compile_commands, file_filter) + compile_commands, file_filter, dependencies, jobs) LOG.info("Get dependent source files for '%s' done.", file_filter) LOG.debug("Dependent source files: %s", @@ -1046,7 +1048,7 @@ def __get_skip_handlers(args, compile_commands) -> SkipListHandlers: skip_handlers = SkipListHandlers() if 'files' in args: source_file_paths = get_affected_file_paths( - args.files, compile_commands) + args.files, compile_commands, args.jobs) # Creates a skip file where all source files will be skipped except # the given source files and all the header files. diff --git a/tools/tu_collector/tu_collector/tu_collector.py b/tools/tu_collector/tu_collector/tu_collector.py index a5cbfdbfad..92b01a15e0 100755 --- a/tools/tu_collector/tu_collector/tu_collector.py +++ b/tools/tu_collector/tu_collector/tu_collector.py @@ -28,10 +28,11 @@ import subprocess import sys import zipfile +from multiprocessing import Pool from shutil import which from pathlib import Path -from typing import Iterable, Iterator, List, Optional, Set, Tuple, Union +from typing import Dict, Iterable, Iterator, List, Optional, Set, Tuple, Union if sys.version_info >= (3, 8): from typing import TypedDict # pylint: disable=no-name-in-module @@ -56,6 +57,7 @@ class CompileAction(TypedDict): CompilationDB = List[CompileAction] +Dependencies = Dict[str, Set[str]] def __random_string(length: int) -> str: @@ -472,21 +474,36 @@ def zip_tu_files( json.dumps(compilation_database, indent=2)) +def __get_dependent_headers_for_build_action(build_action: CompileAction + ) -> Tuple[str, Set[str]]: + """ Return the source file and dependent headers for a build action. """ + files, _ = get_dependent_headers( + build_action['command'], + build_action['directory']) + + source_file = os.path.join(build_action['directory'], + build_action['file']) + return source_file, files + + def get_dependent_sources( compilation_db: CompilationDB, - header_path: Optional[str] = None + header_path: Optional[str] = None, + dependencies: Optional[Dependencies] = None, + jobs: Optional[int] = None ) -> Set[str]: """ Get dependencies for each files in each translation unit. """ - dependencies = collections.defaultdict(set) - for build_action in compilation_db: - files, _ = get_dependent_headers( - build_action['command'], - build_action['directory']) - - source_file = os.path.join(build_action['directory'], - build_action['file']) - for f in files: - dependencies[f].add(source_file) + if dependencies is None: + dependencies = collections.defaultdict(set) + if jobs is None: + jobs = 1 + if not dependencies: + with Pool(jobs) as p: + results = list(p.map(__get_dependent_headers_for_build_action, + compilation_db)) + for source_file, files in results: + for f in files: + dependencies[f].add(source_file) pattern = None if header_path: