|  | // Copyright 2015 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 <algorithm> | 
|  |  | 
|  | #include "base/command_line.h" | 
|  | #include "base/strings/stringprintf.h" | 
|  | #include "tools/gn/commands.h" | 
|  | #include "tools/gn/setup.h" | 
|  | #include "tools/gn/standard_out.h" | 
|  |  | 
|  | namespace commands { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | enum class DepType { NONE, PUBLIC, PRIVATE, DATA }; | 
|  |  | 
|  | // The dependency paths are stored in a vector. Assuming the chain: | 
|  | //    A --[public]--> B --[private]--> C | 
|  | // The stack will look like: | 
|  | //    [0] = A, NONE (this has no dep type since nobody depends on it) | 
|  | //    [1] = B, PUBLIC | 
|  | //    [2] = C, PRIVATE | 
|  | using TargetDep = std::pair<const Target*, DepType>; | 
|  | using PathVector = std::vector<TargetDep>; | 
|  |  | 
|  | // How to search. | 
|  | enum class PrivateDeps { INCLUDE, EXCLUDE }; | 
|  | enum class DataDeps { INCLUDE, EXCLUDE }; | 
|  | enum class PrintWhat { ONE, ALL }; | 
|  |  | 
|  | struct Options { | 
|  | Options() | 
|  | : print_what(PrintWhat::ONE), public_only(false), with_data(false) {} | 
|  |  | 
|  | PrintWhat print_what; | 
|  | bool public_only; | 
|  | bool with_data; | 
|  | }; | 
|  |  | 
|  | typedef std::list<PathVector> WorkQueue; | 
|  |  | 
|  | struct Stats { | 
|  | Stats() : public_paths(0), other_paths(0) {} | 
|  |  | 
|  | int total_paths() const { return public_paths + other_paths; } | 
|  |  | 
|  | int public_paths; | 
|  | int other_paths; | 
|  |  | 
|  | // Stores targets that have a path to the destination, and whether that | 
|  | // path is public, private, or data. | 
|  | std::map<const Target*, DepType> found_paths; | 
|  | }; | 
|  |  | 
|  | // If the implicit_last_dep is not "none", this type indicates the | 
|  | // classification of the elided last part of path. | 
|  | DepType ClassifyPath(const PathVector& path, DepType implicit_last_dep) { | 
|  | DepType result; | 
|  | if (implicit_last_dep != DepType::NONE) | 
|  | result = implicit_last_dep; | 
|  | else | 
|  | result = DepType::PUBLIC; | 
|  |  | 
|  | // Skip the 0th one since that is always NONE. | 
|  | for (size_t i = 1; i < path.size(); i++) { | 
|  | // PRIVATE overrides PUBLIC, and DATA overrides everything (the idea is | 
|  | // to find the worst link in the path). | 
|  | if (path[i].second == DepType::PRIVATE) { | 
|  | if (result == DepType::PUBLIC) | 
|  | result = DepType::PRIVATE; | 
|  | } else if (path[i].second == DepType::DATA) { | 
|  | result = DepType::DATA; | 
|  | } | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | const char* StringForDepType(DepType type) { | 
|  | switch (type) { | 
|  | case DepType::PUBLIC: | 
|  | return "public"; | 
|  | case DepType::PRIVATE: | 
|  | return "private"; | 
|  | case DepType::DATA: | 
|  | return "data"; | 
|  | break; | 
|  | case DepType::NONE: | 
|  | default: | 
|  | return ""; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Prints the given path. If the implicit_last_dep is not "none", the last | 
|  | // dependency will show an elided dependency with the given annotation. | 
|  | void PrintPath(const PathVector& path, DepType implicit_last_dep) { | 
|  | if (path.empty()) | 
|  | return; | 
|  |  | 
|  | // Don't print toolchains unless they differ from the first target. | 
|  | const Label& default_toolchain = path[0].first->label().GetToolchainLabel(); | 
|  |  | 
|  | for (size_t i = 0; i < path.size(); i++) { | 
|  | OutputString(path[i].first->label().GetUserVisibleName(default_toolchain)); | 
|  |  | 
|  | // Output dependency type. | 
|  | if (i == path.size() - 1) { | 
|  | // Last one either gets the implicit last dep type or nothing. | 
|  | if (implicit_last_dep != DepType::NONE) { | 
|  | OutputString(std::string(" --> see ") + | 
|  | StringForDepType(implicit_last_dep) + | 
|  | " chain printed above...", | 
|  | DECORATION_DIM); | 
|  | } | 
|  | } else { | 
|  | // Take type from the next entry. | 
|  | OutputString( | 
|  | std::string(" --[") + StringForDepType(path[i + 1].second) + "]-->", | 
|  | DECORATION_DIM); | 
|  | } | 
|  | OutputString("\n"); | 
|  | } | 
|  |  | 
|  | OutputString("\n"); | 
|  | } | 
|  |  | 
|  | void InsertTargetsIntoFoundPaths(const PathVector& path, | 
|  | DepType implicit_last_dep, | 
|  | Stats* stats) { | 
|  | DepType type = ClassifyPath(path, implicit_last_dep); | 
|  |  | 
|  | bool inserted = false; | 
|  |  | 
|  | // Don't try to insert the 0th item in the list which is the "from" target. | 
|  | // The search will be run more than once (for the different path types) and | 
|  | // if the "from" target was in the list, subsequent passes could never run | 
|  | // the starting point is alredy in the list of targets considered). | 
|  | // | 
|  | // One might imagine an alternate implementation where all items are counted | 
|  | // here but the "from" item is erased at the beginning of each search, but | 
|  | // that will mess up the metrics (the private search pass will find the | 
|  | // same public paths as the previous public pass, "inserted" will be true | 
|  | // here since the item wasn't found, and the public path will be | 
|  | // double-counted in the stats. | 
|  | for (size_t i = 1; i < path.size(); i++) { | 
|  | const auto& pair = path[i]; | 
|  |  | 
|  | // Don't overwrite an existing one. The algorithm works by first doing | 
|  | // public, then private, then data, so anything already there is guaranteed | 
|  | // at least as good as our addition. | 
|  | if (stats->found_paths.find(pair.first) == stats->found_paths.end()) { | 
|  | stats->found_paths.insert(std::make_pair(pair.first, type)); | 
|  | inserted = true; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (inserted) { | 
|  | // Only count this path in the stats if any part of it was actually new. | 
|  | if (type == DepType::PUBLIC) | 
|  | stats->public_paths++; | 
|  | else | 
|  | stats->other_paths++; | 
|  | } | 
|  | } | 
|  |  | 
|  | void BreadthFirstSearch(const Target* from, | 
|  | const Target* to, | 
|  | PrivateDeps private_deps, | 
|  | DataDeps data_deps, | 
|  | PrintWhat print_what, | 
|  | Stats* stats) { | 
|  | // Seed the initial stack with just the "from" target. | 
|  | PathVector initial_stack; | 
|  | initial_stack.emplace_back(from, DepType::NONE); | 
|  | WorkQueue work_queue; | 
|  | work_queue.push_back(initial_stack); | 
|  |  | 
|  | // Track checked targets to avoid checking the same once more than once. | 
|  | std::set<const Target*> visited; | 
|  |  | 
|  | while (!work_queue.empty()) { | 
|  | PathVector current_path = work_queue.front(); | 
|  | work_queue.pop_front(); | 
|  |  | 
|  | const Target* current_target = current_path.back().first; | 
|  |  | 
|  | if (current_target == to) { | 
|  | // Found a new path. | 
|  | if (stats->total_paths() == 0 || print_what == PrintWhat::ALL) | 
|  | PrintPath(current_path, DepType::NONE); | 
|  |  | 
|  | // Insert all nodes on the path into the found paths list. Since we're | 
|  | // doing search breadth first, we know that the current path is the best | 
|  | // path for all nodes on it. | 
|  | InsertTargetsIntoFoundPaths(current_path, DepType::NONE, stats); | 
|  | } else { | 
|  | // Check for a path that connects to an already known-good one. Printing | 
|  | // this here will mean the results aren't strictly in depth-first order | 
|  | // since there could be many items on the found path this connects to. | 
|  | // Doing this here will mean that the output is sorted by length of items | 
|  | // printed (with the redundant parts of the path omitted) rather than | 
|  | // complete path length. | 
|  | const auto& found_current_target = | 
|  | stats->found_paths.find(current_target); | 
|  | if (found_current_target != stats->found_paths.end()) { | 
|  | if (stats->total_paths() == 0 || print_what == PrintWhat::ALL) | 
|  | PrintPath(current_path, found_current_target->second); | 
|  |  | 
|  | // Insert all nodes on the path into the found paths list since we know | 
|  | // everything along this path also leads to the destination. | 
|  | InsertTargetsIntoFoundPaths(current_path, found_current_target->second, | 
|  | stats); | 
|  | continue; | 
|  | } | 
|  | } | 
|  |  | 
|  | // If we've already checked this one, stop. This should be after the above | 
|  | // check for a known-good check, because known-good ones will always have | 
|  | // been previously visited. | 
|  | if (visited.find(current_target) == visited.end()) | 
|  | visited.insert(current_target); | 
|  | else | 
|  | continue; | 
|  |  | 
|  | // Add public deps for this target to the queue. | 
|  | for (const auto& pair : current_target->public_deps()) { | 
|  | work_queue.push_back(current_path); | 
|  | work_queue.back().push_back(TargetDep(pair.ptr, DepType::PUBLIC)); | 
|  | } | 
|  |  | 
|  | if (private_deps == PrivateDeps::INCLUDE) { | 
|  | // Add private deps. | 
|  | for (const auto& pair : current_target->private_deps()) { | 
|  | work_queue.push_back(current_path); | 
|  | work_queue.back().push_back(TargetDep(pair.ptr, DepType::PRIVATE)); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (data_deps == DataDeps::INCLUDE) { | 
|  | // Add data deps. | 
|  | for (const auto& pair : current_target->data_deps()) { | 
|  | work_queue.push_back(current_path); | 
|  | work_queue.back().push_back(TargetDep(pair.ptr, DepType::DATA)); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void DoSearch(const Target* from, | 
|  | const Target* to, | 
|  | const Options& options, | 
|  | Stats* stats) { | 
|  | BreadthFirstSearch(from, to, PrivateDeps::EXCLUDE, DataDeps::EXCLUDE, | 
|  | options.print_what, stats); | 
|  | if (!options.public_only) { | 
|  | // Check private deps. | 
|  | BreadthFirstSearch(from, to, PrivateDeps::INCLUDE, DataDeps::EXCLUDE, | 
|  | options.print_what, stats); | 
|  | if (options.with_data) { | 
|  | // Check data deps. | 
|  | BreadthFirstSearch(from, to, PrivateDeps::INCLUDE, DataDeps::INCLUDE, | 
|  | options.print_what, stats); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | const char kPath[] = "path"; | 
|  | const char kPath_HelpShort[] = "path: Find paths between two targets."; | 
|  | const char kPath_Help[] = | 
|  | R"(gn path <out_dir> <target_one> <target_two> | 
|  |  | 
|  | Finds paths of dependencies between two targets. Each unique path will be | 
|  | printed in one group, and groups will be separate by newlines. The two | 
|  | targets can appear in either order (paths will be found going in either | 
|  | direction). | 
|  |  | 
|  | By default, a single path will be printed. If there is a path with only | 
|  | public dependencies, the shortest public path will be printed. Otherwise, the | 
|  | shortest path using either public or private dependencies will be printed. If | 
|  | --with-data is specified, data deps will also be considered. If there are | 
|  | multiple shortest paths, an arbitrary one will be selected. | 
|  |  | 
|  | Interesting paths | 
|  |  | 
|  | In a large project, there can be 100's of millions of unique paths between a | 
|  | very high level and a common low-level target. To make the output more useful | 
|  | (and terminate in a reasonable time), GN will not revisit sub-paths | 
|  | previously known to lead to the target. | 
|  |  | 
|  | Options | 
|  |  | 
|  | --all | 
|  | Prints all "interesting" paths found rather than just the first one. | 
|  | Public paths will be printed first in order of increasing length, followed | 
|  | by non-public paths in order of increasing length. | 
|  |  | 
|  | --public | 
|  | Considers only public paths. Can't be used with --with-data. | 
|  |  | 
|  | --with-data | 
|  | Additionally follows data deps. Without this flag, only public and private | 
|  | linked deps will be followed. Can't be used with --public. | 
|  |  | 
|  | Example | 
|  |  | 
|  | gn path out/Default //base //tools/gn | 
|  | )"; | 
|  |  | 
|  | int RunPath(const std::vector<std::string>& args) { | 
|  | if (args.size() != 3) { | 
|  | Err(Location(), "You're holding it wrong.", | 
|  | "Usage: \"gn path <out_dir> <target_one> <target_two>\"") | 
|  | .PrintToStdout(); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | Setup* setup = new Setup; | 
|  | if (!setup->DoSetup(args[0], false)) | 
|  | return 1; | 
|  | if (!setup->Run()) | 
|  | return 1; | 
|  |  | 
|  | const Target* target1 = ResolveTargetFromCommandLineString(setup, args[1]); | 
|  | if (!target1) | 
|  | return 1; | 
|  | const Target* target2 = ResolveTargetFromCommandLineString(setup, args[2]); | 
|  | if (!target2) | 
|  | return 1; | 
|  |  | 
|  | Options options; | 
|  | options.print_what = base::CommandLine::ForCurrentProcess()->HasSwitch("all") | 
|  | ? PrintWhat::ALL | 
|  | : PrintWhat::ONE; | 
|  | options.public_only = | 
|  | base::CommandLine::ForCurrentProcess()->HasSwitch("public"); | 
|  | options.with_data = | 
|  | base::CommandLine::ForCurrentProcess()->HasSwitch("with-data"); | 
|  | if (options.public_only && options.with_data) { | 
|  | Err(Location(), "Can't use --public with --with-data for 'gn path'.", | 
|  | "Your zealous over-use of arguments has inevitably resulted in an " | 
|  | "invalid\ncombination of flags.") | 
|  | .PrintToStdout(); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | Stats stats; | 
|  | DoSearch(target1, target2, options, &stats); | 
|  | if (stats.total_paths() == 0) { | 
|  | // If we don't find a path going "forwards", try the reverse direction. | 
|  | // Deps can only go in one direction without having a cycle, which will | 
|  | // have caused a run failure above. | 
|  | DoSearch(target2, target1, options, &stats); | 
|  | } | 
|  |  | 
|  | // This string is inserted in the results to annotate whether the result | 
|  | // is only public or includes data deps or not. | 
|  | const char* path_annotation = ""; | 
|  | if (options.public_only) | 
|  | path_annotation = "public "; | 
|  | else if (!options.with_data) | 
|  | path_annotation = "non-data "; | 
|  |  | 
|  | if (stats.total_paths() == 0) { | 
|  | // No results. | 
|  | OutputString( | 
|  | base::StringPrintf("No %spaths found between these two targets.\n", | 
|  | path_annotation), | 
|  | DECORATION_YELLOW); | 
|  | } else if (stats.total_paths() == 1) { | 
|  | // Exactly one result. | 
|  | OutputString(base::StringPrintf("1 %spath found.", path_annotation), | 
|  | DECORATION_YELLOW); | 
|  | if (!options.public_only) { | 
|  | if (stats.public_paths) | 
|  | OutputString(" It is public."); | 
|  | else | 
|  | OutputString(" It is not public."); | 
|  | } | 
|  | OutputString("\n"); | 
|  | } else { | 
|  | if (options.print_what == PrintWhat::ALL) { | 
|  | // Showing all paths when there are many. | 
|  | OutputString(base::StringPrintf("%d \"interesting\" %spaths found.", | 
|  | stats.total_paths(), path_annotation), | 
|  | DECORATION_YELLOW); | 
|  | if (!options.public_only) { | 
|  | OutputString( | 
|  | base::StringPrintf(" %d of them are public.", stats.public_paths)); | 
|  | } | 
|  | OutputString("\n"); | 
|  | } else { | 
|  | // Showing one path when there are many. | 
|  | OutputString( | 
|  | base::StringPrintf("Showing one of %d \"interesting\" %spaths.", | 
|  | stats.total_paths(), path_annotation), | 
|  | DECORATION_YELLOW); | 
|  | if (!options.public_only) { | 
|  | OutputString( | 
|  | base::StringPrintf(" %d of them are public.", stats.public_paths)); | 
|  | } | 
|  | OutputString("\nUse --all to print all paths.\n"); | 
|  | } | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | }  // namespace commands |