| // 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 "gn/commands.h" |
| #include "gn/setup.h" |
| #include "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; |
| }; |
| |
| using WorkQueue = std::list<PathVector>; |
| |
| 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 //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; |
| } |
| |
| // Deliberately leaked to avoid expensive process teardown. |
| 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 |