#include <stddef.h>
#include <map>
#include <set>
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "gn/commands.h"
#include "gn/config_values_extractors.h"
#include "gn/deps_iterator.h"
#include "gn/filesystem_utils.h"
#include "gn/input_file.h"
#include "gn/item.h"
#include "gn/setup.h"
#include "gn/standard_out.h"
#include "gn/switches.h"
#include "gn/target.h"
#include "gn/unique_vector.h"
namespace commands {
namespace {
using TargetSet = TargetSet;
using TargetVector = std::vector<const Target*>;
using DepMap = std::multimap<const Target*, const Target*>;
void FillDepMap(Setup* setup, DepMap* dep_map) {
for (auto* target : setup->builder().GetAllResolvedTargets()) {
for (const auto& dep_pair : target->GetDeps(Target::DEPS_ALL))
dep_map->insert(std::make_pair(dep_pair.ptr, target));
for (const auto& validation_pair : target->validations())
dep_map->insert(std::make_pair(validation_pair.ptr, target));
}
}
size_t RecursivePrintTargetDeps(const DepMap& dep_map,
const Target* target,
TargetSet* seen_targets,
int indent_level);
size_t RecursivePrintTarget(const DepMap& dep_map,
const Target* target,
TargetSet* seen_targets,
int indent_level) {
std::string indent(indent_level * 2, ' ');
size_t count = 1;
OutputString(indent + target->label().GetUserVisibleName(
!target->settings()->is_default()));
bool print_children = true;
if (seen_targets) {
if (!seen_targets->add(target)) {
print_children = false;
if (dep_map.lower_bound(target) != dep_map.upper_bound(target))
OutputString("...");
}
}
OutputString("\n");
if (print_children) {
count += RecursivePrintTargetDeps(dep_map, target, seen_targets,
indent_level + 1);
}
return count;
}
size_t RecursivePrintTargetDeps(const DepMap& dep_map,
const Target* target,
TargetSet* seen_targets,
int indent_level) {
DepMap::const_iterator dep_begin = dep_map.lower_bound(target);
DepMap::const_iterator dep_end = dep_map.upper_bound(target);
size_t count = 0;
for (DepMap::const_iterator cur_dep = dep_begin; cur_dep != dep_end;
cur_dep++) {
count += RecursivePrintTarget(dep_map, cur_dep->second, seen_targets,
indent_level);
}
return count;
}
void RecursiveCollectChildRefs(const DepMap& dep_map,
const Target* target,
TargetSet* results);
void RecursiveCollectRefs(const DepMap& dep_map,
const Target* target,
TargetSet* results) {
if (!results->add(target))
return;
RecursiveCollectChildRefs(dep_map, target, results);
}
void RecursiveCollectChildRefs(const DepMap& dep_map,
const Target* target,
TargetSet* results) {
DepMap::const_iterator dep_begin = dep_map.lower_bound(target);
DepMap::const_iterator dep_end = dep_map.upper_bound(target);
for (DepMap::const_iterator cur_dep = dep_begin; cur_dep != dep_end;
cur_dep++)
RecursiveCollectRefs(dep_map, cur_dep->second, results);
}
bool TargetReferencesConfig(const Target* target, const Config* config) {
for (const LabelConfigPair& cur : target->configs()) {
if (cur.ptr == config)
return true;
}
for (const LabelConfigPair& cur : target->public_configs()) {
if (cur.ptr == config)
return true;
}
return false;
}
void GetTargetsReferencingConfig(Setup* setup,
const std::vector<const Target*>& all_targets,
const Config* config,
bool default_toolchain_only,
UniqueVector<const Target*>* matches) {
Label default_toolchain = setup->loader()->default_toolchain_label();
for (auto* target : all_targets) {
if (default_toolchain_only) {
if (target->label().GetToolchainLabel() != default_toolchain)
continue;
}
if (TargetReferencesConfig(target, config))
matches->push_back(target);
}
}
size_t DoTreeOutput(const DepMap& dep_map,
const UniqueVector<const Target*>& implicit_target_matches,
const UniqueVector<const Target*>& explicit_target_matches,
bool all) {
TargetSet seen_targets;
size_t count = 0;
for (const Target* target : implicit_target_matches) {
if (all)
count += RecursivePrintTargetDeps(dep_map, target, nullptr, 0);
else
count += RecursivePrintTargetDeps(dep_map, target, &seen_targets, 0);
}
for (const Target* target : implicit_target_matches) {
if (all)
count += RecursivePrintTarget(dep_map, target, nullptr, 0);
else
count += RecursivePrintTarget(dep_map, target, &seen_targets, 0);
}
return count;
}
size_t DoAllListOutput(
const DepMap& dep_map,
const UniqueVector<const Target*>& implicit_target_matches,
const UniqueVector<const Target*>& explicit_target_matches) {
TargetSet results;
for (const Target* target : implicit_target_matches)
RecursiveCollectChildRefs(dep_map, target, &results);
for (const Target* target : explicit_target_matches) {
results.insert(target);
RecursiveCollectChildRefs(dep_map, target, &results);
}
FilterAndPrintTargetSet(false, results);
return results.size();
}
size_t DoDirectListOutput(
const DepMap& dep_map,
const UniqueVector<const Target*>& implicit_target_matches,
const UniqueVector<const Target*>& explicit_target_matches) {
TargetSet results;
for (const Target* target : implicit_target_matches) {
DepMap::const_iterator dep_begin = dep_map.lower_bound(target);
DepMap::const_iterator dep_end = dep_map.upper_bound(target);
for (DepMap::const_iterator cur_dep = dep_begin; cur_dep != dep_end;
cur_dep++)
results.insert(cur_dep->second);
}
for (const Target* target : explicit_target_matches)
results.insert(target);
FilterAndPrintTargetSet(false, results);
return results.size();
}
}
const char kRefs[] = "refs";
const char kRefs_HelpShort[] = "refs: Find stuff referencing a target or file.";
const char kRefs_Help[] =
R"(gn refs
gn refs <out_dir> (<label_pattern>|<label>|<file>|@<response_file>)* [--all]
[--default-toolchain] [--as=...] [--testonly=...] [--type=...]
Finds reverse dependencies (which targets reference something). The input is
a list containing:
- Target label: The result will be which targets depend on it.
- Config label: The result will be which targets list the given config in
its "configs" or "public_configs" list.
- Label pattern: The result will be which targets depend on any target
matching the given pattern. Patterns will not match configs. These are not
general regular expressions, see "gn help label_pattern" for details.
- File name: The result will be which targets list the given file in its
"inputs", "sources", "public", "data", or "outputs". Any input that does
not contain wildcards and does not match a target or a config will be
treated as a file.
- Response file: If the input starts with an "@", it will be interpreted as
a path to a file containing a list of labels or file names, one per line.
This allows us to handle long lists of inputs without worrying about
command line limits.
Options
--all
When used without --tree, will recurse and display all unique
dependencies of the given targets. For example, if the input is a target,
this will output all targets that depend directly or indirectly on the
input. If the input is a file, this will output all targets that depend
directly or indirectly on that file.
When used with --tree, turns off eliding to show a complete tree.
)"
TARGET_PRINTING_MODE_COMMAND_LINE_HELP "\n" DEFAULT_TOOLCHAIN_SWITCH_HELP
R"(
-q
Quiet. If nothing matches, don't print any output. Without this option, if
there are no matches there will be an informational message printed which
might interfere with scripts processing the output.
)"
TARGET_TESTONLY_FILTER_COMMAND_LINE_HELP
R"(
--tree
Outputs a reverse dependency tree from the given target. Duplicates will
be elided. Combine with --all to see a full dependency tree.
Tree output can not be used with the filtering or output flags: --as,
--type, --testonly.
)"
TARGET_TYPE_FILTER_COMMAND_LINE_HELP
R"(
--relation=(source|public|input|data|script|output)
Restricts output to targets which refer to input files by a specific
relation. Defaults to any relation. Can be provided multiple times to
include multiple relations.
)"
R"(
Examples (target input)
gn refs out/Debug //gn:gn
Find all targets depending on the given exact target name.
gn refs out/Debug //base:i18n --as=buildfile | xargs gvim
Edit all .gn files containing references to //base:i18n
gn refs out/Debug //base --all
List all targets depending directly or indirectly on //base:base.
gn refs out/Debug "//base/*"
List all targets depending directly on any target in //base or
its subdirectories.
gn refs out/Debug "//base:*"
List all targets depending directly on any target in
//base/BUILD.gn.
gn refs out/Debug //base --tree
Print a reverse dependency tree of //base:base
Examples (file input)
gn refs out/Debug //base/macros.h
Print target(s) listing //base/macros.h as a source.
gn refs out/Debug //base/macros.h --tree
Display a reverse dependency tree to get to the given file. This
will show how dependencies will reference that file.
gn refs out/Debug //base/macros.h //base/at_exit.h --all
Display all unique targets with some dependency path to a target
containing either of the given files as a source.
gn refs out/Debug //base/macros.h --testonly=true --type=executable
--all --as=output
Display the executable file names of all test executables
potentially affected by a change to the given file.
)";
int RunRefs(const std::vector<std::string>& args) {
if (args.size() <= 1) {
Err(Location(), "Unknown command format. See \"gn help refs\"",
"Usage: \"gn refs <out_dir> (<label_pattern>|<file>)*\"")
.PrintToStdout();
return 1;
}
const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
bool tree = cmdline->HasSwitch("tree");
bool all = cmdline->HasSwitch("all");
UniqueVector<HowTargetContainsFile> include_relations;
for (const std::string& relation :
cmdline->GetSwitchValueStrings("relation")) {
if (relation == "source") {
include_relations.push_back(HowTargetContainsFile::kSources);
} else if (relation == "public") {
include_relations.push_back(HowTargetContainsFile::kPublic);
} else if (relation == "input") {
include_relations.push_back(HowTargetContainsFile::kInputs);
} else if (relation == "data") {
include_relations.push_back(HowTargetContainsFile::kData);
} else if (relation == "script") {
include_relations.push_back(HowTargetContainsFile::kScript);
} else if (relation == "output") {
include_relations.push_back(HowTargetContainsFile::kOutput);
} else {
Err(Location(), "Unknown relation: " + relation).PrintToStdout();
return 1;
}
}
bool default_toolchain_only = cmdline->HasSwitch(switches::kDefaultToolchain);
Setup* setup = new Setup;
if (!setup->DoSetup(args[0], false) || !setup->Run())
return 1;
std::vector<std::string> inputs;
for (size_t i = 1; i < args.size(); i++) {
if (args[i][0] == '@') {
std::string contents;
bool ret =
base::ReadFileToString(UTF8ToFilePath(args[i].substr(1)), &contents);
if (!ret) {
Err(Location(), "Response file " + args[i].substr(1) + " not found.")
.PrintToStdout();
return 1;
}
for (const std::string& line : base::SplitString(
contents, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
if (!line.empty())
inputs.push_back(line);
}
} else {
inputs.push_back(args[i]);
}
}
UniqueVector<const Target*> target_matches;
UniqueVector<const Config*> config_matches;
UniqueVector<const Toolchain*> toolchain_matches;
UniqueVector<SourceFile> file_matches;
if (!ResolveFromCommandLineInput(setup, inputs, default_toolchain_only,
&target_matches, &config_matches,
&toolchain_matches, &file_matches))
return 1;
std::vector<const Target*> all_targets =
setup->builder().GetAllResolvedTargets();
UniqueVector<const Target*> explicit_target_matches;
for (const auto& file : file_matches) {
std::vector<TargetContainingFile> target_containing;
GetTargetsContainingFile(setup, all_targets, file, default_toolchain_only,
&target_containing);
for (const TargetContainingFile& pair : target_containing) {
if (!include_relations.empty() &&
!include_relations.Contains(pair.second)) {
continue;
}
explicit_target_matches.push_back(pair.first);
}
}
for (auto* config : config_matches) {
GetTargetsReferencingConfig(setup, all_targets, config,
default_toolchain_only,
&explicit_target_matches);
}
bool quiet = cmdline->HasSwitch("q");
if (!quiet && config_matches.empty() && explicit_target_matches.empty() &&
target_matches.empty()) {
OutputString("The input matches no targets, configs, or files.\n",
DECORATION_YELLOW);
return 1;
}
DepMap dep_map;
FillDepMap(setup, &dep_map);
size_t cnt = 0;
if (tree)
cnt = DoTreeOutput(dep_map, target_matches, explicit_target_matches, all);
else if (all)
cnt = DoAllListOutput(dep_map, target_matches, explicit_target_matches);
else
cnt = DoDirectListOutput(dep_map, target_matches, explicit_target_matches);
if (!quiet && cnt == 0)
OutputString("Nothing references this.\n", DECORATION_YELLOW);
return 0;
}
}