aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/cpp/option_processor.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/cpp/option_processor.cc')
-rw-r--r--src/main/cpp/option_processor.cc480
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