diff options
Diffstat (limited to 'src/main/cpp/option_processor.cc')
-rw-r--r-- | src/main/cpp/option_processor.cc | 480 |
1 files changed, 480 insertions, 0 deletions
diff --git a/src/main/cpp/option_processor.cc b/src/main/cpp/option_processor.cc new file mode 100644 index 0000000000..ce4cd631e1 --- /dev/null +++ b/src/main/cpp/option_processor.cc @@ -0,0 +1,480 @@ +// Copyright 2014 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "option_processor.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <algorithm> +#include <cassert> +#include <utility> + +#include "blaze_util.h" +#include "blaze_util_platform.h" +#include "util/file.h" +#include "util/strings.h" + +using std::list; +using std::map; +using std::vector; + +// On OSX, there apparently is no header that defines this. +extern char **environ; + +namespace blaze { + +OptionProcessor::RcOption::RcOption(int rcfile_index, const string& option) { + rcfile_index_ = rcfile_index; + option_ = option; +} + + +OptionProcessor::RcFile::RcFile(const string& filename, int index) { + filename_ = filename; + index_ = index; +} + +blaze_exit_code::ExitCode OptionProcessor::RcFile::Parse( + vector<RcFile>* rcfiles, + map<string, vector<RcOption> >* rcoptions, + string* error) { + list<string> initial_import_stack; + initial_import_stack.push_back(filename_); + return Parse( + filename_, index_, rcfiles, rcoptions, &initial_import_stack, error); +} + +blaze_exit_code::ExitCode OptionProcessor::RcFile::Parse( + string filename, const int index, + vector<RcFile>* rcfiles, + map<string, vector<RcOption> >* rcoptions, + list<string>* import_stack, + string* error) { + string contents; + if (!ReadFile(filename, &contents)) { + // We checked for file readability before, so this is unexpected. + blaze_util::StringPrintf(error, + "Unexpected error reading .blazerc file '%s'", filename.c_str()); + return blaze_exit_code::INTERNAL_ERROR; + } + + // A '\' at the end of a line continues the line. + blaze_util::Replace("\\\r\n", "", &contents); + blaze_util::Replace("\\\n", "", &contents); + vector<string> startup_options; + + vector<string> lines = blaze_util::Split(contents, '\n'); + for (int line = 0; line < lines.size(); ++line) { + blaze_util::StripWhitespace(&lines[line]); + + // Check for an empty line. + if (lines[line].empty()) { + continue; + } + + vector<string> words; + + // This will treat "#" as a comment, and properly + // quote single and double quotes, and treat '\' + // as an escape character. + // TODO(bazel-team): This function silently ignores + // dangling backslash escapes and missing end-quotes. + blaze_util::Tokenize(lines[line], '#', &words); + + if (words.empty()) { + // Could happen if line starts with "#" + continue; + } + + string command = words[0]; + + if (command == "import") { + if (words.size() != 2) { + blaze_util::StringPrintf(error, + "Invalid import declaration in .blazerc file '%s': '%s'", + filename.c_str(), lines[line].c_str()); + return blaze_exit_code::BAD_ARGV; + } + + if (std::find(import_stack->begin(), import_stack->end(), words[1]) != + import_stack->end()) { + string loop; + for (list<string>::const_iterator imported_rc = import_stack->begin(); + imported_rc != import_stack->end(); ++imported_rc) { + loop += " " + *imported_rc + "\n"; + } + blaze_util::StringPrintf(error, + "Import loop detected:\n%s", loop.c_str()); + return blaze_exit_code::BAD_ARGV; + } + + rcfiles->push_back(RcFile(words[1], rcfiles->size())); + import_stack->push_back(words[1]); + blaze_exit_code::ExitCode parse_exit_code = RcFile::Parse( + rcfiles->back().Filename(), rcfiles->back().Index(), + rcfiles, rcoptions, import_stack, error); + if (parse_exit_code != blaze_exit_code::SUCCESS) { + return parse_exit_code; + } + import_stack->pop_back(); + } else { + for (int word = 1; word < words.size(); ++word) { + (*rcoptions)[command].push_back(RcOption(index, words[word])); + if (command == "startup") { + startup_options.push_back(words[word]); + } + } + } + } + + if (!startup_options.empty()) { + string startup_args; + blaze_util::JoinStrings(startup_options, ' ', &startup_args); + fprintf(stderr, "INFO: Reading 'startup' options from %s: %s\n", + filename.c_str(), startup_args.c_str()); + } + return blaze_exit_code::SUCCESS; +} + +OptionProcessor::OptionProcessor() + : initialized_(false), parsed_startup_options_(new BlazeStartupOptions()) { +} + +// Return the path of the depot .blazerc file. +string OptionProcessor::FindDepotBlazerc(const string& workspace) { + // Package semantics are ignored here, but that's acceptable because + // blaze.blazerc is a configuration file. + vector<string> candidates; + BlazeStartupOptions::WorkspaceRcFileSearchPath(&candidates); + for (const auto& candidate : candidates) { + string blazerc = blaze_util::JoinPath(workspace, candidate); + if (!access(blazerc.c_str(), R_OK)) { + return blazerc; + } + } + + return ""; +} + +// Return the path of the .blazerc file that sits alongside the binary. +// This allows for canary or cross-platform Blazes operating on the same depot +// to have customized behavior. +string OptionProcessor::FindAlongsideBinaryBlazerc(const string& cwd, + const string& arg0) { + string path = arg0[0] == '/' ? arg0 : blaze_util::JoinPath(cwd, arg0); + string base = blaze_util::Basename(arg0); + string binary_blazerc_path = path + "." + base + "rc"; + if (!access(binary_blazerc_path.c_str(), R_OK)) { + return binary_blazerc_path; + } + return ""; +} + + +// Return the path the the user rc file. If cmdLineRcFile != NULL, +// use it, dying if it is not readable. Otherwise, return the first +// readable file called rc_basename from [workspace, $HOME] +// +// If no readable .blazerc file is found, return the empty string. +blaze_exit_code::ExitCode OptionProcessor::FindUserBlazerc( + const char* cmdLineRcFile, + const string& rc_basename, + const string& workspace, + string* blaze_rc_file, + string* error) { + if (cmdLineRcFile != NULL) { + string rcFile = MakeAbsolute(cmdLineRcFile); + if (access(rcFile.c_str(), R_OK)) { + blaze_util::StringPrintf(error, + "Error: Unable to read .blazerc file '%s'.", rcFile.c_str()); + return blaze_exit_code::BAD_ARGV; + } + *blaze_rc_file = rcFile; + return blaze_exit_code::SUCCESS; + } + + string workspaceRcFile = blaze_util::JoinPath(workspace, rc_basename); + if (!access(workspaceRcFile.c_str(), R_OK)) { + *blaze_rc_file = workspaceRcFile; + return blaze_exit_code::SUCCESS; + } + + const char* home = getenv("HOME"); + if (home == NULL) { + *blaze_rc_file = ""; + return blaze_exit_code::SUCCESS; + } + + string userRcFile = blaze_util::JoinPath(home, rc_basename); + if (!access(userRcFile.c_str(), R_OK)) { + *blaze_rc_file = userRcFile; + return blaze_exit_code::SUCCESS; + } + *blaze_rc_file = ""; + return blaze_exit_code::SUCCESS; +} + +blaze_exit_code::ExitCode OptionProcessor::ParseOptions( + const vector<string>& args, + const string& workspace, + const string& cwd, + string* error) { + assert(!initialized_); + initialized_ = true; + + const char* blazerc = NULL; + bool use_master_blazerc = true; + + // Check if there is a blazerc related option given + args_ = args; + for (int i= 1; i < args.size(); i++) { + const char* arg_chr = args[i].c_str(); + const char* next_arg_chr = (i + 1) < args.size() + ? args[i + 1].c_str() + : NULL; + if (blazerc == NULL) { + blazerc = GetUnaryOption(arg_chr, next_arg_chr, "--blazerc"); + } + if (use_master_blazerc && + GetNullaryOption(arg_chr, "--nomaster_blazerc")) { + use_master_blazerc = false; + } + } + + // Parse depot and user blazerc files. + // This is not a little ineffective (copying a multimap around), but it is a + // small one and this way I don't have to care about memory management. + if (use_master_blazerc) { + string depot_blazerc_path = FindDepotBlazerc(workspace); + if (!depot_blazerc_path.empty()) { + blazercs_.push_back(RcFile(depot_blazerc_path, blazercs_.size())); + blaze_exit_code::ExitCode parse_exit_code = + blazercs_.back().Parse(&blazercs_, &rcoptions_, error); + if (parse_exit_code != blaze_exit_code::SUCCESS) { + return parse_exit_code; + } + } + string alongside_binary_blazerc = FindAlongsideBinaryBlazerc(cwd, args[0]); + if (!alongside_binary_blazerc.empty()) { + blazercs_.push_back(RcFile(alongside_binary_blazerc, blazercs_.size())); + blaze_exit_code::ExitCode parse_exit_code = + blazercs_.back().Parse(&blazercs_, &rcoptions_, error); + if (parse_exit_code != blaze_exit_code::SUCCESS) { + return parse_exit_code; + } + } + } + + string user_blazerc_path; + blaze_exit_code::ExitCode find_blazerc_exit_code = FindUserBlazerc( + blazerc, BlazeStartupOptions::RcBasename(), workspace, &user_blazerc_path, + error); + if (find_blazerc_exit_code != blaze_exit_code::SUCCESS) { + return find_blazerc_exit_code; + } + if (!user_blazerc_path.empty()) { + blazercs_.push_back(RcFile(user_blazerc_path, blazercs_.size())); + blaze_exit_code::ExitCode parse_exit_code = + blazercs_.back().Parse(&blazercs_, &rcoptions_, error); + if (parse_exit_code != blaze_exit_code::SUCCESS) { + return parse_exit_code; + } + } + + blaze_exit_code::ExitCode parse_startup_options_exit_code = + ParseStartupOptions(error); + if (parse_startup_options_exit_code != blaze_exit_code::SUCCESS) { + return parse_startup_options_exit_code; + } + + // Determine command + if (startup_args_ + 1 >= args.size()) { + command_ = ""; + return blaze_exit_code::SUCCESS; + } + + command_ = args[startup_args_ + 1]; + +#if __APPLE__ + // This is a temporary hack until we work out how to actually reference the + // system JDK in a sound way. + if (command_ == "build" || + command_ == "test" || + command_ == "coverage" || + command_ == "run" || + command_ == "info" || + command_ == "version") { + string javabase = blaze::GetDefaultHostJavabase(); + command_arguments_.push_back("--javabase=" + javabase); + command_arguments_.push_back("--host_javabase=" + javabase); + } +#endif + + AddRcfileArgsAndOptions(parsed_startup_options_->batch, cwd); + for (unsigned int cmd_arg = startup_args_ + 2; + cmd_arg < args.size(); cmd_arg++) { + command_arguments_.push_back(args[cmd_arg]); + } + return blaze_exit_code::SUCCESS; +} + +blaze_exit_code::ExitCode OptionProcessor::ParseOptions( + int argc, + const char* argv[], + const string& workspace, + const string& cwd, + string* error) { + vector<string> args(argc); + for (int arg = 0; arg < argc; arg++) { + args[arg] = argv[arg]; + } + + return ParseOptions(args, workspace, cwd, error); +} + +static bool IsArg(const string& arg) { + return blaze_util::starts_with(arg, "-") && (arg != "--help") + && (arg != "-help") && (arg != "-h"); +} + +blaze_exit_code::ExitCode OptionProcessor::ParseStartupOptions(string *error) { + // Process rcfile startup options + map< string, vector<RcOption> >::const_iterator it = + rcoptions_.find("startup"); + blaze_exit_code::ExitCode process_arg_exit_code; + bool is_space_separated; + if (it != rcoptions_.end()) { + const vector<RcOption>& startup_options = it->second; + int i = 0; + // Process all elements except the last one. + for (; i < startup_options.size() - 1; i++) { + const RcOption& option = startup_options[i]; + const string& blazerc = blazercs_[option.rcfile_index()].Filename(); + process_arg_exit_code = parsed_startup_options_->ProcessArg( + option.option(), startup_options[i + 1].option(), blazerc, + &is_space_separated, error); + if (process_arg_exit_code != blaze_exit_code::SUCCESS) { + return process_arg_exit_code; + } + if (is_space_separated) { + i++; + } + } + // Process last element, if any. + if (i < startup_options.size()) { + const RcOption& option = startup_options[i]; + if (IsArg(option.option())) { + const string& blazerc = blazercs_[option.rcfile_index()].Filename(); + process_arg_exit_code = parsed_startup_options_->ProcessArg( + option.option(), "", blazerc, &is_space_separated, error); + if (process_arg_exit_code != blaze_exit_code::SUCCESS) { + return process_arg_exit_code; + } + } + } + } + + // Process command-line args next, so they override any of the same options + // from .blazerc. Stop on first non-arg, this includes --help + unsigned int i = 1; + if (!args_.empty()) { + for (; (i < args_.size() - 1) && IsArg(args_[i]); i++) { + process_arg_exit_code = parsed_startup_options_->ProcessArg( + args_[i], args_[i + 1], "", &is_space_separated, error); + if (process_arg_exit_code != blaze_exit_code::SUCCESS) { + return process_arg_exit_code; + } + if (is_space_separated) { + i++; + } + } + if (i < args_.size() && IsArg(args_[i])) { + process_arg_exit_code = parsed_startup_options_->ProcessArg( + args_[i], "", "", &is_space_separated, error); + if (process_arg_exit_code != blaze_exit_code::SUCCESS) { + return process_arg_exit_code; + } + i++; + } + } + startup_args_ = i -1; + + return blaze_exit_code::SUCCESS; +} + +// Appends the command and arguments from argc/argv to the end of arg_vector, +// and also splices in some additional terminal and environment options between +// the command and the arguments. NB: Keep the options added here in sync with +// BlazeCommandDispatcher.INTERNAL_COMMAND_OPTIONS! +void OptionProcessor::AddRcfileArgsAndOptions(bool batch, const string& cwd) { + // Push the options mapping .blazerc numbers to filenames. + for (int i_blazerc = 0; i_blazerc < blazercs_.size(); i_blazerc++) { + const RcFile& blazerc = blazercs_[i_blazerc]; + command_arguments_.push_back("--rc_source=" + blazerc.Filename()); + } + + // Push the option defaults + for (map<string, vector<RcOption> >::const_iterator it = rcoptions_.begin(); + it != rcoptions_.end(); ++it) { + if (it->first == "startup") { + // Skip startup options, they are parsed in the C++ wrapper + continue; + } + + for (int ii = 0; ii < it->second.size(); ii++) { + const RcOption& rcoption = it->second[ii]; + command_arguments_.push_back( + "--default_override=" + std::to_string(rcoption.rcfile_index()) + ":" + + it->first + "=" + rcoption.option()); + } + } + + // Splice the terminal options. + command_arguments_.push_back( + "--isatty=" + std::to_string(IsStandardTerminal())); + command_arguments_.push_back( + "--terminal_columns=" + std::to_string(GetTerminalColumns())); + + // Pass the client environment to the server in server mode. + if (batch) { + command_arguments_.push_back("--ignore_client_env"); + } else { + for (char** env = environ; *env != NULL; env++) { + command_arguments_.push_back("--client_env=" + string(*env)); + } + } + command_arguments_.push_back("--client_cwd=" + cwd); + + const char *emacs = getenv("EMACS"); + if (emacs != NULL && strcmp(emacs, "t") == 0) { + command_arguments_.push_back("--emacs"); + } +} + +void OptionProcessor::GetCommandArguments(vector<string>* result) const { + result->insert(result->end(), + command_arguments_.begin(), + command_arguments_.end()); +} + +const string& OptionProcessor::GetCommand() const { + return command_; +} + +const BlazeStartupOptions& OptionProcessor::GetParsedStartupOptions() const { + return *parsed_startup_options_.get(); +} +} // namespace blaze |