Refactor blazerc parsing out of OptionProcessor. This change should be a no-op behavior-wise.
RELNOTES: None
PiperOrigin-RevId: 184843442
diff --git a/src/main/cpp/BUILD b/src/main/cpp/BUILD
index 82b7859..7f85db7 100644
--- a/src/main/cpp/BUILD
+++ b/src/main/cpp/BUILD
@@ -125,6 +125,7 @@
],
deps = [
":blaze_util",
+ ":rc_file",
":startup_options",
":workspace_layout",
"//src/main/cpp/util",
@@ -164,6 +165,22 @@
],
)
+cc_library(
+ name = "rc_file",
+ srcs = ["rc_file.cc"],
+ hdrs = ["rc_file.h"],
+ visibility = [
+ "//src:__pkg__",
+ "//src/test/cpp:__pkg__",
+ ],
+ deps = [
+ ":blaze_util",
+ ":workspace_layout",
+ "//src/main/cpp/util",
+ "//src/main/cpp/util:logging",
+ ],
+)
+
filegroup(
name = "srcs",
srcs = glob(["**"]) + ["//src/main/cpp/util:srcs"],
diff --git a/src/main/cpp/option_processor.cc b/src/main/cpp/option_processor.cc
index 89fcf2b..83a184c 100644
--- a/src/main/cpp/option_processor.cc
+++ b/src/main/cpp/option_processor.cc
@@ -20,6 +20,7 @@
#include <algorithm>
#include <cassert>
#include <set>
+#include <sstream>
#include <utility>
#include "src/main/cpp/blaze_util.h"
@@ -41,123 +42,7 @@
using std::vector;
constexpr char WorkspaceLayout::WorkspacePrefix[];
-
-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(
- const string& workspace,
- const WorkspaceLayout* workspace_layout,
- vector<RcFile*>* rcfiles,
- map<string, vector<RcOption> >* rcoptions,
- string* error) {
- list<string> initial_import_stack;
- initial_import_stack.push_back(filename_);
- return Parse(
- workspace, filename_, index_, workspace_layout,
- rcfiles, rcoptions, &initial_import_stack,
- error);
-}
-
-blaze_exit_code::ExitCode OptionProcessor::RcFile::Parse(
- const string& workspace,
- const string& filename_ref,
- const int index,
- const WorkspaceLayout* workspace_layout,
- vector<RcFile*>* rcfiles,
- map<string, vector<RcOption> >* rcoptions,
- list<string>* import_stack,
- string* error) {
- string filename(filename_ref); // file
- BAZEL_LOG(INFO) << "Parsing the RcFile " << filename;
- string contents;
- if (!blaze_util::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> lines = blaze_util::Split(contents, '\n');
- for (string& line : lines) {
- blaze_util::StripWhitespace(&line);
-
- // Check for an empty line.
- if (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(line, '#', &words);
-
- if (words.empty()) {
- // Could happen if line starts with "#"
- continue;
- }
-
- string command = words[0];
-
- if (command == "import") {
- if (words.size() != 2
- || (words[1].compare(0, workspace_layout->WorkspacePrefixLength,
- workspace_layout->WorkspacePrefix) == 0
- && !workspace_layout->WorkspaceRelativizeRcFilePath(
- workspace, &words[1]))) {
- blaze_util::StringPrintf(
- error,
- "Invalid import declaration in .blazerc file '%s': '%s'"
- " (are you in your source checkout/WORKSPACE?)",
- filename.c_str(), 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(new RcFile(words[1], rcfiles->size()));
- import_stack->push_back(words[1]);
- blaze_exit_code::ExitCode parse_exit_code =
- RcFile::Parse(workspace, rcfiles->back()->Filename(),
- rcfiles->back()->Index(), workspace_layout,
- rcfiles, rcoptions, import_stack, error);
- if (parse_exit_code != blaze_exit_code::SUCCESS) {
- return parse_exit_code;
- }
- import_stack->pop_back();
- } else {
- vector<string>::const_iterator words_it = words.begin();
- words_it++; // Advance past command.
- for (; words_it != words.end(); words_it++) {
- (*rcoptions)[command].push_back(RcOption(index, *words_it));
- }
- }
- }
-
- return blaze_exit_code::SUCCESS;
-}
+static std::vector<std::string> GetProcessedEnv();
OptionProcessor::OptionProcessor(
const WorkspaceLayout* workspace_layout,
@@ -307,6 +192,21 @@
return result;
}
+blaze_exit_code::ExitCode ParseErrorToExitCode(RcFile::ParseError parse_error) {
+ switch (parse_error) {
+ case RcFile::ParseError::NONE:
+ return blaze_exit_code::SUCCESS;
+ case RcFile::ParseError::UNREADABLE_FILE:
+ // We check readability before parsing, so this is unexpected.
+ return blaze_exit_code::INTERNAL_ERROR;
+ case RcFile::ParseError::INVALID_FORMAT:
+ case RcFile::ParseError::IMPORT_LOOP:
+ return blaze_exit_code::BAD_ARGV;
+ default:
+ return blaze_exit_code::INTERNAL_ERROR;
+ }
+}
+
} // namespace internal
// Parses the arguments provided in args using the workspace path and the
@@ -358,13 +258,13 @@
for (const auto& blazerc_path : deduped_blazerc_paths) {
if (!blazerc_path.empty()) {
- blazercs_.push_back(new RcFile(blazerc_path, blazercs_.size()));
- blaze_exit_code::ExitCode parse_exit_code =
- blazercs_.back()->Parse(workspace, workspace_layout_, &blazercs_,
- &rcoptions_, error);
- if (parse_exit_code != blaze_exit_code::SUCCESS) {
- return parse_exit_code;
+ RcFile::ParseError parse_error;
+ auto rcfile = RcFile::Parse(blazerc_path, workspace_layout_, workspace,
+ &parse_error, error);
+ if (rcfile == nullptr) {
+ return internal::ParseErrorToExitCode(parse_error);
}
+ blazercs_.push_back(std::move(rcfile));
}
}
@@ -374,8 +274,8 @@
return parse_startup_options_exit_code;
}
- blazerc_and_env_command_args_ = GetBlazercAndEnvCommandArgs(
- cwd, blazercs_, rcoptions_);
+ blazerc_and_env_command_args_ =
+ GetBlazercAndEnvCommandArgs(cwd, blazercs_, GetProcessedEnv());
return blaze_exit_code::SUCCESS;
}
@@ -425,13 +325,12 @@
// option_sources tracking and for sending to the server.
std::vector<RcStartupFlag> rcstartup_flags;
- auto iter = rcoptions_.find("startup");
- if (iter != rcoptions_.end()) {
- const vector<RcOption>& startup_rcoptions = iter->second;
- for (const RcOption& option : startup_rcoptions) {
- rcstartup_flags.push_back(
- RcStartupFlag(blazercs_[option.rcfile_index()]->Filename(),
- option.option()));
+ for (const auto& blazerc : blazercs_) {
+ const auto iter = blazerc->options().find("startup");
+ if (iter == blazerc->options().end()) continue;
+
+ for (const RcOption& option : iter->second) {
+ rcstartup_flags.push_back({*option.source_path, option.option});
}
}
@@ -495,13 +394,26 @@
}
#endif // defined(COMPILER_MSVC)
+static std::vector<std::string> GetProcessedEnv() {
+ std::vector<std::string> processed_env;
+ for (char** env = environ; *env != NULL; env++) {
+ string env_str(*env);
+ if (IsValidEnvName(*env)) {
+ PreprocessEnvString(&env_str);
+ processed_env.push_back(std::move(env_str));
+ }
+ }
+ return processed_env;
+}
+
// IMPORTANT: The options added here do not come from the user. In order for
// their source to be correctly tracked, the options must either be passed
// as --default_override=0, 0 being "client", or must be listed in
// BlazeOptionHandler.INTERNAL_COMMAND_OPTIONS!
std::vector<std::string> OptionProcessor::GetBlazercAndEnvCommandArgs(
- const std::string& cwd, const std::vector<RcFile*>& blazercs,
- const std::map<std::string, std::vector<RcOption>>& rcoptions) {
+ const std::string& cwd,
+ const std::vector<std::unique_ptr<RcFile>>& blazercs,
+ const std::vector<std::string>& env) {
// Provide terminal options as coming from the least important rc file.
std::vector<std::string> result = {
"--rc_source=client",
@@ -514,31 +426,42 @@
EnsurePythonPathOption(&result);
- // Push the options mapping .blazerc numbers to filenames.
- for (const RcFile* blazerc : blazercs) {
- result.push_back("--rc_source=" + blaze::ConvertPath(blazerc->Filename()));
+ // Map .blazerc numbers to filenames. The indexes here start at 1 because #0
+ // is reserved the "client" options created by this function.
+ int cur_index = 1;
+ std::map<std::string, int> rcfile_indexes;
+ for (const auto& blazerc : blazercs) {
+ for (const std::string& source_path : blazerc->sources()) {
+ // Deduplicate the rc_source list because the same file might be included
+ // from multiple places.
+ if (rcfile_indexes.find(source_path) != rcfile_indexes.end()) continue;
+
+ result.push_back("--rc_source=" + blaze::ConvertPath(source_path));
+ rcfile_indexes[source_path] = cur_index;
+ cur_index++;
+ }
}
- // Process RcOptions except for the startup flags that are already parsed
- // by the client and shouldn't be included in the command args.
- for (auto it = rcoptions.begin(); it != rcoptions.end(); ++it) {
- if (it->first != "startup") {
- for (const RcOption& rcoption : it->second) {
- result.push_back(
- "--default_override=" + ToString(rcoption.rcfile_index() + 1) +
- ":" + it->first + "=" + rcoption.option());
+ // Add RcOptions as default_overrides.
+ for (const auto& blazerc : blazercs) {
+ for (const auto& command_options : blazerc->options()) {
+ const string& command = command_options.first;
+ // Skip startup flags, which are already parsed by the client.
+ if (command == "startup") continue;
+
+ for (const RcOption& rcoption : command_options.second) {
+ std::ostringstream oss;
+ oss << "--default_override=" << rcfile_indexes[*rcoption.source_path]
+ << ':' << command << '=' << rcoption.option;
+ result.push_back(oss.str());
}
}
}
// Pass the client environment to the server.
- for (char** env = environ; *env != NULL; env++) {
- string env_str(*env);
- if (IsValidEnvName(*env)) {
- PreprocessEnvString(&env_str);
- debug_log("--client_env=%s", env_str.c_str());
- result.push_back("--client_env=" + env_str);
- }
+ for (const string& env_var : env) {
+ debug_log("--client_env=%s", env_var.c_str());
+ result.push_back("--client_env=" + env_var);
}
result.push_back("--client_cwd=" + blaze::ConvertPath(cwd));
return result;
@@ -574,10 +497,4 @@
return parsed_startup_options_.get();
}
-OptionProcessor::~OptionProcessor() {
- for (auto it : blazercs_) {
- delete it;
- }
-}
-
} // namespace blaze
diff --git a/src/main/cpp/option_processor.h b/src/main/cpp/option_processor.h
index 0fe45ba..d205301 100644
--- a/src/main/cpp/option_processor.h
+++ b/src/main/cpp/option_processor.h
@@ -21,6 +21,7 @@
#include <string>
#include <vector>
+#include "src/main/cpp/rc_file.h"
#include "src/main/cpp/startup_options.h"
#include "src/main/cpp/util/exit_code.h"
@@ -56,7 +57,7 @@
OptionProcessor(const WorkspaceLayout* workspace_layout,
std::unique_ptr<StartupOptions> default_startup_options);
- virtual ~OptionProcessor();
+ virtual ~OptionProcessor() {}
// Splits the arguments of a command line invocation.
//
@@ -113,51 +114,17 @@
// to the failure. Otherwise, the server will handle any required logging.
void PrintStartupOptionsProvenanceMessage() const;
- private:
- class RcOption {
- public:
- RcOption(int rcfile_index, const std::string& option);
-
- const int rcfile_index() const { return rcfile_index_; }
- const std::string& option() const { return option_; }
-
- private:
- int rcfile_index_;
- std::string option_;
- };
-
- class RcFile {
- public:
- RcFile(const std::string& filename, int index);
- blaze_exit_code::ExitCode Parse(
- const std::string& workspace, const WorkspaceLayout* workspace_layout,
- std::vector<RcFile*>* rcfiles,
- std::map<std::string, std::vector<RcOption>>* rcoptions,
- std::string* error);
- const std::string& Filename() const { return filename_; }
- const int Index() const { return index_; }
-
- private:
- static blaze_exit_code::ExitCode Parse(
- const std::string& workspace, const std::string& filename,
- const int index, const WorkspaceLayout* workspace_layout,
- std::vector<RcFile*>* rcfiles,
- std::map<std::string, std::vector<RcOption>>* rcoptions,
- std::list<std::string>* import_stack, std::string* error);
-
- std::string filename_;
- int index_;
- };
-
blaze_exit_code::ExitCode ParseStartupOptions(std::string* error);
+ // Constructs all synthetic command args that should be passed to the
+ // server to configure blazerc options and client environment.
static std::vector<std::string> GetBlazercAndEnvCommandArgs(
const std::string& cwd,
- const std::vector<RcFile*>& blazercs,
- const std::map<std::string, std::vector<RcOption>>& rcoptions);
+ const std::vector<std::unique_ptr<RcFile>>& blazercs,
+ const std::vector<std::string>& env);
// The list of parsed rc files, this field is initialized by ParseOptions.
- std::vector<RcFile*> blazercs_;
+ std::vector<std::unique_ptr<RcFile>> blazercs_;
// A map representing the flags parsed from the bazelrc files.
// A key is a command (e.g. 'build', 'startup') and its value is an ordered
diff --git a/src/main/cpp/rc_file.cc b/src/main/cpp/rc_file.cc
new file mode 100644
index 0000000..bacfd5c
--- /dev/null
+++ b/src/main/cpp/rc_file.cc
@@ -0,0 +1,136 @@
+// Copyright 2018 The Bazel Authors. 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 "src/main/cpp/rc_file.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "src/main/cpp/blaze_util.h"
+#include "src/main/cpp/blaze_util_platform.h"
+#include "src/main/cpp/util/file.h"
+#include "src/main/cpp/util/logging.h"
+#include "src/main/cpp/util/strings.h"
+#include "src/main/cpp/workspace_layout.h"
+
+namespace blaze {
+
+using std::deque;
+using std::string;
+using std::vector;
+
+RcFile::RcFile(string filename, const WorkspaceLayout* workspace_layout,
+ string workspace)
+ : filename_(std::move(filename)),
+ workspace_layout_(workspace_layout),
+ workspace_(std::move(workspace)) {}
+
+/*static*/ std::unique_ptr<RcFile> RcFile::Parse(
+ std::string filename, const WorkspaceLayout* workspace_layout,
+ std::string workspace, ParseError* error, std::string* error_text) {
+ std::unique_ptr<RcFile> rcfile(new RcFile(
+ std::move(filename), workspace_layout, std::move(workspace)));
+ deque<string> initial_import_stack = {rcfile->filename_};
+ *error = rcfile->ParseFile(
+ rcfile->filename_, &initial_import_stack, error_text);
+ return (*error == ParseError::NONE) ? std::move(rcfile) : nullptr;
+}
+
+RcFile::ParseError RcFile::ParseFile(const string& filename,
+ deque<string>* import_stack,
+ string* error_text) {
+ BAZEL_LOG(INFO) << "Parsing the RcFile " << filename;
+ string contents;
+ if (!blaze_util::ReadFile(filename, &contents)) {
+ blaze_util::StringPrintf(error_text,
+ "Unexpected error reading .blazerc file '%s'", filename.c_str());
+ return ParseError::UNREADABLE_FILE;
+ }
+
+ rcfile_paths_.push_back(filename);
+ // Keep a pointer to the filename string in rcfile_paths_ for the RcOptions.
+ string* filename_ptr = &rcfile_paths_.back();
+
+ // A '\' at the end of a line continues the line.
+ blaze_util::Replace("\\\r\n", "", &contents);
+ blaze_util::Replace("\\\n", "", &contents);
+
+ vector<string> lines = blaze_util::Split(contents, '\n');
+ for (string& line : lines) {
+ blaze_util::StripWhitespace(&line);
+
+ // Check for an empty line.
+ if (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(line, '#', &words);
+
+ if (words.empty()) {
+ // Could happen if line starts with "#"
+ continue;
+ }
+
+ string command = words[0];
+
+ if (command == "import") {
+ if (words.size() != 2 ||
+ (words[1].compare(0, workspace_layout_->WorkspacePrefixLength,
+ workspace_layout_->WorkspacePrefix) == 0 &&
+ !workspace_layout_->WorkspaceRelativizeRcFilePath(workspace_,
+ &words[1]))) {
+ blaze_util::StringPrintf(
+ error_text,
+ "Invalid import declaration in .blazerc file '%s': '%s'"
+ " (are you in your source checkout/WORKSPACE?)",
+ filename.c_str(), line.c_str());
+ return ParseError::INVALID_FORMAT;
+ }
+ if (std::find(import_stack->begin(), import_stack->end(), words[1]) !=
+ import_stack->end()) {
+ string loop;
+ for (const string& imported_rc : *import_stack) {
+ loop += " " + imported_rc + "\n";
+ }
+ blaze_util::StringPrintf(error_text,
+ "Import loop detected:\n%s", loop.c_str());
+ return ParseError::IMPORT_LOOP;
+ }
+
+ import_stack->push_back(words[1]);
+ ParseError parse_error = ParseFile(words[1], import_stack, error_text);
+ if (parse_error != ParseError::NONE) {
+ return parse_error;
+ }
+ import_stack->pop_back();
+ } else {
+ auto words_it = words.begin();
+ words_it++; // Advance past command.
+ for (; words_it != words.end(); words_it++) {
+ options_[command].push_back({filename_ptr, *words_it});
+ }
+ }
+ }
+
+ return ParseError::NONE;
+}
+
+} // namespace blaze
diff --git a/src/main/cpp/rc_file.h b/src/main/cpp/rc_file.h
new file mode 100644
index 0000000..0d462dc
--- /dev/null
+++ b/src/main/cpp/rc_file.h
@@ -0,0 +1,80 @@
+// Copyright 2018 The Bazel Authors. 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.
+#ifndef BAZEL_SRC_MAIN_CPP_RC_FILE_H_
+#define BAZEL_SRC_MAIN_CPP_RC_FILE_H_
+
+#include <deque>
+#include <memory>
+#include <unordered_map>
+#include <string>
+#include <vector>
+
+#include "src/main/cpp/workspace_layout.h"
+
+namespace blaze {
+
+// Single option in an rc file.
+struct RcOption {
+ // Keep a pointer to the path string to avoid copying it over and over.
+ std::string* source_path;
+ std::string option;
+};
+
+// Reads and parses a single rc file with all its imports.
+class RcFile {
+ public:
+ // Constructs a parsed rc file object, or returns a nullptr and sets the
+ // error and error text on failure.
+ enum class ParseError { NONE, UNREADABLE_FILE, INVALID_FORMAT, IMPORT_LOOP };
+ static std::unique_ptr<RcFile> Parse(
+ std::string filename, const WorkspaceLayout* workspace_layout,
+ std::string workspace, ParseError* error, std::string* error_text);
+
+ // Returns all relevant rc sources for this file (including itself).
+ const std::deque<std::string>& sources() const { return rcfile_paths_; }
+
+ // Command -> all options for that command (in order of appearance).
+ using OptionMap = std::unordered_map<std::string, std::vector<RcOption>>;
+ const OptionMap& options() const { return options_; }
+
+ private:
+ RcFile(std::string filename, const WorkspaceLayout* workspace_layout,
+ std::string workspace);
+ // Don't allow copying or moving because it can be tricky with the RcOption
+ // string pointers.
+ RcFile(const RcFile&) = delete;
+ RcFile& operator=(const RcFile&) = delete;
+
+ // Recursive call to parse a file and its imports.
+ ParseError ParseFile(const std::string& filename,
+ std::deque<std::string>* import_stack,
+ std::string* error_text);
+
+ const std::string filename_;
+
+ // Workspace definition.
+ const WorkspaceLayout* const workspace_layout_;
+ const std::string workspace_;
+
+ // Full closure of rcfile paths imported from this file (including itself).
+ // The RcOption structs point to the strings in here so they need to be stored
+ // in a container that offers stable pointers, like a deque (and not vector).
+ std::deque<std::string> rcfile_paths_;
+ // All options parsed from the file.
+ OptionMap options_;
+};
+
+} // namespace blaze
+
+#endif // BAZEL_SRC_MAIN_CPP_RC_FILE_H_