blob: 8993c642044bc0f5bde9759d3ac9ab9fa36f933e [file] [log] [blame]
// Copyright 2019 The Goma Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "rust/rustc_compiler_info_builder.h"
#include "absl/strings/match.h"
#include "absl/strings/str_split.h"
#include "absl/strings/strip.h"
#include "base/file_dir.h"
#include "base/path.h"
#include "counterz.h"
#include "glog/logging.h"
#include "glog/stl_logging.h"
#include "ioutil.h"
#include "lib/path_resolver.h"
#include "rustc_flags.h"
#include "util.h"
#ifdef _WIN32
#include "posix_helper_win.h"
#endif // _WIN32
namespace devtools_goma {
namespace {
bool IsExecutable(const std::string& path) {
return access(path.c_str(), X_OK) == 0;
}
bool AddFilesFromDirectory(const std::string& dirname,
int remaining_depth,
std::vector<std::string>* resource_paths) {
if (remaining_depth <= 0)
return true;
std::vector<DirEntry> entries;
if (!ListDirectory(dirname, &entries)) {
LOG(ERROR) << "Failed to list contents from directory " << dirname;
return false;
}
for (const auto& entry : entries) {
if (entry.name == "." || entry.name == "..") {
continue;
}
std::string entry_path = file::JoinPathRespectAbsolute(dirname, entry.name);
if (entry.is_dir) {
if (!AddFilesFromDirectory(entry_path, remaining_depth - 1,
resource_paths)) {
return false;
}
} else {
resource_paths->push_back(std::move(entry_path));
}
}
return true;
}
bool AddResourceAsExecutableBinaryInternal(
const std::string& resource_path,
const std::string& cwd,
int remaining_symlink_follow_count,
absl::flat_hash_set<std::string>* visited_paths,
CompilerInfoData* data) {
std::string abs_resource_path =
file::JoinPathRespectAbsolute(cwd, resource_path);
if (!visited_paths->insert(PathResolver::ResolvePath(abs_resource_path))
.second) {
// This path has been visited before. Abort.
return true;
}
CompilerInfoData::ResourceInfo r;
if (!CompilerInfoBuilder::ResourceInfoFromPath(
cwd, resource_path, CompilerInfoData::EXECUTABLE_BINARY, &r)) {
CompilerInfoBuilder::AddErrorMessage(
"failed to get resource info for " + resource_path, data);
LOG(ERROR) << "failed to get resource info for " + resource_path;
return false;
}
if (r.symlink_path().empty()) {
// Not a symlink, add it as a resource directly.
*data->add_resource() = std::move(r);
return true;
}
// It's a symlink.
if (remaining_symlink_follow_count <= 0) {
// Too many nested symlink. Abort and return an error.
CompilerInfoBuilder::AddErrorMessage(
"too deep nested symlink: " + resource_path, data);
return false;
}
std::string symlink_path = file::JoinPathRespectAbsolute(
file::Dirname(resource_path), r.symlink_path());
// Implementation Note: the original resource must come first. If the resource
// is a symlink, the actual file must be added after the symlink. The server
// assumes the first resource is a compiler used in a command line, even
// if it's a symlink.
*data->add_resource() = std::move(r);
return AddResourceAsExecutableBinaryInternal(
symlink_path, cwd, remaining_symlink_follow_count - 1, visited_paths,
data);
}
} // namespace
void RustcCompilerInfoBuilder::SetTypeSpecificCompilerInfo(
const CompilerFlags& compiler_flags,
const std::string& local_compiler_path,
const std::string& abs_local_compiler_path,
const std::vector<std::string>& compiler_info_envs,
CompilerInfoData* data) const {
// Ensure rustc extension exists.
(void)data->mutable_rustc();
DCHECK_EQ(CompilerFlagType::Rustc, compiler_flags.type());
const RustcFlags& flags = static_cast<const RustcFlags&>(compiler_flags);
std::string host;
if (!GetRustcVersionHost(local_compiler_path, compiler_info_envs, flags.cwd(),
data->mutable_version(), &host)) {
AddErrorMessage("Failed to get rustc version for " + local_compiler_path,
data);
LOG(ERROR) << data->error_message();
return;
}
data->set_target(std::move(host));
std::vector<std::string> resources_path;
if (!CollectRustcResources(data->real_compiler_path(), &resources_path)) {
AddErrorMessage(
"Failed to get rustc resources for " + data->real_compiler_path(),
data);
LOG(ERROR) << data->error_message();
return;
}
absl::flat_hash_set<std::string> visited_paths;
for (const auto& file : resources_path) {
if (!AddResourceAsExecutableBinary(file, compiler_flags.cwd(),
&visited_paths, data)) {
return;
}
}
}
void RustcCompilerInfoBuilder::SetLanguageExtension(
CompilerInfoData* data) const {
(void)data->mutable_rustc();
}
bool RustcCompilerInfoBuilder::GetRustcVersionHost(
const std::string& rustc_path,
const std::vector<std::string>& compiler_info_envs,
const std::string& cwd,
std::string* version,
std::string* host) const {
const std::vector<std::string> args{
rustc_path,
"--version",
"-v",
};
std::vector<std::string> env(compiler_info_envs);
env.emplace_back("LC_ALL=C");
int32_t status = 0;
std::string output = ReadCommandOutput(rustc_path, args, env, cwd,
MERGE_STDOUT_STDERR, &status);
if (status != 0) {
LOG(ERROR) << "ReadCommandOutput exited with non zero status code."
<< " rustc_path=" << rustc_path << " status=" << status
<< " args=" << args << " env=" << env << " cwd=" << cwd
<< " output=" << output;
return false;
}
LOG(INFO) << "output=" << output;
return ParseRustcVersionHost(output, version, host);
}
bool RustcCompilerInfoBuilder::ParseRustcVersionHost(
absl::string_view compiler_output,
std::string* version,
std::string* host) const {
// output example:
//
// rustc 1.29.0-nightly (9bd8458c9 2018-07-09)
// binary: rustc
// commit-hash: 9bd8458c92f7166b827e4eb5cf5effba8c0e615d
// commit-date: 2018-07-09
// host: x86_64-unknown-linux-gnu
// release: 1.29.0-nightly
// LLVM version: 6.0
bool first = true;
bool host_was_found = false;
for (auto line : absl::StrSplit(compiler_output, absl::ByAnyChar("\r\n"),
absl::SkipEmpty())) {
if (first) {
// first line must be version.
if (!absl::ConsumePrefix(&line, "rustc ")) {
return false;
}
*version = std::string(line);
first = false;
continue;
}
// not sure `host: ` is correct.
if (absl::ConsumePrefix(&line, "host: ")) {
*host = std::string(line);
host_was_found = true;
break;
}
}
return host_was_found;
}
void RustcCompilerInfoBuilder::SetCompilerPath(
const CompilerFlags& flags,
const std::string& local_compiler_path,
const std::vector<std::string>& compiler_info_envs,
CompilerInfoData* data) const {
// If rustc is installed by rustup, the local_compiler_path
// is actually pointing to a rustup wrapper, which shouldn't
// be used for hashing. The real rustc
// should be located at `rustc --print sysroot`/bin/rustc
data->set_local_compiler_path(local_compiler_path);
data->set_real_compiler_path(local_compiler_path);
const std::vector<std::string> args{
local_compiler_path,
"--print",
"sysroot",
};
std::vector<std::string> env(compiler_info_envs);
env.emplace_back("LC_ALL=C");
int32_t status = 0;
std::string output =
ReadCommandOutput(local_compiler_path, args, env, flags.cwd(),
MERGE_STDOUT_STDERR, &status);
LOG(INFO) << "Rust sysroot " << output;
if (status != 0) {
LOG(ERROR) << "ReadCommandOutput exited with non zero status code."
<< " rustc_path=" << local_compiler_path << " status=" << status
<< " args=" << args << " env=" << env << " cwd=" << flags.cwd()
<< " output=" << output;
return;
}
std::string real_rustc = file::JoinPathRespectAbsolute(
absl::StripTrailingAsciiWhitespace(output), "bin/rustc");
if (IsExecutable(real_rustc)) {
data->set_real_compiler_path(std::move(real_rustc));
}
}
// static
bool RustcCompilerInfoBuilder::CollectRustcResources(
absl::string_view real_compiler_path,
std::vector<std::string>* resource_paths) {
resource_paths->emplace_back(real_compiler_path);
std::vector<std::string> rustc_resource_directories = {"lib"};
// The real toolchain is located at
// ~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin/rustc
// We should use ~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu
// as rust_root.
absl::string_view rust_root =
file::Dirname(file::Dirname(real_compiler_path));
constexpr int kMaxNestedDirs = 8;
for (const auto& dir : rustc_resource_directories) {
std::string resource_dir = file::JoinPathRespectAbsolute(rust_root, dir);
if (!AddFilesFromDirectory(resource_dir, kMaxNestedDirs, resource_paths)) {
return false;
}
}
return true;
}
// static
bool RustcCompilerInfoBuilder::AddResourceAsExecutableBinary(
const std::string& resource_path,
const std::string& cwd,
absl::flat_hash_set<std::string>* visited_paths,
CompilerInfoData* data) {
// On Linux, MAX_NESTED_LINKS is 8.
constexpr int kMaxNestedLinks = 8;
return AddResourceAsExecutableBinaryInternal(
resource_path, cwd, kMaxNestedLinks, visited_paths, data);
}
} // namespace devtools_goma