| // 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, bool files_only) { | |
| base::ListValue* patterns = nullptr; | |
| base::ListValue* outputs = nullptr; | |
| target->GetList("output_patterns", &patterns); | |
| target->GetList(variables::kOutputs, &outputs); | |
| int indent = 0; | |
| if (outputs || patterns) { | |
| if (!files_only) { | |
| OutputString("\noutputs\n"); | |
| indent = 1; | |
| } | |
| if (patterns) { | |
| if (!files_only) { | |
| OutputString(" Output patterns\n"); | |
| indent = 2; | |
| } | |
| PrintValue(patterns, indent); | |
| if (!files_only) | |
| 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}, | |
| {variables::kDataKeys, DefaultHandler}, | |
| {variables::kRebase, DefaultHandler}, | |
| {variables::kWalkKeys, DefaultHandler}, | |
| {variables::kWriteOutputConversion, DefaultHandler}, | |
| {"runtime_deps", 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) { | |
| if (what == variables::kOutputs) { | |
| ProcessOutputs(dict.get(), true); | |
| return true; | |
| } | |
| 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(), false); | |
| 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); | |
| HandleProperty(variables::kDataKeys, handler_map, v, dict); | |
| HandleProperty(variables::kRebase, handler_map, v, dict); | |
| HandleProperty(variables::kWalkKeys, handler_map, v, dict); | |
| HandleProperty(variables::kWriteOutputConversion, 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 | |
| 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) | |
| data_keys | |
| defines [--blame] | |
| depfile | |
| deps [--all] [--tree] (see below) | |
| include_dirs [--blame] | |
| inputs | |
| ldflags [--blame] | |
| lib_dirs | |
| libs | |
| metadata | |
| output_conversion | |
| outputs | |
| public_configs | |
| public | |
| rebase | |
| script | |
| sources | |
| testonly | |
| visibility | |
| walk_keys | |
| 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 |