diff options
Diffstat (limited to 'gn/tools/gn/command_refs.cc')
-rw-r--r-- | gn/tools/gn/command_refs.cc | 498 |
1 files changed, 498 insertions, 0 deletions
diff --git a/gn/tools/gn/command_refs.cc b/gn/tools/gn/command_refs.cc new file mode 100644 index 00000000000..92ecf10376b --- /dev/null +++ b/gn/tools/gn/command_refs.cc @@ -0,0 +1,498 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#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 "tools/gn/commands.h" +#include "tools/gn/config_values_extractors.h" +#include "tools/gn/deps_iterator.h" +#include "tools/gn/filesystem_utils.h" +#include "tools/gn/input_file.h" +#include "tools/gn/item.h" +#include "tools/gn/setup.h" +#include "tools/gn/standard_out.h" +#include "tools/gn/switches.h" +#include "tools/gn/target.h" + +namespace commands { + +namespace { + +typedef std::set<const Target*> TargetSet; +typedef std::vector<const Target*> TargetVector; + +// Maps targets to the list of targets that depend on them. +typedef std::multimap<const Target*, const Target*> DepMap; + +// Populates the reverse dependency map for the targets in the Setup. +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)); + } +} + +// Forward declaration for function below. +size_t RecursivePrintTargetDeps(const DepMap& dep_map, + const Target* target, + TargetSet* seen_targets, + int indent_level); + +// Prints the target and its dependencies in tree form. If the set is non-null, +// new targets encountered will be added to the set, and if a ref is in the set +// already, it will not be recused into. When the set is null, all refs will be +// printed. +// +// Returns the number of items printed. +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; + + // Only print the toolchain for non-default-toolchain targets. + OutputString(indent + target->label().GetUserVisibleName( + !target->settings()->is_default())); + + bool print_children = true; + if (seen_targets) { + if (seen_targets->find(target) == seen_targets->end()) { + // New target, mark it visited. + seen_targets->insert(target); + } else { + // Already seen. + print_children = false; + // Only print "..." if something is actually elided, which means that + // the current target has children. + 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; +} + +// Prints refs of the given target (not the target itself). See +// RecursivePrintTarget. +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); + +// Recursively finds all targets that reference the given one, and additionally +// adds the current one to the list. +void RecursiveCollectRefs(const DepMap& dep_map, + const Target* target, + TargetSet* results) { + if (results->find(target) != results->end()) + return; // Already found this target. + results->insert(target); + RecursiveCollectChildRefs(dep_map, target, results); +} + +// Recursively finds all targets that reference the given one. +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 TargetContainsFile(const Target* target, const SourceFile& file) { + for (const auto& cur_file : target->sources()) { + if (cur_file == file) + return true; + } + for (const auto& cur_file : target->public_headers()) { + if (cur_file == file) + return true; + } + for (ConfigValuesIterator iter(target); !iter.done(); iter.Next()) { + for (const auto& cur_file : iter.cur().inputs()) { + if (cur_file == file) + return true; + } + } + for (const auto& cur_file : target->data()) { + if (cur_file == file.value()) + return true; + if (cur_file.back() == '/' && + base::StartsWith(file.value(), cur_file, base::CompareCase::SENSITIVE)) + return true; + } + + if (target->action_values().script().value() == file.value()) + return true; + + std::vector<SourceFile> output_sources; + target->action_values().GetOutputsAsSourceFiles(target, &output_sources); + for (const auto& cur_file : output_sources) { + if (cur_file == file) + return true; + } + + for (const auto& cur_file : target->computed_outputs()) { + if (cur_file.AsSourceFile(target->settings()->build_settings()) == file) + return true; + } + return false; +} + +void GetTargetsContainingFile(Setup* setup, + const std::vector<const Target*>& all_targets, + const SourceFile& file, + bool all_toolchains, + UniqueVector<const Target*>* matches) { + Label default_toolchain = setup->loader()->default_toolchain_label(); + for (auto* target : all_targets) { + if (!all_toolchains) { + // Only check targets in the default toolchain. + if (target->label().GetToolchainLabel() != default_toolchain) + continue; + } + if (TargetContainsFile(target, file)) + matches->push_back(target); + } +} + +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 all_toolchains, + UniqueVector<const Target*>* matches) { + Label default_toolchain = setup->loader()->default_toolchain_label(); + for (auto* target : all_targets) { + if (!all_toolchains) { + // Only check targets in the default toolchain. + if (target->label().GetToolchainLabel() != default_toolchain) + continue; + } + if (TargetReferencesConfig(target, config)) + matches->push_back(target); + } +} + +// Returns the number of matches printed. +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; + + // Implicit targets don't get printed themselves. + 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); + } + + // Explicit targets appear in the output. + 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; +} + +// Returns the number of matches printed. +size_t DoAllListOutput( + const DepMap& dep_map, + const UniqueVector<const Target*>& implicit_target_matches, + const UniqueVector<const Target*>& explicit_target_matches) { + // Output recursive dependencies, uniquified and flattened. + TargetSet results; + + for (const Target* target : implicit_target_matches) + RecursiveCollectChildRefs(dep_map, target, &results); + for (const Target* target : explicit_target_matches) { + // Explicit targets also get added to the output themselves. + results.insert(target); + RecursiveCollectChildRefs(dep_map, target, &results); + } + + FilterAndPrintTargetSet(false, results); + return results.size(); +} + +// Returns the number of matches printed. +size_t DoDirectListOutput( + const DepMap& dep_map, + const UniqueVector<const Target*>& implicit_target_matches, + const UniqueVector<const Target*>& explicit_target_matches) { + TargetSet results; + + // Output everything that refers to the implicit ones. + 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); + } + + // And just output the explicit ones directly (these are the target matches + // when referring to what references a file or config). + for (const Target* target : explicit_target_matches) + results.insert(target); + + FilterAndPrintTargetSet(false, results); + return results.size(); +} + +} // namespace + +const char kRefs[] = "refs"; +const char kRefs_HelpShort[] = "refs: Find stuff referencing a target or file."; +const char kRefs_Help[] = + R"(gn refs <out_dir> (<label_pattern>|<label>|<file>|@<response_file>)* + [--all] [--all-toolchains] [--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. +)" + + ALL_TOOLCHAINS_SWITCH_HELP "\n" TARGET_PRINTING_MODE_COMMAND_LINE_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"( + +Examples (target input) + + gn refs out/Debug //tools/gn:gn + Find all targets depending on the given exact target name. + + gn refs out/Debug //base:i18n --as=buildfiles | 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(), "You're holding it wrong.", + "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"); + bool all_toolchains = cmdline->HasSwitch(switches::kAllToolchains); + + Setup* setup = new Setup; + if (!setup->DoSetup(args[0], false) || !setup->Run()) + return 1; + + // The inputs are everything but the first arg (which is the build dir). + std::vector<std::string> inputs; + for (size_t i = 1; i < args.size(); i++) { + if (args[i][0] == '@') { + // The argument is as a path to a response file. + 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 { + // The argument is a label or a path. + inputs.push_back(args[i]); + } + } + + // Get the matches for the command-line input. + UniqueVector<const Target*> target_matches; + UniqueVector<const Config*> config_matches; + UniqueVector<const Toolchain*> toolchain_matches; + UniqueVector<SourceFile> file_matches; + if (!ResolveFromCommandLineInput(setup, inputs, all_toolchains, + &target_matches, &config_matches, + &toolchain_matches, &file_matches)) + return 1; + + // When you give a file or config as an input, you want the targets that are + // associated with it. We don't want to just append this to the + // target_matches, however, since these targets should actually be listed in + // the output, while for normal targets you don't want to see the inputs, + // only what refers to them. + std::vector<const Target*> all_targets = + setup->builder().GetAllResolvedTargets(); + UniqueVector<const Target*> explicit_target_matches; + for (const auto& file : file_matches) { + GetTargetsContainingFile(setup, all_targets, file, all_toolchains, + &explicit_target_matches); + } + for (auto* config : config_matches) { + GetTargetsReferencingConfig(setup, all_targets, config, all_toolchains, + &explicit_target_matches); + } + + // Tell the user if their input matches no files or labels. We need to check + // both that it matched no targets and no configs. File input will already + // have been converted to targets at this point. Configs will have been + // converted to targets also, but there could be no targets referencing the + // config, which is different than no config with that name. + 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; + } + + // Construct the reverse dependency tree. + 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 you ask for the references of a valid target, but that target has + // nothing referencing it, we'll get here without having printed anything. + if (!quiet && cnt == 0) + OutputString("Nothing references this.\n", DECORATION_YELLOW); + + return 0; +} + +} // namespace commands |