blob: 5491d7ae575e0be1543e1c04e8a639dbad5f8062 [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 <memory>
#include <string>
#include <utility>
#include <vector>
#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"
#include "absl/algorithm/container.h"
#include "absl/functional/function_ref.h"
#include "absl/memory/memory.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
namespace blaze {
static constexpr absl::string_view kCommandImport = "import";
static constexpr absl::string_view kCommandTryImport = "try-import";
/*static*/ std::unique_ptr<RcFile> RcFile::Parse(
const std::string& filename, const WorkspaceLayout* workspace_layout,
const std::string& workspace, ParseError* error, std::string* error_text,
ReadFileFn read_file, CanonicalizePathFn canonicalize_path) {
auto rcfile = absl::WrapUnique(new RcFile());
std::vector<std::string> initial_import_stack = {filename};
*error = rcfile->ParseFile(filename, workspace, *workspace_layout, read_file,
canonicalize_path, initial_import_stack,
error_text);
return (*error == ParseError::NONE) ? std::move(rcfile) : nullptr;
}
RcFile::ParseError RcFile::ParseFile(const std::string& filename,
const std::string& workspace,
const WorkspaceLayout& workspace_layout,
ReadFileFn read_file,
CanonicalizePathFn canonicalize_path,
std::vector<std::string>& import_stack,
std::string* error_text) {
BAZEL_LOG(INFO) << "Parsing the RcFile " << filename;
std::string contents;
if (std::string error_msg; !read_file(filename, &contents, &error_msg)) {
*error_text = absl::StrFormat(
"Unexpected error reading config file '%s': %s", filename, error_msg);
return ParseError::UNREADABLE_FILE;
}
const std::string canonical_filename = canonicalize_path(filename);
const absl::string_view workspace_prefix(
workspace_layout.WorkspacePrefix, workspace_layout.WorkspacePrefixLength);
int rcfile_index = canonical_rcfile_paths_.size();
canonical_rcfile_paths_.push_back(canonical_filename);
// A '\' at the end of a line continues the line.
blaze_util::Replace("\\\r\n", "", &contents);
blaze_util::Replace("\\\n", "", &contents);
std::vector<std::string> lines = absl::StrSplit(contents, '\n');
for (std::string& line : lines) {
blaze_util::StripWhitespace(&line);
// Check for an empty line.
if (line.empty()) continue;
std::vector<std::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);
// Could happen if line starts with "#"
if (words.empty()) continue;
const absl::string_view command = words[0];
if (command != kCommandImport && command != kCommandTryImport) {
for (absl::string_view word : absl::MakeConstSpan(words).subspan(1)) {
options_[command].push_back({std::string(word), rcfile_index});
}
continue;
}
if (words.size() != 2) {
*error_text = absl::StrFormat(
"Invalid import declaration in config file '%s': '%s'",
canonical_filename, line);
return ParseError::INVALID_FORMAT;
}
std::string& import_filename = words[1];
if (absl::StartsWith(import_filename, workspace_prefix)) {
const bool could_relativize =
workspace_layout.WorkspaceRelativizeRcFilePath(workspace,
&import_filename);
if (!could_relativize && command == kCommandImport) {
*error_text = absl::StrFormat(
"Nonexistent path in import declaration in config file '%s': '%s'"
" (are you in your source checkout/WORKSPACE?)",
canonical_filename, line);
return ParseError::INVALID_FORMAT;
}
}
if (absl::c_linear_search(import_stack, import_filename)) {
std::string loop;
for (const std::string& imported_rc : import_stack) {
absl::StrAppend(&loop, " ", imported_rc, "\n");
}
absl::StrAppend(&loop, " ", import_filename, "\n"); // Include the loop.
*error_text = absl::StrCat("Import loop detected:\n", loop);
return ParseError::IMPORT_LOOP;
}
import_stack.push_back(import_filename);
if (ParseError parse_error =
ParseFile(import_filename, workspace, workspace_layout, read_file,
canonicalize_path, import_stack, error_text);
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 " << import_filename
<< ", 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();
}
return ParseError::NONE;
}
bool RcFile::ReadFileDefault(const std::string& filename, std::string* contents,
std::string* error_msg) {
return blaze_util::ReadFile(filename, contents, error_msg);
}
std::string RcFile::CanonicalizePathDefault(const std::string& filename) {
return blaze_util::MakeCanonical(filename.c_str());
}
} // namespace blaze