# 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, gn_path, build_dir,
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_path, "desc", build_dir, 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, gn_path):
"""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_path, "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,
gn_path, build_dir), target_names)
return all_transitive_sources.keys()
def dictionary_of_all_transitive_sources(gn_target, build_dir, gn_path):
"""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, gn_path)
return _convert_gn_sources_list_to_dict(gn_sources_list, build_dir)