| // 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 "tools/gn/commands.h" | 
 |  | 
 | #include "base/command_line.h" | 
 | #include "base/environment.h" | 
 | #include "base/strings/string_split.h" | 
 | #include "base/values.h" | 
 | #include "tools/gn/builder.h" | 
 | #include "tools/gn/filesystem_utils.h" | 
 | #include "tools/gn/item.h" | 
 | #include "tools/gn/label.h" | 
 | #include "tools/gn/label_pattern.h" | 
 | #include "tools/gn/setup.h" | 
 | #include "tools/gn/standard_out.h" | 
 | #include "tools/gn/target.h" | 
 | #include "util/build_config.h" | 
 |  | 
 | 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_toolchains is false, a pattern with an unspecified toolchain will | 
 | // 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()) { | 
 |     // Not a valid label, assume this must be a file. | 
 |     err = Err(); | 
 |     file_matches->push_back(current_dir.ResolveRelativeFile( | 
 |         Value(nullptr, input), &err, setup->build_settings().root_path_utf8())); | 
 |     if (err.has_error()) { | 
 |       err.PrintToStdout(); | 
 |       return false; | 
 |     } | 
 |     return true; | 
 |   } | 
 |  | 
 |   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( | 
 |         Value(nullptr, input), &err, setup->build_settings().root_path_utf8())); | 
 |     if (err.has_error()) { | 
 |       err.PrintToStdout(); | 
 |       return false; | 
 |     } | 
 |   } | 
 |  | 
 |   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 == "loadable_module") { | 
 |     *type = Target::LOADABLE_MODULE; | 
 |     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(const std::vector<const Target*>& targets, | 
 |                               base::ListValue* out) { | 
 |   // 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) { | 
 |     out->AppendString(file); | 
 |   } | 
 | } | 
 |  | 
 | void PrintTargetsAsLabels(const std::vector<const Target*>& targets, | 
 |                           base::ListValue* out) { | 
 |   // Putting the labels into a set automatically sorts them for us. | 
 |   std::set<Label> unique_labels; | 
 |   for (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. | 
 |     out->AppendString(label.GetUserVisibleName(label.GetToolchainLabel() != | 
 |                                                default_tc_label)); | 
 |   } | 
 | } | 
 |  | 
 | void PrintTargetsAsOutputs(const std::vector<const Target*>& targets, | 
 |                            base::ListValue* out) { | 
 |   if (targets.empty()) | 
 |     return; | 
 |  | 
 |   // Grab the build settings from a random target. | 
 |   const BuildSettings* build_settings = | 
 |       targets[0]->settings()->build_settings(); | 
 |  | 
 |   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(), build_settings->build_dir(), | 
 |                    build_settings->root_path_utf8()); | 
 |     out->AppendString(result); | 
 |   } | 
 | } | 
 |  | 
 | #if defined(OS_WIN) | 
 | // Git bash will remove the first "/" in "//" paths | 
 | // This also happens for labels assigned to command line parameters, e.g. | 
 | // --filters | 
 | // Fix "//" paths, but not absolute and relative paths | 
 | inline std::string FixGitBashLabelEdit(const std::string& label) { | 
 |   static std::unique_ptr<base::Environment> git_bash_env; | 
 |   if (!git_bash_env) | 
 |     git_bash_env = base::Environment::Create(); | 
 |  | 
 |   std::string temp_label(label); | 
 |  | 
 |   if (git_bash_env->HasVar( | 
 |           "MSYSTEM") &&        // Only for MinGW based shells like Git Bash | 
 |       temp_label[0] == '/' &&  // Only fix for //foo paths, not /f:oo paths | 
 |       (temp_label.length() < 2 || | 
 |        (temp_label[1] != '/' && | 
 |         (temp_label.length() < 3 || temp_label[1] != ':')))) | 
 |     temp_label.insert(0, "/"); | 
 |   return temp_label; | 
 | } | 
 | #else | 
 | // Only repair on Windows | 
 | inline std::string FixGitBashLabelEdit(const std::string& label) { | 
 |   return label; | 
 | } | 
 | #endif | 
 |  | 
 | }  // namespace | 
 |  | 
 | CommandInfo::CommandInfo() | 
 |     : help_short(nullptr), help(nullptr), runner(nullptr) {} | 
 |  | 
 | CommandInfo::CommandInfo(const char* in_help_short, | 
 |                          const char* in_help, | 
 |                          CommandRunner in_runner) | 
 |     : help_short(in_help_short), help(in_help), runner(in_runner) {} | 
 |  | 
 | const CommandInfoMap& GetCommands() { | 
 |   static CommandInfoMap info_map; | 
 |   if (info_map.empty()) { | 
 | #define INSERT_COMMAND(cmd) \ | 
 |   info_map[k##cmd] = CommandInfo(k##cmd##_HelpShort, k##cmd##_Help, &Run##cmd); | 
 |  | 
 |     INSERT_COMMAND(Analyze) | 
 |     INSERT_COMMAND(Args) | 
 |     INSERT_COMMAND(Check) | 
 |     INSERT_COMMAND(Clean) | 
 |     INSERT_COMMAND(Desc) | 
 |     INSERT_COMMAND(Gen) | 
 |     INSERT_COMMAND(Format) | 
 |     INSERT_COMMAND(Help) | 
 |     INSERT_COMMAND(Ls) | 
 |     INSERT_COMMAND(Path) | 
 |     INSERT_COMMAND(Refs) | 
 |  | 
 | #undef INSERT_COMMAND | 
 |   } | 
 |   return info_map; | 
 | } | 
 |  | 
 | const Target* ResolveTargetFromCommandLineString( | 
 |     Setup* setup, | 
 |     const std::string& label_string) { | 
 |   // Need to resolve the label after we know the default toolchain. | 
 |   Label default_toolchain = setup->loader()->default_toolchain_label(); | 
 |   Value arg_value(nullptr, FixGitBashLabelEdit(label_string)); | 
 |   Err err; | 
 |   Label label = Label::Resolve( | 
 |       SourceDirForCurrentDirectory(setup->build_settings().root_path()), | 
 |       default_toolchain, arg_value, &err); | 
 |   if (err.has_error()) { | 
 |     err.PrintToStdout(); | 
 |     return nullptr; | 
 |   } | 
 |  | 
 |   const Item* item = setup->builder().GetItem(label); | 
 |   if (!item) { | 
 |     Err(Location(), "Label not found.", | 
 |         label.GetUserVisibleName(false) + " not found.") | 
 |         .PrintToStdout(); | 
 |     return nullptr; | 
 |   } | 
 |  | 
 |   const Target* target = item->AsTarget(); | 
 |   if (!target) { | 
 |     Err(Location(), "Not a target.", | 
 |         "The \"" + label.GetUserVisibleName(false) + | 
 |             "\" thing\n" | 
 |             "is not a target. Somebody should probably implement this command " | 
 |             "for " | 
 |             "other\nitem types.") | 
 |         .PrintToStdout(); | 
 |     return nullptr; | 
 |   } | 
 |  | 
 |   return target; | 
 | } | 
 |  | 
 | bool ResolveFromCommandLineInput( | 
 |     Setup* setup, | 
 |     const std::vector<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 (input.empty()) { | 
 |     Err(Location(), "You need to specify a label, file, or pattern.") | 
 |         .PrintToStdout(); | 
 |     return false; | 
 |   } | 
 |  | 
 |   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, | 
 |                              const std::vector<LabelPattern>& filter, | 
 |                              std::vector<const Target*>* output) { | 
 |   for (auto* target : input) { | 
 |     for (const auto& pattern : filter) { | 
 |       if (pattern.Matches(target->label())) { | 
 |         output->push_back(target); | 
 |         break; | 
 |       } | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | void FilterTargetsByPatterns(const std::vector<const Target*>& input, | 
 |                              const std::vector<LabelPattern>& filter, | 
 |                              UniqueVector<const Target*>* output) { | 
 |   for (auto* target : input) { | 
 |     for (const auto& pattern : filter) { | 
 |       if (pattern.Matches(target->label())) { | 
 |         output->push_back(target); | 
 |         break; | 
 |       } | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | bool FilterPatternsFromString(const BuildSettings* build_settings, | 
 |                               const std::string& label_list_string, | 
 |                               std::vector<LabelPattern>* filters, | 
 |                               Err* err) { | 
 |   std::vector<std::string> tokens = base::SplitString( | 
 |       label_list_string, ";", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); | 
 |   SourceDir root_dir("//"); | 
 |  | 
 |   filters->reserve(tokens.size()); | 
 |   for (const std::string& token : tokens) { | 
 |     LabelPattern pattern = LabelPattern::GetPattern( | 
 |         root_dir, Value(nullptr, FixGitBashLabelEdit(token)), err); | 
 |     if (err->has_error()) | 
 |       return false; | 
 |     filters->push_back(pattern); | 
 |   } | 
 |  | 
 |   return true; | 
 | } | 
 |  | 
 | void FilterAndPrintTargets(std::vector<const Target*>* targets, | 
 |                            base::ListValue* out) { | 
 |   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(*targets, out); | 
 |       break; | 
 |     case TARGET_PRINT_LABEL: | 
 |       PrintTargetsAsLabels(*targets, out); | 
 |       break; | 
 |     case TARGET_PRINT_OUTPUT: | 
 |       PrintTargetsAsOutputs(*targets, out); | 
 |       break; | 
 |   } | 
 | } | 
 |  | 
 | void FilterAndPrintTargets(bool indent, std::vector<const Target*>* targets) { | 
 |   base::ListValue tmp; | 
 |   FilterAndPrintTargets(targets, &tmp); | 
 |   for (const auto& value : tmp) { | 
 |     std::string string; | 
 |     value.GetAsString(&string); | 
 |     if (indent) | 
 |       OutputString("  "); | 
 |     OutputString(string); | 
 |     OutputString("\n"); | 
 |   } | 
 | } | 
 |  | 
 | void FilterAndPrintTargetSet(bool indent, | 
 |                              const std::set<const Target*>& targets) { | 
 |   std::vector<const Target*> target_vector(targets.begin(), targets.end()); | 
 |   FilterAndPrintTargets(indent, &target_vector); | 
 | } | 
 |  | 
 | void FilterAndPrintTargetSet(const std::set<const Target*>& targets, | 
 |                              base::ListValue* out) { | 
 |   std::vector<const Target*> target_vector(targets.begin(), targets.end()); | 
 |   FilterAndPrintTargets(&target_vector, out); | 
 | } | 
 |  | 
 | }  // namespace commands |