| // 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 "gn/commands.h" |
| |
| #include <optional> |
| |
| #include "base/command_line.h" |
| #include "base/environment.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/string_util.h" |
| #include "base/values.h" |
| #include "gn/builder.h" |
| #include "gn/config_values_extractors.h" |
| #include "gn/filesystem_utils.h" |
| #include "gn/item.h" |
| #include "gn/label.h" |
| #include "gn/label_pattern.h" |
| #include "gn/setup.h" |
| #include "gn/standard_out.h" |
| #include "gn/switches.h" |
| #include "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 default_toolchain_only is true, 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 default_toolchain_only, |
| std::vector<const Target*>* matches) { |
| Value pattern_value(nullptr, label_pattern); |
| |
| Err err; |
| LabelPattern pattern = LabelPattern::GetPattern( |
| SourceDirForCurrentDirectory(setup->build_settings().root_path()), |
| setup->build_settings().root_path_utf8(), pattern_value, &err); |
| if (err.has_error()) { |
| err.PrintToStdout(); |
| return false; |
| } |
| |
| if (default_toolchain_only) { |
| // 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 default_toolchain_only, |
| 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, default_toolchain_only, &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->build_settings().root_path_utf8(), |
| 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; |
| } |
| |
| // 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(CommandSwitches::TargetPrintMode* mode) { |
| *mode = CommandSwitches::Get().target_print_mode(); |
| return true; |
| } |
| |
| // 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) { |
| *type = CommandSwitches::Get().target_type(); |
| return true; |
| } |
| |
| // 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) { |
| CommandSwitches::TestonlyMode testonly_mode = |
| CommandSwitches::Get().testonly_mode(); |
| |
| if (targets->empty() || testonly_mode == CommandSwitches::TESTONLY_NONE) |
| return true; |
| |
| bool testonly = (testonly_mode == CommandSwitches::TESTONLY_TRUE); |
| |
| // Filter into a copy of the vector, then replace the output. |
| std::vector<const Target*> result; |
| result.reserve(targets->size()); |
| |
| for (const Target* target : *targets) { |
| if (target->testonly() == testonly) |
| result.push_back(target); |
| } |
| |
| *targets = std::move(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 replace the 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 = std::move(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 |
| |
| std::optional<HowTargetContainsFile> TargetContainsFile( |
| const Target* target, |
| const SourceFile& file) { |
| for (const auto& cur_file : target->sources()) { |
| if (cur_file == file) |
| return HowTargetContainsFile::kSources; |
| } |
| for (const auto& cur_file : target->public_headers()) { |
| if (cur_file == file) |
| return HowTargetContainsFile::kPublic; |
| } |
| for (ConfigValuesIterator iter(target); !iter.done(); iter.Next()) { |
| for (const auto& cur_file : iter.cur().inputs()) { |
| if (cur_file == file) |
| return HowTargetContainsFile::kInputs; |
| } |
| } |
| for (const auto& cur_file : target->data()) { |
| if (cur_file == file.value()) |
| return HowTargetContainsFile::kData; |
| if (cur_file.back() == '/' && |
| base::StartsWith(file.value(), cur_file, base::CompareCase::SENSITIVE)) |
| return HowTargetContainsFile::kData; |
| } |
| |
| if (target->action_values().script().value() == file.value()) |
| return HowTargetContainsFile::kScript; |
| |
| std::vector<SourceFile> output_sources; |
| target->action_values().GetOutputsAsSourceFiles(target, &output_sources); |
| for (const auto& cur_file : output_sources) { |
| if (cur_file == file) |
| return HowTargetContainsFile::kOutput; |
| } |
| |
| for (const auto& cur_file : target->computed_outputs()) { |
| if (cur_file.AsSourceFile(target->settings()->build_settings()) == file) |
| return HowTargetContainsFile::kOutput; |
| } |
| return std::nullopt; |
| } |
| |
| } // 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(Meta) |
| INSERT_COMMAND(Ls) |
| INSERT_COMMAND(Outputs) |
| INSERT_COMMAND(Path) |
| INSERT_COMMAND(Refs) |
| INSERT_COMMAND(CleanStale) |
| |
| #undef INSERT_COMMAND |
| } |
| return info_map; |
| } |
| |
| // static |
| CommandSwitches CommandSwitches::s_global_switches_ = {}; |
| |
| // static |
| bool CommandSwitches::Init(const base::CommandLine& cmdline) { |
| CHECK(!s_global_switches_.is_initialized()) |
| << "Only call this once from main()"; |
| return s_global_switches_.InitFrom(cmdline); |
| } |
| |
| // static |
| const CommandSwitches& CommandSwitches::Get() { |
| CHECK(s_global_switches_.is_initialized()) |
| << "Missing previous succesful call to CommandSwitches::Init()"; |
| return s_global_switches_; |
| } |
| |
| // static |
| CommandSwitches CommandSwitches::Set(CommandSwitches new_switches) { |
| CHECK(s_global_switches_.is_initialized()) |
| << "Missing previous succesful call to CommandSwitches::Init()"; |
| CommandSwitches result = std::move(s_global_switches_); |
| s_global_switches_ = std::move(new_switches); |
| return result; |
| } |
| |
| bool CommandSwitches::InitFrom(const base::CommandLine& cmdline) { |
| CommandSwitches result; |
| result.initialized_ = true; |
| result.has_quiet_ = cmdline.HasSwitch("a"); |
| result.has_force_ = cmdline.HasSwitch("force"); |
| result.has_all_ = cmdline.HasSwitch("all"); |
| result.has_blame_ = cmdline.HasSwitch("blame"); |
| result.has_tree_ = cmdline.HasSwitch("tree"); |
| result.has_format_json_ = cmdline.GetSwitchValueASCII("format") == "json"; |
| result.has_default_toolchain_ = |
| cmdline.HasSwitch(switches::kDefaultToolchain); |
| |
| result.has_check_generated_ = cmdline.HasSwitch("check-generated"); |
| result.has_check_system_ = cmdline.HasSwitch("check-system"); |
| result.has_public_ = cmdline.HasSwitch("public"); |
| result.has_with_data_ = cmdline.HasSwitch("with-data"); |
| |
| std::string_view target_print_switch = "as"; |
| if (cmdline.HasSwitch(target_print_switch)) { |
| std::string value = cmdline.GetSwitchValueASCII(target_print_switch); |
| if (value == "buildfile") { |
| result.target_print_mode_ = TARGET_PRINT_BUILDFILE; |
| } else if (value == "label") { |
| result.target_print_mode_ = TARGET_PRINT_LABEL; |
| } else if (value == "output") { |
| result.target_print_mode_ = TARGET_PRINT_OUTPUT; |
| } else { |
| Err(Location(), "Invalid value for \"--as\".", |
| "I was expecting \"buildfile\", \"label\", or \"output\" but you\n" |
| "said \"" + |
| value + "\".") |
| .PrintToStdout(); |
| return false; |
| } |
| } |
| |
| std::string_view target_type_switch = "type"; |
| if (cmdline.HasSwitch(target_type_switch)) { |
| std::string value = cmdline.GetSwitchValueASCII(target_type_switch); |
| static const struct { |
| const char* name; |
| Target::OutputType type; |
| } kTypes[] = { |
| {"group", Target::GROUP}, |
| {"executable", Target::EXECUTABLE}, |
| {"shared_library", Target::SHARED_LIBRARY}, |
| {"loadable_module", Target::LOADABLE_MODULE}, |
| {"static_library", Target::STATIC_LIBRARY}, |
| {"source_set", Target::SOURCE_SET}, |
| {"copy", Target::COPY_FILES}, |
| {"action", Target::ACTION}, |
| }; |
| bool found = false; |
| for (const auto& type : kTypes) { |
| if (value == type.name) { |
| result.target_type_ = type.type; |
| found = true; |
| break; |
| } |
| } |
| if (!found) { |
| Err(Location(), "Invalid value for \"--type\".").PrintToStdout(); |
| return false; |
| } |
| } |
| std::string_view testonly_switch = "testonly"; |
| if (cmdline.HasSwitch(testonly_switch)) { |
| std::string value = cmdline.GetSwitchValueASCII(testonly_switch); |
| if (value == "true") { |
| testonly_mode_ = TESTONLY_TRUE; |
| } else if (value == "false") { |
| testonly_mode_ = TESTONLY_FALSE; |
| } else { |
| Err(Location(), "Bad value for --testonly.", |
| "I was expecting --testonly=true or --testonly=false.") |
| .PrintToStdout(); |
| return false; |
| } |
| } |
| |
| result.meta_rebase_dir_ = cmdline.GetSwitchValueASCII("rebase"); |
| result.meta_data_keys_ = cmdline.GetSwitchValueASCII("data"); |
| result.meta_walk_keys_ = cmdline.GetSwitchValueASCII("walk"); |
| *this = result; |
| return true; |
| } |
| |
| 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()), |
| setup->build_settings().root_path_utf8(), 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 default_toolchain_only, |
| 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, default_toolchain_only, 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; |
| } |
| } |
| } |
| } |
| |
| void FilterOutTargetsByPatterns(const std::vector<const Target*>& input, |
| const std::vector<LabelPattern>& filter, |
| std::vector<const Target*>* output) { |
| for (auto* target : input) { |
| bool match = false; |
| for (const auto& pattern : filter) { |
| if (pattern.Matches(target->label())) { |
| match = true; |
| break; |
| } |
| } |
| if (!match) { |
| output->push_back(target); |
| } |
| } |
| } |
| |
| 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, build_settings->root_path_utf8(), |
| 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; |
| |
| CommandSwitches::TargetPrintMode printing_mode = |
| CommandSwitches::TARGET_PRINT_LABEL; |
| if (targets->empty() || !GetTargetPrintingMode(&printing_mode)) |
| return; |
| switch (printing_mode) { |
| case CommandSwitches::TARGET_PRINT_BUILDFILE: |
| PrintTargetsAsBuildfiles(*targets, out); |
| break; |
| case CommandSwitches::TARGET_PRINT_LABEL: |
| PrintTargetsAsLabels(*targets, out); |
| break; |
| case CommandSwitches::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 TargetSet& targets) { |
| std::vector<const Target*> target_vector(targets.begin(), targets.end()); |
| FilterAndPrintTargets(indent, &target_vector); |
| } |
| |
| void FilterAndPrintTargetSet(const TargetSet& targets, base::ListValue* out) { |
| std::vector<const Target*> target_vector(targets.begin(), targets.end()); |
| FilterAndPrintTargets(&target_vector, out); |
| } |
| |
| void GetTargetsContainingFile(Setup* setup, |
| const std::vector<const Target*>& all_targets, |
| const SourceFile& file, |
| bool default_toolchain_only, |
| std::vector<TargetContainingFile>* matches) { |
| Label default_toolchain = setup->loader()->default_toolchain_label(); |
| for (auto* target : all_targets) { |
| if (default_toolchain_only) { |
| // Only check targets in the default toolchain. |
| if (target->label().GetToolchainLabel() != default_toolchain) |
| continue; |
| } |
| if (auto how = TargetContainsFile(target, file)) |
| matches->emplace_back(target, *how); |
| } |
| } |
| |
| } // namespace commands |