| // 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 <algorithm> | |
| #include <fstream> | |
| #include <optional> | |
| #include "base/command_line.h" | |
| #include "base/environment.h" | |
| #include "base/files/file_util.h" | |
| #include "base/strings/string_split.h" | |
| #include "base/strings/string_util.h" | |
| #include "base/strings/utf_string_conversions.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/ninja_build_writer.h" | |
| #include "gn/setup.h" | |
| #include "gn/standard_out.h" | |
| #include "gn/switches.h" | |
| #include "gn/target.h" | |
| #include "util/atomic_write.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. | |
| // | |
| // The vector will be empty 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(std::vector<Target::OutputType>* types) { | |
| *types = CommandSwitches::Get().target_types(); | |
| return true; | |
| } | |
| // Returns the target exclude 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. | |
| // | |
| // The vector will be empty 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 GetTargetExcludeTypeFilter(std::vector<Target::OutputType>* types) { | |
| *types = CommandSwitches::Get().target_exclude_types(); | |
| 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 and exclude 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) { | |
| std::vector<Target::OutputType> types; | |
| if (!GetTargetTypeFilter(&types)) | |
| return false; | |
| std::vector<Target::OutputType> exclude_types; | |
| if (!GetTargetExcludeTypeFilter(&exclude_types)) | |
| return false; | |
| if (targets->empty() || (types.empty() && exclude_types.empty())) | |
| 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) { | |
| Target::OutputType target_type = | |
| target->output_type() == Target::ACTION_FOREACH ? Target::ACTION | |
| : target->output_type(); | |
| if (!types.empty() && | |
| std::ranges::find(types, target_type) == types.end()) { | |
| continue; | |
| } | |
| if (!exclude_types.empty() && | |
| std::ranges::find(exclude_types, target_type) != exclude_types.end()) { | |
| continue; | |
| } | |
| result.push_back(target); | |
| } | |
| *targets = std::move(result); | |
| return true; | |
| } | |
| // Returns the file path of the BUILD.gn file generating this item. | |
| base::FilePath BuildFileForItem(const Item* item) { | |
| // Find the only BUILD.gn file listed in build_dependency_files() for | |
| // this Item. This may not exist if the item is defined in BUILDCONFIG.gn | |
| // instead, so account for this too. | |
| const SourceFile* buildconfig_gn = nullptr; | |
| const SourceFile* build_gn = nullptr; | |
| for (const SourceFile& build_file : item->build_dependency_files()) { | |
| const std::string& name = build_file.GetName(); | |
| if (name == "BUILDCONFIG.gn") { | |
| buildconfig_gn = &build_file; | |
| } else if (name == "BUILD.gn") { | |
| build_gn = &build_file; | |
| break; | |
| } | |
| } | |
| if (!build_gn) | |
| build_gn = buildconfig_gn; | |
| CHECK(build_gn) << "No BUILD.gn or BUILDCONFIG.gn file defining " | |
| << item->label().GetUserVisibleName(true); | |
| return build_gn->Resolve(item->settings()->build_settings()->root_path()); | |
| } | |
| 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() && target->has_dependency_output()) | |
| output_file = target->dependency_output(); | |
| // This output might be an omitted phony target, but that would mean we | |
| // don't have an output file to list. | |
| if (output_file.value().empty()) | |
| continue; | |
| 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() == '/' && file.value().starts_with(cur_file)) | |
| 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; | |
| } | |
| std::string ToUTF8(base::FilePath::StringType in) { | |
| #if defined(OS_WIN) | |
| return base::UTF16ToUTF8(in); | |
| #else | |
| return in; | |
| #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(Edit) | |
| INSERT_COMMAND(Help) | |
| INSERT_COMMAND(Meta) | |
| INSERT_COMMAND(Ls) | |
| INSERT_COMMAND(Outputs) | |
| INSERT_COMMAND(Path) | |
| INSERT_COMMAND(Refs) | |
| INSERT_COMMAND(Suggest) | |
| 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 successful call to CommandSwitches::Init()"; | |
| return s_global_switches_; | |
| } | |
| // static | |
| CommandSwitches CommandSwitches::Set(CommandSwitches new_switches) { | |
| CHECK(s_global_switches_.is_initialized()) | |
| << "Missing previous successful 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.GetSwitchValueString("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.GetSwitchValueString(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"; | |
| std::string_view target_exclude_type_switch = "exclude-type"; | |
| if (cmdline.HasSwitch(target_type_switch) && | |
| cmdline.HasSwitch(target_exclude_type_switch)) { | |
| Err(Location(), "Can't specify both \"--type\" and \"--exclude-type\".") | |
| .PrintToStdout(); | |
| return false; | |
| } | |
| if (cmdline.HasSwitch(target_type_switch) || | |
| cmdline.HasSwitch(target_exclude_type_switch)) { | |
| std::string_view switch_name = cmdline.HasSwitch(target_type_switch) | |
| ? target_type_switch | |
| : target_exclude_type_switch; | |
| std::string value = cmdline.GetSwitchValueString(switch_name); | |
| std::vector<std::string> tokens = base::SplitString( | |
| value, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); | |
| 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}, | |
| {"generated_file", Target::GENERATED_FILE}, | |
| {"rust_library", Target::RUST_LIBRARY}, | |
| {"rust_proc_macro", Target::RUST_PROC_MACRO}, | |
| {"bundle_data", Target::BUNDLE_DATA}, | |
| {"create_bundle", Target::CREATE_BUNDLE}, | |
| }; | |
| std::vector<Target::OutputType>& target_types_vector = | |
| cmdline.HasSwitch(target_type_switch) ? result.target_types_ | |
| : result.target_exclude_types_; | |
| for (const std::string& token : tokens) { | |
| bool found = false; | |
| for (const auto& type : kTypes) { | |
| if (token == type.name) { | |
| target_types_vector.push_back(type.type); | |
| found = true; | |
| break; | |
| } | |
| } | |
| if (!found) { | |
| Err(Location(), "Invalid value \"" + token + "\" for \"--" + | |
| std::string(switch_name) + "\".") | |
| .PrintToStdout(); | |
| return false; | |
| } | |
| } | |
| } | |
| std::string_view testonly_switch = "testonly"; | |
| if (cmdline.HasSwitch(testonly_switch)) { | |
| std::string value = cmdline.GetSwitchValueString(testonly_switch); | |
| if (value == "true") { | |
| result.testonly_mode_ = TESTONLY_TRUE; | |
| } else if (value == "false") { | |
| result.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.GetSwitchValueString("rebase"); | |
| result.meta_data_keys_ = cmdline.GetSwitchValueString("data"); | |
| result.meta_walk_keys_ = cmdline.GetSwitchValueString("walk"); | |
| *this = result; | |
| return true; | |
| } | |
| bool PrepareForRegeneration(const BuildSettings* settings) { | |
| // Write a .d file for the build which references a nonexistent file. | |
| // This will make Ninja always mark the build as dirty. | |
| base::FilePath build_ninja_d_file(settings->GetFullPath( | |
| SourceFile(settings->build_dir().value() + "build.ninja.d"))); | |
| std::string dummy_depfile("build.ninja.stamp: nonexistent_file.gn\n"); | |
| if (util::WriteFileAtomically(build_ninja_d_file, dummy_depfile.data(), | |
| static_cast<int>(dummy_depfile.size())) == -1) { | |
| Err(Location(), std::string("Failed to write build.ninja.d.")) | |
| .PrintToStdout(); | |
| return false; | |
| } | |
| // Write a stripped down build.ninja file with just the commands needed | |
| // for ninja to call GN and regenerate ninja files. | |
| base::FilePath build_ninja_path(settings->GetFullPath( | |
| SourceFile(settings->build_dir().value() + "build.ninja"))); | |
| std::ifstream build_ninja_file(ToUTF8(build_ninja_path.value())); | |
| if (!build_ninja_file) { | |
| // Couldn't open the build.ninja file. | |
| Err(Location(), "Couldn't open build.ninja in this directory.", | |
| "Try running \"gn gen\" on it and then re-running \"gn clean\".") | |
| .PrintToStdout(); | |
| return false; | |
| } | |
| std::string build_commands = | |
| NinjaBuildWriter::ExtractRegenerationCommands(build_ninja_file); | |
| if (build_commands.empty()) { | |
| Err(Location(), "Unexpected build.ninja contents in this directory.", | |
| "Try running \"gn gen\" on it and then re-running \"gn clean\".") | |
| .PrintToStdout(); | |
| return false; | |
| } | |
| // Close build.ninja or else WriteFileAtomically will fail on Windows. | |
| build_ninja_file.close(); | |
| if (util::WriteFileAtomically(build_ninja_path, build_commands.data(), | |
| static_cast<int>(build_commands.size())) == | |
| -1) { | |
| Err(Location(), std::string("Failed to write build.ninja.")) | |
| .PrintToStdout(); | |
| return false; | |
| } | |
| 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 |