Index: tools/gn/command_refs.cc |
diff --git a/tools/gn/command_refs.cc b/tools/gn/command_refs.cc |
index e54d1f393c9047424bb2ad658f7ad7b0114d1666..d00149f372add32a03c9a771fa78796aca362f25 100644 |
--- a/tools/gn/command_refs.cc |
+++ b/tools/gn/command_refs.cc |
@@ -2,6 +2,7 @@ |
// 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" |
@@ -9,7 +10,6 @@ |
#include "tools/gn/filesystem_utils.h" |
#include "tools/gn/input_file.h" |
#include "tools/gn/item.h" |
-#include "tools/gn/pattern.h" |
#include "tools/gn/setup.h" |
#include "tools/gn/standard_out.h" |
#include "tools/gn/target.h" |
@@ -18,12 +18,142 @@ namespace commands { |
namespace { |
-// Returns the file path generating this record. |
-base::FilePath FilePathForRecord(const BuilderRecord* record) { |
- if (!record->item()) |
- return base::FilePath(FILE_PATH_LITERAL("=UNRESOLVED DEPENDENCY=")); |
- return record->item()->defined_from()->GetRange().begin().file() |
- ->physical_name(); |
+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) { |
+ std::vector<const Target*> targets = |
+ setup->builder()->GetAllResolvedTargets(); |
+ |
+ for (size_t target_i = 0; target_i < targets.size(); target_i++) { |
+ const Target* target = targets[target_i]; |
+ |
+ // Add all deps to the map. |
+ const LabelTargetVector& deps = target->deps(); |
+ for (size_t dep_i = 0; dep_i < deps.size(); dep_i++) |
+ dep_map->insert(std::make_pair(deps[dep_i].ptr, target)); |
+ |
+ // Include data deps as well. |
+ const LabelTargetVector& datadeps = target->datadeps(); |
+ for (size_t dep_i = 0; dep_i < datadeps.size(); dep_i++) |
+ dep_map->insert(std::make_pair(datadeps[dep_i].ptr, target)); |
+ } |
+} |
+ |
+// Returns the file path generating this item. |
+base::FilePath FilePathForItem(const Item* item) { |
+ return item->defined_from()->GetRange().begin().file()->physical_name(); |
+} |
+ |
+// Prints the targets which are the result of a query. This list is sorted |
+// and, if as_files is set, the unique filenames matching those targets will |
+// be used. |
+void OutputResultSet(const TargetSet& results, bool as_files) { |
+ if (results.empty()) |
+ return; |
+ |
+ if (as_files) { |
+ // Output the set of unique source files. |
+ std::set<std::string> unique_files; |
+ for (TargetSet::const_iterator iter = results.begin(); |
+ iter != results.end(); ++iter) |
+ unique_files.insert(FilePathToUTF8(FilePathForItem(*iter))); |
+ |
+ for (std::set<std::string>::const_iterator iter = unique_files.begin(); |
+ iter != unique_files.end(); ++iter) { |
+ OutputString(*iter + "\n"); |
+ } |
+ } else { |
+ // Output sorted and uniquified list of labels. The set will sort the |
+ // labels. |
+ std::set<Label> unique_labels; |
+ for (TargetSet::const_iterator iter = results.begin(); |
+ iter != results.end(); ++iter) |
+ unique_labels.insert((*iter)->label()); |
+ |
+ // Grab the label of the default toolchain from a random target. |
+ Label default_tc_label = |
+ (*results.begin())->settings()->default_toolchain_label(); |
+ |
+ for (std::set<Label>::const_iterator iter = unique_labels.begin(); |
+ iter != unique_labels.end(); ++iter) { |
+ // Print toolchain only for ones not in the default toolchain. |
+ OutputString(iter->GetUserVisibleName( |
+ iter->GetToolchainLabel() != default_tc_label)); |
+ OutputString("\n"); |
+ } |
+ } |
+} |
+ |
+// Prints refs of the given target (not the target itself). 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. |
+void RecursivePrintTree(const DepMap& dep_map, |
+ const Target* target, |
+ TargetSet* seen_targets, |
+ int indent_level) { |
+ std::string indent(indent_level * 2, ' '); |
+ |
+ 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++) { |
+ const Target* cur_target = cur_dep->second; |
+ |
+ // Only print the toolchain for non-default-toolchain targets. |
+ OutputString(indent + cur_target->label().GetUserVisibleName( |
+ !cur_target->settings()->is_default())); |
+ |
+ bool print_children = true; |
+ if (seen_targets) { |
+ if (seen_targets->find(cur_target) == seen_targets->end()) { |
+ // New target, mark it visited. |
+ seen_targets->insert(cur_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(cur_target) != dep_map.upper_bound(cur_target)) |
+ OutputString("..."); |
+ } |
+ } |
+ |
+ OutputString("\n"); |
+ if (print_children) |
+ RecursivePrintTree(dep_map, cur_target, seen_targets, indent_level + 1); |
+ } |
+} |
+ |
+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); |
} |
} // namespace |
@@ -32,95 +162,130 @@ const char kRefs[] = "refs"; |
const char kRefs_HelpShort[] = |
"refs: Find stuff referencing a target, directory, or config."; |
const char kRefs_Help[] = |
- "gn refs <label_pattern> [--files]\n" |
+ "gn refs <build_dir> <label_pattern> [--files] [--tree] [--all]\n" |
+ " [--all-toolchains]\n" |
"\n" |
- " Finds code referencing a given label. The label can be a\n" |
- " target or config name. Unlike most other commands, unresolved\n" |
- " dependencies will be tolerated. This allows you to use this command\n" |
- " to find references to targets you're in the process of moving.\n" |
+ " Finds which targets reference a given target or targets (reverse\n" |
+ " dependencies).\n" |
"\n" |
- " By default, the mapping from source item to dest item (where the\n" |
- " pattern matches the dest item). See \"gn help pattern\" for\n" |
- " information on pattern-matching rules.\n" |
+ " The <label_pattern> can take exact labels or patterns that match more\n" |
+ " than one (although not general regular expressions).\n" |
+ " See \"gn help label_pattern\" for details.\n" |
+ "\n" |
+ " --all\n" |
+ " When used without --tree, will recurse and display all unique\n" |
+ " dependencies of the given targets. When used with --tree, turns\n" |
+ " off eliding to show a complete tree.\n" |
+ "\n" |
+ " --all-toolchains\n" |
+ " Make the label pattern matche all toolchains. If the label pattern\n" |
+ " does not specify an explicit toolchain, labels from all toolchains\n" |
+ " will be matched (normally only the default toolchain is matched\n" |
+ " when no toolchain is specified).\n" |
"\n" |
- "Option:\n" |
" --files\n" |
" Output unique filenames referencing a matched target or config.\n" |
+ " These will be relative to the source root directory such that they\n" |
+ " are suitable for piping to other commands.\n" |
+ "\n" |
+ " --tree\n" |
+ " Outputs a reverse dependency tree from the given target. The label\n" |
+ " pattern must match one target exactly. Duplicates will be elided.\n" |
+ " Combine with --all to see a full dependency tree.\n" |
"\n" |
- "Examples:\n" |
- " gn refs \"//tools/gn/*\"\n" |
- " Find all targets depending on any target or config in the\n" |
- " \"tools/gn\" directory.\n" |
+ "Examples\n" |
"\n" |
- " gn refs //tools/gn:gn\n" |
+ " gn refs out/Debug //tools/gn:gn\n" |
" Find all targets depending on the given exact target name.\n" |
"\n" |
- " gn refs \"*gtk*\" --files\n" |
- " Find all unique buildfiles with a dependency on a target that has\n" |
- " the substring \"gtk\" in the name.\n"; |
+ " gn refs out/Debug //base:i18n --files | xargs gvim\n" |
+ " Edit all 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"; |
int RunRefs(const std::vector<std::string>& args) { |
- if (args.size() != 1 && args.size() != 2) { |
+ if (args.size() != 2) { |
Err(Location(), "You're holding it wrong.", |
- "Usage: \"gn refs <label_pattern>\"").PrintToStdout(); |
+ "Usage: \"gn refs <build_dir> <label_pattern>\"").PrintToStdout(); |
return 1; |
} |
- Pattern pattern(args[0]); |
- // Check for common errors on input. |
- if (args[0].find('*') == std::string::npos) { |
- // We need to begin with a "//" and have a colon if there's no "*" or it |
- // will be impossible to match anything. |
- if (args[0].size() < 2 || |
- (args[0][0] != '/' && args[0][1] != '/') || |
- args[0].find(':') == std::string::npos) { |
- OutputString("Assuming \"*" + args[0] + |
- "*\". See \"gn help refs\" for more information.\n", |
- DECORATION_YELLOW); |
- pattern = Pattern("*" + args[0] + "*"); |
- } |
- } |
+ const CommandLine* cmdline = CommandLine::ForCurrentProcess(); |
+ bool tree = cmdline->HasSwitch("tree"); |
+ bool all = cmdline->HasSwitch("all"); |
+ bool all_toolchains = cmdline->HasSwitch("all-toolchains"); |
+ bool files = cmdline->HasSwitch("files"); |
Setup* setup = new Setup; |
setup->set_check_for_bad_items(false); |
- // TODO(brettw) bug 343726: Use a temporary directory instead of this |
- // default one to avoid messing up any build that's in there. |
- if (!setup->DoSetup("//out/Default/") || !setup->Run()) |
+ if (!setup->DoSetup(args[0]) || !setup->Run()) |
return 1; |
- std::vector<const BuilderRecord*> records = setup->builder()->GetAllRecords(); |
+ // Figure out the target or targets that the user is querying. |
+ std::vector<const Target*> query; |
+ if (!ResolveTargetsFromCommandLinePattern(setup, args[1], all_toolchains, |
+ &query)) |
+ return 1; |
+ if (query.empty()) { |
+ OutputString("\"" + args[1] + "\" matches no targets.\n"); |
+ return 0; |
+ } |
- const CommandLine* cmdline = CommandLine::ForCurrentProcess(); |
+ // Construct the reverse dependency tree. |
+ DepMap dep_map; |
+ FillDepMap(setup, &dep_map); |
- bool file_output = cmdline->HasSwitch("files"); |
- std::set<std::string> unique_output; |
- |
- for (size_t record_index = 0; record_index < records.size(); record_index++) { |
- const BuilderRecord* record = records[record_index]; |
- const BuilderRecord::BuilderRecordSet& deps = record->all_deps(); |
- for (BuilderRecord::BuilderRecordSet::const_iterator d = deps.begin(); |
- d != deps.end(); ++d) { |
- std::string label = (*d)->label().GetUserVisibleName(false); |
- if (pattern.MatchesString(label)) { |
- // Got a match. |
- if (file_output) { |
- unique_output.insert(FilePathToUTF8(FilePathForRecord(record))); |
- break; // Found a match for this target's file, don't need more. |
- } else { |
- // We can get dupes when there are differnet toolchains involved, |
- // so we want to send all output through the de-duper. |
- unique_output.insert( |
- record->item()->label().GetUserVisibleName(false) + " -> " + |
- label); |
- } |
- } |
+ if (tree) { |
+ // Output dependency tree. |
+ if (files) { |
+ Err(NULL, "--files option can't be used with --tree option.") |
+ .PrintToStdout(); |
+ return 1; |
+ } |
+ if (query.size() != 1) { |
+ Err(NULL, "Query matches more than one target.", |
+ "--tree only supports a single target as input.").PrintToStdout(); |
+ return 1; |
} |
+ if (all) { |
+ // Recursively print all targets. |
+ RecursivePrintTree(dep_map, query[0], NULL, 0); |
+ } else { |
+ // Recursively print unique targets. |
+ TargetSet seen_targets; |
+ RecursivePrintTree(dep_map, query[0], &seen_targets, 0); |
+ } |
+ } else if (all) { |
+ // Output recursive dependencies, uniquified and flattened. |
+ TargetSet results; |
+ for (size_t query_i = 0; query_i < query.size(); query_i++) |
+ RecursiveCollectChildRefs(dep_map, query[query_i], &results); |
+ OutputResultSet(results, files); |
+ } else { |
+ // Output direct references of everything in the query. |
+ TargetSet results; |
+ for (size_t query_i = 0; query_i < query.size(); query_i++) { |
+ DepMap::const_iterator dep_begin = dep_map.lower_bound(query[query_i]); |
+ DepMap::const_iterator dep_end = dep_map.upper_bound(query[query_i]); |
+ for (DepMap::const_iterator cur_dep = dep_begin; |
+ cur_dep != dep_end; cur_dep++) |
+ results.insert(cur_dep->second); |
+ } |
+ OutputResultSet(results, files); |
} |
- for (std::set<std::string>::iterator i = unique_output.begin(); |
- i != unique_output.end(); ++i) |
- OutputString(*i + "\n"); |
- |
return 0; |
} |