| // 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 <stddef.h> |
| |
| #include <algorithm> |
| #include <memory> |
| #include <set> |
| #include <sstream> |
| |
| #include "base/command_line.h" |
| #include "base/json/json_writer.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "tools/gn/commands.h" |
| #include "tools/gn/config.h" |
| #include "tools/gn/desc_builder.h" |
| #include "tools/gn/setup.h" |
| #include "tools/gn/standard_out.h" |
| #include "tools/gn/switches.h" |
| #include "tools/gn/target.h" |
| #include "tools/gn/variables.h" |
| |
| namespace commands { |
| |
| namespace { |
| |
| // Desc-specific command line switches. |
| const char kBlame[] = "blame"; |
| const char kTree[] = "tree"; |
| const char kAll[] = "all"; |
| |
| void PrintDictValue(const base::Value* value, |
| int indentLevel, |
| bool use_first_indent) { |
| std::string indent(indentLevel * 2, ' '); |
| const base::ListValue* list_value = nullptr; |
| const base::DictionaryValue* dict_value = nullptr; |
| std::string string_value; |
| bool bool_value = false; |
| int int_value = 0; |
| if (use_first_indent) |
| OutputString(indent); |
| if (value->GetAsList(&list_value)) { |
| OutputString("[\n"); |
| bool first = true; |
| for (const auto& v : *list_value) { |
| if (!first) |
| OutputString(",\n"); |
| PrintDictValue(&v, indentLevel + 1, true); |
| first = false; |
| } |
| OutputString("\n" + indent + "]"); |
| } else if (value->GetAsString(&string_value)) { |
| OutputString("\"" + string_value + "\""); |
| } else if (value->GetAsBoolean(&bool_value)) { |
| OutputString(bool_value ? "true" : "false"); |
| } else if (value->GetAsDictionary(&dict_value)) { |
| OutputString("{\n"); |
| std::string indent_plus_one((indentLevel + 1) * 2, ' '); |
| base::DictionaryValue::Iterator iter(*dict_value); |
| bool first = true; |
| while (!iter.IsAtEnd()) { |
| if (!first) |
| OutputString(",\n"); |
| OutputString(indent_plus_one + iter.key() + " = "); |
| PrintDictValue(&iter.value(), indentLevel + 1, false); |
| iter.Advance(); |
| first = false; |
| } |
| OutputString("\n" + indent + "}"); |
| } else if (value->GetAsInteger(&int_value)) { |
| OutputString(base::IntToString(int_value)); |
| } else if (value->is_none()) { |
| OutputString("<null>"); |
| } |
| } |
| |
| // Prints value with specified indentation level |
| void PrintValue(const base::Value* value, int indentLevel) { |
| std::string indent(indentLevel * 2, ' '); |
| const base::ListValue* list_value = nullptr; |
| const base::DictionaryValue* dict_value = nullptr; |
| std::string string_value; |
| bool bool_value = false; |
| int int_value = 0; |
| if (value->GetAsList(&list_value)) { |
| for (const auto& v : *list_value) { |
| PrintValue(&v, indentLevel); |
| } |
| } else if (value->GetAsString(&string_value)) { |
| OutputString(indent); |
| OutputString(string_value); |
| OutputString("\n"); |
| } else if (value->GetAsBoolean(&bool_value)) { |
| OutputString(indent); |
| OutputString(bool_value ? "true" : "false"); |
| OutputString("\n"); |
| } else if (value->GetAsDictionary(&dict_value)) { |
| base::DictionaryValue::Iterator iter(*dict_value); |
| while (!iter.IsAtEnd()) { |
| OutputString(indent + iter.key() + "\n"); |
| PrintValue(&iter.value(), indentLevel + 1); |
| iter.Advance(); |
| } |
| } else if (value->GetAsInteger(&int_value)) { |
| OutputString(indent); |
| OutputString(base::IntToString(int_value)); |
| OutputString("\n"); |
| } else if (value->is_none()) { |
| OutputString(indent + "<null>\n"); |
| } |
| } |
| |
| // Default handler for property |
| void DefaultHandler(const std::string& name, |
| const base::Value* value, |
| bool value_only) { |
| if (value_only) { |
| PrintValue(value, 0); |
| return; |
| } |
| OutputString("\n"); |
| OutputString(name); |
| OutputString("\n"); |
| PrintValue(value, 1); |
| } |
| |
| // Specific handler for properties that need different treatment |
| |
| // Prints the dict in GN scope-sytle. |
| void MetadataHandler(const std::string& name, |
| const base::Value* value, |
| bool value_only) { |
| if (value_only) { |
| PrintDictValue(value, 0, true); |
| OutputString("\n"); |
| return; |
| } |
| OutputString("\n"); |
| OutputString(name); |
| OutputString("\n"); |
| PrintDictValue(value, 1, true); |
| OutputString("\n"); |
| } |
| |
| // Prints label and property value on one line, capitalizing the label. |
| void LabelHandler(const std::string& name, |
| const base::Value* value, |
| bool value_only) { |
| if (value_only) { |
| PrintValue(value, 0); |
| return; |
| } |
| std::string label = name; |
| label[0] = base::ToUpperASCII(label[0]); |
| std::string string_value; |
| if (value->GetAsString(&string_value)) { |
| OutputString(name + ": ", DECORATION_YELLOW); |
| OutputString(string_value + "\n"); |
| } |
| } |
| |
| void VisibilityHandler(const std::string& name, |
| const base::Value* value, |
| bool value_only) { |
| if (value_only) { |
| PrintValue(value, 0); |
| return; |
| } |
| const base::ListValue* list; |
| if (value->GetAsList(&list)) { |
| if (list->empty()) { |
| base::Value str("(no visibility)"); |
| DefaultHandler(name, &str, value_only); |
| } else { |
| DefaultHandler(name, value, value_only); |
| } |
| } |
| } |
| |
| void PublicHandler(const std::string& name, |
| const base::Value* value, |
| bool value_only) { |
| if (value_only) { |
| PrintValue(value, 0); |
| return; |
| } |
| std::string p; |
| if (value->GetAsString(&p)) { |
| if (p == "*") { |
| base::Value str("[All headers listed in the sources are public.]"); |
| DefaultHandler(name, &str, value_only); |
| return; |
| } |
| } |
| DefaultHandler(name, value, value_only); |
| } |
| |
| void ConfigsHandler(const std::string& name, |
| const base::Value* value, |
| bool value_only) { |
| bool tree = base::CommandLine::ForCurrentProcess()->HasSwitch(kTree); |
| if (tree) |
| DefaultHandler(name + " tree (in order applying)", value, value_only); |
| else |
| DefaultHandler(name + " (in order applying, try also --tree)", value, |
| value_only); |
| } |
| |
| void DepsHandler(const std::string& name, |
| const base::Value* value, |
| bool value_only) { |
| bool tree = base::CommandLine::ForCurrentProcess()->HasSwitch(kTree); |
| bool all = base::CommandLine::ForCurrentProcess()->HasSwitch(kTree); |
| if (tree) { |
| DefaultHandler("Dependency tree", value, value_only); |
| } else { |
| if (!all) { |
| DefaultHandler( |
| "Direct dependencies " |
| "(try also \"--all\", \"--tree\", or even \"--all --tree\")", |
| value, value_only); |
| } else { |
| DefaultHandler("All recursive dependencies", value, value_only); |
| } |
| } |
| } |
| |
| // Outputs need special processing when output patterns are present. |
| void ProcessOutputs(base::DictionaryValue* target) { |
| base::ListValue* patterns = nullptr; |
| base::ListValue* outputs = nullptr; |
| target->GetList("output_patterns", &patterns); |
| target->GetList(variables::kOutputs, &outputs); |
| |
| if (outputs || patterns) { |
| OutputString("\noutputs\n"); |
| int indent = 1; |
| if (patterns) { |
| OutputString(" Output patterns\n"); |
| indent = 2; |
| PrintValue(patterns, indent); |
| OutputString("\n Resolved output file list\n"); |
| } |
| if (outputs) |
| PrintValue(outputs, indent); |
| |
| target->Remove("output_patterns", nullptr); |
| target->Remove(variables::kOutputs, nullptr); |
| } |
| } |
| |
| using DescHandlerFunc = void (*)(const std::string& name, |
| const base::Value* value, |
| bool value_only); |
| std::map<std::string, DescHandlerFunc> GetHandlers() { |
| return {{"type", LabelHandler}, |
| {"toolchain", LabelHandler}, |
| {variables::kVisibility, VisibilityHandler}, |
| {variables::kMetadata, MetadataHandler}, |
| {variables::kTestonly, DefaultHandler}, |
| {variables::kCheckIncludes, DefaultHandler}, |
| {variables::kAllowCircularIncludesFrom, DefaultHandler}, |
| {variables::kSources, DefaultHandler}, |
| {variables::kPublic, PublicHandler}, |
| {variables::kInputs, DefaultHandler}, |
| {variables::kConfigs, ConfigsHandler}, |
| {variables::kPublicConfigs, ConfigsHandler}, |
| {variables::kAllDependentConfigs, ConfigsHandler}, |
| {variables::kScript, DefaultHandler}, |
| {variables::kArgs, DefaultHandler}, |
| {variables::kDepfile, DefaultHandler}, |
| {"bundle_data", DefaultHandler}, |
| {variables::kArflags, DefaultHandler}, |
| {variables::kAsmflags, DefaultHandler}, |
| {variables::kCflags, DefaultHandler}, |
| {variables::kCflagsC, DefaultHandler}, |
| {variables::kCflagsCC, DefaultHandler}, |
| {variables::kCflagsObjC, DefaultHandler}, |
| {variables::kCflagsObjCC, DefaultHandler}, |
| {variables::kDefines, DefaultHandler}, |
| {variables::kIncludeDirs, DefaultHandler}, |
| {variables::kLdflags, DefaultHandler}, |
| {variables::kPrecompiledHeader, DefaultHandler}, |
| {variables::kPrecompiledSource, DefaultHandler}, |
| {variables::kDeps, DepsHandler}, |
| {variables::kLibs, DefaultHandler}, |
| {variables::kLibDirs, DefaultHandler}}; |
| } |
| |
| void HandleProperty(const std::string& what, |
| const std::map<std::string, DescHandlerFunc>& handler_map, |
| std::unique_ptr<base::Value>& v, |
| std::unique_ptr<base::DictionaryValue>& dict) { |
| if (dict->Remove(what, &v)) { |
| auto pair = handler_map.find(what); |
| if (pair != handler_map.end()) |
| pair->second(what, v.get(), false); |
| } |
| } |
| |
| bool PrintTarget(const Target* target, |
| const std::string& what, |
| bool single_target, |
| const std::map<std::string, DescHandlerFunc>& handler_map, |
| bool all, |
| bool tree, |
| bool blame) { |
| std::unique_ptr<base::DictionaryValue> dict = |
| DescBuilder::DescriptionForTarget(target, what, all, tree, blame); |
| if (!what.empty() && dict->empty()) { |
| OutputString("Don't know how to display \"" + what + "\" for \"" + |
| Target::GetStringForOutputType(target->output_type()) + |
| "\".\n"); |
| return false; |
| } |
| // Print single value |
| if (!what.empty() && dict->size() == 1 && single_target) { |
| base::DictionaryValue::Iterator iter(*dict); |
| auto pair = handler_map.find(what); |
| if (pair != handler_map.end()) |
| pair->second(what, &iter.value(), true); |
| return true; |
| } |
| |
| OutputString("Target ", DECORATION_YELLOW); |
| OutputString(target->label().GetUserVisibleName(false)); |
| OutputString("\n"); |
| |
| std::unique_ptr<base::Value> v; |
| // Entries with DefaultHandler are present to enforce order |
| HandleProperty("type", handler_map, v, dict); |
| HandleProperty("toolchain", handler_map, v, dict); |
| HandleProperty(variables::kVisibility, handler_map, v, dict); |
| HandleProperty(variables::kMetadata, handler_map, v, dict); |
| HandleProperty(variables::kTestonly, handler_map, v, dict); |
| HandleProperty(variables::kCheckIncludes, handler_map, v, dict); |
| HandleProperty(variables::kAllowCircularIncludesFrom, handler_map, v, dict); |
| HandleProperty(variables::kSources, handler_map, v, dict); |
| HandleProperty(variables::kPublic, handler_map, v, dict); |
| HandleProperty(variables::kInputs, handler_map, v, dict); |
| HandleProperty(variables::kConfigs, handler_map, v, dict); |
| HandleProperty(variables::kPublicConfigs, handler_map, v, dict); |
| HandleProperty(variables::kAllDependentConfigs, handler_map, v, dict); |
| HandleProperty(variables::kScript, handler_map, v, dict); |
| HandleProperty(variables::kArgs, handler_map, v, dict); |
| HandleProperty(variables::kDepfile, handler_map, v, dict); |
| ProcessOutputs(dict.get()); |
| HandleProperty("bundle_data", handler_map, v, dict); |
| HandleProperty(variables::kArflags, handler_map, v, dict); |
| HandleProperty(variables::kAsmflags, handler_map, v, dict); |
| HandleProperty(variables::kCflags, handler_map, v, dict); |
| HandleProperty(variables::kCflagsC, handler_map, v, dict); |
| HandleProperty(variables::kCflagsCC, handler_map, v, dict); |
| HandleProperty(variables::kCflagsObjC, handler_map, v, dict); |
| HandleProperty(variables::kCflagsObjCC, handler_map, v, dict); |
| HandleProperty(variables::kDefines, handler_map, v, dict); |
| HandleProperty(variables::kIncludeDirs, handler_map, v, dict); |
| HandleProperty(variables::kLdflags, handler_map, v, dict); |
| HandleProperty(variables::kPrecompiledHeader, handler_map, v, dict); |
| HandleProperty(variables::kPrecompiledSource, handler_map, v, dict); |
| HandleProperty(variables::kDeps, handler_map, v, dict); |
| HandleProperty(variables::kLibs, handler_map, v, dict); |
| HandleProperty(variables::kLibDirs, handler_map, v, dict); |
| |
| #undef HandleProperty |
| |
| // Process the rest (if any) |
| base::DictionaryValue::Iterator iter(*dict); |
| while (!iter.IsAtEnd()) { |
| DefaultHandler(iter.key(), &iter.value(), false); |
| iter.Advance(); |
| } |
| |
| return true; |
| } |
| |
| bool PrintConfig(const Config* config, |
| const std::string& what, |
| bool single_config, |
| const std::map<std::string, DescHandlerFunc>& handler_map) { |
| std::unique_ptr<base::DictionaryValue> dict = |
| DescBuilder::DescriptionForConfig(config, what); |
| if (!what.empty() && dict->empty()) { |
| OutputString("Don't know how to display \"" + what + "\" for a config.\n"); |
| return false; |
| } |
| // Print single value |
| if (!what.empty() && dict->size() == 1 && single_config) { |
| base::DictionaryValue::Iterator iter(*dict); |
| auto pair = handler_map.find(what); |
| if (pair != handler_map.end()) |
| pair->second(what, &iter.value(), true); |
| return true; |
| } |
| |
| OutputString("Config: ", DECORATION_YELLOW); |
| OutputString(config->label().GetUserVisibleName(false)); |
| OutputString("\n"); |
| |
| std::unique_ptr<base::Value> v; |
| HandleProperty("toolchain", handler_map, v, dict); |
| if (!config->configs().empty()) { |
| OutputString( |
| "(This is a composite config, the values below are after the\n" |
| "expansion of the child configs.)\n"); |
| } |
| HandleProperty(variables::kArflags, handler_map, v, dict); |
| HandleProperty(variables::kAsmflags, handler_map, v, dict); |
| HandleProperty(variables::kCflags, handler_map, v, dict); |
| HandleProperty(variables::kCflagsC, handler_map, v, dict); |
| HandleProperty(variables::kCflagsCC, handler_map, v, dict); |
| HandleProperty(variables::kCflagsObjC, handler_map, v, dict); |
| HandleProperty(variables::kCflagsObjCC, handler_map, v, dict); |
| HandleProperty(variables::kDefines, handler_map, v, dict); |
| HandleProperty(variables::kIncludeDirs, handler_map, v, dict); |
| HandleProperty(variables::kInputs, handler_map, v, dict); |
| HandleProperty(variables::kLdflags, handler_map, v, dict); |
| HandleProperty(variables::kLibs, handler_map, v, dict); |
| HandleProperty(variables::kLibDirs, handler_map, v, dict); |
| HandleProperty(variables::kPrecompiledHeader, handler_map, v, dict); |
| HandleProperty(variables::kPrecompiledSource, handler_map, v, dict); |
| |
| #undef HandleProperty |
| |
| return true; |
| } |
| |
| } // namespace |
| |
| // desc ------------------------------------------------------------------------ |
| |
| const char kDesc[] = "desc"; |
| const char kDesc_HelpShort[] = |
| "desc: Show lots of insightful information about a target or config."; |
| const char kDesc_Help[] = |
| R"(gn desc <out_dir> <label or pattern> [<what to show>] [--blame] " |
| [--format=json] |
| |
| Displays information about a given target or config. The build parameters |
| will be taken for the build in the given <out_dir>. |
| |
| The <label or pattern> can be a target label, a config label, or a label |
| pattern (see "gn help label_pattern"). A label pattern will only match |
| targets. |
| |
| Possibilities for <what to show> |
| |
| (If unspecified an overall summary will be displayed.) |
| |
| all_dependent_configs |
| allow_circular_includes_from |
| arflags [--blame] |
| args |
| cflags [--blame] |
| cflags_c [--blame] |
| cflags_cc [--blame] |
| check_includes |
| configs [--tree] (see below) |
| defines [--blame] |
| depfile |
| deps [--all] [--tree] (see below) |
| include_dirs [--blame] |
| inputs |
| ldflags [--blame] |
| lib_dirs |
| libs |
| metadata |
| outputs |
| public_configs |
| public |
| script |
| sources |
| testonly |
| visibility |
| |
| runtime_deps |
| Compute all runtime deps for the given target. This is a computed list |
| and does not correspond to any GN variable, unlike most other values |
| here. |
| |
| The output is a list of file names relative to the build directory. See |
| "gn help runtime_deps" for how this is computed. This also works with |
| "--blame" to see the source of the dependency. |
| |
| Shared flags |
| )" |
| |
| ALL_TOOLCHAINS_SWITCH_HELP |
| |
| R"( |
| --format=json |
| Format the output as JSON instead of text. |
| |
| Target flags |
| |
| --blame |
| Used with any value specified on a config, this will name the config that |
| causes that target to get the flag. This doesn't currently work for libs |
| and lib_dirs because those are inherited and are more complicated to |
| figure out the blame (patches welcome). |
| |
| Configs |
| |
| The "configs" section will list all configs that apply. For targets this will |
| include configs specified in the "configs" variable of the target, and also |
| configs pushed onto this target via public or "all dependent" configs. |
| |
| Configs can have child configs. Specifying --tree will show the hierarchy. |
| |
| Printing outputs |
| |
| The "outputs" section will list all outputs that apply, including the outputs |
| computed from the tool definition (eg for "executable", "static_library", ... |
| targets). |
| |
| Printing deps |
| |
| Deps will include all public, private, and data deps (TODO this could be |
| clarified and enhanced) sorted in order applying. The following may be used: |
| |
| --all |
| Collects all recursive dependencies and prints a sorted flat list. Also |
| usable with --tree (see below). |
| )" |
| |
| TARGET_PRINTING_MODE_COMMAND_LINE_HELP |
| "\n" TARGET_TESTONLY_FILTER_COMMAND_LINE_HELP |
| |
| R"( |
| --tree |
| Print a dependency tree. By default, duplicates will be elided with "..." |
| but when --all and -tree are used together, no eliding will be performed. |
| |
| The "deps", "public_deps", and "data_deps" will all be included in the |
| tree. |
| |
| Tree output can not be used with the filtering or output flags: --as, |
| --type, --testonly. |
| )" |
| |
| TARGET_TYPE_FILTER_COMMAND_LINE_HELP |
| |
| R"(Note |
| |
| This command will show the full name of directories and source files, but |
| when directories and source paths are written to the build file, they will be |
| adjusted to be relative to the build directory. So the values for paths |
| displayed by this command won't match (but should mean the same thing). |
| |
| Examples |
| |
| gn desc out/Debug //base:base |
| Summarizes the given target. |
| |
| gn desc out/Foo :base_unittests deps --tree |
| Shows a dependency tree of the "base_unittests" project in |
| the current directory. |
| |
| gn desc out/Debug //base defines --blame |
| Shows defines set for the //base:base target, annotated by where |
| each one was set from. |
| )"; |
| |
| int RunDesc(const std::vector<std::string>& args) { |
| if (args.size() != 2 && args.size() != 3) { |
| Err(Location(), "You're holding it wrong.", |
| "Usage: \"gn desc <out_dir> <target_name> [<what to display>]\"") |
| .PrintToStdout(); |
| return 1; |
| } |
| const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess(); |
| |
| // Deliberately leaked to avoid expensive process teardown. |
| Setup* setup = new Setup; |
| if (!setup->DoSetup(args[0], false)) |
| return 1; |
| if (!setup->Run()) |
| return 1; |
| |
| // Resolve target(s) and config from inputs. |
| UniqueVector<const Target*> target_matches; |
| UniqueVector<const Config*> config_matches; |
| UniqueVector<const Toolchain*> toolchain_matches; |
| UniqueVector<SourceFile> file_matches; |
| |
| std::vector<std::string> target_list; |
| target_list.push_back(args[1]); |
| |
| if (!ResolveFromCommandLineInput( |
| setup, target_list, cmdline->HasSwitch(switches::kAllToolchains), |
| &target_matches, &config_matches, &toolchain_matches, &file_matches)) |
| return 1; |
| |
| std::string what_to_print; |
| if (args.size() == 3) |
| what_to_print = args[2]; |
| |
| bool json = cmdline->GetSwitchValueASCII("format") == "json"; |
| |
| if (target_matches.empty() && config_matches.empty()) { |
| OutputString( |
| "The input " + args[1] + " matches no targets, configs or files.\n", |
| DECORATION_YELLOW); |
| return 1; |
| } |
| |
| if (json) { |
| // Convert all targets/configs to JSON, serialize and print them |
| auto res = std::make_unique<base::DictionaryValue>(); |
| if (!target_matches.empty()) { |
| for (const auto* target : target_matches) { |
| res->SetWithoutPathExpansion( |
| target->label().GetUserVisibleName( |
| target->settings()->default_toolchain_label()), |
| DescBuilder::DescriptionForTarget( |
| target, what_to_print, cmdline->HasSwitch(kAll), |
| cmdline->HasSwitch(kTree), cmdline->HasSwitch(kBlame))); |
| } |
| } else if (!config_matches.empty()) { |
| for (const auto* config : config_matches) { |
| res->SetWithoutPathExpansion( |
| config->label().GetUserVisibleName(false), |
| DescBuilder::DescriptionForConfig(config, what_to_print)); |
| } |
| } |
| std::string s; |
| base::JSONWriter::WriteWithOptions( |
| *res.get(), base::JSONWriter::OPTIONS_PRETTY_PRINT, &s); |
| OutputString(s); |
| } else { |
| // Regular (non-json) formatted output |
| bool multiple_outputs = (target_matches.size() + config_matches.size()) > 1; |
| std::map<std::string, DescHandlerFunc> handlers = GetHandlers(); |
| |
| bool printed_output = false; |
| for (const Target* target : target_matches) { |
| if (printed_output) |
| OutputString("\n\n"); |
| printed_output = true; |
| |
| if (!PrintTarget(target, what_to_print, !multiple_outputs, handlers, |
| cmdline->HasSwitch(kAll), cmdline->HasSwitch(kTree), |
| cmdline->HasSwitch(kBlame))) |
| return 1; |
| } |
| for (const Config* config : config_matches) { |
| if (printed_output) |
| OutputString("\n\n"); |
| printed_output = true; |
| |
| if (!PrintConfig(config, what_to_print, !multiple_outputs, handlers)) |
| return 1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| } // namespace commands |