# Copyright 2023 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import subprocess import multiprocessing import functools def _convert_gn_sources_list_to_dict(gn_sources_list, build_dir): """ Given a list of gn sources, transform them into standard filepaths and place them in a dictionary. """ outpath_pattern = "//%s/" % build_dir gn_sources_dict = {} for line in gn_sources_list: fixedline = line.replace(outpath_pattern, "").replace("//", "../../").strip() gn_sources_dict[fixedline] = True return gn_sources_dict def _get_sources_for_gn_target(all_transitive_sources, target_name): """ Given a particular target, stores all the source files for that target in the given multiprocessing.Manager().dict(). Example input: Args: target_name: The name of a GN target e.g. '//base/allocator/partition_allocator:partition_alloc' all_transitive_sources: A multiprocess.Manager().dict(). Returns: Nothing, but at the end of this function's execution, args[1] will look like: { '//base/allocator/.../atomic_ref_count.h': True, '//base/allocator/.../bit_cast.h': True, ... }""" if target_name is not None: get_sources_command = ["gn", "desc", "out/release", target_name, "sources"] sources_output = subprocess.run(get_sources_command, check=False, capture_output=True) if sources_output.returncode != 0: # Some `gn desc` are expected to fail # (because there's no `sources` for them). pass else: for file in sources_output.stdout.decode(encoding='utf-8').split("\n"): all_transitive_sources[file] = True def _fetch_all_transitive_sources_for_gn_target(gn_target, build_dir): """Fetches a list of all transitive source dependencies for a GN target. For a given GN target and build directory, returns a list with all the *transitive sources* upon which that target depends. This list can be useful for constructing a CodeQL database, since that list will be the 'minimal set' of commands required to generate a database. Args: gn_target: The name of a GN target e.g. `//components:components_unittests`. build_dir: The relative path to a Chromium build directory e.g. `out/release`. Returns: A list of sources, for example: ['//base/allocator.../atomic_ref_count.h', '//base/allocator/.../partition_alloc_base/bit_cast.h', ... '//ui/platform_window/extensions/workspace_extension.cc', ...] """ get_deps_command = ["gn", "desc", build_dir, gn_target, "deps", "--all"] deps_output = subprocess.run(get_deps_command, check=True, capture_output=True) target_names = deps_output.stdout.decode(encoding='utf-8').split("\n") my_cpu_count = int(multiprocessing.cpu_count()) all_transitive_sources = multiprocessing.Manager().dict() with multiprocessing.Pool(my_cpu_count) as p: p.map(functools.partial(_get_sources_for_gn_target, all_transitive_sources), target_names[:25]) return all_transitive_sources.keys() def dictionary_of_all_transitive_sources(gn_target, build_dir): """Constructs a dictionary of all transitive GN source deps for a target. For a given GN target (e.g. `//components:components_unittests`) and the path to some build directory (e.g. `out/release`), outputs a list of all *transitive sources* for that GN target, in the form of a dictionary where each entry has the value True. Args: gn_target: The name of a GN target e.g. `//components:components_unittests`. build_dir: The relative path to a Chromium build directory e.g. `out/release`. Returns: A dictionary that maps sources to True, for example: {'//base/allocator.../atomic_ref_count.h': True, '//base/allocator/.../partition_alloc_base/bit_cast.h': True, ... '//ui/platform_window/extensions/workspace_extension.cc': True, ...} """ gn_sources_list = _fetch_all_transitive_sources_for_gn_target( gn_target, build_dir) return _convert_gn_sources_list_to_dict(gn_sources_list, build_dir)