#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, VALIDATION };
using TargetDep = std::pair<const Target*, DepType>;
using PathVector = std::vector<TargetDep>;
enum class PrivateDeps { INCLUDE, EXCLUDE };
enum class DataDeps { INCLUDE, EXCLUDE };
enum class ValidationDeps { 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;
std::map<const Target*, DepType> found_paths;
};
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;
for (size_t i = 1; i < path.size(); i++) {
if (path[i].second == DepType::PRIVATE) {
if (result == DepType::PUBLIC)
result = DepType::PRIVATE;
} else if (path[i].second == DepType::DATA) {
result = DepType::DATA;
} else if (path[i].second == DepType::VALIDATION) {
result = DepType::VALIDATION;
}
}
return result;
}
const char* StringForDepType(DepType type) {
switch (type) {
case DepType::PUBLIC:
return "public";
case DepType::PRIVATE:
return "private";
case DepType::DATA:
return "data";
case DepType::VALIDATION:
return "validation";
case DepType::NONE:
default:
return "";
}
}
void PrintPath(const PathVector& path, DepType implicit_last_dep) {
if (path.empty())
return;
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));
if (i == path.size() - 1) {
if (implicit_last_dep != DepType::NONE) {
OutputString(std::string(" --> see ") +
StringForDepType(implicit_last_dep) +
" chain printed above...",
DECORATION_DIM);
}
} else {
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;
for (size_t i = 1; i < path.size(); i++) {
const auto& pair = path[i];
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) {
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,
ValidationDeps validation_deps,
PrintWhat print_what,
Stats* stats) {
PathVector initial_stack;
initial_stack.emplace_back(from, DepType::NONE);
WorkQueue work_queue;
work_queue.push_back(initial_stack);
TargetSet 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) {
if (stats->total_paths() == 0 || print_what == PrintWhat::ALL)
PrintPath(current_path, DepType::NONE);
InsertTargetsIntoFoundPaths(current_path, DepType::NONE, stats);
} else {
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);
InsertTargetsIntoFoundPaths(current_path, found_current_target->second,
stats);
continue;
}
}
if (!visited.add(current_target))
continue;
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) {
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) {
for (const auto& pair : current_target->data_deps()) {
work_queue.push_back(current_path);
work_queue.back().push_back(TargetDep(pair.ptr, DepType::DATA));
}
}
if (validation_deps == ValidationDeps::INCLUDE) {
for (const auto& pair : current_target->validations()) {
work_queue.push_back(current_path);
work_queue.back().push_back(TargetDep(pair.ptr, DepType::VALIDATION));
}
}
}
}
void DoSearch(const Target* from,
const Target* to,
const Options& options,
Stats* stats) {
BreadthFirstSearch(from, to, PrivateDeps::EXCLUDE, DataDeps::EXCLUDE,
ValidationDeps::EXCLUDE, options.print_what, stats);
if (!options.public_only) {
BreadthFirstSearch(from, to, PrivateDeps::INCLUDE, DataDeps::EXCLUDE,
ValidationDeps::INCLUDE, options.print_what, stats);
if (options.with_data) {
BreadthFirstSearch(from, to, PrivateDeps::INCLUDE, DataDeps::INCLUDE,
ValidationDeps::INCLUDE, options.print_what, stats);
}
}
}
}
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(), "Unknown command format. See \"gn help path\"",
"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) {
DoSearch(target2, target1, options, &stats);
}
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) {
OutputString(
base::StringPrintf("No %spaths found between these two targets.\n",
path_annotation),
DECORATION_YELLOW);
} else if (stats.total_paths() == 1) {
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) {
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 {
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;
}
}