| // 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 <map> |
| #include <set> |
| |
| #include "base/command_line.h" |
| #include "base/files/file_util.h" |
| #include "base/strings/string_split.h" |
| #include "tools/gn/commands.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/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 (const 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 (const auto& cur_file : target->inputs()) { |
| if (cur_file == file) |
| return true; |
| } |
| for (const auto& cur_file : target->data()) { |
| if (cur_file == file.value()) |
| return true; |
| } |
| |
| std::vector<SourceFile> outputs; |
| target->action_values().GetOutputsAsSourceFiles(target, &outputs); |
| for (const auto& cur_file : outputs) { |
| if (cur_file == 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 (const 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 (const 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[] = |
| "gn refs <out_dir> (<label_pattern>|<label>|<file>|@<response_file>)* " |
| "[--all]\n" |
| " [--all-toolchains] [--as=...] [--testonly=...] [--type=...]\n" |
| "\n" |
| " Finds reverse dependencies (which targets reference something). The\n" |
| " input is a list containing:\n" |
| "\n" |
| " - Target label: The result will be which targets depend on it.\n" |
| "\n" |
| " - Config label: The result will be which targets list the given\n" |
| " config in its \"configs\" or \"public_configs\" list.\n" |
| "\n" |
| " - Label pattern: The result will be which targets depend on any\n" |
| " target matching the given pattern. Patterns will not match\n" |
| " configs. These are not general regular expressions, see\n" |
| " \"gn help label_pattern\" for details.\n" |
| "\n" |
| " - File name: The result will be which targets list the given file in\n" |
| " its \"inputs\", \"sources\", \"public\", \"data\", or \"outputs\".\n" |
| " Any input that does not contain wildcards and does not match a\n" |
| " target or a config will be treated as a file.\n" |
| "\n" |
| " - Response file: If the input starts with an \"@\", it will be\n" |
| " interpreted as a path to a file containing a list of labels or\n" |
| " file names, one per line. This allows us to handle long lists\n" |
| " of inputs without worrying about command line limits.\n" |
| "\n" |
| "Options\n" |
| "\n" |
| " --all\n" |
| " When used without --tree, will recurse and display all unique\n" |
| " dependencies of the given targets. For example, if the input is\n" |
| " a target, this will output all targets that depend directly or\n" |
| " indirectly on the input. If the input is a file, this will output\n" |
| " all targets that depend directly or indirectly on that file.\n" |
| "\n" |
| " When used with --tree, turns off eliding to show a complete tree.\n" |
| "\n" |
| " --all-toolchains\n" |
| " Normally only inputs in the default toolchain will be included.\n" |
| " This switch will turn on matching all toolchains.\n" |
| "\n" |
| " For example, a file is in a target might be compiled twice:\n" |
| " once in the default toolchain and once in a secondary one. Without\n" |
| " this flag, only the default toolchain one will be matched and\n" |
| " printed (potentially with its recursive dependencies, depending on\n" |
| " the other options). With this flag, both will be printed\n" |
| " (potentially with both of their recursive dependencies).\n" |
| "\n" |
| TARGET_PRINTING_MODE_COMMAND_LINE_HELP |
| "\n" |
| " -q\n" |
| " Quiet. If nothing matches, don't print any output. Without this\n" |
| " option, if there are no matches there will be an informational\n" |
| " message printed which might interfere with scripts processing the\n" |
| " output.\n" |
| "\n" |
| TARGET_TESTONLY_FILTER_COMMAND_LINE_HELP |
| "\n" |
| " --tree\n" |
| " Outputs a reverse dependency tree from the given target.\n" |
| " Duplicates will be elided. Combine with --all to see a full\n" |
| " dependency tree.\n" |
| "\n" |
| " Tree output can not be used with the filtering or output flags:\n" |
| " --as, --type, --testonly.\n" |
| "\n" |
| TARGET_TYPE_FILTER_COMMAND_LINE_HELP |
| "\n" |
| "Examples (target input)\n" |
| "\n" |
| " gn refs out/Debug //tools/gn:gn\n" |
| " Find all targets depending on the given exact target name.\n" |
| "\n" |
| " gn refs out/Debug //base:i18n --as=buildfiles | xargs gvim\n" |
| " Edit all .gn files containing references to //base:i18n\n" |
| "\n" |
| " gn refs out/Debug //base --all\n" |
| " List all targets depending directly or indirectly on //base:base.\n" |
| "\n" |
| " gn refs out/Debug \"//base/*\"\n" |
| " List all targets depending directly on any target in //base or\n" |
| " its subdirectories.\n" |
| "\n" |
| " gn refs out/Debug \"//base:*\"\n" |
| " List all targets depending directly on any target in\n" |
| " //base/BUILD.gn.\n" |
| "\n" |
| " gn refs out/Debug //base --tree\n" |
| " Print a reverse dependency tree of //base:base\n" |
| "\n" |
| "Examples (file input)\n" |
| "\n" |
| " gn refs out/Debug //base/macros.h\n" |
| " Print target(s) listing //base/macros.h as a source.\n" |
| "\n" |
| " gn refs out/Debug //base/macros.h --tree\n" |
| " Display a reverse dependency tree to get to the given file. This\n" |
| " will show how dependencies will reference that file.\n" |
| "\n" |
| " gn refs out/Debug //base/macros.h //base/at_exit.h --all\n" |
| " Display all unique targets with some dependency path to a target\n" |
| " containing either of the given files as a source.\n" |
| "\n" |
| " gn refs out/Debug //base/macros.h --testonly=true --type=executable\n" |
| " --all --as=output\n" |
| " Display the executable file names of all test executables\n" |
| " potentially affected by a change to the given file.\n"; |
| |
| 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("all-toolchains"); |
| |
| Setup* setup = new Setup; |
| setup->build_settings().set_check_for_bad_items(false); |
| 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 (const 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 |