Chromium Code Reviews| Index: tools/gn/commands.cc | 
| diff --git a/tools/gn/commands.cc b/tools/gn/commands.cc | 
| index ace78a7dff9c2a713e2f92383da72fb711638334..5c77d51d1a45d0457ac1e4eeb8af6aa24ad184f6 100644 | 
| --- a/tools/gn/commands.cc | 
| +++ b/tools/gn/commands.cc | 
| @@ -2,6 +2,8 @@ | 
| // Use of this source code is governed by a BSD-style license that can be | 
| // found in the LICENSE file. | 
| +#include "base/command_line.h" | 
| +#include "tools/gn/builder.h" | 
| #include "tools/gn/commands.h" | 
| #include "tools/gn/filesystem_utils.h" | 
| #include "tools/gn/item.h" | 
| @@ -13,6 +15,320 @@ | 
| namespace commands { | 
| +namespace { | 
| + | 
| +// Like above but the input string can be a pattern that matches multiple | 
| +// targets. If the input does not parse as a pattern, prints and error and | 
| +// returns false. If the pattern is valid, fills the vector (which might be | 
| +// empty if there are no matches) and returns true. | 
| +// | 
| +// If all_tolchains is false, a pattern with an unspecified toolchain will | 
| 
 
scottmg
2015/02/19 21:52:01
all_toolchains
 
 | 
| +// match the default toolchain only. If true, all toolchains will be matched. | 
| +bool ResolveTargetsFromCommandLinePattern( | 
| + Setup* setup, | 
| + const std::string& label_pattern, | 
| + bool all_toolchains, | 
| + std::vector<const Target*>* matches) { | 
| + Value pattern_value(nullptr, label_pattern); | 
| + | 
| + Err err; | 
| + LabelPattern pattern = LabelPattern::GetPattern( | 
| + SourceDirForCurrentDirectory(setup->build_settings().root_path()), | 
| + pattern_value, | 
| + &err); | 
| + if (err.has_error()) { | 
| + err.PrintToStdout(); | 
| + return false; | 
| + } | 
| + | 
| + if (!all_toolchains) { | 
| + // By default a pattern with an empty toolchain will match all toolchains. | 
| + // If the caller wants to default to the main toolchain only, set it | 
| + // explicitly. | 
| + if (pattern.toolchain().is_null()) { | 
| + // No explicit toolchain set. | 
| + pattern.set_toolchain(setup->loader()->default_toolchain_label()); | 
| + } | 
| + } | 
| + | 
| + std::vector<LabelPattern> pattern_vector; | 
| + pattern_vector.push_back(pattern); | 
| + FilterTargetsByPatterns(setup->builder()->GetAllResolvedTargets(), | 
| + pattern_vector, matches); | 
| + return true; | 
| +} | 
| + | 
| + | 
| +// If there's an error, it will be printed and false will be returned. | 
| +bool ResolveStringFromCommandLineInput( | 
| + Setup* setup, | 
| + const SourceDir& current_dir, | 
| + const std::string& input, | 
| + bool all_toolchains, | 
| + UniqueVector<const Target*>* target_matches, | 
| + UniqueVector<const Config*>* config_matches, | 
| + UniqueVector<const Toolchain*>* toolchain_matches, | 
| + UniqueVector<SourceFile>* file_matches) { | 
| + if (LabelPattern::HasWildcard(input)) { | 
| + // For now, only match patterns against targets. It might be nice in the | 
| + // future to allow the user to specify which types of things they want to | 
| + // match, but it should probably only match targets by default. | 
| + std::vector<const Target*> target_match_vector; | 
| + if (!ResolveTargetsFromCommandLinePattern(setup, input, all_toolchains, | 
| + &target_match_vector)) | 
| + return false; | 
| + for (const Target* target : target_match_vector) | 
| + target_matches->push_back(target); | 
| + return true; | 
| + } | 
| + | 
| + // Try to figure out what this thing is. | 
| + Err err; | 
| + Label label = Label::Resolve(current_dir, | 
| + setup->loader()->default_toolchain_label(), | 
| + Value(nullptr, input), &err); | 
| + if (err.has_error()) { | 
| + err.PrintToStdout(); | 
| + return false; | 
| + } | 
| + | 
| + const Item* item = setup->builder()->GetItem(label); | 
| + if (item) { | 
| + if (const Config* as_config = item->AsConfig()) | 
| + config_matches->push_back(as_config); | 
| + else if (const Target* as_target = item->AsTarget()) | 
| + target_matches->push_back(as_target); | 
| + else if (const Toolchain* as_toolchain = item->AsToolchain()) | 
| + toolchain_matches->push_back(as_toolchain); | 
| + } else { | 
| + // Not an item, assume this must be a file. | 
| + file_matches->push_back(current_dir.ResolveRelativeFile( | 
| + input, setup->build_settings().root_path_utf8())); | 
| + } | 
| + | 
| + return true; | 
| +} | 
| + | 
| +enum TargetPrintingMode { | 
| + TARGET_PRINT_BUILDFILE, | 
| + TARGET_PRINT_LABEL, | 
| + TARGET_PRINT_OUTPUT, | 
| +}; | 
| + | 
| +// Retrieves the target printing mode based on the command line flags for the | 
| +// current process. Returns true on success. On error, prints a message to the | 
| +// console and returns false. | 
| +bool GetTargetPrintingMode(TargetPrintingMode* mode) { | 
| + std::string switch_key = "as"; | 
| + const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess(); | 
| + | 
| + if (!cmdline->HasSwitch(switch_key)) { | 
| + // Default to labels. | 
| + *mode = TARGET_PRINT_LABEL; | 
| + return true; | 
| + } | 
| + | 
| + std::string value = cmdline->GetSwitchValueASCII(switch_key); | 
| + if (value == "buildfile") { | 
| + *mode = TARGET_PRINT_BUILDFILE; | 
| + return true; | 
| + } | 
| + if (value == "label") { | 
| + *mode = TARGET_PRINT_LABEL; | 
| + return true; | 
| + } | 
| + if (value == "output") { | 
| + *mode = TARGET_PRINT_OUTPUT; | 
| + return true; | 
| + } | 
| + | 
| + Err(Location(), "Invalid value for \"--as\".", | 
| + "I was expecting \"buildfile\", \"label\", or \"output\" but you\n" | 
| + "said \"" + value + "\".").PrintToStdout(); | 
| + return false; | 
| +} | 
| + | 
| +// Returns the target type filter based on the command line flags for the | 
| +// current process. Returns true on success. On error, prints a message to the | 
| +// console and returns false. | 
| +// | 
| +// Target::UNKNOWN will be set if there is no filter. Target::ACTION_FOREACH | 
| +// will never be returned. Code applying the filters should apply Target::ACTION | 
| +// to both ACTION and ACTION_FOREACH. | 
| +bool GetTargetTypeFilter(Target::OutputType* type) { | 
| + std::string switch_key = "type"; | 
| + const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess(); | 
| + | 
| + if (!cmdline->HasSwitch(switch_key)) { | 
| + // Default to unknown -> no filtering. | 
| + *type = Target::UNKNOWN; | 
| + return true; | 
| + } | 
| + | 
| + std::string value = cmdline->GetSwitchValueASCII(switch_key); | 
| + if (value == "group") { | 
| + *type = Target::GROUP; | 
| + return true; | 
| + } | 
| + if (value == "executable") { | 
| + *type = Target::EXECUTABLE; | 
| + return true; | 
| + } | 
| + if (value == "shared_library") { | 
| + *type = Target::SHARED_LIBRARY; | 
| + return true; | 
| + } | 
| + if (value == "static_library") { | 
| + *type = Target::STATIC_LIBRARY; | 
| + return true; | 
| + } | 
| + if (value == "source_set") { | 
| + *type = Target::SOURCE_SET; | 
| + return true; | 
| + } | 
| + if (value == "copy") { | 
| + *type = Target::COPY_FILES; | 
| + return true; | 
| + } | 
| + if (value == "action") { | 
| + *type = Target::ACTION; | 
| + return true; | 
| + } | 
| + | 
| + Err(Location(), "Invalid value for \"--type\".").PrintToStdout(); | 
| + return false; | 
| +} | 
| + | 
| + | 
| +// Applies any testonly filtering specified on the command line to the given | 
| +// target set. On failure, prints an error and returns false. | 
| +bool ApplyTestonlyFilter(std::vector<const Target*>* targets) { | 
| + const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess(); | 
| + std::string testonly_key = "testonly"; | 
| + | 
| + if (targets->empty() || !cmdline->HasSwitch(testonly_key)) | 
| + return true; | 
| + | 
| + std::string testonly_value = cmdline->GetSwitchValueASCII(testonly_key); | 
| + bool testonly = false; | 
| + if (testonly_value == "true") { | 
| + testonly = true; | 
| + } else if (testonly_value != "false") { | 
| + Err(Location(), "Bad value for --testonly.", | 
| + "I was expecting --testonly=true or --testonly=false.") | 
| + .PrintToStdout(); | 
| + return false; | 
| + } | 
| + | 
| + // Filter into a copy of the vector, then swap to output. | 
| + std::vector<const Target*> result; | 
| + result.reserve(targets->size()); | 
| + | 
| + for (const Target* target : *targets) { | 
| + if (target->testonly() == testonly) | 
| + result.push_back(target); | 
| + } | 
| + | 
| + targets->swap(result); | 
| + return true; | 
| +} | 
| + | 
| +// Applies any target type filtering specified on the command line to the given | 
| +// target set. On failure, prints an error and returns false. | 
| +bool ApplyTypeFilter(std::vector<const Target*>* targets) { | 
| + Target::OutputType type = Target::UNKNOWN; | 
| + if (!GetTargetTypeFilter(&type)) | 
| + return false; | 
| + if (targets->empty() || type == Target::UNKNOWN) | 
| + return true; // Nothing to filter out. | 
| + | 
| + // Filter into a copy of the vector, then swap to output. | 
| + std::vector<const Target*> result; | 
| + result.reserve(targets->size()); | 
| + | 
| + for (const Target* target : *targets) { | 
| + // Make "action" also apply to ACTION_FOREACH. | 
| + if (target->output_type() == type || | 
| + (type == Target::ACTION && | 
| + target->output_type() == Target::ACTION_FOREACH)) | 
| + result.push_back(target); | 
| + } | 
| + | 
| + targets->swap(result); | 
| + return true; | 
| +} | 
| + | 
| +// Returns the file path generating this item. | 
| +base::FilePath BuildFileForItem(const Item* item) { | 
| + return item->defined_from()->GetRange().begin().file()->physical_name(); | 
| +} | 
| + | 
| +void PrintTargetsAsBuildfiles(bool indent, | 
| + const std::vector<const Target*>& targets) { | 
| + // Output the set of unique source files. | 
| + std::set<std::string> unique_files; | 
| + for (const Target* target : targets) | 
| + unique_files.insert(FilePathToUTF8(BuildFileForItem(target))); | 
| + | 
| + for (const std::string& file : unique_files) { | 
| + if (indent) | 
| + OutputString(" "); | 
| + OutputString(file + "\n"); | 
| + } | 
| +} | 
| + | 
| +void PrintTargetsAsLabels(bool indent, | 
| + const std::vector<const Target*>& targets) { | 
| + // Putting the labels into a set automatically sorts them for us. | 
| + std::set<Label> unique_labels; | 
| + for (const auto& target : targets) | 
| + unique_labels.insert(target->label()); | 
| + | 
| + // Grab the label of the default toolchain from the first target. | 
| + Label default_tc_label = | 
| + targets[0]->settings()->default_toolchain_label(); | 
| + | 
| + for (const Label& label : unique_labels) { | 
| + // Print toolchain only for ones not in the default toolchain. | 
| + if (indent) | 
| + OutputString(" "); | 
| + OutputString(label.GetUserVisibleName( | 
| + label.GetToolchainLabel() != default_tc_label)); | 
| + OutputString("\n"); | 
| + } | 
| +} | 
| + | 
| +void PrintTargetsAsOutputs(bool indent, | 
| + const std::vector<const Target*>& targets) { | 
| + if (targets.empty()) | 
| + return; | 
| + | 
| + // Grab the build settings from a random target. | 
| + const BuildSettings* build_settings = | 
| + targets[0]->settings()->build_settings(); | 
| + | 
| + SourceDir current_dir = SourceDirForCurrentDirectory( | 
| + build_settings->root_path()); | 
| + for (const Target* target : targets) { | 
| + // Use the link output file if there is one, otherwise fall back to the | 
| + // dependency output file (for actions, for example). | 
| + OutputFile output_file = target->link_output_file(); | 
| + if (output_file.value().empty()) | 
| + output_file = target->dependency_output_file(); | 
| + | 
| + SourceFile output_as_source = | 
| + output_file.AsSourceFile(build_settings); | 
| + std::string result = RebasePath(output_as_source.value(), current_dir, | 
| + build_settings->root_path_utf8()); | 
| + if (indent) | 
| + OutputString(" "); | 
| + OutputString(result); | 
| + OutputString("\n"); | 
| + } | 
| +} | 
| + | 
| +} // namespace | 
| + | 
| CommandInfo::CommandInfo() | 
| : help_short(nullptr), | 
| help(nullptr), | 
| @@ -84,38 +400,87 @@ const Target* ResolveTargetFromCommandLineString( | 
| return target; | 
| } | 
| -bool ResolveTargetsFromCommandLinePattern( | 
| +bool ResolveFromCommandLineInput( | 
| Setup* setup, | 
| - const std::string& label_pattern, | 
| + const std::vector<std::string>& input, | 
| bool all_toolchains, | 
| - std::vector<const Target*>* matches) { | 
| - Value pattern_value(nullptr, label_pattern); | 
| - | 
| - Err err; | 
| - LabelPattern pattern = LabelPattern::GetPattern( | 
| - SourceDirForCurrentDirectory(setup->build_settings().root_path()), | 
| - pattern_value, | 
| - &err); | 
| - if (err.has_error()) { | 
| - err.PrintToStdout(); | 
| + UniqueVector<const Target*>* target_matches, | 
| + UniqueVector<const Config*>* config_matches, | 
| + UniqueVector<const Toolchain*>* toolchain_matches, | 
| + UniqueVector<SourceFile>* file_matches) { | 
| + if (input.empty()) { | 
| + Err(Location(), "You need to specify a label, file, or pattern.") | 
| + .PrintToStdout(); | 
| return false; | 
| } | 
| - if (!all_toolchains) { | 
| - // By default a pattern with an empty toolchain will match all toolchains. | 
| - // IF the caller wants to default to the main toolchain only, set it | 
| - // explicitly. | 
| - if (pattern.toolchain().is_null()) { | 
| - // No explicit toolchain set. | 
| - pattern.set_toolchain(setup->loader()->default_toolchain_label()); | 
| + SourceDir cur_dir = | 
| + SourceDirForCurrentDirectory(setup->build_settings().root_path()); | 
| + for (const auto& cur : input) { | 
| + if (!ResolveStringFromCommandLineInput(setup, cur_dir, cur, | 
| + all_toolchains, target_matches, | 
| + config_matches, toolchain_matches, | 
| + file_matches)) | 
| + return false; | 
| + } | 
| + return true; | 
| +} | 
| + | 
| +void FilterTargetsByPatterns(const std::vector<const Target*>& input, | 
| 
 
scottmg
2015/02/19 21:52:01
this could be template <class O> for output
 
brettw
2015/02/19 22:12:02
Yeah, but I didn't feel like I should implement th
 
 | 
| + const std::vector<LabelPattern>& filter, | 
| + std::vector<const Target*>* output) { | 
| + for (const auto& target : input) { | 
| + for (const auto& pattern : filter) { | 
| + if (pattern.Matches(target->label())) { | 
| + output->push_back(target); | 
| + break; | 
| + } | 
| } | 
| } | 
| +} | 
| - std::vector<LabelPattern> pattern_vector; | 
| - pattern_vector.push_back(pattern); | 
| - FilterTargetsByPatterns(setup->builder()->GetAllResolvedTargets(), | 
| - pattern_vector, matches); | 
| - return true; | 
| +void FilterTargetsByPatterns(const std::vector<const Target*>& input, | 
| + const std::vector<LabelPattern>& filter, | 
| + UniqueVector<const Target*>* output) { | 
| + for (const auto& target : input) { | 
| + for (const auto& pattern : filter) { | 
| + if (pattern.Matches(target->label())) { | 
| + output->push_back(target); | 
| + break; | 
| + } | 
| + } | 
| + } | 
| +} | 
| + | 
| +void FilterAndPrintTargets(bool indent, std::vector<const Target*>* targets) { | 
| + if (targets->empty()) | 
| + return; | 
| + | 
| + if (!ApplyTestonlyFilter(targets)) | 
| + return; | 
| + if (!ApplyTypeFilter(targets)) | 
| + return; | 
| + | 
| + TargetPrintingMode printing_mode = TARGET_PRINT_LABEL; | 
| + if (targets->empty() || !GetTargetPrintingMode(&printing_mode)) | 
| + return; | 
| + switch (printing_mode) { | 
| + case TARGET_PRINT_BUILDFILE: | 
| + PrintTargetsAsBuildfiles(indent, *targets); | 
| + break; | 
| + case TARGET_PRINT_LABEL: | 
| + PrintTargetsAsLabels(indent, *targets); | 
| + break; | 
| + case TARGET_PRINT_OUTPUT: | 
| + PrintTargetsAsOutputs(indent, *targets); | 
| + break; | 
| + } | 
| +} | 
| + | 
| +void FilterAndPrintTargetSet(bool indent, | 
| + const std::set<const Target*>& targets) { | 
| + std::vector<const Target*> target_vector(targets.begin(), targets.end()); | 
| + FilterAndPrintTargets(indent, &target_vector); | 
| } | 
| } // namespace commands |