blob: 1a0f8429952d93e3696bfcfa4a94128a900cdd10 [file] [log] [blame]
// 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;
static constexpr const char* kCommandImport = "import";
static constexpr const char* kCommandTryImport = "try-import";
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;
}
const std::string canonical_filename =
blaze_util::MakeCanonical(filename.c_str());
canonical_rcfile_paths_.push_back(canonical_filename);
// Keep a pointer to the canonical_filename string in canonical_rcfile_paths_
// for the RcOptions.
string* filename_ptr = &canonical_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 == kCommandImport || command == kCommandTryImport) {
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?)",
canonical_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";
}
loop += " " + words[1] + "\n"; // Include the loop.
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) {
if (parse_error == ParseError::UNREADABLE_FILE &&
command == kCommandTryImport) {
// For try-import, we ignore it if we couldn't find a file.
BAZEL_LOG(INFO) << "Skipped optional import of " << words[1]
<< ", the specified rc file either does not exist or "
"is not readable.";
*error_text = "";
} else {
// Files that are there but are malformed or introduce a loop are
// still a problem, though, so perpetuate those errors as we would
// for a normal import statement.
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